mirror of
https://github.com/scotttomaszewski/obsidian-disciples-journal.git
synced 2026-07-22 05:42:13 +00:00
Formatting
This commit is contained in:
parent
93c26d5a25
commit
95a34cf070
14 changed files with 1658 additions and 1653 deletions
3
main.ts
3
main.ts
|
|
@ -1,4 +1,3 @@
|
|||
import { Plugin } from 'obsidian';
|
||||
import DisciplesJournalPlugin from './src/core/DisciplesJournalPlugin';
|
||||
|
||||
export default DisciplesJournalPlugin;
|
||||
export default DisciplesJournalPlugin;
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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<void> {
|
||||
try {
|
||||
await this.bibleBookFiles.openChapterNote(new BibleReference(book, chapter).toString());
|
||||
} catch (error) {
|
||||
console.error('Error navigating to chapter:', error);
|
||||
new Notice(`Error navigating to chapter: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error navigating to chapter:', error);
|
||||
new Notice(`Error navigating to chapter: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,73 +2,73 @@
|
|||
* Interface for Bible styling options
|
||||
*/
|
||||
export interface BibleStyleOptions {
|
||||
fontSize: string;
|
||||
wordsOfChristColor: string;
|
||||
verseNumberColor: string;
|
||||
headingColor: string;
|
||||
blockIndentation: string;
|
||||
fontSize: string;
|
||||
wordsOfChristColor: string;
|
||||
verseNumberColor: string;
|
||||
headingColor: string;
|
||||
blockIndentation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme preset definitions
|
||||
*/
|
||||
export interface ThemePreset {
|
||||
light: BibleStyleOptions;
|
||||
dark: BibleStyleOptions;
|
||||
light: BibleStyleOptions;
|
||||
dark: BibleStyleOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available theme presets
|
||||
*/
|
||||
export const THEME_PRESETS: Record<string, ThemePreset> = {
|
||||
default: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: 'var(--text-accent)',
|
||||
verseNumberColor: 'var(--text-accent)',
|
||||
headingColor: 'var(--text-accent)',
|
||||
blockIndentation: '2em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: 'var(--text-accent)',
|
||||
verseNumberColor: 'var(--text-accent)',
|
||||
headingColor: 'var(--text-accent)',
|
||||
blockIndentation: '2em'
|
||||
}
|
||||
},
|
||||
classic: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#b91c1c',
|
||||
verseNumberColor: '#2563eb',
|
||||
headingColor: '#4b5563',
|
||||
blockIndentation: '2em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#ef4444',
|
||||
verseNumberColor: '#60a5fa',
|
||||
headingColor: '#9ca3af',
|
||||
blockIndentation: '2em'
|
||||
}
|
||||
},
|
||||
minimal: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#000000',
|
||||
verseNumberColor: '#6b7280',
|
||||
headingColor: '#374151',
|
||||
blockIndentation: '1.5em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#ffffff',
|
||||
verseNumberColor: '#9ca3af',
|
||||
headingColor: '#e5e7eb',
|
||||
blockIndentation: '1.5em'
|
||||
}
|
||||
}
|
||||
default: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: 'var(--text-accent)',
|
||||
verseNumberColor: 'var(--text-accent)',
|
||||
headingColor: 'var(--text-accent)',
|
||||
blockIndentation: '2em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: 'var(--text-accent)',
|
||||
verseNumberColor: 'var(--text-accent)',
|
||||
headingColor: 'var(--text-accent)',
|
||||
blockIndentation: '2em'
|
||||
}
|
||||
},
|
||||
classic: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#b91c1c',
|
||||
verseNumberColor: '#2563eb',
|
||||
headingColor: '#4b5563',
|
||||
blockIndentation: '2em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#ef4444',
|
||||
verseNumberColor: '#60a5fa',
|
||||
headingColor: '#9ca3af',
|
||||
blockIndentation: '2em'
|
||||
}
|
||||
},
|
||||
minimal: {
|
||||
light: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#000000',
|
||||
verseNumberColor: '#6b7280',
|
||||
headingColor: '#374151',
|
||||
blockIndentation: '1.5em'
|
||||
},
|
||||
dark: {
|
||||
fontSize: '100%',
|
||||
wordsOfChristColor: '#ffffff',
|
||||
verseNumberColor: '#9ca3af',
|
||||
headingColor: '#e5e7eb',
|
||||
blockIndentation: '1.5em'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -77,60 +77,60 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
|
|||
*/
|
||||
export class BibleStyles {
|
||||
|
||||
constructor() {
|
||||
// No initial style element creation needed
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
// Check if display of inline verses is enabled in settings
|
||||
if (!this.settings.displayInlineVerses) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
|
||||
} catch (error) {
|
||||
console.error('Error processing inline Bible references:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
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<void> {
|
||||
// Check if display of inline verses is enabled in settings
|
||||
if (!this.settings.displayInlineVerses) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
|
||||
} catch (error) {
|
||||
console.error('Error processing inline Bible references:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<BiblePassage | null> {
|
||||
try {
|
||||
// Parse the reference
|
||||
const parsedRef = BibleReference.parse(referenceString);
|
||||
if (!parsedRef) {
|
||||
console.error(`Invalid reference: ${referenceString}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to get from local Bible data first
|
||||
let passage = this.getPassage(parsedRef);
|
||||
|
||||
// If not available locally and download on demand is enabled, try the API
|
||||
if (!passage && this.plugin.settings.downloadOnDemand) {
|
||||
// Try to get from the ESV API
|
||||
passage = await this.esvApiService.downloadFromESVApi(referenceString);
|
||||
}
|
||||
|
||||
return passage;
|
||||
} catch (error) {
|
||||
console.error('Error getting Bible content:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
* Get a passage by reference
|
||||
* This handles everything from single verses to full chapters
|
||||
*/
|
||||
public getPassage(reference: BibleReference): BiblePassage | null {
|
||||
if (!this.bible) return null;
|
||||
|
||||
try {
|
||||
const normalizedBook = BookNames.normalize(reference.book);
|
||||
if (!normalizedBook) return null;
|
||||
|
||||
// Prepare the result
|
||||
const result: BiblePassage = {
|
||||
reference: reference.toString(),
|
||||
verses: []
|
||||
};
|
||||
|
||||
// Get the chapter
|
||||
const chapterStr = reference.chapter.toString();
|
||||
if (!this.bible[normalizedBook][chapterStr]) return null;
|
||||
|
||||
// If verse is specified, get just that verse or verse range
|
||||
if (reference.verse) {
|
||||
// Single verse
|
||||
if (!reference.endVerse) {
|
||||
const verse = this.getVerse(normalizedBook, reference.chapter, reference.verse);
|
||||
if (verse) {
|
||||
result.verses.push(verse);
|
||||
}
|
||||
}
|
||||
// Verse range
|
||||
else {
|
||||
// Add all verses in the range
|
||||
for (let v = reference.verse; v <= reference.endVerse; v++) {
|
||||
const verse = this.getVerse(normalizedBook, reference.chapter, v);
|
||||
if (verse) {
|
||||
result.verses.push(verse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No verse specified, get the whole chapter
|
||||
else {
|
||||
// Get all verses in the chapter
|
||||
const chapter = this.bible[normalizedBook][chapterStr];
|
||||
if (chapter) {
|
||||
const verseNumbers = Object.keys(chapter).map(v => parseInt(v)).sort((a, b) => a - b);
|
||||
|
||||
for (const verseNum of verseNumbers) {
|
||||
const verse = this.getVerse(normalizedBook, reference.chapter, verseNum);
|
||||
if (verse) {
|
||||
result.verses.push(verse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error getting passage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bible content from any source (local or API)
|
||||
*/
|
||||
public async getBibleContent(referenceString: string): Promise<BiblePassage | null> {
|
||||
try {
|
||||
// Parse the reference
|
||||
const parsedRef = BibleReference.parse(referenceString);
|
||||
if (!parsedRef) {
|
||||
console.error(`Invalid reference: ${referenceString}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, string> = new Map();
|
||||
private static _instance: BookNames = new BookNames();
|
||||
|
||||
private constructor() {
|
||||
this.initializeBookNameMap();
|
||||
}
|
||||
private bookNameMap: Map<string, string> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,329 +1,329 @@
|
|||
import { requestUrl } from "obsidian";
|
||||
import { BiblePassage } from "./BibleContentService";
|
||||
import {requestUrl} from "obsidian";
|
||||
import {BiblePassage} from "./BibleContentService";
|
||||
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
||||
|
||||
/**
|
||||
* Interface for ESV API Response
|
||||
*/
|
||||
export interface ESVApiResponse {
|
||||
query: string;
|
||||
canonical: string;
|
||||
parsed: number[][];
|
||||
passage_meta: ESVPassageMeta[];
|
||||
passages: string[];
|
||||
query: string;
|
||||
canonical: string;
|
||||
parsed: number[][];
|
||||
passage_meta: ESVPassageMeta[];
|
||||
passages: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for ESV Passage Metadata
|
||||
*/
|
||||
export interface ESVPassageMeta {
|
||||
canonical: string;
|
||||
chapter_start: number[];
|
||||
chapter_end: number[];
|
||||
prev_verse: number | null;
|
||||
next_verse: number | null;
|
||||
prev_chapter: number | null;
|
||||
next_chapter: number[] | null;
|
||||
canonical: string;
|
||||
chapter_start: number[];
|
||||
chapter_end: number[];
|
||||
prev_verse: number | null;
|
||||
next_verse: number | null;
|
||||
prev_chapter: number | null;
|
||||
next_chapter: number[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for interacting with the ESV API
|
||||
*/
|
||||
export class ESVApiService {
|
||||
private plugin: DisciplesJournalPlugin;
|
||||
private plugin: DisciplesJournalPlugin;
|
||||
|
||||
// Store HTML formatted Bible chapters
|
||||
private htmlFormattedBible: {
|
||||
[reference: string]: {
|
||||
canonical: string;
|
||||
htmlContent: string;
|
||||
}
|
||||
} = {};
|
||||
// Store HTML formatted Bible chapters
|
||||
private htmlFormattedBible: {
|
||||
[reference: string]: {
|
||||
canonical: string;
|
||||
htmlContent: string;
|
||||
}
|
||||
} = {};
|
||||
|
||||
constructor(plugin: DisciplesJournalPlugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
constructor(plugin: DisciplesJournalPlugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full vault path including version subdirectory
|
||||
*/
|
||||
private getFullContentPath(): string {
|
||||
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
|
||||
}
|
||||
/**
|
||||
* Get the full vault path including version subdirectory
|
||||
*/
|
||||
private getFullContentPath(): string {
|
||||
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML formatted Bible content from in-memory storage
|
||||
*/
|
||||
public getHTMLContent(reference: string): BiblePassage | null {
|
||||
if (this.htmlFormattedBible[reference]) {
|
||||
return {
|
||||
reference: this.htmlFormattedBible[reference].canonical,
|
||||
verses: [], // Empty since we're using HTML
|
||||
htmlContent: this.htmlFormattedBible[reference].htmlContent
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Get HTML formatted Bible content from in-memory storage
|
||||
*/
|
||||
public getHTMLContent(reference: string): BiblePassage | null {
|
||||
if (this.htmlFormattedBible[reference]) {
|
||||
return {
|
||||
reference: this.htmlFormattedBible[reference].canonical,
|
||||
verses: [], // Empty since we're using HTML
|
||||
htmlContent: this.htmlFormattedBible[reference].htmlContent
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a collection of HTML formatted Bible chapters from files in the vault
|
||||
*/
|
||||
public async loadBibleChaptersFromVault(): Promise<void> {
|
||||
try {
|
||||
// Get the path where Bible content is stored
|
||||
const fullPath = this.getFullContentPath();
|
||||
|
||||
// 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<void> {
|
||||
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<BiblePassage | null> {
|
||||
if (!this.plugin.settings.esvApiToken) {
|
||||
console.warn('ESV API token not set. Cannot download content.');
|
||||
|
||||
return {
|
||||
reference: reference,
|
||||
verses: [],
|
||||
htmlContent: this.createErrorMessageContent(
|
||||
document,
|
||||
'missing-token',
|
||||
'To display Bible passages, you need to set up an ESV API token in the plugin settings.'
|
||||
).outerHTML,
|
||||
missingToken: true
|
||||
};
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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<BiblePassage | null> {
|
||||
if (!this.plugin.settings.esvApiToken) {
|
||||
console.warn('ESV API token not set. Cannot download content.');
|
||||
|
||||
return {
|
||||
reference: reference,
|
||||
verses: [],
|
||||
htmlContent: this.createErrorMessageContent(
|
||||
document,
|
||||
'missing-token',
|
||||
'To display Bible passages, you need to set up an ESV API token in the plugin settings.'
|
||||
).outerHTML,
|
||||
missingToken: true
|
||||
};
|
||||
}
|
||||
|
||||
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<void> {
|
||||
try {
|
||||
// Extract book and chapter from the canonical reference
|
||||
const parts = data.canonical.split(' ');
|
||||
if (parts.length < 2) return;
|
||||
const book = parts.slice(0, -1).join(' ');
|
||||
|
||||
// Create the directory structure with version subdirectory
|
||||
const fullPath = this.getFullContentPath();
|
||||
const bookPath = `${fullPath}/${book}`;
|
||||
await this.ensureVaultDirectoryExists(fullPath);
|
||||
await this.ensureVaultDirectoryExists(bookPath);
|
||||
|
||||
// Save the raw API response as JSON
|
||||
const jsonPath = `${bookPath}/${data.canonical}.json`;
|
||||
await this.plugin.app.vault.adapter.write(jsonPath, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error('Error saving ESV API response:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure vault directory exists, creating it if necessary
|
||||
*/
|
||||
private async ensureVaultDirectoryExists(path: string): Promise<void> {
|
||||
const parts = path.split('/').filter(p => p.length > 0);
|
||||
let currentPath = '';
|
||||
|
||||
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<void> {
|
||||
try {
|
||||
// Check if we have data by looking for specific files in the vault
|
||||
const hasData = await this.checkBibleDataExists();
|
||||
|
||||
if (!hasData) {
|
||||
console.log('Bible data not found, loading from vault...');
|
||||
await this.loadBibleChaptersFromVault();
|
||||
} else {
|
||||
console.log('Bible data already exists');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error ensuring Bible data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bible data exists in the vault
|
||||
*/
|
||||
private async checkBibleDataExists(): Promise<boolean> {
|
||||
try {
|
||||
// Check for a common book like Genesis
|
||||
const genesisPath = `${this.getFullContentPath()}/Genesis/Genesis 1.md`;
|
||||
return await this.plugin.app.vault.adapter.exists(genesisPath);
|
||||
} catch (error) {
|
||||
console.error('Error checking if Bible data exists:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Save the raw API response as JSON
|
||||
const jsonPath = `${bookPath}/${data.canonical}.json`;
|
||||
await this.plugin.app.vault.adapter.write(jsonPath, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error('Error saving ESV API response:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure vault directory exists, creating it if necessary
|
||||
*/
|
||||
private async ensureVaultDirectoryExists(path: string): Promise<void> {
|
||||
const parts = path.split('/').filter(p => p.length > 0);
|
||||
let currentPath = '';
|
||||
|
||||
for (const part of parts) {
|
||||
currentPath += (currentPath ? '/' : '') + part;
|
||||
if (!(await this.plugin.app.vault.adapter.exists(currentPath))) {
|
||||
await this.plugin.app.vault.adapter.mkdir(currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures Bible data is loaded, downloading if necessary
|
||||
*/
|
||||
public async ensureBibleData(): Promise<void> {
|
||||
try {
|
||||
// Check if we have data by looking for specific files in the vault
|
||||
const hasData = await this.checkBibleDataExists();
|
||||
|
||||
if (!hasData) {
|
||||
console.log('Bible data not found, loading from vault...');
|
||||
await this.loadBibleChaptersFromVault();
|
||||
} else {
|
||||
console.log('Bible data already exists');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error ensuring Bible data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bible data exists in the vault
|
||||
*/
|
||||
private async checkBibleDataExists(): Promise<boolean> {
|
||||
try {
|
||||
// Check for a common book like Genesis
|
||||
const genesisPath = `${this.getFullContentPath()}/Genesis/Genesis 1.md`;
|
||||
return await this.plugin.app.vault.adapter.exists(genesisPath);
|
||||
} catch (error) {
|
||||
console.error('Error checking if Bible data exists:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/types.d.ts
vendored
20
src/types.d.ts
vendored
|
|
@ -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[];
|
||||
declare const ESV: ESVVerse[];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue