Formatting

This commit is contained in:
Scott Tomaszewski 2025-04-24 16:38:23 -04:00
parent 93c26d5a25
commit 95a34cf070
14 changed files with 1658 additions and 1653 deletions

View file

@ -1,4 +1,3 @@
import { Plugin } from 'obsidian';
import DisciplesJournalPlugin from './src/core/DisciplesJournalPlugin';
export default DisciplesJournalPlugin;

View file

@ -1,6 +1,6 @@
import { ButtonComponent, DropdownComponent, Notice } from "obsidian";
import { BibleReference } from "../core/BibleReference";
import { BookNames } from "../services/BookNames";
import {ButtonComponent, DropdownComponent, Notice} from "obsidian";
import {BibleReference} from "../core/BibleReference";
import {BookNames} from "../services/BookNames";
import {BibleBookFiles} from "../services/BibleBookFiles";
/**
@ -9,44 +9,44 @@ import {BibleBookFiles} from "../services/BibleBookFiles";
export class BibleNavigation {
private bibleBookFiles: BibleBookFiles;
constructor(noteCreationService: BibleBookFiles) {
constructor(noteCreationService: BibleBookFiles) {
this.bibleBookFiles = noteCreationService;
}
}
/**
* Create navigation elements for a Bible chapter
*/
public createNavigationElements(containerEl: HTMLElement, reference: BibleReference): void {
const book = BookNames.normalize(reference.book) || reference.book;
const chapter = reference.chapter;
/**
* Create navigation elements for a Bible chapter
*/
public createNavigationElements(containerEl: HTMLElement, reference: BibleReference): void {
const book = BookNames.normalize(reference.book) || reference.book;
const chapter = reference.chapter;
// Get book chapter count
const chapterCount = BookNames.getChapterCount(book);
// Get book chapter count
const chapterCount = BookNames.getChapterCount(book);
// Determine if we're at the first or last chapter of the book
const isFirstChapter = chapter === 1;
const isLastChapter = chapter === chapterCount;
// Determine if we're at the first or last chapter of the book
const isFirstChapter = chapter === 1;
const isLastChapter = chapter === chapterCount;
// Determine if we're at the first or last book of the Bible
const bookOrder = BookNames.getBookOrder();
const bookIndex = bookOrder.indexOf(book);
const isFirstBook = bookIndex === 0;
const isLastBook = bookIndex === bookOrder.length - 1;
// Determine if we're at the first or last book of the Bible
const bookOrder = BookNames.getBookOrder();
const bookIndex = bookOrder.indexOf(book);
const isFirstBook = bookIndex === 0;
const isLastBook = bookIndex === bookOrder.length - 1;
// Create the navigation container
const navEl = containerEl.createDiv({ cls: 'bible-navigation' });
// Create the navigation container
const navEl = containerEl.createDiv({cls: 'bible-navigation'});
// Previous chapter button
if (isFirstChapter && isFirstBook) {
// No previous chapter if we're at Genesis 1
navEl.createSpan({
cls: 'nav-prev nav-disabled',
text: '◄ Previous'
});
} else if (isFirstChapter) {
// Previous book, last chapter
const prevBook = bookOrder[bookIndex - 1];
const prevChapter = BookNames.getChapterCount(prevBook);
// Previous chapter button
if (isFirstChapter && isFirstBook) {
// No previous chapter if we're at Genesis 1
navEl.createSpan({
cls: 'nav-prev nav-disabled',
text: '◄ Previous'
});
} else if (isFirstChapter) {
// Previous book, last chapter
const prevBook = bookOrder[bookIndex - 1];
const prevChapter = BookNames.getChapterCount(prevBook);
new ButtonComponent(navEl)
.setButtonText(`${prevBook} ${prevChapter}`)
.setClass('nav-prev')
@ -70,38 +70,38 @@ export class BibleNavigation {
}
// Book and chapter selector
const selectorEl = navEl.createSpan({
cls: 'nav-book-selector',
text: `📖 ${book} ${chapter}`
});
const selectorEl = navEl.createSpan({
cls: 'nav-book-selector',
text: `📖 ${book} ${chapter}`
});
const selectorContainer = navEl.createDiv({
cls: 'nav-book-data dj-hidden'
});
const selectorContainer = navEl.createDiv({
cls: 'nav-book-data dj-hidden'
});
// Create the book dropdown
const bookDropdownContainer = selectorContainer.createDiv();
const bookDropdown = new DropdownComponent(bookDropdownContainer);
// Create the book dropdown
const bookDropdownContainer = selectorContainer.createDiv();
const bookDropdown = new DropdownComponent(bookDropdownContainer);
// Add all books to the dropdown
for (const bookName of bookOrder) {
bookDropdown.addOption(bookName, bookName);
}
bookDropdown.setValue(book);
// Add all books to the dropdown
for (const bookName of bookOrder) {
bookDropdown.addOption(bookName, bookName);
}
bookDropdown.setValue(book);
// Create the chapter dropdown
const chapterDropdownContainer = selectorContainer.createDiv({ cls: 'nav-chapter-container' });
const chapterDropdown = new DropdownComponent(chapterDropdownContainer);
// Create the chapter dropdown
const chapterDropdownContainer = selectorContainer.createDiv({cls: 'nav-chapter-container'});
const chapterDropdown = new DropdownComponent(chapterDropdownContainer);
// Add current book's chapters to the dropdown
this.populateChapterDropdown(chapterDropdown, book, chapter);
// Add current book's chapters to the dropdown
this.populateChapterDropdown(chapterDropdown, book, chapter);
// When book selection changes, update chapter dropdown
bookDropdown.onChange(value => {
this.populateChapterDropdown(chapterDropdown, value, 1);
});
// When book selection changes, update chapter dropdown
bookDropdown.onChange(value => {
this.populateChapterDropdown(chapterDropdown, value, 1);
});
// Add Go button
// Add Go button
new ButtonComponent(selectorContainer)
.setButtonText('Go')
.setClass('nav-go-button')
@ -113,20 +113,20 @@ export class BibleNavigation {
loadingNotice.hide();
});
// Toggle selector on click
selectorEl.addEventListener('click', () => {
selectorContainer.classList.toggle('dj-hidden');
});
selectorEl.addEventListener('click', () => {
selectorContainer.classList.toggle('dj-hidden');
});
// Next chapter button
if (isLastChapter && isLastBook) {
// No next chapter if we're at Revelation 22
navEl.createSpan({
cls: 'nav-next nav-disabled',
text: 'Next ►'
});
} else if (isLastChapter) {
// Next book, first chapter
const nextBook = bookOrder[bookIndex + 1];
// Next chapter button
if (isLastChapter && isLastBook) {
// No next chapter if we're at Revelation 22
navEl.createSpan({
cls: 'nav-next nav-disabled',
text: 'Next ►'
});
} else if (isLastChapter) {
// Next book, first chapter
const nextBook = bookOrder[bookIndex + 1];
new ButtonComponent(navEl)
.setButtonText(`${nextBook} 1`)
.setClass('nav-next')
@ -151,37 +151,37 @@ export class BibleNavigation {
}
/**
* Populate the chapter dropdown for a specific book
*/
private populateChapterDropdown(
dropdown: DropdownComponent,
book: string,
selectedChapter: number
): void {
// Clear existing options
dropdown.selectEl.empty();
* Populate the chapter dropdown for a specific book
*/
private populateChapterDropdown(
dropdown: DropdownComponent,
book: string,
selectedChapter: number
): void {
// Clear existing options
dropdown.selectEl.empty();
// Get the chapter count for this book
const chapterCount = BookNames.getChapterCount(book);
// Get the chapter count for this book
const chapterCount = BookNames.getChapterCount(book);
// Add chapter options
for (let i = 1; i <= chapterCount; i++) {
dropdown.addOption(i.toString(), i.toString());
}
// Add chapter options
for (let i = 1; i <= chapterCount; i++) {
dropdown.addOption(i.toString(), i.toString());
}
// Set the selected chapter
dropdown.setValue(selectedChapter.toString());
}
// Set the selected chapter
dropdown.setValue(selectedChapter.toString());
}
/**
* Navigate to a specific chapter
*/
public async navigateToChapter(book: string, chapter: number): Promise<void> {
try {
/**
* Navigate to a specific chapter
*/
public async navigateToChapter(book: string, chapter: number): Promise<void> {
try {
await this.bibleBookFiles.openChapterNote(new BibleReference(book, chapter).toString());
} catch (error) {
console.error('Error navigating to chapter:', error);
new Notice(`Error navigating to chapter: ${error.message}`);
}
}
} catch (error) {
console.error('Error navigating to chapter:', error);
new Notice(`Error navigating to chapter: ${error.message}`);
}
}
}

View file

@ -2,73 +2,73 @@
* Interface for Bible styling options
*/
export interface BibleStyleOptions {
fontSize: string;
wordsOfChristColor: string;
verseNumberColor: string;
headingColor: string;
blockIndentation: string;
fontSize: string;
wordsOfChristColor: string;
verseNumberColor: string;
headingColor: string;
blockIndentation: string;
}
/**
* Theme preset definitions
*/
export interface ThemePreset {
light: BibleStyleOptions;
dark: BibleStyleOptions;
light: BibleStyleOptions;
dark: BibleStyleOptions;
}
/**
* Available theme presets
*/
export const THEME_PRESETS: Record<string, ThemePreset> = {
default: {
light: {
fontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em'
}
},
classic: {
light: {
fontSize: '100%',
wordsOfChristColor: '#b91c1c',
verseNumberColor: '#2563eb',
headingColor: '#4b5563',
blockIndentation: '2em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: '#ef4444',
verseNumberColor: '#60a5fa',
headingColor: '#9ca3af',
blockIndentation: '2em'
}
},
minimal: {
light: {
fontSize: '100%',
wordsOfChristColor: '#000000',
verseNumberColor: '#6b7280',
headingColor: '#374151',
blockIndentation: '1.5em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: '#ffffff',
verseNumberColor: '#9ca3af',
headingColor: '#e5e7eb',
blockIndentation: '1.5em'
}
}
default: {
light: {
fontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em'
}
},
classic: {
light: {
fontSize: '100%',
wordsOfChristColor: '#b91c1c',
verseNumberColor: '#2563eb',
headingColor: '#4b5563',
blockIndentation: '2em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: '#ef4444',
verseNumberColor: '#60a5fa',
headingColor: '#9ca3af',
blockIndentation: '2em'
}
},
minimal: {
light: {
fontSize: '100%',
wordsOfChristColor: '#000000',
verseNumberColor: '#6b7280',
headingColor: '#374151',
blockIndentation: '1.5em'
},
dark: {
fontSize: '100%',
wordsOfChristColor: '#ffffff',
verseNumberColor: '#9ca3af',
headingColor: '#e5e7eb',
blockIndentation: '1.5em'
}
}
};
/**
@ -77,60 +77,60 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
*/
export class BibleStyles {
constructor() {
// No initial style element creation needed
}
constructor() {
// No initial style element creation needed
}
/**
* Apply styles by updating CSS variables based on theme, preset, and settings.
*/
public applyStyles(doc: Document, theme: 'light' | 'dark', presetName: string = 'default', fontSize: string = '100%', settings?: {
wordsOfChristColor?: string;
verseNumberColor?: string;
headingColor?: string;
blockIndentation?: string;
}): void {
/**
* Apply styles by updating CSS variables based on theme, preset, and settings.
*/
public applyStyles(doc: Document, theme: 'light' | 'dark', presetName: string = 'default', fontSize: string = '100%', settings?: {
wordsOfChristColor?: string;
verseNumberColor?: string;
headingColor?: string;
blockIndentation?: string;
}): void {
// Get the preset, defaulting to 'default' if not found
const preset = THEME_PRESETS[presetName] || THEME_PRESETS.default;
// Get the preset, defaulting to 'default' if not found
const preset = THEME_PRESETS[presetName] || THEME_PRESETS.default;
// Get the base options for the current theme
const baseOptions = preset[theme];
// Get the base options for the current theme
const baseOptions = preset[theme];
// Start with base options and apply the specific font size
const options: BibleStyleOptions = {
...baseOptions,
fontSize: fontSize || '100%' // Ensure fontSize always has a value
};
// Start with base options and apply the specific font size
const options: BibleStyleOptions = {
...baseOptions,
fontSize: fontSize || '100%' // Ensure fontSize always has a value
};
// Apply custom settings from DisciplesJournalSettings if provided
// These will override the preset values
if (settings) {
if (settings.wordsOfChristColor) options.wordsOfChristColor = settings.wordsOfChristColor;
if (settings.verseNumberColor) options.verseNumberColor = settings.verseNumberColor;
if (settings.headingColor) options.headingColor = settings.headingColor;
if (settings.blockIndentation) options.blockIndentation = settings.blockIndentation;
}
// Apply custom settings from DisciplesJournalSettings if provided
// These will override the preset values
if (settings) {
if (settings.wordsOfChristColor) options.wordsOfChristColor = settings.wordsOfChristColor;
if (settings.verseNumberColor) options.verseNumberColor = settings.verseNumberColor;
if (settings.headingColor) options.headingColor = settings.headingColor;
if (settings.blockIndentation) options.blockIndentation = settings.blockIndentation;
}
// Update the CSS variables on the root element
const rootStyle = doc.body.style;
rootStyle.setProperty('--dj-font-size', options.fontSize);
rootStyle.setProperty('--dj-heading-color', options.headingColor);
rootStyle.setProperty('--dj-verse-number-color', options.verseNumberColor);
rootStyle.setProperty('--dj-woc-color', options.wordsOfChristColor);
rootStyle.setProperty('--dj-block-indentation', options.blockIndentation);
}
// Update the CSS variables on the root element
const rootStyle = doc.body.style;
rootStyle.setProperty('--dj-font-size', options.fontSize);
rootStyle.setProperty('--dj-heading-color', options.headingColor);
rootStyle.setProperty('--dj-verse-number-color', options.verseNumberColor);
rootStyle.setProperty('--dj-woc-color', options.wordsOfChristColor);
rootStyle.setProperty('--dj-block-indentation', options.blockIndentation);
}
/**
* Resets the custom styles potentially set by this class by removing the CSS variables.
* This allows the default styles in styles.css to take effect.
*/
public resetStyles(doc: Document): void {
const rootStyle = doc.body.style;
rootStyle.removeProperty('--dj-font-size');
rootStyle.removeProperty('--dj-heading-color');
rootStyle.removeProperty('--dj-verse-number-color');
rootStyle.removeProperty('--dj-woc-color');
rootStyle.removeProperty('--dj-block-indentation');
}
/**
* Resets the custom styles potentially set by this class by removing the CSS variables.
* This allows the default styles in styles.css to take effect.
*/
public resetStyles(doc: Document): void {
const rootStyle = doc.body.style;
rootStyle.removeProperty('--dj-font-size');
rootStyle.removeProperty('--dj-heading-color');
rootStyle.removeProperty('--dj-verse-number-color');
rootStyle.removeProperty('--dj-woc-color');
rootStyle.removeProperty('--dj-block-indentation');
}
}

View file

@ -1,131 +1,132 @@
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
import {BibleReferenceRenderer} from '../components/BibleReferenceRenderer';
/**
* Handles all Bible reference-related DOM events
*/
export class BibleEventHandlers {
private bibleReferenceRenderer: BibleReferenceRenderer;
private previewPopper: HTMLElement | null = null;
private bibleReferenceRenderer: BibleReferenceRenderer;
private previewPopper: HTMLElement | null = null;
constructor(bibleReferenceRenderer: BibleReferenceRenderer) {
this.bibleReferenceRenderer = bibleReferenceRenderer;
}
constructor(bibleReferenceRenderer: BibleReferenceRenderer) {
this.bibleReferenceRenderer = bibleReferenceRenderer;
}
/**
* Handle hover on Bible references
*/
async handleBibleReferenceHover(event: MouseEvent) {
// Don't create new preview if we already have one active
if (this.previewPopper) return;
* Handle hover on Bible references
*/
async handleBibleReferenceHover(event: MouseEvent) {
// Don't create new preview if we already have one active
if (this.previewPopper) return;
const target = event.target as HTMLElement;
if (!target || !target.closest) return;
const target = event.target as HTMLElement;
if (!target || !target.closest) return;
const referenceEl = target.closest('.bible-reference') as HTMLElement;
if (!referenceEl) return;
const referenceEl = target.closest('.bible-reference') as HTMLElement;
if (!referenceEl) return;
const referenceText = referenceEl.textContent;
if (!referenceText) return;
const referenceText = referenceEl.textContent;
if (!referenceText) return;
try {
// Create new preview
this.previewPopper = await this.bibleReferenceRenderer.showVersePreview(
referenceEl,
referenceText,
event
);
try {
// Create new preview
this.previewPopper = await this.bibleReferenceRenderer.showVersePreview(
referenceEl,
referenceText,
event
);
// Add event listeners directly to the preview for better control
if (this.previewPopper) {
// When mouse enters the popup, mark it as locked
this.previewPopper.addEventListener('mouseenter', () => {
this.previewPopper?.classList.add('popup-locked');
});
// Add event listeners directly to the preview for better control
if (this.previewPopper) {
// When mouse enters the popup, mark it as locked
this.previewPopper.addEventListener('mouseenter', () => {
this.previewPopper?.classList.add('popup-locked');
});
// When mouse leaves the popup, check if we should close it
this.previewPopper.addEventListener('mouseleave', (e) => {
// Only close if not moving to the reference or another part of the popup
const relatedTarget = e.relatedTarget as HTMLElement;
if (relatedTarget &&
!relatedTarget.classList.contains('bible-reference') &&
!relatedTarget.closest('.bible-verse-preview')) {
this.previewPopper?.classList.remove('popup-locked');
this.removePreviewPopper(relatedTarget.doc);
}
});
}
// When mouse leaves the popup, check if we should close it
this.previewPopper.addEventListener('mouseleave', (e) => {
// Only close if not moving to the reference or another part of the popup
const relatedTarget = e.relatedTarget as HTMLElement;
if (relatedTarget &&
!relatedTarget.classList.contains('bible-reference') &&
!relatedTarget.closest('.bible-verse-preview')) {
this.previewPopper?.classList.remove('popup-locked');
this.removePreviewPopper(relatedTarget.doc);
}
});
}
// Also add listeners to the reference element
referenceEl.addEventListener('mouseleave', (e) => {
// Don't close if the popup is locked (being hovered) or we're moving to the popup
if (this.previewPopper) {
const relatedTarget = e.relatedTarget as HTMLElement;
// Also add listeners to the reference element
referenceEl.addEventListener('mouseleave', (e) => {
// Don't close if the popup is locked (being hovered) or we're moving to the popup
if (this.previewPopper) {
const relatedTarget = e.relatedTarget as HTMLElement;
// If moving to the popup or if popup is locked, don't close
if (relatedTarget &&
(relatedTarget.classList.contains('bible-verse-preview') ||
relatedTarget.closest('.bible-verse-preview') ||
this.previewPopper.classList.contains('popup-locked'))) {
return;
}
// If moving to the popup or if popup is locked, don't close
if (relatedTarget &&
(relatedTarget.classList.contains('bible-verse-preview') ||
relatedTarget.closest('.bible-verse-preview') ||
this.previewPopper.classList.contains('popup-locked'))) {
return;
}
// Add a 100ms delay before closing to allow for cursor movement
setTimeout(() => {
// If locked during this delay, don't close
if (!this.previewPopper || this.previewPopper.classList.contains('popup-locked')) {
return;
}
this.removePreviewPopper(relatedTarget.doc);
}, 100);
}
});
} catch (error) {
console.error('Error showing Bible reference preview:', error);
}
}
// Add a 100ms delay before closing to allow for cursor movement
setTimeout(() => {
// If locked during this delay, don't close
if (!this.previewPopper || this.previewPopper.classList.contains('popup-locked')) {
return;
}
this.removePreviewPopper(relatedTarget.doc);
}, 100);
}
});
} catch (error) {
console.error('Error showing Bible reference preview:', error);
}
}
/**
* Handle mouse out from Bible references
*/
handleBibleReferenceMouseOut(event: MouseEvent) {
// If we don't have a popup, nothing to do
if (!this.previewPopper) return;
/**
* Handle mouse out from Bible references
*/
handleBibleReferenceMouseOut(event: MouseEvent) {
// If we don't have a popup, nothing to do
if (!this.previewPopper) return;
// If the popup is locked (being hovered), don't close it
if (this.previewPopper.classList.contains('popup-locked')) {
return;
}
// If the popup is locked (being hovered), don't close it
if (this.previewPopper.classList.contains('popup-locked')) {
return;
}
const target = event.target as HTMLElement;
const relatedTarget = event.relatedTarget as HTMLElement;
const target = event.target as HTMLElement;
const relatedTarget = event.relatedTarget as HTMLElement;
// If either target is missing, can't make a good decision
if (!target || !relatedTarget) return;
// If either target is missing, can't make a good decision
if (!target || !relatedTarget) return;
// If moving to/from the popup or reference, don't close
if (target.classList.contains('bible-reference') ||
target.classList.contains('bible-verse-preview') ||
target.closest('.bible-verse-preview') ||
relatedTarget.classList.contains('bible-reference') ||
relatedTarget.classList.contains('bible-verse-preview') ||
relatedTarget.closest('.bible-verse-preview')) {
return;
}
// If moving to/from the popup or reference, don't close
if (target.classList.contains('bible-reference') ||
target.classList.contains('bible-verse-preview') ||
target.closest('.bible-verse-preview') ||
relatedTarget.classList.contains('bible-reference') ||
relatedTarget.classList.contains('bible-verse-preview') ||
relatedTarget.closest('.bible-verse-preview')) {
return;
}
// In all other cases, remove the popup
this.removePreviewPopper(target.doc);
}
// In all other cases, remove the popup
this.removePreviewPopper(target.doc);
}
/**
* Remove the preview popper if it exists
*/
removePreviewPopper(doc: Document) {
if (this.previewPopper) {
// Remove any hover gap elements
const hoverGaps = doc.querySelectorAll('.bible-hover-gap');
hoverGaps.forEach(gap => gap.remove());
/**
* Remove the preview popper if it exists
*/
removePreviewPopper(doc: Document) {
if (this.previewPopper) {
// Remove any hover gap elements
const hoverGaps = doc.querySelectorAll('.bible-hover-gap');
hoverGaps.forEach(gap => gap.remove());
this.previewPopper.remove();
this.previewPopper = null;
}
}
this.previewPopper.remove();
this.previewPopper = null;
}
}
}

View file

@ -1,55 +1,55 @@
import { MarkdownPostProcessorContext } from 'obsidian';
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
import { DisciplesJournalSettings } from '../settings/DisciplesJournalSettings';
import {MarkdownPostProcessorContext} from 'obsidian';
import {BibleReferenceRenderer} from '../components/BibleReferenceRenderer';
import {DisciplesJournalSettings} from '../settings/DisciplesJournalSettings';
/**
* Handles processing of Bible references in markdown text
*/
export class BibleMarkupProcessor {
private bibleReferenceRenderer: BibleReferenceRenderer;
private settings: DisciplesJournalSettings;
private bibleReferenceRenderer: BibleReferenceRenderer;
private settings: DisciplesJournalSettings;
constructor(
bibleReferenceRenderer: BibleReferenceRenderer,
settings: DisciplesJournalSettings
) {
this.bibleReferenceRenderer = bibleReferenceRenderer;
this.settings = settings;
}
constructor(
bibleReferenceRenderer: BibleReferenceRenderer,
settings: DisciplesJournalSettings
) {
this.bibleReferenceRenderer = bibleReferenceRenderer;
this.settings = settings;
}
/**
* Process Bible code blocks
*/
async processBibleCodeBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
// Check if display of full passages is enabled in settings
if (!this.settings.displayFullPassages) {
return;
}
/**
* Process Bible code blocks
*/
async processBibleCodeBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
// Check if display of full passages is enabled in settings
if (!this.settings.displayFullPassages) {
return;
}
try {
await this.bibleReferenceRenderer.processFullBiblePassage(source, el);
} catch (error) {
console.error('Error processing Bible code block:', error);
el.createEl('div', {
text: `Error processing Bible reference: ${error.message}`,
cls: 'bible-reference-error'
});
}
}
try {
await this.bibleReferenceRenderer.processFullBiblePassage(source, el);
} catch (error) {
console.error('Error processing Bible code block:', error);
el.createEl('div', {
text: `Error processing Bible reference: ${error.message}`,
cls: 'bible-reference-error'
});
}
}
/**
* Process inline Bible references in text
*/
async processInlineBibleReferences(el: HTMLElement, ctx: MarkdownPostProcessorContext): Promise<void> {
// Check if display of inline verses is enabled in settings
if (!this.settings.displayInlineVerses) {
return;
}
/**
* Process inline Bible references in text
*/
async processInlineBibleReferences(el: HTMLElement, ctx: MarkdownPostProcessorContext): Promise<void> {
// Check if display of inline verses is enabled in settings
if (!this.settings.displayInlineVerses) {
return;
}
try {
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
} catch (error) {
console.error('Error processing inline Bible references:', error);
}
}
try {
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
} catch (error) {
console.error('Error processing inline Bible references:', error);
}
}
}

View file

@ -4,22 +4,22 @@ import {BookNames} from "../services/BookNames";
* Represents a Bible Reference with book, chapter, and verse information
*/
export class BibleReference {
book: string;
chapter: number;
verse?: number;
endVerse?: number;
endChapter?: number;
book: string;
chapter: number;
verse?: number;
endVerse?: number;
endChapter?: number;
/**
* Create a new Bible reference
*/
constructor(book: string, chapter: number, verse?: number, endVerse?: number, endChapter?: number) {
this.book = book;
this.chapter = chapter;
this.verse = verse;
this.endVerse = endVerse;
this.endChapter = endChapter;
}
/**
* Create a new Bible reference
*/
constructor(book: string, chapter: number, verse?: number, endVerse?: number, endChapter?: number) {
this.book = book;
this.chapter = chapter;
this.verse = verse;
this.endVerse = endVerse;
this.endChapter = endChapter;
}
/**
* Parse a string into a BibleReference object
@ -112,95 +112,95 @@ export class BibleReference {
}
/**
* Get the formatted reference as a string
*/
public toString(): string {
let reference = `${this.book} ${this.chapter}`;
* Get the formatted reference as a string
*/
public toString(): string {
let reference = `${this.book} ${this.chapter}`;
// Add verse if present
if (this.verse !== undefined) {
reference += `:${this.verse}`;
// Add verse if present
if (this.verse !== undefined) {
reference += `:${this.verse}`;
// Add end verse if present (for ranges within same chapter)
if (this.endVerse !== undefined && this.endChapter === undefined) {
reference += `-${this.endVerse}`;
}
}
// Add end verse if present (for ranges within same chapter)
if (this.endVerse !== undefined && this.endChapter === undefined) {
reference += `-${this.endVerse}`;
}
}
// Handle cross-chapter reference
if (this.endChapter !== undefined) {
reference += `-${this.endChapter}`;
// Handle cross-chapter reference
if (this.endChapter !== undefined) {
reference += `-${this.endChapter}`;
// Add end verse if present for a cross-chapter reference
if (this.endVerse !== undefined) {
reference += `:${this.endVerse}`;
}
}
// Add end verse if present for a cross-chapter reference
if (this.endVerse !== undefined) {
reference += `:${this.endVerse}`;
}
}
return reference;
}
return reference;
}
/**
* Create a deep copy of the reference
*/
public clone(): BibleReference {
return new BibleReference(
this.book,
this.chapter,
this.verse,
this.endVerse,
this.endChapter
);
}
/**
* Create a deep copy of the reference
*/
public clone(): BibleReference {
return new BibleReference(
this.book,
this.chapter,
this.verse,
this.endVerse,
this.endChapter
);
}
/**
* Determine if this reference is a range (spans multiple verses)
*/
public isRange(): boolean {
return this.endVerse !== undefined || this.endChapter !== undefined;
}
/**
* Determine if this reference is a range (spans multiple verses)
*/
public isRange(): boolean {
return this.endVerse !== undefined || this.endChapter !== undefined;
}
/**
* Check if this is a chapter reference (no specific verse)
*/
public isChapterReference(): boolean {
return this.verse === undefined;
}
/**
* Check if this is a chapter reference (no specific verse)
*/
public isChapterReference(): boolean {
return this.verse === undefined;
}
/**
* Compare this reference with another to check if they refer to the same passage
*/
public equals(other: BibleReference): boolean {
return this.book === other.book &&
this.chapter === other.chapter &&
this.verse === other.verse &&
this.endVerse === other.endVerse &&
this.endChapter === other.endChapter;
}
/**
* Compare this reference with another to check if they refer to the same passage
*/
public equals(other: BibleReference): boolean {
return this.book === other.book &&
this.chapter === other.chapter &&
this.verse === other.verse &&
this.endVerse === other.endVerse &&
this.endChapter === other.endChapter;
}
/**
* Returns a reference string suitable for display in UI
*/
public getDisplayReference(): string {
// For UI display, we might want to abbreviate the book name
// or format the reference in a specific way
return this.toString();
}
/**
* Returns a reference string suitable for display in UI
*/
public getDisplayReference(): string {
// For UI display, we might want to abbreviate the book name
// or format the reference in a specific way
return this.toString();
}
/**
* Returns a reference string suitable for use as a key in the Bible data
*/
public getDataKey(): string {
// For data keys, we often want to use a standardized format
// This assumes the book name is already standardized
return `${this.book} ${this.chapter}${this.verse ? `:${this.verse}` : ''}`;
}
/**
* Returns a reference string suitable for use as a key in the Bible data
*/
public getDataKey(): string {
// For data keys, we often want to use a standardized format
// This assumes the book name is already standardized
return `${this.book} ${this.chapter}${this.verse ? `:${this.verse}` : ''}`;
}
/**
* Creates a reference for the entire chapter containing this reference
*/
public getChapterReference(): BibleReference {
// Create a new reference with the same book and chapter, but no verse info
return new BibleReference(this.book, this.chapter);
}
/**
* Creates a reference for the entire chapter containing this reference
*/
public getChapterReference(): BibleReference {
// Create a new reference with the same book and chapter, but no verse info
return new BibleReference(this.book, this.chapter);
}
}

View file

@ -1,139 +1,143 @@
import { Plugin, MarkdownView, Notice } from 'obsidian';
import { ESVApiService } from '../services/ESVApiService';
import { BibleContentService } from '../services/BibleContentService';
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
import { BibleStyles } from '../components/BibleStyles';
import { DisciplesJournalSettings, DEFAULT_SETTINGS, DisciplesJournalSettingsTab } from '../settings/DisciplesJournalSettings';
import { BibleBookFiles } from 'src/services/BibleBookFiles';
import { BibleMarkupProcessor } from './BibleMarkupProcessor';
import {Plugin, MarkdownView, Notice} from 'obsidian';
import {ESVApiService} from '../services/ESVApiService';
import {BibleContentService} from '../services/BibleContentService';
import {BibleReferenceRenderer} from '../components/BibleReferenceRenderer';
import {BibleStyles} from '../components/BibleStyles';
import {
DisciplesJournalSettings,
DEFAULT_SETTINGS,
DisciplesJournalSettingsTab
} from '../settings/DisciplesJournalSettings';
import {BibleBookFiles} from 'src/services/BibleBookFiles';
import {BibleMarkupProcessor} from './BibleMarkupProcessor';
/**
* Disciples Journal Plugin for Obsidian
* Enhances Bible references with hover previews and in-note embedding
*/
export default class DisciplesJournalPlugin extends Plugin {
settings: DisciplesJournalSettings;
settings: DisciplesJournalSettings;
// Services
private esvApiService: ESVApiService;
private bibleContentService: BibleContentService;
private bibleBookFiles: BibleBookFiles;
// Services
private esvApiService: ESVApiService;
private bibleContentService: BibleContentService;
private bibleBookFiles: BibleBookFiles;
// Components
private bibleStyles: BibleStyles;
private bibleReferenceRenderer: BibleReferenceRenderer;
private bibleMarkupProcessor: BibleMarkupProcessor;
// Components
private bibleStyles: BibleStyles;
private bibleReferenceRenderer: BibleReferenceRenderer;
private bibleMarkupProcessor: BibleMarkupProcessor;
async onload() {
console.log('Loading Disciples Journal plugin');
async onload() {
console.log('Loading Disciples Journal plugin');
// Initialize settings
await this.loadSettings();
// Initialize settings
await this.loadSettings();
// Initialize services
this.esvApiService = new ESVApiService(this);
this.bibleContentService = new BibleContentService(this, this.esvApiService);
// Initialize services
this.esvApiService = new ESVApiService(this);
this.bibleContentService = new BibleContentService(this, this.esvApiService);
// Check if ESV API token is set and show a notice if it's not
if (!this.settings.esvApiToken) {
new Notice('Disciples Journal: ESV API token not set. Bible content may not load correctly. Visit the plugin settings to add your API token.', 10000);
}
// Check if ESV API token is set and show a notice if it's not
if (!this.settings.esvApiToken) {
new Notice('Disciples Journal: ESV API token not set. Bible content may not load correctly. Visit the plugin settings to add your API token.', 10000);
}
// Initialize components
this.bibleStyles = new BibleStyles();
// Initialize components
this.bibleStyles = new BibleStyles();
this.bibleBookFiles = new BibleBookFiles(this, this.bibleContentService);
this.bibleReferenceRenderer = new BibleReferenceRenderer(
this.bibleContentService,
this.bibleReferenceRenderer = new BibleReferenceRenderer(
this.bibleContentService,
this.bibleBookFiles,
this
);
this
);
// Initialize markup processor
this.bibleMarkupProcessor = new BibleMarkupProcessor(this.bibleReferenceRenderer, this.settings);
// Initialize markup processor
this.bibleMarkupProcessor = new BibleMarkupProcessor(this.bibleReferenceRenderer, this.settings);
// Register bible reference processor
this.registerMarkdownCodeBlockProcessor('bible', this.bibleMarkupProcessor.processBibleCodeBlock.bind(this.bibleMarkupProcessor));
// Register bible reference processor
this.registerMarkdownCodeBlockProcessor('bible', this.bibleMarkupProcessor.processBibleCodeBlock.bind(this.bibleMarkupProcessor));
// Register markdown post processor for inline references
this.registerMarkdownPostProcessor(this.bibleMarkupProcessor.processInlineBibleReferences.bind(this.bibleMarkupProcessor));
// Register markdown post processor for inline references
this.registerMarkdownPostProcessor(this.bibleMarkupProcessor.processInlineBibleReferences.bind(this.bibleMarkupProcessor));
// Register settings tab
this.addSettingTab(new DisciplesJournalSettingsTab(this.app, this));
// Register settings tab
this.addSettingTab(new DisciplesJournalSettingsTab(this.app, this));
// Register active leaf change to update styles
this.registerEvent(this.app.workspace.on('active-leaf-change', this.handleActiveLeafChange.bind(this)));
// Register active leaf change to update styles
this.registerEvent(this.app.workspace.on('active-leaf-change', this.handleActiveLeafChange.bind(this)));
// Load Bible data
await this.loadBibleData();
}
// Load Bible data
await this.loadBibleData();
}
onunload() {
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
async saveSettings() {
await this.saveData(this.settings);
}
/**
* Load the Bible data from source
*/
async loadBibleData() {
try {
console.log('Loading Bible data...');
await this.esvApiService.ensureBibleData();
console.log('Bible data loaded successfully');
} catch (error) {
console.error('Error loading Bible data:', error);
}
}
/**
* Load the Bible data from source
*/
async loadBibleData() {
try {
console.log('Loading Bible data...');
await this.esvApiService.ensureBibleData();
console.log('Bible data loaded successfully');
} catch (error) {
console.error('Error loading Bible data:', error);
}
}
/**
* Handle leaf change event to check for verse references in the URL
*/
handleActiveLeafChange() {
// Refresh theme/styling when active leaf changes
this.updateBibleStyles();
}
/**
* Handle leaf change event to check for verse references in the URL
*/
handleActiveLeafChange() {
// Refresh theme/styling when active leaf changes
this.updateBibleStyles();
}
/**
* Update Bible styles with current settings
*/
public updateBibleStyles(): void {
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeLeaf) return;
/**
* Update Bible styles with current settings
*/
public updateBibleStyles(): void {
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeLeaf) return;
// Each popup window has a unique document, so we need to apply styles to each one
// See https://discord.com/channels/686053708261228577/840286264964022302/1362059117190975519
// Note: this doesnt completely work. When a new popout is created the style cars arent ported over
const docList: Document[] = [];
this.app.workspace.iterateAllLeaves(leaf => docList.push(leaf.getContainer().doc));
docList.unique().forEach(doc => {
const isDarkMode = activeLeaf.containerEl.doc.body.classList.contains('theme-dark');
const theme = isDarkMode ? 'dark' : 'light';
this.bibleStyles.applyStyles(
doc,
theme,
this.settings.stylePreset,
this.settings.bibleTextFontSize,
{
wordsOfChristColor: this.settings.wordsOfChristColor,
verseNumberColor: this.settings.verseNumberColor,
headingColor: this.settings.headingColor,
blockIndentation: this.settings.blockIndentation
}
);
});
}
// Each popup window has a unique document, so we need to apply styles to each one
// See https://discord.com/channels/686053708261228577/840286264964022302/1362059117190975519
// Note: this doesnt completely work. When a new popout is created the style cars arent ported over
const docList: Document[] = [];
this.app.workspace.iterateAllLeaves(leaf => docList.push(leaf.getContainer().doc));
docList.unique().forEach(doc => {
const isDarkMode = activeLeaf.containerEl.doc.body.classList.contains('theme-dark');
const theme = isDarkMode ? 'dark' : 'light';
this.bibleStyles.applyStyles(
doc,
theme,
this.settings.stylePreset,
this.settings.bibleTextFontSize,
{
wordsOfChristColor: this.settings.wordsOfChristColor,
verseNumberColor: this.settings.verseNumberColor,
headingColor: this.settings.headingColor,
blockIndentation: this.settings.blockIndentation
}
);
});
}
/**
* Open or create a chapter note (Public method for external access)
*/
public async openChapterNote(reference: string) {
return this.bibleBookFiles.openChapterNote(reference);
}
/**
* Open or create a chapter note (Public method for external access)
*/
public async openChapterNote(reference: string) {
return this.bibleBookFiles.openChapterNote(reference);
}
}

View file

@ -1,110 +1,110 @@
import { TFile } from 'obsidian';
import { BibleContentService } from './BibleContentService';
import { BibleReference } from '../core/BibleReference';
import { BibleFormatter } from '../utils/BibleFormatter';
import {TFile} from 'obsidian';
import {BibleContentService} from './BibleContentService';
import {BibleReference} from '../core/BibleReference';
import {BibleFormatter} from '../utils/BibleFormatter';
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
/**
* Service for creating and opening Bible chapter notes
*/
export class BibleBookFiles {
private plugin: DisciplesJournalPlugin;
private bibleContentService: BibleContentService;
private plugin: DisciplesJournalPlugin;
private bibleContentService: BibleContentService;
constructor(
constructor(
plugin: DisciplesJournalPlugin,
bibleContentService: BibleContentService,
) {
this.plugin = plugin;
this.bibleContentService = bibleContentService;
}
bibleContentService: BibleContentService,
) {
this.plugin = plugin;
this.bibleContentService = bibleContentService;
}
/**
* Open or create a chapter note
*/
public async openChapterNote(reference: string) {
try {
// Parse the reference string to ensure it's valid
const parsedRef = BibleReference.parse(reference);
if (!parsedRef) {
console.error(`Invalid reference: ${reference}`);
return;
}
/**
* Open or create a chapter note
*/
public async openChapterNote(reference: string) {
try {
// Parse the reference string to ensure it's valid
const parsedRef = BibleReference.parse(reference);
if (!parsedRef) {
console.error(`Invalid reference: ${reference}`);
return;
}
// Get full content path including version
const fullPath = this.getFullContentPath();
const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`;
// Get full content path including version
const fullPath = this.getFullContentPath();
const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`;
// Check if note exists
const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath);
// Check if note exists
const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath);
if (!fileExists && this.plugin.settings.downloadOnDemand) {
// Create the note with content from the API
await this.createChapterNote(parsedRef);
}
if (!fileExists && this.plugin.settings.downloadOnDemand) {
// Create the note with content from the API
await this.createChapterNote(parsedRef);
}
// Try opening the note
const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath);
// Try opening the note
const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath);
if (file && file instanceof TFile) {
const leaf = this.plugin.app.workspace.getLeaf(false);
await leaf.openFile(file);
if (file && file instanceof TFile) {
const leaf = this.plugin.app.workspace.getLeaf(false);
await leaf.openFile(file);
// If there's a specific verse, scroll to it
if (parsedRef.verse) {
setTimeout(() => {
// Find the verse element and scroll to it
const verseEl = leaf.getContainer().doc.querySelector(`.verse-${parsedRef.verse}`);
if (verseEl) {
verseEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, 300); // Give it a moment to load
}
} else {
console.error(`Could not find or create the chapter note: ${chapterPath}`);
}
} catch (error) {
console.error('Error opening chapter note:', error);
}
}
// If there's a specific verse, scroll to it
if (parsedRef.verse) {
setTimeout(() => {
// Find the verse element and scroll to it
const verseEl = leaf.getContainer().doc.querySelector(`.verse-${parsedRef.verse}`);
if (verseEl) {
verseEl.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}, 300); // Give it a moment to load
}
} else {
console.error(`Could not find or create the chapter note: ${chapterPath}`);
}
} catch (error) {
console.error('Error opening chapter note:', error);
}
}
/**
* Create a new chapter note
*/
/**
* Create a new chapter note
*/
private async createChapterNote(reference: BibleReference) {
try {
// Create a properly formatted reference string
const referenceStr = reference.toString();
try {
// Create a properly formatted reference string
const referenceStr = reference.toString();
// Get the content from the Bible API and format it for a note
const passage = await this.bibleContentService.getBibleContent(referenceStr);
// Get the content from the Bible API and format it for a note
const passage = await this.bibleContentService.getBibleContent(referenceStr);
// Check if passage is null
if (!passage) {
console.error(`Failed to get Bible content for ${referenceStr}`);
throw new Error(`Failed to get Bible content for ${referenceStr}`);
}
// Check if passage is null
if (!passage) {
console.error(`Failed to get Bible content for ${referenceStr}`);
throw new Error(`Failed to get Bible content for ${referenceStr}`);
}
// Use the formatter utility to format the content
const content = BibleFormatter.formatChapterContent(passage);
// Use the formatter utility to format the content
const content = BibleFormatter.formatChapterContent(passage);
// Save the content to a note with the version path
const fullPath = this.getFullContentPath();
const bookPath = `${fullPath}/${reference.book}`;
const chapterPath = `${fullPath}/${reference.book}/${reference.book} ${reference.chapter}.md`;
// Save the content to a note with the version path
const fullPath = this.getFullContentPath();
const bookPath = `${fullPath}/${reference.book}`;
const chapterPath = `${fullPath}/${reference.book}/${reference.book} ${reference.chapter}.md`;
// Ensure the directory exists
await this.plugin.app.vault.adapter.mkdir(bookPath);
// Ensure the directory exists
await this.plugin.app.vault.adapter.mkdir(bookPath);
// Create the note
await this.plugin.app.vault.create(chapterPath, content);
// Create the note
await this.plugin.app.vault.create(chapterPath, content);
return chapterPath;
} catch (error) {
console.error('Error creating chapter note:', error);
throw error;
}
}
return chapterPath;
} catch (error) {
console.error('Error creating chapter note:', error);
throw error;
}
}
/**
* Get the full path with version

View file

@ -1,26 +1,26 @@
import { BibleReference } from "../core/BibleReference";
import { BookNames } from "./BookNames";
import { ESVApiService } from "./ESVApiService";
import {BibleReference} from "../core/BibleReference";
import {BookNames} from "./BookNames";
import {ESVApiService} from "./ESVApiService";
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
/**
* Interface for a single Bible verse
*/
export interface BibleVerse {
book: string;
chapter: number;
verse: number;
text: string;
book: string;
chapter: number;
verse: number;
text: string;
}
/**
* Interface for Bible passage content
*/
export interface BiblePassage {
reference: string;
verses: BibleVerse[];
htmlContent?: string;
missingToken?: boolean;
reference: string;
verses: BibleVerse[];
htmlContent?: string;
missingToken?: boolean;
}
/**
@ -28,8 +28,8 @@ export interface BiblePassage {
*/
export class BibleContentService {
private plugin: DisciplesJournalPlugin;
private bible: any = null;
private esvApiService: ESVApiService;
private bible: any = null;
private esvApiService: ESVApiService;
constructor(plugin: DisciplesJournalPlugin, esvApiService: ESVApiService) {
this.plugin = plugin;
@ -37,127 +37,128 @@ export class BibleContentService {
}
/**
* Get a single verse by reference
*/
public getVerse(book: string, chapter: number, verse: number): BibleVerse | null {
if (!this.bible) return null;
* Get a single verse by reference
*/
public getVerse(book: string, chapter: number, verse: number): BibleVerse | null {
if (!this.bible) return null;
try {
const normalizedBook = BookNames.normalize(book);
if (!normalizedBook) return null;
try {
const normalizedBook = BookNames.normalize(book);
if (!normalizedBook) return null;
// Check if the book exists in the Bible data
if (!this.bible[normalizedBook]) return null;
// Check if the book exists in the Bible data
if (!this.bible[normalizedBook]) return null;
// Check if the chapter exists
const chapterStr = chapter.toString();
if (!this.bible[normalizedBook][chapterStr]) return null;
// Check if the chapter exists
const chapterStr = chapter.toString();
if (!this.bible[normalizedBook][chapterStr]) return null;
// Check if the verse exists
const verseStr = verse.toString();
if (!this.bible[normalizedBook][chapterStr][verseStr]) return null;
// Check if the verse exists
const verseStr = verse.toString();
if (!this.bible[normalizedBook][chapterStr][verseStr]) return null;
// Return the verse
return {
book: normalizedBook,
chapter: chapter,
verse: verse,
text: this.bible[normalizedBook][chapterStr][verseStr]
};
} catch (error) {
console.error('Error getting verse:', error);
return null;
}
}
// Return the verse
return {
book: normalizedBook,
chapter: chapter,
verse: verse,
text: this.bible[normalizedBook][chapterStr][verseStr]
};
} catch (error) {
console.error('Error getting verse:', error);
return null;
}
}
/**
* Get a passage by reference
* This handles everything from single verses to full chapters
*/
public getPassage(reference: BibleReference): BiblePassage | null {
if (!this.bible) return null;
try {
const normalizedBook = BookNames.normalize(reference.book);
if (!normalizedBook) return null;
// Prepare the result
const result: BiblePassage = {
reference: reference.toString(),
verses: []
};
// Get the chapter
const chapterStr = reference.chapter.toString();
if (!this.bible[normalizedBook][chapterStr]) return null;
// If verse is specified, get just that verse or verse range
if (reference.verse) {
// Single verse
if (!reference.endVerse) {
const verse = this.getVerse(normalizedBook, reference.chapter, reference.verse);
if (verse) {
result.verses.push(verse);
}
}
// Verse range
else {
// Add all verses in the range
for (let v = reference.verse; v <= reference.endVerse; v++) {
const verse = this.getVerse(normalizedBook, reference.chapter, v);
if (verse) {
result.verses.push(verse);
}
}
}
}
// No verse specified, get the whole chapter
else {
// Get all verses in the chapter
const chapter = this.bible[normalizedBook][chapterStr];
if (chapter) {
const verseNumbers = Object.keys(chapter).map(v => parseInt(v)).sort((a, b) => a - b);
for (const verseNum of verseNumbers) {
const verse = this.getVerse(normalizedBook, reference.chapter, verseNum);
if (verse) {
result.verses.push(verse);
}
}
}
}
return result;
} catch (error) {
console.error('Error getting passage:', error);
return null;
}
}
/**
* Get Bible content from any source (local or API)
*/
public async getBibleContent(referenceString: string): Promise<BiblePassage | null> {
try {
// Parse the reference
const parsedRef = BibleReference.parse(referenceString);
if (!parsedRef) {
console.error(`Invalid reference: ${referenceString}`);
return null;
}
* Get a passage by reference
* This handles everything from single verses to full chapters
*/
public getPassage(reference: BibleReference): BiblePassage | null {
if (!this.bible) return null;
// Try to get from local Bible data first
let passage = this.getPassage(parsedRef);
try {
const normalizedBook = BookNames.normalize(reference.book);
if (!normalizedBook) return null;
// If not available locally and download on demand is enabled, try the API
if (!passage && this.plugin.settings.downloadOnDemand) {
// Try to get from the ESV API
passage = await this.esvApiService.downloadFromESVApi(referenceString);
}
// Prepare the result
const result: BiblePassage = {
reference: reference.toString(),
verses: []
};
return passage;
} catch (error) {
console.error('Error getting Bible content:', error);
return null;
}
}
// Get the chapter
const chapterStr = reference.chapter.toString();
if (!this.bible[normalizedBook][chapterStr]) return null;
// If verse is specified, get just that verse or verse range
if (reference.verse) {
// Single verse
if (!reference.endVerse) {
const verse = this.getVerse(normalizedBook, reference.chapter, reference.verse);
if (verse) {
result.verses.push(verse);
}
}
// Verse range
else {
// Add all verses in the range
for (let v = reference.verse; v <= reference.endVerse; v++) {
const verse = this.getVerse(normalizedBook, reference.chapter, v);
if (verse) {
result.verses.push(verse);
}
}
}
}
// No verse specified, get the whole chapter
else {
// Get all verses in the chapter
const chapter = this.bible[normalizedBook][chapterStr];
if (chapter) {
const verseNumbers = Object.keys(chapter).map(v => parseInt(v)).sort((a, b) => a - b);
for (const verseNum of verseNumbers) {
const verse = this.getVerse(normalizedBook, reference.chapter, verseNum);
if (verse) {
result.verses.push(verse);
}
}
}
}
return result;
} catch (error) {
console.error('Error getting passage:', error);
return null;
}
}
/**
* Get Bible content from any source (local or API)
*/
public async getBibleContent(referenceString: string): Promise<BiblePassage | null> {
try {
// Parse the reference
const parsedRef = BibleReference.parse(referenceString);
if (!parsedRef) {
console.error(`Invalid reference: ${referenceString}`);
return null;
}
// Try to get from local Bible data first
let passage = this.getPassage(parsedRef);
// If not available locally and download on demand is enabled, try the API
if (!passage && this.plugin.settings.downloadOnDemand) {
// Try to get from the ESV API
passage = await this.esvApiService.downloadFromESVApi(referenceString);
}
return passage;
} catch (error) {
console.error('Error getting Bible content:', error);
return null;
}
}
}

View file

@ -2,245 +2,245 @@
* Service for handling Bible book name standardization and mapping
*/
export class BookNames {
/**
* Standardize a book name to its canonical form
*/
public static normalize(bookName: string): string | null {
if (!bookName) {
return null;
}
/**
* Standardize a book name to its canonical form
*/
public static normalize(bookName: string): string | null {
if (!bookName) {
return null;
}
// Clean up the book name
const cleanedName = bookName.trim();
// Clean up the book name
const cleanedName = bookName.trim();
// First try a direct lookup
if (BookNames._instance.bookNameMap.has(cleanedName.toLowerCase())) {
return BookNames._instance.bookNameMap.get(cleanedName.toLowerCase()) as string;
}
// First try a direct lookup
if (BookNames._instance.bookNameMap.has(cleanedName.toLowerCase())) {
return BookNames._instance.bookNameMap.get(cleanedName.toLowerCase()) as string;
}
// Try to find a partial match
for (const [key, value] of BookNames._instance.bookNameMap.entries()) {
// Check if the key is a substring of the cleaned name
if (cleanedName.toLowerCase().startsWith(key)) {
return value;
}
}
// Try to find a partial match
for (const [key, value] of BookNames._instance.bookNameMap.entries()) {
// Check if the key is a substring of the cleaned name
if (cleanedName.toLowerCase().startsWith(key)) {
return value;
}
}
return null;
}
return null;
}
/**
* Extract a book name from a reference string
*/
public static extractBookFromReference(reference: string): string {
if (!reference) {
return "";
}
/**
* Extract a book name from a reference string
*/
public static extractBookFromReference(reference: string): string {
if (!reference) {
return "";
}
// First try to match book names with numbers (e.g., "1 John")
const numberedBookRegex = /^(\d+\s*[A-Za-z]+)/;
const numberedMatch = reference.match(numberedBookRegex);
if (numberedMatch) {
return numberedMatch[1];
}
// First try to match book names with numbers (e.g., "1 John")
const numberedBookRegex = /^(\d+\s*[A-Za-z]+)/;
const numberedMatch = reference.match(numberedBookRegex);
if (numberedMatch) {
return numberedMatch[1];
}
// If no numbered book was found, try to match a regular book name
// We iterate through all keys in the book name map to find the longest matching book name
let longestMatch = "";
let bookName = "";
// If no numbered book was found, try to match a regular book name
// We iterate through all keys in the book name map to find the longest matching book name
let longestMatch = "";
let bookName = "";
// Start by extracting the first word (potential book name)
const firstWordMatch = reference.match(/^([A-Za-z]+)/);
if (firstWordMatch) {
bookName = firstWordMatch[1];
// Start by extracting the first word (potential book name)
const firstWordMatch = reference.match(/^([A-Za-z]+)/);
if (firstWordMatch) {
bookName = firstWordMatch[1];
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
// Try matching with two words (for books like "Song of")
const twoWordsMatch = reference.match(/^([A-Za-z]+\s+[A-Za-z]+)/);
if (twoWordsMatch) {
bookName = twoWordsMatch[1];
// Try matching with two words (for books like "Song of")
const twoWordsMatch = reference.match(/^([A-Za-z]+\s+[A-Za-z]+)/);
if (twoWordsMatch) {
bookName = twoWordsMatch[1];
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
// Try matching with three words (for books like "Song of Solomon")
const threeWordsMatch = reference.match(/^([A-Za-z]+\s+[A-Za-z]+\s+[A-Za-z]+)/);
if (threeWordsMatch) {
bookName = threeWordsMatch[1];
// Try matching with three words (for books like "Song of Solomon")
const threeWordsMatch = reference.match(/^([A-Za-z]+\s+[A-Za-z]+\s+[A-Za-z]+)/);
if (threeWordsMatch) {
bookName = threeWordsMatch[1];
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
// Check if this is a valid book name
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
longestMatch = bookName;
}
}
return longestMatch;
}
return longestMatch;
}
/**
* Get the chapter count for a given book name
*/
public static getChapterCount(bookName: string): number {
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
return BookNames.bibleStructure[standardizedName] || 1;
}
return 1; // Default to 1 if book not found
}
/**
* Get the chapter count for a given book name
*/
public static getChapterCount(bookName: string): number {
const standardizedName = BookNames.normalize(bookName);
if (standardizedName) {
return BookNames.bibleStructure[standardizedName] || 1;
}
return 1; // Default to 1 if book not found
}
/**
* Get the ordered list of book names
*/
public static getBookOrder(): string[] {
return [...BookNames.bookOrder]; // Return a copy to prevent modification
}
/**
* Get the ordered list of book names
*/
public static getBookOrder(): string[] {
return [...BookNames.bookOrder]; // Return a copy to prevent modification
}
// Bible book structure (book name and chapter count)
private static bibleStructure: {[book: string]: number} = {
// Old Testament
"Genesis": 50,"Exodus": 40, "Leviticus": 27, "Numbers": 36, "Deuteronomy": 34,
"Joshua": 24, "Judges": 21, "Ruth": 4, "1 Samuel": 31, "2 Samuel": 24,
"1 Kings": 22, "2 Kings": 25, "1 Chronicles": 29, "2 Chronicles": 36,
"Ezra": 10, "Nehemiah": 13, "Esther": 10, "Job": 42, "Psalms": 150,
"Proverbs": 31, "Ecclesiastes": 12, "Song of Solomon": 8, "Isaiah": 66,
"Jeremiah": 52, "Lamentations": 5, "Ezekiel": 48, "Daniel": 12, "Hosea": 14,
"Joel": 3, "Amos": 9, "Obadiah": 1, "Jonah": 4, "Micah": 7,
"Nahum": 3, "Habakkuk": 3, "Zephaniah": 3, "Haggai": 2, "Zechariah": 14,
"Malachi": 4,
// New Testament
"Matthew": 28, "Mark": 16, "Luke": 24, "John": 21, "Acts": 28,
"Romans": 16, "1 Corinthians": 16, "2 Corinthians": 13, "Galatians": 6,
"Ephesians": 6, "Philippians": 4, "Colossians": 4, "1 Thessalonians": 5,
"2 Thessalonians": 3, "1 Timothy": 6, "2 Timothy": 4, "Titus": 3,
"Philemon": 1, "Hebrews": 13, "James": 5, "1 Peter": 5, "2 Peter": 3,
"1 John": 5, "2 John": 1, "3 John": 1, "Jude": 1, "Revelation": 22
};
private static bibleStructure: { [book: string]: number } = {
// Old Testament
"Genesis": 50, "Exodus": 40, "Leviticus": 27, "Numbers": 36, "Deuteronomy": 34,
"Joshua": 24, "Judges": 21, "Ruth": 4, "1 Samuel": 31, "2 Samuel": 24,
"1 Kings": 22, "2 Kings": 25, "1 Chronicles": 29, "2 Chronicles": 36,
"Ezra": 10, "Nehemiah": 13, "Esther": 10, "Job": 42, "Psalms": 150,
"Proverbs": 31, "Ecclesiastes": 12, "Song of Solomon": 8, "Isaiah": 66,
"Jeremiah": 52, "Lamentations": 5, "Ezekiel": 48, "Daniel": 12, "Hosea": 14,
"Joel": 3, "Amos": 9, "Obadiah": 1, "Jonah": 4, "Micah": 7,
"Nahum": 3, "Habakkuk": 3, "Zephaniah": 3, "Haggai": 2, "Zechariah": 14,
"Malachi": 4,
// New Testament
"Matthew": 28, "Mark": 16, "Luke": 24, "John": 21, "Acts": 28,
"Romans": 16, "1 Corinthians": 16, "2 Corinthians": 13, "Galatians": 6,
"Ephesians": 6, "Philippians": 4, "Colossians": 4, "1 Thessalonians": 5,
"2 Thessalonians": 3, "1 Timothy": 6, "2 Timothy": 4, "Titus": 3,
"Philemon": 1, "Hebrews": 13, "James": 5, "1 Peter": 5, "2 Peter": 3,
"1 John": 5, "2 John": 1, "3 John": 1, "Jude": 1, "Revelation": 22
};
// Order of books in the Bible
private static bookOrder: string[] = [
// Old Testament
"Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
"Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel",
"1 Kings", "2 Kings", "1 Chronicles", "2 Chronicles",
"Ezra", "Nehemiah", "Esther", "Job", "Psalms",
"Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
"Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea",
"Joel", "Amos", "Obadiah", "Jonah", "Micah",
"Nahum", "Habakkuk", "Zephaniah", "Haggai", "Zechariah",
"Malachi",
// New Testament
"Matthew", "Mark", "Luke", "John", "Acts",
"Romans", "1 Corinthians", "2 Corinthians", "Galatians",
"Ephesians", "Philippians", "Colossians", "1 Thessalonians",
"2 Thessalonians", "1 Timothy", "2 Timothy", "Titus",
"Philemon", "Hebrews", "James", "1 Peter", "2 Peter",
"1 John", "2 John", "3 John", "Jude", "Revelation"
];
// Order of books in the Bible
private static bookOrder: string[] = [
// Old Testament
"Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
"Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel",
"1 Kings", "2 Kings", "1 Chronicles", "2 Chronicles",
"Ezra", "Nehemiah", "Esther", "Job", "Psalms",
"Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
"Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea",
"Joel", "Amos", "Obadiah", "Jonah", "Micah",
"Nahum", "Habakkuk", "Zephaniah", "Haggai", "Zechariah",
"Malachi",
// New Testament
"Matthew", "Mark", "Luke", "John", "Acts",
"Romans", "1 Corinthians", "2 Corinthians", "Galatians",
"Ephesians", "Philippians", "Colossians", "1 Thessalonians",
"2 Thessalonians", "1 Timothy", "2 Timothy", "Titus",
"Philemon", "Hebrews", "James", "1 Peter", "2 Peter",
"1 John", "2 John", "3 John", "Jude", "Revelation"
];
private static _instance:BookNames = new BookNames();
private static _instance: BookNames = new BookNames();
private bookNameMap: Map<string, string> = new Map();
private bookNameMap: Map<string, string> = new Map();
private constructor() {
this.initializeBookNameMap();
}
private constructor() {
this.initializeBookNameMap();
}
/**
* Initialize the map of standardized book names
*/
private initializeBookNameMap(): void {
// Old Testament
this.addBookMapping("Genesis", ["Gen", "Ge", "Gn"]);
this.addBookMapping("Exodus", ["Exo", "Ex", "Exod"]);
this.addBookMapping("Leviticus", ["Lev", "Le", "Lv"]);
this.addBookMapping("Numbers", ["Num", "Nu", "Nm", "Nb"]);
this.addBookMapping("Deuteronomy", ["Deut", "De", "Dt"]);
this.addBookMapping("Joshua", ["Josh", "Jos", "Jsh"]);
this.addBookMapping("Judges", ["Judg", "Jdg", "Jg"]);
this.addBookMapping("Ruth", ["Rth", "Ru"]);
this.addBookMapping("1 Samuel", ["1 Sam", "1 Sa", "1S", "I Sa", "1Sam", "1st Samuel"]);
this.addBookMapping("2 Samuel", ["2 Sam", "2 Sa", "2S", "II Sa", "2Sam", "2nd Samuel"]);
this.addBookMapping("1 Kings", ["1 Ki", "1 K", "1K", "I K", "1Ki", "1st Kings"]);
this.addBookMapping("2 Kings", ["2 Ki", "2 K", "2K", "II K", "2Ki", "2nd Kings"]);
this.addBookMapping("1 Chronicles", ["1 Ch", "1 Chr", "I Ch", "1Ch", "1st Chronicles"]);
this.addBookMapping("2 Chronicles", ["2 Ch", "2 Chr", "II Ch", "2Ch", "2nd Chronicles"]);
this.addBookMapping("Ezra", ["Ezr", "Ez"]);
this.addBookMapping("Nehemiah", ["Neh", "Ne"]);
this.addBookMapping("Esther", ["Est", "Es"]);
this.addBookMapping("Job", ["Jb"]);
this.addBookMapping("Psalms", ["Ps", "Psa", "Psalm", "Pslm"]);
this.addBookMapping("Proverbs", ["Prov", "Pro", "Pr"]);
this.addBookMapping("Ecclesiastes", ["Eccl", "Ecc", "Ec", "Qoh"]);
this.addBookMapping("Song of Solomon", ["Song", "SoS", "Canticles", "Song of Songs", "Cant"]);
this.addBookMapping("Isaiah", ["Isa", "Is"]);
this.addBookMapping("Jeremiah", ["Jer", "Je"]);
this.addBookMapping("Lamentations", ["Lam", "La"]);
this.addBookMapping("Ezekiel", ["Ezek", "Eze", "Ezk"]);
this.addBookMapping("Daniel", ["Dan", "Da", "Dn"]);
this.addBookMapping("Hosea", ["Hos", "Ho"]);
this.addBookMapping("Joel", ["Jl"]);
this.addBookMapping("Amos", ["Am"]);
this.addBookMapping("Obadiah", ["Obad", "Ob"]);
this.addBookMapping("Jonah", ["Jon", "Jnh"]);
this.addBookMapping("Micah", ["Mic", "Mi"]);
this.addBookMapping("Nahum", ["Nah", "Na"]);
this.addBookMapping("Habakkuk", ["Hab", "Hb"]);
this.addBookMapping("Zephaniah", ["Zeph", "Zep", "Zp"]);
this.addBookMapping("Haggai", ["Hag", "Hg"]);
this.addBookMapping("Zechariah", ["Zech", "Zec", "Zc"]);
this.addBookMapping("Malachi", ["Mal", "Ml"]);
/**
* Initialize the map of standardized book names
*/
private initializeBookNameMap(): void {
// Old Testament
this.addBookMapping("Genesis", ["Gen", "Ge", "Gn"]);
this.addBookMapping("Exodus", ["Exo", "Ex", "Exod"]);
this.addBookMapping("Leviticus", ["Lev", "Le", "Lv"]);
this.addBookMapping("Numbers", ["Num", "Nu", "Nm", "Nb"]);
this.addBookMapping("Deuteronomy", ["Deut", "De", "Dt"]);
this.addBookMapping("Joshua", ["Josh", "Jos", "Jsh"]);
this.addBookMapping("Judges", ["Judg", "Jdg", "Jg"]);
this.addBookMapping("Ruth", ["Rth", "Ru"]);
this.addBookMapping("1 Samuel", ["1 Sam", "1 Sa", "1S", "I Sa", "1Sam", "1st Samuel"]);
this.addBookMapping("2 Samuel", ["2 Sam", "2 Sa", "2S", "II Sa", "2Sam", "2nd Samuel"]);
this.addBookMapping("1 Kings", ["1 Ki", "1 K", "1K", "I K", "1Ki", "1st Kings"]);
this.addBookMapping("2 Kings", ["2 Ki", "2 K", "2K", "II K", "2Ki", "2nd Kings"]);
this.addBookMapping("1 Chronicles", ["1 Ch", "1 Chr", "I Ch", "1Ch", "1st Chronicles"]);
this.addBookMapping("2 Chronicles", ["2 Ch", "2 Chr", "II Ch", "2Ch", "2nd Chronicles"]);
this.addBookMapping("Ezra", ["Ezr", "Ez"]);
this.addBookMapping("Nehemiah", ["Neh", "Ne"]);
this.addBookMapping("Esther", ["Est", "Es"]);
this.addBookMapping("Job", ["Jb"]);
this.addBookMapping("Psalms", ["Ps", "Psa", "Psalm", "Pslm"]);
this.addBookMapping("Proverbs", ["Prov", "Pro", "Pr"]);
this.addBookMapping("Ecclesiastes", ["Eccl", "Ecc", "Ec", "Qoh"]);
this.addBookMapping("Song of Solomon", ["Song", "SoS", "Canticles", "Song of Songs", "Cant"]);
this.addBookMapping("Isaiah", ["Isa", "Is"]);
this.addBookMapping("Jeremiah", ["Jer", "Je"]);
this.addBookMapping("Lamentations", ["Lam", "La"]);
this.addBookMapping("Ezekiel", ["Ezek", "Eze", "Ezk"]);
this.addBookMapping("Daniel", ["Dan", "Da", "Dn"]);
this.addBookMapping("Hosea", ["Hos", "Ho"]);
this.addBookMapping("Joel", ["Jl"]);
this.addBookMapping("Amos", ["Am"]);
this.addBookMapping("Obadiah", ["Obad", "Ob"]);
this.addBookMapping("Jonah", ["Jon", "Jnh"]);
this.addBookMapping("Micah", ["Mic", "Mi"]);
this.addBookMapping("Nahum", ["Nah", "Na"]);
this.addBookMapping("Habakkuk", ["Hab", "Hb"]);
this.addBookMapping("Zephaniah", ["Zeph", "Zep", "Zp"]);
this.addBookMapping("Haggai", ["Hag", "Hg"]);
this.addBookMapping("Zechariah", ["Zech", "Zec", "Zc"]);
this.addBookMapping("Malachi", ["Mal", "Ml"]);
// New Testament
this.addBookMapping("Matthew", ["Matt", "Mt"]);
this.addBookMapping("Mark", ["Mk", "Mr"]);
this.addBookMapping("Luke", ["Lk", "Lu"]);
this.addBookMapping("John", ["Jn", "Jhn"]);
this.addBookMapping("Acts", ["Act", "Ac"]);
this.addBookMapping("Romans", ["Rom", "Ro", "Rm"]);
this.addBookMapping("1 Corinthians", ["1 Cor", "1 Co", "I Co", "1Cor", "1st Corinthians"]);
this.addBookMapping("2 Corinthians", ["2 Cor", "2 Co", "II Co", "2Cor", "2nd Corinthians"]);
this.addBookMapping("Galatians", ["Gal", "Ga"]);
this.addBookMapping("Ephesians", ["Eph", "Ep"]);
this.addBookMapping("Philippians", ["Phil", "Php", "Pp"]);
this.addBookMapping("Colossians", ["Col", "Co"]);
this.addBookMapping("1 Thessalonians", ["1 Thess", "1 Th", "I Th", "1Thess", "1st Thessalonians"]);
this.addBookMapping("2 Thessalonians", ["2 Thess", "2 Th", "II Th", "2Thess", "2nd Thessalonians"]);
this.addBookMapping("1 Timothy", ["1 Tim", "1 Ti", "I Ti", "1Tim", "1st Timothy"]);
this.addBookMapping("2 Timothy", ["2 Tim", "2 Ti", "II Ti", "2Tim", "2nd Timothy"]);
this.addBookMapping("Titus", ["Tit", "Ti"]);
this.addBookMapping("Philemon", ["Phm", "Pm"]);
this.addBookMapping("Hebrews", ["Heb", "He"]);
this.addBookMapping("James", ["Jas", "Jm"]);
this.addBookMapping("1 Peter", ["1 Pet", "1 Pe", "I Pe", "1Pet", "1st Peter"]);
this.addBookMapping("2 Peter", ["2 Pet", "2 Pe", "II Pe", "2Pet", "2nd Peter"]);
this.addBookMapping("1 John", ["1 Jn", "I Jn", "1Jn", "1st John"]);
this.addBookMapping("2 John", ["2 Jn", "II Jn", "2Jn", "2nd John"]);
this.addBookMapping("3 John", ["3 Jn", "III Jn", "3Jn", "3rd John"]);
this.addBookMapping("Jude", ["Jud", "Jd"]);
this.addBookMapping("Revelation", ["Rev", "Re", "The Revelation"]);
}
// New Testament
this.addBookMapping("Matthew", ["Matt", "Mt"]);
this.addBookMapping("Mark", ["Mk", "Mr"]);
this.addBookMapping("Luke", ["Lk", "Lu"]);
this.addBookMapping("John", ["Jn", "Jhn"]);
this.addBookMapping("Acts", ["Act", "Ac"]);
this.addBookMapping("Romans", ["Rom", "Ro", "Rm"]);
this.addBookMapping("1 Corinthians", ["1 Cor", "1 Co", "I Co", "1Cor", "1st Corinthians"]);
this.addBookMapping("2 Corinthians", ["2 Cor", "2 Co", "II Co", "2Cor", "2nd Corinthians"]);
this.addBookMapping("Galatians", ["Gal", "Ga"]);
this.addBookMapping("Ephesians", ["Eph", "Ep"]);
this.addBookMapping("Philippians", ["Phil", "Php", "Pp"]);
this.addBookMapping("Colossians", ["Col", "Co"]);
this.addBookMapping("1 Thessalonians", ["1 Thess", "1 Th", "I Th", "1Thess", "1st Thessalonians"]);
this.addBookMapping("2 Thessalonians", ["2 Thess", "2 Th", "II Th", "2Thess", "2nd Thessalonians"]);
this.addBookMapping("1 Timothy", ["1 Tim", "1 Ti", "I Ti", "1Tim", "1st Timothy"]);
this.addBookMapping("2 Timothy", ["2 Tim", "2 Ti", "II Ti", "2Tim", "2nd Timothy"]);
this.addBookMapping("Titus", ["Tit", "Ti"]);
this.addBookMapping("Philemon", ["Phm", "Pm"]);
this.addBookMapping("Hebrews", ["Heb", "He"]);
this.addBookMapping("James", ["Jas", "Jm"]);
this.addBookMapping("1 Peter", ["1 Pet", "1 Pe", "I Pe", "1Pet", "1st Peter"]);
this.addBookMapping("2 Peter", ["2 Pet", "2 Pe", "II Pe", "2Pet", "2nd Peter"]);
this.addBookMapping("1 John", ["1 Jn", "I Jn", "1Jn", "1st John"]);
this.addBookMapping("2 John", ["2 Jn", "II Jn", "2Jn", "2nd John"]);
this.addBookMapping("3 John", ["3 Jn", "III Jn", "3Jn", "3rd John"]);
this.addBookMapping("Jude", ["Jud", "Jd"]);
this.addBookMapping("Revelation", ["Rev", "Re", "The Revelation"]);
}
/**
* Add a mapping for a book and its alternative names
*/
private addBookMapping(standardName: string, alternateNames: string[]): void {
// Add the standard name (lowercase for case-insensitive matching)
this.bookNameMap.set(standardName.toLowerCase(), standardName);
/**
* Add a mapping for a book and its alternative names
*/
private addBookMapping(standardName: string, alternateNames: string[]): void {
// Add the standard name (lowercase for case-insensitive matching)
this.bookNameMap.set(standardName.toLowerCase(), standardName);
// Add alternate names
for (const alternateName of alternateNames) {
this.bookNameMap.set(alternateName.toLowerCase(), standardName);
}
}
// Add alternate names
for (const alternateName of alternateNames) {
this.bookNameMap.set(alternateName.toLowerCase(), standardName);
}
}
}

View file

@ -1,329 +1,329 @@
import { requestUrl } from "obsidian";
import { BiblePassage } from "./BibleContentService";
import {requestUrl} from "obsidian";
import {BiblePassage} from "./BibleContentService";
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
/**
* Interface for ESV API Response
*/
export interface ESVApiResponse {
query: string;
canonical: string;
parsed: number[][];
passage_meta: ESVPassageMeta[];
passages: string[];
query: string;
canonical: string;
parsed: number[][];
passage_meta: ESVPassageMeta[];
passages: string[];
}
/**
* Interface for ESV Passage Metadata
*/
export interface ESVPassageMeta {
canonical: string;
chapter_start: number[];
chapter_end: number[];
prev_verse: number | null;
next_verse: number | null;
prev_chapter: number | null;
next_chapter: number[] | null;
canonical: string;
chapter_start: number[];
chapter_end: number[];
prev_verse: number | null;
next_verse: number | null;
prev_chapter: number | null;
next_chapter: number[] | null;
}
/**
* Service for interacting with the ESV API
*/
export class ESVApiService {
private plugin: DisciplesJournalPlugin;
private plugin: DisciplesJournalPlugin;
// Store HTML formatted Bible chapters
private htmlFormattedBible: {
[reference: string]: {
canonical: string;
htmlContent: string;
}
} = {};
// Store HTML formatted Bible chapters
private htmlFormattedBible: {
[reference: string]: {
canonical: string;
htmlContent: string;
}
} = {};
constructor(plugin: DisciplesJournalPlugin) {
this.plugin = plugin;
}
constructor(plugin: DisciplesJournalPlugin) {
this.plugin = plugin;
}
/**
* Get the full vault path including version subdirectory
*/
private getFullContentPath(): string {
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
}
/**
* Get the full vault path including version subdirectory
*/
private getFullContentPath(): string {
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
}
/**
* Get HTML formatted Bible content from in-memory storage
*/
public getHTMLContent(reference: string): BiblePassage | null {
if (this.htmlFormattedBible[reference]) {
return {
reference: this.htmlFormattedBible[reference].canonical,
verses: [], // Empty since we're using HTML
htmlContent: this.htmlFormattedBible[reference].htmlContent
};
}
return null;
}
/**
* Get HTML formatted Bible content from in-memory storage
*/
public getHTMLContent(reference: string): BiblePassage | null {
if (this.htmlFormattedBible[reference]) {
return {
reference: this.htmlFormattedBible[reference].canonical,
verses: [], // Empty since we're using HTML
htmlContent: this.htmlFormattedBible[reference].htmlContent
};
}
return null;
}
/**
* Load a collection of HTML formatted Bible chapters from files in the vault
*/
public async loadBibleChaptersFromVault(): Promise<void> {
try {
// Get the path where Bible content is stored
const fullPath = this.getFullContentPath();
/**
* Load a collection of HTML formatted Bible chapters from files in the vault
*/
public async loadBibleChaptersFromVault(): Promise<void> {
try {
// Get the path where Bible content is stored
const fullPath = this.getFullContentPath();
// Check if path exists
if (!(await this.plugin.app.vault.adapter.exists(fullPath))) {
console.log(`Bible content directory ${fullPath} does not exist yet`);
return;
}
// Check if path exists
if (!(await this.plugin.app.vault.adapter.exists(fullPath))) {
console.log(`Bible content directory ${fullPath} does not exist yet`);
return;
}
// Get all book directories
const bookDirs = await this.plugin.app.vault.adapter.list(fullPath);
// Get all book directories
const bookDirs = await this.plugin.app.vault.adapter.list(fullPath);
// Process each book directory
for (const bookDir of bookDirs.folders) {
// Get all chapter files
const files = await this.plugin.app.vault.adapter.list(bookDir);
// Process each book directory
for (const bookDir of bookDirs.folders) {
// Get all chapter files
const files = await this.plugin.app.vault.adapter.list(bookDir);
// Process each chapter file
for (const file of files.files) {
if (file.endsWith('.json')) {
try {
// Read and parse the file
const content = await this.plugin.app.vault.adapter.read(file);
const data = JSON.parse(content);
// Process each chapter file
for (const file of files.files) {
if (file.endsWith('.json')) {
try {
// Read and parse the file
const content = await this.plugin.app.vault.adapter.read(file);
const data = JSON.parse(content);
// Process the data if it's in the expected format
if (this.isESVApiFormat(data)) {
// Store the chapter content
const canonical = data.canonical;
const htmlContent = data.passages[0];
// Process the data if it's in the expected format
if (this.isESVApiFormat(data)) {
// Store the chapter content
const canonical = data.canonical;
const htmlContent = data.passages[0];
this.htmlFormattedBible[canonical] = {
canonical,
htmlContent
};
}
} catch (error) {
console.error(`Error processing file ${file}:`, error);
}
}
}
}
this.htmlFormattedBible[canonical] = {
canonical,
htmlContent
};
}
} catch (error) {
console.error(`Error processing file ${file}:`, error);
}
}
}
}
console.log(`Loaded ${Object.keys(this.htmlFormattedBible).length} Bible chapters from vault`);
} catch (error) {
console.error('Error loading Bible chapters from vault:', error);
}
}
console.log(`Loaded ${Object.keys(this.htmlFormattedBible).length} Bible chapters from vault`);
} catch (error) {
console.error('Error loading Bible chapters from vault:', error);
}
}
/**
* Check if data is in ESV API format
*/
public isESVApiFormat(data: any): boolean {
return data &&
typeof data === 'object' &&
data.canonical !== undefined &&
data.passages !== undefined &&
Array.isArray(data.passages) &&
data.passages.length > 0;
}
/**
* Check if data is in ESV API format
*/
public isESVApiFormat(data: any): boolean {
return data &&
typeof data === 'object' &&
data.canonical !== undefined &&
data.passages !== undefined &&
Array.isArray(data.passages) &&
data.passages.length > 0;
}
/**
* Create a DOM-friendly error message for missing token or API errors
*/
private createErrorMessageContent(doc: Document, type: 'missing-token' | 'api-error', message: string = '', details: string = ''): HTMLElement {
// We're required to return a string here because it's part of the ESV API response structure
// This method creates the content in a DOM-safe way then serializes to a string
const container = doc.createElement('div');
container.className = type === 'missing-token' ? 'bible-missing-token-warning' : 'bible-api-error';
/**
* Create a DOM-friendly error message for missing token or API errors
*/
private createErrorMessageContent(doc: Document, type: 'missing-token' | 'api-error', message: string = '', details: string = ''): HTMLElement {
// We're required to return a string here because it's part of the ESV API response structure
// This method creates the content in a DOM-safe way then serializes to a string
const container = doc.createElement('div');
container.className = type === 'missing-token' ? 'bible-missing-token-warning' : 'bible-api-error';
const titleEl = doc.createElement('p');
const titleStrong = doc.createElement('strong');
titleStrong.textContent = type === 'missing-token' ? 'ESV API Token Not Set' : 'Error Loading Bible Passage';
titleEl.appendChild(titleStrong);
container.appendChild(titleEl);
const titleEl = doc.createElement('p');
const titleStrong = doc.createElement('strong');
titleStrong.textContent = type === 'missing-token' ? 'ESV API Token Not Set' : 'Error Loading Bible Passage';
titleEl.appendChild(titleStrong);
container.appendChild(titleEl);
if (message) {
const messageEl = doc.createElement('p');
messageEl.textContent = message;
container.appendChild(messageEl);
}
if (message) {
const messageEl = doc.createElement('p');
messageEl.textContent = message;
container.appendChild(messageEl);
}
if (details) {
const detailsEl = doc.createElement('p');
detailsEl.textContent = details;
container.appendChild(detailsEl);
}
if (details) {
const detailsEl = doc.createElement('p');
detailsEl.textContent = details;
container.appendChild(detailsEl);
}
// For the API token link
if (type === 'missing-token') {
const linkPara = doc.createElement('p');
linkPara.textContent = 'You can request a free token from ';
// For the API token link
if (type === 'missing-token') {
const linkPara = doc.createElement('p');
linkPara.textContent = 'You can request a free token from ';
const link = doc.createElement('a');
link.textContent = 'api.esv.org';
link.href = 'https://api.esv.org/docs/';
link.target = '_blank';
const link = doc.createElement('a');
link.textContent = 'api.esv.org';
link.href = 'https://api.esv.org/docs/';
link.target = '_blank';
linkPara.appendChild(link);
linkPara.appendChild(doc.createTextNode('.'));
container.appendChild(linkPara);
}
linkPara.appendChild(link);
linkPara.appendChild(doc.createTextNode('.'));
container.appendChild(linkPara);
}
return container;
}
return container;
}
/**
* Download Bible content from the ESV API
*/
public async downloadFromESVApi(reference: string): Promise<BiblePassage | null> {
if (!this.plugin.settings.esvApiToken) {
console.warn('ESV API token not set. Cannot download content.');
/**
* Download Bible content from the ESV API
*/
public async downloadFromESVApi(reference: string): Promise<BiblePassage | null> {
if (!this.plugin.settings.esvApiToken) {
console.warn('ESV API token not set. Cannot download content.');
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'missing-token',
'To display Bible passages, you need to set up an ESV API token in the plugin settings.'
).outerHTML,
missingToken: true
};
}
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'missing-token',
'To display Bible passages, you need to set up an ESV API token in the plugin settings.'
).outerHTML,
missingToken: true
};
}
try {
// Encode the reference for the URL
const encodedRef = encodeURIComponent(reference);
try {
// Encode the reference for the URL
const encodedRef = encodeURIComponent(reference);
// Build the API URL with parameters
const apiUrl = `https://api.esv.org/v3/passage/html/?q=${encodedRef}&include-passage-references=false&include-verse-numbers=true&include-first-verse-numbers=true&include-footnotes=true&include-headings=true`;
// Build the API URL with parameters
const apiUrl = `https://api.esv.org/v3/passage/html/?q=${encodedRef}&include-passage-references=false&include-verse-numbers=true&include-first-verse-numbers=true&include-footnotes=true&include-headings=true`;
// Make the request
const response = await requestUrl({
url: apiUrl,
method: 'GET',
headers: {
'Authorization': `Token ${this.plugin.settings.esvApiToken}`
}
});
// Make the request
const response = await requestUrl({
url: apiUrl,
method: 'GET',
headers: {
'Authorization': `Token ${this.plugin.settings.esvApiToken}`
}
});
// Check if the request was successful
if (response.status === 200) {
const data = response.json as ESVApiResponse;
// Check if the request was successful
if (response.status === 200) {
const data = response.json as ESVApiResponse;
// Save the response to a file
await this.saveESVApiResponse(reference, data);
// Save the response to a file
await this.saveESVApiResponse(reference, data);
// Return the content
return {
reference: data.canonical,
verses: [], // Empty since we're using HTML
htmlContent: data.passages[0]
};
} else {
console.error(`ESV API request failed with status ${response.status}: ${response.text}`);
// Return the content
return {
reference: data.canonical,
verses: [], // Empty since we're using HTML
htmlContent: data.passages[0]
};
} else {
console.error(`ESV API request failed with status ${response.status}: ${response.text}`);
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'api-error',
'Failed to load the passage from the ESV API.',
`Status: ${response.status}. Please check your API token in the plugin settings.`
).outerHTML
};
}
} catch (error) {
console.error('Error downloading from ESV API:', error);
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'api-error',
'Failed to load the passage from the ESV API.',
`Status: ${response.status}. Please check your API token in the plugin settings.`
).outerHTML
};
}
} catch (error) {
console.error('Error downloading from ESV API:', error);
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'api-error',
'An error occurred when trying to access the ESV API.',
'Please check your internet connection and API token.'
).outerHTML
};
}
}
return {
reference: reference,
verses: [],
htmlContent: this.createErrorMessageContent(
document,
'api-error',
'An error occurred when trying to access the ESV API.',
'Please check your internet connection and API token.'
).outerHTML
};
}
}
/**
* Save ESV API response to a file
*/
private async saveESVApiResponse(reference: string, data: ESVApiResponse): Promise<void> {
try {
// Extract book and chapter from the canonical reference
const parts = data.canonical.split(' ');
if (parts.length < 2) return;
/**
* Save ESV API response to a file
*/
private async saveESVApiResponse(reference: string, data: ESVApiResponse): Promise<void> {
try {
// Extract book and chapter from the canonical reference
const parts = data.canonical.split(' ');
if (parts.length < 2) return;
const book = parts.slice(0, -1).join(' ');
// Create the directory structure with version subdirectory
const fullPath = this.getFullContentPath();
const bookPath = `${fullPath}/${book}`;
await this.ensureVaultDirectoryExists(fullPath);
await this.ensureVaultDirectoryExists(bookPath);
// Create the directory structure with version subdirectory
const fullPath = this.getFullContentPath();
const bookPath = `${fullPath}/${book}`;
await this.ensureVaultDirectoryExists(fullPath);
await this.ensureVaultDirectoryExists(bookPath);
// Save the raw API response as JSON
const jsonPath = `${bookPath}/${data.canonical}.json`;
await this.plugin.app.vault.adapter.write(jsonPath, JSON.stringify(data));
} catch (error) {
console.error('Error saving ESV API response:', error);
}
}
// Save the raw API response as JSON
const jsonPath = `${bookPath}/${data.canonical}.json`;
await this.plugin.app.vault.adapter.write(jsonPath, JSON.stringify(data));
} catch (error) {
console.error('Error saving ESV API response:', error);
}
}
/**
* Ensure vault directory exists, creating it if necessary
*/
private async ensureVaultDirectoryExists(path: string): Promise<void> {
const parts = path.split('/').filter(p => p.length > 0);
let currentPath = '';
/**
* Ensure vault directory exists, creating it if necessary
*/
private async ensureVaultDirectoryExists(path: string): Promise<void> {
const parts = path.split('/').filter(p => p.length > 0);
let currentPath = '';
for (const part of parts) {
currentPath += (currentPath ? '/' : '') + part;
if (!(await this.plugin.app.vault.adapter.exists(currentPath))) {
await this.plugin.app.vault.adapter.mkdir(currentPath);
}
}
}
for (const part of parts) {
currentPath += (currentPath ? '/' : '') + part;
if (!(await this.plugin.app.vault.adapter.exists(currentPath))) {
await this.plugin.app.vault.adapter.mkdir(currentPath);
}
}
}
/**
* Ensures Bible data is loaded, downloading if necessary
*/
public async ensureBibleData(): Promise<void> {
try {
// Check if we have data by looking for specific files in the vault
const hasData = await this.checkBibleDataExists();
/**
* Ensures Bible data is loaded, downloading if necessary
*/
public async ensureBibleData(): Promise<void> {
try {
// Check if we have data by looking for specific files in the vault
const hasData = await this.checkBibleDataExists();
if (!hasData) {
console.log('Bible data not found, loading from vault...');
await this.loadBibleChaptersFromVault();
} else {
console.log('Bible data already exists');
}
} catch (error) {
console.error('Error ensuring Bible data:', error);
throw error;
}
}
if (!hasData) {
console.log('Bible data not found, loading from vault...');
await this.loadBibleChaptersFromVault();
} else {
console.log('Bible data already exists');
}
} catch (error) {
console.error('Error ensuring Bible data:', error);
throw error;
}
}
/**
* Check if Bible data exists in the vault
*/
private async checkBibleDataExists(): Promise<boolean> {
try {
// Check for a common book like Genesis
const genesisPath = `${this.getFullContentPath()}/Genesis/Genesis 1.md`;
return await this.plugin.app.vault.adapter.exists(genesisPath);
} catch (error) {
console.error('Error checking if Bible data exists:', error);
return false;
}
}
/**
* Check if Bible data exists in the vault
*/
private async checkBibleDataExists(): Promise<boolean> {
try {
// Check for a common book like Genesis
const genesisPath = `${this.getFullContentPath()}/Genesis/Genesis 1.md`;
return await this.plugin.app.vault.adapter.exists(genesisPath);
} catch (error) {
console.error('Error checking if Bible data exists:', error);
return false;
}
}
}

