diff --git a/main.ts b/main.ts index 85611ea..9b0a0ff 100644 --- a/main.ts +++ b/main.ts @@ -1,4 +1,3 @@ -import { Plugin } from 'obsidian'; import DisciplesJournalPlugin from './src/core/DisciplesJournalPlugin'; -export default DisciplesJournalPlugin; \ No newline at end of file +export default DisciplesJournalPlugin; diff --git a/src/components/BibleNavigation.ts b/src/components/BibleNavigation.ts index 55967eb..25bb9d4 100644 --- a/src/components/BibleNavigation.ts +++ b/src/components/BibleNavigation.ts @@ -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; - - // 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 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' }); - - // 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); + } + + /** + * 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); + + // 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; + + // 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); 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 selectorContainer = navEl.createDiv({ - cls: 'nav-book-data dj-hidden' - }); - - // 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); - - // 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); - - // When book selection changes, update chapter dropdown - bookDropdown.onChange(value => { - this.populateChapterDropdown(chapterDropdown, value, 1); - }); - - // Add Go button + const selectorEl = navEl.createSpan({ + cls: 'nav-book-selector', + text: `📖 ${book} ${chapter}` + }); + + const selectorContainer = navEl.createDiv({ + cls: 'nav-book-data dj-hidden' + }); + + // 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); + + // 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); + + // When book selection changes, update chapter dropdown + bookDropdown.onChange(value => { + this.populateChapterDropdown(chapterDropdown, value, 1); + }); + + // 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'); - }); - - // 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]; + 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]; 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(); - - // 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()); - } - - // Set the selected chapter - dropdown.setValue(selectedChapter.toString()); - } - - /** - * Navigate to a specific chapter - */ - public async navigateToChapter(book: string, chapter: number): Promise { - try { + * 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); + + // Add chapter options + for (let i = 1; i <= chapterCount; i++) { + dropdown.addOption(i.toString(), i.toString()); + } + + // Set the selected chapter + dropdown.setValue(selectedChapter.toString()); + } + + /** + * Navigate to a specific chapter + */ + public async navigateToChapter(book: string, chapter: number): Promise { + 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}`); + } + } } diff --git a/src/components/BibleStyles.ts b/src/components/BibleStyles.ts index e57b4dc..797b7fd 100644 --- a/src/components/BibleStyles.ts +++ b/src/components/BibleStyles.ts @@ -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 = { - 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 = { */ export class BibleStyles { - 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 { - - // 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]; + constructor() { + // No initial style element creation needed + } - // 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 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 { - // 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); - } + // Get the preset, defaulting to 'default' if not found + const preset = THEME_PRESETS[presetName] || THEME_PRESETS.default; - /** - * 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'); - } -} \ No newline at end of file + // 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 + }; + + // 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); + } + + /** + * 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'); + } +} diff --git a/src/core/BibleEventHandlers.ts b/src/core/BibleEventHandlers.ts index fac21ad..b91cf1e 100644 --- a/src/core/BibleEventHandlers.ts +++ b/src/core/BibleEventHandlers.ts @@ -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; - - constructor(bibleReferenceRenderer: BibleReferenceRenderer) { - this.bibleReferenceRenderer = bibleReferenceRenderer; - } + private bibleReferenceRenderer: BibleReferenceRenderer; + private previewPopper: HTMLElement | null = null; + + 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; - - const target = event.target as HTMLElement; - if (!target || !target.closest) return; - - const referenceEl = target.closest('.bible-reference') as HTMLElement; - if (!referenceEl) return; - - const referenceText = referenceEl.textContent; - if (!referenceText) return; - - 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'); - }); - - // 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; - - // 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); - } - } - - /** - * 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; - } - - 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 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); - } - - /** - * 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; - } - } + * 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 referenceEl = target.closest('.bible-reference') as HTMLElement; + if (!referenceEl) return; + + const referenceText = referenceEl.textContent; + if (!referenceText) return; + + 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'); + }); + + // 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; + + // 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); + } + } + + /** + * 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; + } + + 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 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); + } + + /** + * 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; + } + } } diff --git a/src/core/BibleMarkupProcessor.ts b/src/core/BibleMarkupProcessor.ts index e1e584b..96bf818 100644 --- a/src/core/BibleMarkupProcessor.ts +++ b/src/core/BibleMarkupProcessor.ts @@ -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; - - 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; - } - - 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 { - // 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); - } - } -} \ No newline at end of file + private bibleReferenceRenderer: BibleReferenceRenderer; + private settings: DisciplesJournalSettings; + + 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; + } + + 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 { + // 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); + } + } +} diff --git a/src/core/BibleReference.ts b/src/core/BibleReference.ts index 5bffd9e..ec63c8d 100644 --- a/src/core/BibleReference.ts +++ b/src/core/BibleReference.ts @@ -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; - - /** - * 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; - } + 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; + } /** * 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}`; - - // 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}`; - } - } - - // 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}`; - } - } - - 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 - ); - } - - /** - * 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; - } - - /** - * 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 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); - } + * 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 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}`; + + // Add end verse if present for a cross-chapter reference + if (this.endVerse !== undefined) { + reference += `:${this.endVerse}`; + } + } + + 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 + ); + } + + /** + * 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; + } + + /** + * 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 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); + } } diff --git a/src/core/DisciplesJournalPlugin.ts b/src/core/DisciplesJournalPlugin.ts index d8ac372..2c25478 100644 --- a/src/core/DisciplesJournalPlugin.ts +++ b/src/core/DisciplesJournalPlugin.ts @@ -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); + } } diff --git a/src/services/BibleBookFiles.ts b/src/services/BibleBookFiles.ts index c51131c..91b48a8 100644 --- a/src/services/BibleBookFiles.ts +++ b/src/services/BibleBookFiles.ts @@ -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; - } - - /** - * 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; - } + bibleContentService: BibleContentService, + ) { + this.plugin = plugin; + this.bibleContentService = bibleContentService; + } - // Get full content path including version - const fullPath = this.getFullContentPath(); - const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`; + /** + * 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; + } - // Check if note exists - const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath); + // Get full content path including version + const fullPath = this.getFullContentPath(); + const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`; - if (!fileExists && this.plugin.settings.downloadOnDemand) { - // Create the note with content from the API - await this.createChapterNote(parsedRef); - } + // Check if note exists + const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath); - // Try opening the note - const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath); + if (!fileExists && this.plugin.settings.downloadOnDemand) { + // Create the note with content from the API + await this.createChapterNote(parsedRef); + } - if (file && file instanceof TFile) { - const leaf = this.plugin.app.workspace.getLeaf(false); - await leaf.openFile(file); + // Try opening the note + const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath); - // 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 (file && file instanceof TFile) { + const leaf = this.plugin.app.workspace.getLeaf(false); + await leaf.openFile(file); - /** - * Create a new chapter note - */ + // 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 + */ 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 diff --git a/src/services/BibleContentService.ts b/src/services/BibleContentService.ts index fef81c0..55bc1a8 100644 --- a/src/services/BibleContentService.ts +++ b/src/services/BibleContentService.ts @@ -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; - - 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 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; - - // 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 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; + + // 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 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; + } + } - /** - * 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 { - 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; - } - } + * 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 { + 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; + } + } } diff --git a/src/services/BookNames.ts b/src/services/BookNames.ts index e9cef2b..9a0a0c7 100644 --- a/src/services/BookNames.ts +++ b/src/services/BookNames.ts @@ -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; - } - - // 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; - } - - // 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; - } - - /** - * 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]; - } - - // 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]; - - // 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]; - - // 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]; - - // Check if this is a valid book name - const standardizedName = BookNames.normalize(bookName); - if (standardizedName) { - longestMatch = bookName; - } - } - - 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 - } + /** + * Standardize a book name to its canonical form + */ + public static normalize(bookName: string): string | null { + if (!bookName) { + return null; + } - /** - * Get the ordered list of book names - */ - public static getBookOrder(): string[] { - return [...BookNames.bookOrder]; // Return a copy to prevent modification - } + // 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; + } + + // 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; + } + + /** + * 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]; + } + + // 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]; + + // 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]; + + // 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]; + + // Check if this is a valid book name + const standardizedName = BookNames.normalize(bookName); + if (standardizedName) { + longestMatch = bookName; + } + } + + 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 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 - }; - - // 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 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 _instance:BookNames = new BookNames(); + // 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 bookNameMap: Map = new Map(); + private static _instance: BookNames = new BookNames(); - private constructor() { - this.initializeBookNameMap(); - } + private bookNameMap: Map = new Map(); - /** - * 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"]); + private constructor() { + this.initializeBookNameMap(); + } - // 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"]); - } + /** + * 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"]); - /** - * 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); - } - } + // 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 alternate names + for (const alternateName of alternateNames) { + this.bookNameMap.set(alternateName.toLowerCase(), standardName); + } + } } diff --git a/src/services/ESVApiService.ts b/src/services/ESVApiService.ts index 2cc8006..33de978 100644 --- a/src/services/ESVApiService.ts +++ b/src/services/ESVApiService.ts @@ -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 { - 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; - } - - // 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 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]; - - 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); - } - } + /** + * Load a collection of HTML formatted Bible chapters from files in the vault + */ + public async loadBibleChaptersFromVault(): Promise { + try { + // Get the path where Bible content is stored + const fullPath = this.getFullContentPath(); - /** - * 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 path exists + if (!(await this.plugin.app.vault.adapter.exists(fullPath))) { + console.log(`Bible content directory ${fullPath} does not exist yet`); + return; + } - /** - * 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); - - 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); - } - - // 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'; - - linkPara.appendChild(link); - linkPara.appendChild(doc.createTextNode('.')); - container.appendChild(linkPara); - } - - return container; - } + // Get all book directories + const bookDirs = await this.plugin.app.vault.adapter.list(fullPath); - /** - * Download Bible content from the ESV API - */ - public async downloadFromESVApi(reference: string): Promise { - 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 - }; - } - - 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`; - - // 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; - - // 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 { - 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 - }; - } - } + // Process each book directory + for (const bookDir of bookDirs.folders) { + // Get all chapter files + const files = await this.plugin.app.vault.adapter.list(bookDir); - /** - * Save ESV API response to a file - */ - private async saveESVApiResponse(reference: string, data: ESVApiResponse): Promise { - try { - // Extract book and chapter from the canonical reference - const parts = data.canonical.split(' '); - if (parts.length < 2) return; + // 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]; + + 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); + } + } + + /** + * 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'; + + 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 (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 '; + + 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); + } + + return container; + } + + /** + * Download Bible content from the ESV API + */ + public async downloadFromESVApi(reference: string): Promise { + 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 + }; + } + + 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`; + + // 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; + + // 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 { + 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 + }; + } + } + + /** + * Save ESV API response to a file + */ + private async saveESVApiResponse(reference: string, data: ESVApiResponse): Promise { + 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); - - // 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 { - 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); - } - } - } + // Create the directory structure with version subdirectory + const fullPath = this.getFullContentPath(); + const bookPath = `${fullPath}/${book}`; + await this.ensureVaultDirectoryExists(fullPath); + await this.ensureVaultDirectoryExists(bookPath); - /** - * Ensures Bible data is loaded, downloading if necessary - */ - public async ensureBibleData(): Promise { - 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; - } - } - - /** - * Check if Bible data exists in the vault - */ - private async checkBibleDataExists(): Promise { - 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; - } - } + // 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 { + 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); + } + } + } + + /** + * Ensures Bible data is loaded, downloading if necessary + */ + public async ensureBibleData(): Promise { + 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; + } + } + + /** + * Check if Bible data exists in the vault + */ + private async checkBibleDataExists(): Promise { + 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; + } + } } diff --git a/src/settings/DisciplesJournalSettings.ts b/src/settings/DisciplesJournalSettings.ts index 0ccc5ce..1620d83 100644 --- a/src/settings/DisciplesJournalSettings.ts +++ b/src/settings/DisciplesJournalSettings.ts @@ -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('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(); - }); - }); + 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('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('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('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('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('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('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(); - - 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: 'To get a free ESV API token:', - attr: { style: 'font-weight: bold;' } - }); - - 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' - ]; - - 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.' - }); - - 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('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 - })); - } + 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('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('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('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('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('ESV API').setHeading(); + + 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: 'To get a free ESV API token:', + attr: {style: 'font-weight: bold;'} + }); + + 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' + ]; + + 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.' + }); + + 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('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 + })); + } } diff --git a/src/types.d.ts b/src/types.d.ts index a943040..072ed64 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -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[]; \ No newline at end of file +declare const ESV: ESVVerse[]; diff --git a/src/utils/BibleFormatter.ts b/src/utils/BibleFormatter.ts index 6fcc8b1..55b1f63 100644 --- a/src/utils/BibleFormatter.ts +++ b/src/utils/BibleFormatter.ts @@ -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."; - - let content = ""; - - // 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`; - } - } - - return content; - } + /** + * 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 = ""; + + // 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`; + } + } + + return content; + } }