View file

@ -1,272 +1,272 @@
import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
import {App, Notice, PluginSettingTab, Setting} from 'obsidian';
import DisciplesJournalPlugin from '../core/DisciplesJournalPlugin';
import { THEME_PRESETS } from '../components/BibleStyles';
import {THEME_PRESETS} from '../components/BibleStyles';
export interface DisciplesJournalSettings {
displayInlineVerses: boolean;
displayFullPassages: boolean;
bibleTextFontSize: string;
wordsOfChristColor: string;
verseNumberColor: string;
headingColor: string;
blockIndentation: string;
preferredBibleVersion: string;
esvApiToken: string;
downloadOnDemand: boolean;
bibleContentVaultPath: string;
stylePreset: string;
showNavigationForVerses: boolean;
displayInlineVerses: boolean;
displayFullPassages: boolean;
bibleTextFontSize: string;
wordsOfChristColor: string;
verseNumberColor: string;
headingColor: string;
blockIndentation: string;
preferredBibleVersion: string;
esvApiToken: string;
downloadOnDemand: boolean;
bibleContentVaultPath: string;
stylePreset: string;
showNavigationForVerses: boolean;
}
export const DEFAULT_SETTINGS: DisciplesJournalSettings = {
displayInlineVerses: true,
displayFullPassages: true,
bibleTextFontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em',
preferredBibleVersion: 'ESV',
esvApiToken: '',
downloadOnDemand: true,
bibleContentVaultPath: 'Bible',
stylePreset: 'default',
showNavigationForVerses: false
displayInlineVerses: true,
displayFullPassages: true,
bibleTextFontSize: '100%',
wordsOfChristColor: 'var(--text-accent)',
verseNumberColor: 'var(--text-accent)',
headingColor: 'var(--text-accent)',
blockIndentation: '2em',
preferredBibleVersion: 'ESV',
esvApiToken: '',
downloadOnDemand: true,
bibleContentVaultPath: 'Bible',
stylePreset: 'default',
showNavigationForVerses: false
};
export class DisciplesJournalSettingsTab extends PluginSettingTab {
plugin: DisciplesJournalPlugin;
plugin: DisciplesJournalPlugin;
constructor(app: App, plugin: DisciplesJournalPlugin) {
super(app, plugin);
this.plugin = plugin;
}
constructor(app: App, plugin: DisciplesJournalPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
display(): void {
const {containerEl} = this;
containerEl.empty();
new Setting(containerEl).setName('Display Customization').setHeading();
new Setting(containerEl).setName('Display Customization').setHeading();
new Setting(containerEl)
.setName('Display Inline Verses')
.setDesc('Enable rendering of inline Bible references (in `code blocks`).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.displayInlineVerses)
.onChange(async (value) => {
this.plugin.settings.displayInlineVerses = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Display Inline Verses')
.setDesc('Enable rendering of inline Bible references (in `code blocks`).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.displayInlineVerses)
.onChange(async (value) => {
this.plugin.settings.displayInlineVerses = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Display Full Passages')
.setDesc('Enable rendering of full Bible passages (in ```bible code blocks).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.displayFullPassages)
.onChange(async (value) => {
this.plugin.settings.displayFullPassages = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Display Full Passages')
.setDesc('Enable rendering of full Bible passages (in ```bible code blocks).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.displayFullPassages)
.onChange(async (value) => {
this.plugin.settings.displayFullPassages = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Show Navigation for Verses')
.setDesc('Show chapter navigation when displaying specific verses (by default, navigation only shows for full chapters).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showNavigationForVerses)
.onChange(async (value) => {
this.plugin.settings.showNavigationForVerses = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Show Navigation for Verses')
.setDesc('Show chapter navigation when displaying specific verses (by default, navigation only shows for full chapters).')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showNavigationForVerses)
.onChange(async (value) => {
this.plugin.settings.showNavigationForVerses = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Style Preset')
.setDesc('Choose a predefined style preset for Bible content.')
.addDropdown(dropdown => {
// Add all theme presets
Object.keys(THEME_PRESETS).forEach(preset => {
dropdown.addOption(preset, preset.charAt(0).toUpperCase() + preset.slice(1));
});
new Setting(containerEl)
.setName('Style Preset')
.setDesc('Choose a predefined style preset for Bible content.')
.addDropdown(dropdown => {
// Add all theme presets
Object.keys(THEME_PRESETS).forEach(preset => {
dropdown.addOption(preset, preset.charAt(0).toUpperCase() + preset.slice(1));
});
dropdown.setValue(this.plugin.settings.stylePreset);
dropdown.onChange(async (value) => {
this.plugin.settings.stylePreset = value;
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
});
});
dropdown.setValue(this.plugin.settings.stylePreset);
dropdown.onChange(async (value) => {
this.plugin.settings.stylePreset = value;
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
});
});
new Setting(containerEl)
.setName('Bible Text Font Size')
.setDesc('Set the font size for Bible verses and passages.')
.addDropdown(dropdown => dropdown
.addOption('80%', 'Smaller (80%)')
.addOption('90%', 'Small (90%)')
.addOption('100%', 'Normal (100%)')
.addOption('110%', 'Large (110%)')
.addOption('120%', 'Larger (120%)')
.setValue(this.plugin.settings.bibleTextFontSize)
.onChange(async (value) => {
this.plugin.settings.bibleTextFontSize = value;
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
})
);
new Setting(containerEl)
.setName('Bible Text Font Size')
.setDesc('Set the font size for Bible verses and passages.')
.addDropdown(dropdown => dropdown
.addOption('80%', 'Smaller (80%)')
.addOption('90%', 'Small (90%)')
.addOption('100%', 'Normal (100%)')
.addOption('110%', 'Large (110%)')
.addOption('120%', 'Larger (120%)')
.setValue(this.plugin.settings.bibleTextFontSize)
.onChange(async (value) => {
this.plugin.settings.bibleTextFontSize = value;
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
})
);
new Setting(containerEl).setName('Text Styling').setHeading();
new Setting(containerEl).setName('Text Styling').setHeading();
new Setting(containerEl)
.setName('Words of Christ Color')
.setDesc('Set the color for Words of Christ (use `var(--text-normal)` for no special color).')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.wordsOfChristColor)
.onChange(async (value) => {
this.plugin.settings.wordsOfChristColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Words of Christ Color')
.setDesc('Set the color for Words of Christ (use `var(--text-normal)` for no special color).')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.wordsOfChristColor)
.onChange(async (value) => {
this.plugin.settings.wordsOfChristColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Verse Number Color')
.setDesc('Set the color for verse numbers.')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.verseNumberColor)
.onChange(async (value) => {
this.plugin.settings.verseNumberColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Verse Number Color')
.setDesc('Set the color for verse numbers.')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.verseNumberColor)
.onChange(async (value) => {
this.plugin.settings.verseNumberColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Heading Color')
.setDesc('Set the color for Bible passage headings.')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.headingColor)
.onChange(async (value) => {
this.plugin.settings.headingColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Heading Color')
.setDesc('Set the color for Bible passage headings.')
.addText(text => text
.setPlaceholder('var(--text-accent)')
.setValue(this.plugin.settings.headingColor)
.onChange(async (value) => {
this.plugin.settings.headingColor = value || 'var(--text-accent)';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Block Indentation')
.setDesc('Set the indentation for block sections.')
.addText(text => text
.setPlaceholder('2em')
.setValue(this.plugin.settings.blockIndentation)
.onChange(async (value) => {
this.plugin.settings.blockIndentation = value || '2em';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl)
.setName('Block Indentation')
.setDesc('Set the indentation for block sections.')
.addText(text => text
.setPlaceholder('2em')
.setValue(this.plugin.settings.blockIndentation)
.onChange(async (value) => {
this.plugin.settings.blockIndentation = value || '2em';
await this.plugin.saveSettings();
this.plugin.updateBibleStyles();
}));
new Setting(containerEl).setName('Bible').setHeading();
new Setting(containerEl).setName('Bible').setHeading();
new Setting(containerEl)
.setName('Preferred Bible Version')
.setDesc('Select your preferred Bible version (only ESV currently supported).')
.addDropdown(dropdown => dropdown
.addOption('ESV', 'English Standard Version (ESV)')
.setValue(this.plugin.settings.preferredBibleVersion)
.onChange(async (value) => {
this.plugin.settings.preferredBibleVersion = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Preferred Bible Version')
.setDesc('Select your preferred Bible version (only ESV currently supported).')
.addDropdown(dropdown => dropdown
.addOption('ESV', 'English Standard Version (ESV)')
.setValue(this.plugin.settings.preferredBibleVersion)
.onChange(async (value) => {
this.plugin.settings.preferredBibleVersion = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Bible Content Vault Path')
.setDesc('Root path in your vault where Bible content will be stored. Each translation will be stored in a subdirectory.')
.addText(text => text
.setPlaceholder('Bible')
.setValue(this.plugin.settings.bibleContentVaultPath)
.onChange(async (value) => {
this.plugin.settings.bibleContentVaultPath = value || 'Bible';
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Bible Content Vault Path')
.setDesc('Root path in your vault where Bible content will be stored. Each translation will be stored in a subdirectory.')
.addText(text => text
.setPlaceholder('Bible')
.setValue(this.plugin.settings.bibleContentVaultPath)
.onChange(async (value) => {
this.plugin.settings.bibleContentVaultPath = value || 'Bible';
await this.plugin.saveSettings();
}));
new Setting(containerEl).setName('ESV API').setHeading();
new Setting(containerEl).setName('ESV API').setHeading();
const apiInfoDiv = containerEl.createDiv({ cls: 'disciples-journal-api-info' });
const apiInfoDiv = containerEl.createDiv({cls: 'disciples-journal-api-info'});
apiInfoDiv.createEl('p', {
text: 'The ESV API allows this plugin to download and display Bible passages from the ESV translation.'
});
apiInfoDiv.createEl('p', {
text: 'The ESV API allows this plugin to download and display Bible passages from the ESV translation.'
});
apiInfoDiv.createEl('p', {
text: 'To get a free ESV API token:',
attr: { style: 'font-weight: bold;' }
});
apiInfoDiv.createEl('p', {
text: 'To get a free ESV API token:',
attr: {style: 'font-weight: bold;'}
});
const instructionsList = apiInfoDiv.createEl('ol');
const instructionsList = apiInfoDiv.createEl('ol');
const steps = [
'Visit api.esv.org',
'Sign up for a free account',
'After logging in, go to "API Keys" in your account',
'Create a new token and copy it here'
];
const steps = [
'Visit api.esv.org',
'Sign up for a free account',
'After logging in, go to "API Keys" in your account',
'Create a new token and copy it here'
];
steps.forEach(step => {
if (step === 'Visit api.esv.org') {
const listItem = instructionsList.createEl('li');
listItem.createEl('span', { text: 'Visit ' });
listItem.createEl('a', {
text: 'api.esv.org',
href: 'https://api.esv.org/docs/',
attr: { target: '_blank' }
});
} else {
instructionsList.createEl('li', { text: step });
}
});
steps.forEach(step => {
if (step === 'Visit api.esv.org') {
const listItem = instructionsList.createEl('li');
listItem.createEl('span', {text: 'Visit '});
listItem.createEl('a', {
text: 'api.esv.org',
href: 'https://api.esv.org/docs/',
attr: {target: '_blank'}
});
} else {
instructionsList.createEl('li', {text: step});
}
});
apiInfoDiv.createEl('p', {
text: 'With a valid token, the plugin can download and display Bible passages directly in your notes.'
});
apiInfoDiv.createEl('p', {
text: 'With a valid token, the plugin can download and display Bible passages directly in your notes.'
});
new Setting(containerEl)
.setName('ESV API Token')
.setDesc('Enter your ESV API token from api.esv.org.')
.addText(text => text
.setPlaceholder('Enter your ESV API token')
.setValue(this.plugin.settings.esvApiToken)
.onChange(async (value) => {
this.plugin.settings.esvApiToken = value;
await this.plugin.saveSettings();
if (value) {
new Notice('ESV API token updated', 2000);
}
}));
new Setting(containerEl)
.setName('ESV API Token')
.setDesc('Enter your ESV API token from api.esv.org.')
.addText(text => text
.setPlaceholder('Enter your ESV API token')
.setValue(this.plugin.settings.esvApiToken)
.onChange(async (value) => {
this.plugin.settings.esvApiToken = value;
await this.plugin.saveSettings();
if (value) {
new Notice('ESV API token updated', 2000);
}
}));
new Setting(containerEl)
.setName('Download on Demand')
.setDesc('Enable automatic downloading of Bible content when requested.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.downloadOnDemand)
.onChange(async (value) => {
this.plugin.settings.downloadOnDemand = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Download on Demand')
.setDesc('Enable automatic downloading of Bible content when requested.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.downloadOnDemand)
.onChange(async (value) => {
this.plugin.settings.downloadOnDemand = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Bible Data Status')
.setDesc('Click to reload Bible data from source')
.addButton(button => button
.setButtonText('Reload Bible Data')
.onClick(async () => {
button.setButtonText('Loading...');
button.setDisabled(true);
await this.plugin.loadBibleData();
button.setButtonText('Reload Bible Data');
button.setDisabled(false);
this.display(); // Refresh the settings panel
}));
}
new Setting(containerEl)
.setName('Bible Data Status')
.setDesc('Click to reload Bible data from source')
.addButton(button => button
.setButtonText('Reload Bible Data')
.onClick(async () => {
button.setButtonText('Loading...');
button.setDisabled(true);
await this.plugin.loadBibleData();
button.setButtonText('Reload Bible Data');
button.setDisabled(false);
this.display(); // Refresh the settings panel
}));
}
}

18
src/types.d.ts vendored
View file

@ -1,17 +1,17 @@
declare module '*.json' {
const value: any;
export default value;
const value: any;
export default value;
}
// Define the structure of the Bible data for better type checking
interface ESVVerse {
pk: number;
translation: string;
book: number | string;
chapter: number;
verse: number;
text: string;
comment?: string;
pk: number;
translation: string;
book: number | string;
chapter: number;
verse: number;
text: string;
comment?: string;
}
declare const ESV: ESVVerse[];

View file

@ -1,29 +1,29 @@
import { BiblePassage } from "../services/BibleContentService";
import {BiblePassage} from "../services/BibleContentService";
/**
* Utility for formatting Bible content consistently across the application
*/
export class BibleFormatter {
/**
* Format chapter content as Markdown
*/
public static formatChapterContent(passage: BiblePassage): string {
if (!passage) return "# Error: No passage content\n\nThe requested passage could not be loaded.";
/**
* Format chapter content as Markdown
*/
public static formatChapterContent(passage: BiblePassage): string {
if (!passage) return "# Error: No passage content\n\nThe requested passage could not be loaded.";
let content = "";
let content = "";
// Add code block for rendering
content += "```bible\n";
content += passage.reference;
content += "\n```\n\n";
// Add code block for rendering
content += "```bible\n";
content += passage.reference;
content += "\n```\n\n";
// Alternatively, add each verse separately
if (passage.verses && passage.verses.length > 0) {
for (const verse of passage.verses) {
content += `**${verse.verse}** ${verse.text}\n\n`;
}
}
// Alternatively, add each verse separately
if (passage.verses && passage.verses.length > 0) {
for (const verse of passage.verses) {
content += `**${verse.verse}** ${verse.text}\n\n`;
}
}
return content;
}
return content;
}
}