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';
|
import DisciplesJournalPlugin from './src/core/DisciplesJournalPlugin';
|
||||||
|
|
||||||
export default DisciplesJournalPlugin;
|
export default DisciplesJournalPlugin;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ButtonComponent, DropdownComponent, Notice } from "obsidian";
|
import {ButtonComponent, DropdownComponent, Notice} from "obsidian";
|
||||||
import { BibleReference } from "../core/BibleReference";
|
import {BibleReference} from "../core/BibleReference";
|
||||||
import { BookNames } from "../services/BookNames";
|
import {BookNames} from "../services/BookNames";
|
||||||
import {BibleBookFiles} from "../services/BibleBookFiles";
|
import {BibleBookFiles} from "../services/BibleBookFiles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -9,44 +9,44 @@ import {BibleBookFiles} from "../services/BibleBookFiles";
|
||||||
export class BibleNavigation {
|
export class BibleNavigation {
|
||||||
private bibleBookFiles: BibleBookFiles;
|
private bibleBookFiles: BibleBookFiles;
|
||||||
|
|
||||||
constructor(noteCreationService: BibleBookFiles) {
|
constructor(noteCreationService: BibleBookFiles) {
|
||||||
this.bibleBookFiles = noteCreationService;
|
this.bibleBookFiles = noteCreationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create navigation elements for a Bible chapter
|
* Create navigation elements for a Bible chapter
|
||||||
*/
|
*/
|
||||||
public createNavigationElements(containerEl: HTMLElement, reference: BibleReference): void {
|
public createNavigationElements(containerEl: HTMLElement, reference: BibleReference): void {
|
||||||
const book = BookNames.normalize(reference.book) || reference.book;
|
const book = BookNames.normalize(reference.book) || reference.book;
|
||||||
const chapter = reference.chapter;
|
const chapter = reference.chapter;
|
||||||
|
|
||||||
// Get book chapter count
|
// Get book chapter count
|
||||||
const chapterCount = BookNames.getChapterCount(book);
|
const chapterCount = BookNames.getChapterCount(book);
|
||||||
|
|
||||||
// Determine if we're at the first or last chapter of the book
|
// Determine if we're at the first or last chapter of the book
|
||||||
const isFirstChapter = chapter === 1;
|
const isFirstChapter = chapter === 1;
|
||||||
const isLastChapter = chapter === chapterCount;
|
const isLastChapter = chapter === chapterCount;
|
||||||
|
|
||||||
// Determine if we're at the first or last book of the Bible
|
// Determine if we're at the first or last book of the Bible
|
||||||
const bookOrder = BookNames.getBookOrder();
|
const bookOrder = BookNames.getBookOrder();
|
||||||
const bookIndex = bookOrder.indexOf(book);
|
const bookIndex = bookOrder.indexOf(book);
|
||||||
const isFirstBook = bookIndex === 0;
|
const isFirstBook = bookIndex === 0;
|
||||||
const isLastBook = bookIndex === bookOrder.length - 1;
|
const isLastBook = bookIndex === bookOrder.length - 1;
|
||||||
|
|
||||||
// Create the navigation container
|
// Create the navigation container
|
||||||
const navEl = containerEl.createDiv({ cls: 'bible-navigation' });
|
const navEl = containerEl.createDiv({cls: 'bible-navigation'});
|
||||||
|
|
||||||
// Previous chapter button
|
// Previous chapter button
|
||||||
if (isFirstChapter && isFirstBook) {
|
if (isFirstChapter && isFirstBook) {
|
||||||
// No previous chapter if we're at Genesis 1
|
// No previous chapter if we're at Genesis 1
|
||||||
navEl.createSpan({
|
navEl.createSpan({
|
||||||
cls: 'nav-prev nav-disabled',
|
cls: 'nav-prev nav-disabled',
|
||||||
text: '◄ Previous'
|
text: '◄ Previous'
|
||||||
});
|
});
|
||||||
} else if (isFirstChapter) {
|
} else if (isFirstChapter) {
|
||||||
// Previous book, last chapter
|
// Previous book, last chapter
|
||||||
const prevBook = bookOrder[bookIndex - 1];
|
const prevBook = bookOrder[bookIndex - 1];
|
||||||
const prevChapter = BookNames.getChapterCount(prevBook);
|
const prevChapter = BookNames.getChapterCount(prevBook);
|
||||||
new ButtonComponent(navEl)
|
new ButtonComponent(navEl)
|
||||||
.setButtonText(`◄ ${prevBook} ${prevChapter}`)
|
.setButtonText(`◄ ${prevBook} ${prevChapter}`)
|
||||||
.setClass('nav-prev')
|
.setClass('nav-prev')
|
||||||
|
|
@ -70,38 +70,38 @@ export class BibleNavigation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Book and chapter selector
|
// Book and chapter selector
|
||||||
const selectorEl = navEl.createSpan({
|
const selectorEl = navEl.createSpan({
|
||||||
cls: 'nav-book-selector',
|
cls: 'nav-book-selector',
|
||||||
text: `📖 ${book} ${chapter}`
|
text: `📖 ${book} ${chapter}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectorContainer = navEl.createDiv({
|
const selectorContainer = navEl.createDiv({
|
||||||
cls: 'nav-book-data dj-hidden'
|
cls: 'nav-book-data dj-hidden'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the book dropdown
|
// Create the book dropdown
|
||||||
const bookDropdownContainer = selectorContainer.createDiv();
|
const bookDropdownContainer = selectorContainer.createDiv();
|
||||||
const bookDropdown = new DropdownComponent(bookDropdownContainer);
|
const bookDropdown = new DropdownComponent(bookDropdownContainer);
|
||||||
|
|
||||||
// Add all books to the dropdown
|
// Add all books to the dropdown
|
||||||
for (const bookName of bookOrder) {
|
for (const bookName of bookOrder) {
|
||||||
bookDropdown.addOption(bookName, bookName);
|
bookDropdown.addOption(bookName, bookName);
|
||||||
}
|
}
|
||||||
bookDropdown.setValue(book);
|
bookDropdown.setValue(book);
|
||||||
|
|
||||||
// Create the chapter dropdown
|
// Create the chapter dropdown
|
||||||
const chapterDropdownContainer = selectorContainer.createDiv({ cls: 'nav-chapter-container' });
|
const chapterDropdownContainer = selectorContainer.createDiv({cls: 'nav-chapter-container'});
|
||||||
const chapterDropdown = new DropdownComponent(chapterDropdownContainer);
|
const chapterDropdown = new DropdownComponent(chapterDropdownContainer);
|
||||||
|
|
||||||
// Add current book's chapters to the dropdown
|
// Add current book's chapters to the dropdown
|
||||||
this.populateChapterDropdown(chapterDropdown, book, chapter);
|
this.populateChapterDropdown(chapterDropdown, book, chapter);
|
||||||
|
|
||||||
// When book selection changes, update chapter dropdown
|
// When book selection changes, update chapter dropdown
|
||||||
bookDropdown.onChange(value => {
|
bookDropdown.onChange(value => {
|
||||||
this.populateChapterDropdown(chapterDropdown, value, 1);
|
this.populateChapterDropdown(chapterDropdown, value, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Go button
|
// Add Go button
|
||||||
new ButtonComponent(selectorContainer)
|
new ButtonComponent(selectorContainer)
|
||||||
.setButtonText('Go')
|
.setButtonText('Go')
|
||||||
.setClass('nav-go-button')
|
.setClass('nav-go-button')
|
||||||
|
|
@ -113,20 +113,20 @@ export class BibleNavigation {
|
||||||
loadingNotice.hide();
|
loadingNotice.hide();
|
||||||
});
|
});
|
||||||
// Toggle selector on click
|
// Toggle selector on click
|
||||||
selectorEl.addEventListener('click', () => {
|
selectorEl.addEventListener('click', () => {
|
||||||
selectorContainer.classList.toggle('dj-hidden');
|
selectorContainer.classList.toggle('dj-hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Next chapter button
|
// Next chapter button
|
||||||
if (isLastChapter && isLastBook) {
|
if (isLastChapter && isLastBook) {
|
||||||
// No next chapter if we're at Revelation 22
|
// No next chapter if we're at Revelation 22
|
||||||
navEl.createSpan({
|
navEl.createSpan({
|
||||||
cls: 'nav-next nav-disabled',
|
cls: 'nav-next nav-disabled',
|
||||||
text: 'Next ►'
|
text: 'Next ►'
|
||||||
});
|
});
|
||||||
} else if (isLastChapter) {
|
} else if (isLastChapter) {
|
||||||
// Next book, first chapter
|
// Next book, first chapter
|
||||||
const nextBook = bookOrder[bookIndex + 1];
|
const nextBook = bookOrder[bookIndex + 1];
|
||||||
new ButtonComponent(navEl)
|
new ButtonComponent(navEl)
|
||||||
.setButtonText(`► ${nextBook} 1`)
|
.setButtonText(`► ${nextBook} 1`)
|
||||||
.setClass('nav-next')
|
.setClass('nav-next')
|
||||||
|
|
@ -151,37 +151,37 @@ export class BibleNavigation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the chapter dropdown for a specific book
|
* Populate the chapter dropdown for a specific book
|
||||||
*/
|
*/
|
||||||
private populateChapterDropdown(
|
private populateChapterDropdown(
|
||||||
dropdown: DropdownComponent,
|
dropdown: DropdownComponent,
|
||||||
book: string,
|
book: string,
|
||||||
selectedChapter: number
|
selectedChapter: number
|
||||||
): void {
|
): void {
|
||||||
// Clear existing options
|
// Clear existing options
|
||||||
dropdown.selectEl.empty();
|
dropdown.selectEl.empty();
|
||||||
|
|
||||||
// Get the chapter count for this book
|
// Get the chapter count for this book
|
||||||
const chapterCount = BookNames.getChapterCount(book);
|
const chapterCount = BookNames.getChapterCount(book);
|
||||||
|
|
||||||
// Add chapter options
|
// Add chapter options
|
||||||
for (let i = 1; i <= chapterCount; i++) {
|
for (let i = 1; i <= chapterCount; i++) {
|
||||||
dropdown.addOption(i.toString(), i.toString());
|
dropdown.addOption(i.toString(), i.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the selected chapter
|
// Set the selected chapter
|
||||||
dropdown.setValue(selectedChapter.toString());
|
dropdown.setValue(selectedChapter.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to a specific chapter
|
* Navigate to a specific chapter
|
||||||
*/
|
*/
|
||||||
public async navigateToChapter(book: string, chapter: number): Promise<void> {
|
public async navigateToChapter(book: string, chapter: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.bibleBookFiles.openChapterNote(new BibleReference(book, chapter).toString());
|
await this.bibleBookFiles.openChapterNote(new BibleReference(book, chapter).toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error navigating to chapter:', error);
|
console.error('Error navigating to chapter:', error);
|
||||||
new Notice(`Error navigating to chapter: ${error.message}`);
|
new Notice(`Error navigating to chapter: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,73 +2,73 @@
|
||||||
* Interface for Bible styling options
|
* Interface for Bible styling options
|
||||||
*/
|
*/
|
||||||
export interface BibleStyleOptions {
|
export interface BibleStyleOptions {
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
wordsOfChristColor: string;
|
wordsOfChristColor: string;
|
||||||
verseNumberColor: string;
|
verseNumberColor: string;
|
||||||
headingColor: string;
|
headingColor: string;
|
||||||
blockIndentation: string;
|
blockIndentation: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme preset definitions
|
* Theme preset definitions
|
||||||
*/
|
*/
|
||||||
export interface ThemePreset {
|
export interface ThemePreset {
|
||||||
light: BibleStyleOptions;
|
light: BibleStyleOptions;
|
||||||
dark: BibleStyleOptions;
|
dark: BibleStyleOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Available theme presets
|
* Available theme presets
|
||||||
*/
|
*/
|
||||||
export const THEME_PRESETS: Record<string, ThemePreset> = {
|
export const THEME_PRESETS: Record<string, ThemePreset> = {
|
||||||
default: {
|
default: {
|
||||||
light: {
|
light: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: 'var(--text-accent)',
|
wordsOfChristColor: 'var(--text-accent)',
|
||||||
verseNumberColor: 'var(--text-accent)',
|
verseNumberColor: 'var(--text-accent)',
|
||||||
headingColor: 'var(--text-accent)',
|
headingColor: 'var(--text-accent)',
|
||||||
blockIndentation: '2em'
|
blockIndentation: '2em'
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: 'var(--text-accent)',
|
wordsOfChristColor: 'var(--text-accent)',
|
||||||
verseNumberColor: 'var(--text-accent)',
|
verseNumberColor: 'var(--text-accent)',
|
||||||
headingColor: 'var(--text-accent)',
|
headingColor: 'var(--text-accent)',
|
||||||
blockIndentation: '2em'
|
blockIndentation: '2em'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
classic: {
|
classic: {
|
||||||
light: {
|
light: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: '#b91c1c',
|
wordsOfChristColor: '#b91c1c',
|
||||||
verseNumberColor: '#2563eb',
|
verseNumberColor: '#2563eb',
|
||||||
headingColor: '#4b5563',
|
headingColor: '#4b5563',
|
||||||
blockIndentation: '2em'
|
blockIndentation: '2em'
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: '#ef4444',
|
wordsOfChristColor: '#ef4444',
|
||||||
verseNumberColor: '#60a5fa',
|
verseNumberColor: '#60a5fa',
|
||||||
headingColor: '#9ca3af',
|
headingColor: '#9ca3af',
|
||||||
blockIndentation: '2em'
|
blockIndentation: '2em'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
minimal: {
|
minimal: {
|
||||||
light: {
|
light: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: '#000000',
|
wordsOfChristColor: '#000000',
|
||||||
verseNumberColor: '#6b7280',
|
verseNumberColor: '#6b7280',
|
||||||
headingColor: '#374151',
|
headingColor: '#374151',
|
||||||
blockIndentation: '1.5em'
|
blockIndentation: '1.5em'
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
wordsOfChristColor: '#ffffff',
|
wordsOfChristColor: '#ffffff',
|
||||||
verseNumberColor: '#9ca3af',
|
verseNumberColor: '#9ca3af',
|
||||||
headingColor: '#e5e7eb',
|
headingColor: '#e5e7eb',
|
||||||
blockIndentation: '1.5em'
|
blockIndentation: '1.5em'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -77,60 +77,60 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
|
||||||
*/
|
*/
|
||||||
export class BibleStyles {
|
export class BibleStyles {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// No initial style element creation needed
|
// 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];
|
|
||||||
|
|
||||||
// Start with base options and apply the specific font size
|
/**
|
||||||
const options: BibleStyleOptions = {
|
* Apply styles by updating CSS variables based on theme, preset, and settings.
|
||||||
...baseOptions,
|
*/
|
||||||
fontSize: fontSize || '100%' // Ensure fontSize always has a value
|
public applyStyles(doc: Document, theme: 'light' | 'dark', presetName: string = 'default', fontSize: string = '100%', settings?: {
|
||||||
};
|
wordsOfChristColor?: string;
|
||||||
|
verseNumberColor?: string;
|
||||||
// Apply custom settings from DisciplesJournalSettings if provided
|
headingColor?: string;
|
||||||
// These will override the preset values
|
blockIndentation?: string;
|
||||||
if (settings) {
|
}): void {
|
||||||
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
|
// Get the preset, defaulting to 'default' if not found
|
||||||
const rootStyle = doc.body.style;
|
const preset = THEME_PRESETS[presetName] || THEME_PRESETS.default;
|
||||||
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 base options for the current theme
|
||||||
* Resets the custom styles potentially set by this class by removing the CSS variables.
|
const baseOptions = preset[theme];
|
||||||
* This allows the default styles in styles.css to take effect.
|
|
||||||
*/
|
// Start with base options and apply the specific font size
|
||||||
public resetStyles(doc: Document): void {
|
const options: BibleStyleOptions = {
|
||||||
const rootStyle = doc.body.style;
|
...baseOptions,
|
||||||
rootStyle.removeProperty('--dj-font-size');
|
fontSize: fontSize || '100%' // Ensure fontSize always has a value
|
||||||
rootStyle.removeProperty('--dj-heading-color');
|
};
|
||||||
rootStyle.removeProperty('--dj-verse-number-color');
|
|
||||||
rootStyle.removeProperty('--dj-woc-color');
|
// Apply custom settings from DisciplesJournalSettings if provided
|
||||||
rootStyle.removeProperty('--dj-block-indentation');
|
// 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
|
* Handles all Bible reference-related DOM events
|
||||||
*/
|
*/
|
||||||
export class BibleEventHandlers {
|
export class BibleEventHandlers {
|
||||||
private bibleReferenceRenderer: BibleReferenceRenderer;
|
private bibleReferenceRenderer: BibleReferenceRenderer;
|
||||||
private previewPopper: HTMLElement | null = null;
|
private previewPopper: HTMLElement | null = null;
|
||||||
|
|
||||||
constructor(bibleReferenceRenderer: BibleReferenceRenderer) {
|
constructor(bibleReferenceRenderer: BibleReferenceRenderer) {
|
||||||
this.bibleReferenceRenderer = bibleReferenceRenderer;
|
this.bibleReferenceRenderer = bibleReferenceRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle hover on Bible references
|
* Handle hover on Bible references
|
||||||
*/
|
*/
|
||||||
async handleBibleReferenceHover(event: MouseEvent) {
|
async handleBibleReferenceHover(event: MouseEvent) {
|
||||||
// Don't create new preview if we already have one active
|
// Don't create new preview if we already have one active
|
||||||
if (this.previewPopper) return;
|
if (this.previewPopper) return;
|
||||||
|
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
if (!target || !target.closest) return;
|
if (!target || !target.closest) return;
|
||||||
|
|
||||||
const referenceEl = target.closest('.bible-reference') as HTMLElement;
|
const referenceEl = target.closest('.bible-reference') as HTMLElement;
|
||||||
if (!referenceEl) return;
|
if (!referenceEl) return;
|
||||||
|
|
||||||
const referenceText = referenceEl.textContent;
|
const referenceText = referenceEl.textContent;
|
||||||
if (!referenceText) return;
|
if (!referenceText) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create new preview
|
// Create new preview
|
||||||
this.previewPopper = await this.bibleReferenceRenderer.showVersePreview(
|
this.previewPopper = await this.bibleReferenceRenderer.showVersePreview(
|
||||||
referenceEl,
|
referenceEl,
|
||||||
referenceText,
|
referenceText,
|
||||||
event
|
event
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add event listeners directly to the preview for better control
|
// Add event listeners directly to the preview for better control
|
||||||
if (this.previewPopper) {
|
if (this.previewPopper) {
|
||||||
// When mouse enters the popup, mark it as locked
|
// When mouse enters the popup, mark it as locked
|
||||||
this.previewPopper.addEventListener('mouseenter', () => {
|
this.previewPopper.addEventListener('mouseenter', () => {
|
||||||
this.previewPopper?.classList.add('popup-locked');
|
this.previewPopper?.classList.add('popup-locked');
|
||||||
});
|
});
|
||||||
|
|
||||||
// When mouse leaves the popup, check if we should close it
|
// When mouse leaves the popup, check if we should close it
|
||||||
this.previewPopper.addEventListener('mouseleave', (e) => {
|
this.previewPopper.addEventListener('mouseleave', (e) => {
|
||||||
// Only close if not moving to the reference or another part of the popup
|
// Only close if not moving to the reference or another part of the popup
|
||||||
const relatedTarget = e.relatedTarget as HTMLElement;
|
const relatedTarget = e.relatedTarget as HTMLElement;
|
||||||
if (relatedTarget &&
|
if (relatedTarget &&
|
||||||
!relatedTarget.classList.contains('bible-reference') &&
|
!relatedTarget.classList.contains('bible-reference') &&
|
||||||
!relatedTarget.closest('.bible-verse-preview')) {
|
!relatedTarget.closest('.bible-verse-preview')) {
|
||||||
this.previewPopper?.classList.remove('popup-locked');
|
this.previewPopper?.classList.remove('popup-locked');
|
||||||
this.removePreviewPopper(relatedTarget.doc);
|
this.removePreviewPopper(relatedTarget.doc);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also add listeners to the reference element
|
// Also add listeners to the reference element
|
||||||
referenceEl.addEventListener('mouseleave', (e) => {
|
referenceEl.addEventListener('mouseleave', (e) => {
|
||||||
// Don't close if the popup is locked (being hovered) or we're moving to the popup
|
// Don't close if the popup is locked (being hovered) or we're moving to the popup
|
||||||
if (this.previewPopper) {
|
if (this.previewPopper) {
|
||||||
const relatedTarget = e.relatedTarget as HTMLElement;
|
const relatedTarget = e.relatedTarget as HTMLElement;
|
||||||
|
|
||||||
// If moving to the popup or if popup is locked, don't close
|
// If moving to the popup or if popup is locked, don't close
|
||||||
if (relatedTarget &&
|
if (relatedTarget &&
|
||||||
(relatedTarget.classList.contains('bible-verse-preview') ||
|
(relatedTarget.classList.contains('bible-verse-preview') ||
|
||||||
relatedTarget.closest('.bible-verse-preview') ||
|
relatedTarget.closest('.bible-verse-preview') ||
|
||||||
this.previewPopper.classList.contains('popup-locked'))) {
|
this.previewPopper.classList.contains('popup-locked'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a 100ms delay before closing to allow for cursor movement
|
// Add a 100ms delay before closing to allow for cursor movement
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// If locked during this delay, don't close
|
// If locked during this delay, don't close
|
||||||
if (!this.previewPopper || this.previewPopper.classList.contains('popup-locked')) {
|
if (!this.previewPopper || this.previewPopper.classList.contains('popup-locked')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.removePreviewPopper(relatedTarget.doc);
|
this.removePreviewPopper(relatedTarget.doc);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error showing Bible reference preview:', error);
|
console.error('Error showing Bible reference preview:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle mouse out from Bible references
|
* Handle mouse out from Bible references
|
||||||
*/
|
*/
|
||||||
handleBibleReferenceMouseOut(event: MouseEvent) {
|
handleBibleReferenceMouseOut(event: MouseEvent) {
|
||||||
// If we don't have a popup, nothing to do
|
// If we don't have a popup, nothing to do
|
||||||
if (!this.previewPopper) return;
|
if (!this.previewPopper) return;
|
||||||
|
|
||||||
// If the popup is locked (being hovered), don't close it
|
// If the popup is locked (being hovered), don't close it
|
||||||
if (this.previewPopper.classList.contains('popup-locked')) {
|
if (this.previewPopper.classList.contains('popup-locked')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
const relatedTarget = event.relatedTarget as HTMLElement;
|
const relatedTarget = event.relatedTarget as HTMLElement;
|
||||||
|
|
||||||
// If either target is missing, can't make a good decision
|
// If either target is missing, can't make a good decision
|
||||||
if (!target || !relatedTarget) return;
|
if (!target || !relatedTarget) return;
|
||||||
|
|
||||||
// If moving to/from the popup or reference, don't close
|
// If moving to/from the popup or reference, don't close
|
||||||
if (target.classList.contains('bible-reference') ||
|
if (target.classList.contains('bible-reference') ||
|
||||||
target.classList.contains('bible-verse-preview') ||
|
target.classList.contains('bible-verse-preview') ||
|
||||||
target.closest('.bible-verse-preview') ||
|
target.closest('.bible-verse-preview') ||
|
||||||
relatedTarget.classList.contains('bible-reference') ||
|
relatedTarget.classList.contains('bible-reference') ||
|
||||||
relatedTarget.classList.contains('bible-verse-preview') ||
|
relatedTarget.classList.contains('bible-verse-preview') ||
|
||||||
relatedTarget.closest('.bible-verse-preview')) {
|
relatedTarget.closest('.bible-verse-preview')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In all other cases, remove the popup
|
// In all other cases, remove the popup
|
||||||
this.removePreviewPopper(target.doc);
|
this.removePreviewPopper(target.doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the preview popper if it exists
|
* Remove the preview popper if it exists
|
||||||
*/
|
*/
|
||||||
removePreviewPopper(doc: Document) {
|
removePreviewPopper(doc: Document) {
|
||||||
if (this.previewPopper) {
|
if (this.previewPopper) {
|
||||||
// Remove any hover gap elements
|
// Remove any hover gap elements
|
||||||
const hoverGaps = doc.querySelectorAll('.bible-hover-gap');
|
const hoverGaps = doc.querySelectorAll('.bible-hover-gap');
|
||||||
hoverGaps.forEach(gap => gap.remove());
|
hoverGaps.forEach(gap => gap.remove());
|
||||||
|
|
||||||
this.previewPopper.remove();
|
this.previewPopper.remove();
|
||||||
this.previewPopper = null;
|
this.previewPopper = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,55 @@
|
||||||
import { MarkdownPostProcessorContext } from 'obsidian';
|
import {MarkdownPostProcessorContext} from 'obsidian';
|
||||||
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
|
import {BibleReferenceRenderer} from '../components/BibleReferenceRenderer';
|
||||||
import { DisciplesJournalSettings } from '../settings/DisciplesJournalSettings';
|
import {DisciplesJournalSettings} from '../settings/DisciplesJournalSettings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles processing of Bible references in markdown text
|
* Handles processing of Bible references in markdown text
|
||||||
*/
|
*/
|
||||||
export class BibleMarkupProcessor {
|
export class BibleMarkupProcessor {
|
||||||
private bibleReferenceRenderer: BibleReferenceRenderer;
|
private bibleReferenceRenderer: BibleReferenceRenderer;
|
||||||
private settings: DisciplesJournalSettings;
|
private settings: DisciplesJournalSettings;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
bibleReferenceRenderer: BibleReferenceRenderer,
|
bibleReferenceRenderer: BibleReferenceRenderer,
|
||||||
settings: DisciplesJournalSettings
|
settings: DisciplesJournalSettings
|
||||||
) {
|
) {
|
||||||
this.bibleReferenceRenderer = bibleReferenceRenderer;
|
this.bibleReferenceRenderer = bibleReferenceRenderer;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process Bible code blocks
|
* Process Bible code blocks
|
||||||
*/
|
*/
|
||||||
async processBibleCodeBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
async processBibleCodeBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
||||||
// Check if display of full passages is enabled in settings
|
// Check if display of full passages is enabled in settings
|
||||||
if (!this.settings.displayFullPassages) {
|
if (!this.settings.displayFullPassages) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.bibleReferenceRenderer.processFullBiblePassage(source, el);
|
await this.bibleReferenceRenderer.processFullBiblePassage(source, el);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing Bible code block:', error);
|
console.error('Error processing Bible code block:', error);
|
||||||
el.createEl('div', {
|
el.createEl('div', {
|
||||||
text: `Error processing Bible reference: ${error.message}`,
|
text: `Error processing Bible reference: ${error.message}`,
|
||||||
cls: 'bible-reference-error'
|
cls: 'bible-reference-error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process inline Bible references in text
|
* Process inline Bible references in text
|
||||||
*/
|
*/
|
||||||
async processInlineBibleReferences(el: HTMLElement, ctx: MarkdownPostProcessorContext): Promise<void> {
|
async processInlineBibleReferences(el: HTMLElement, ctx: MarkdownPostProcessorContext): Promise<void> {
|
||||||
// Check if display of inline verses is enabled in settings
|
// Check if display of inline verses is enabled in settings
|
||||||
if (!this.settings.displayInlineVerses) {
|
if (!this.settings.displayInlineVerses) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
|
await this.bibleReferenceRenderer.processInlineCodeBlocks(el, ctx);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing inline Bible references:', 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
|
* Represents a Bible Reference with book, chapter, and verse information
|
||||||
*/
|
*/
|
||||||
export class BibleReference {
|
export class BibleReference {
|
||||||
book: string;
|
book: string;
|
||||||
chapter: number;
|
chapter: number;
|
||||||
verse?: number;
|
verse?: number;
|
||||||
endVerse?: number;
|
endVerse?: number;
|
||||||
endChapter?: number;
|
endChapter?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Bible reference
|
* Create a new Bible reference
|
||||||
*/
|
*/
|
||||||
constructor(book: string, chapter: number, verse?: number, endVerse?: number, endChapter?: number) {
|
constructor(book: string, chapter: number, verse?: number, endVerse?: number, endChapter?: number) {
|
||||||
this.book = book;
|
this.book = book;
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
this.verse = verse;
|
this.verse = verse;
|
||||||
this.endVerse = endVerse;
|
this.endVerse = endVerse;
|
||||||
this.endChapter = endChapter;
|
this.endChapter = endChapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string into a BibleReference object
|
* Parse a string into a BibleReference object
|
||||||
|
|
@ -112,95 +112,95 @@ export class BibleReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the formatted reference as a string
|
* Get the formatted reference as a string
|
||||||
*/
|
*/
|
||||||
public toString(): string {
|
public toString(): string {
|
||||||
let reference = `${this.book} ${this.chapter}`;
|
let reference = `${this.book} ${this.chapter}`;
|
||||||
|
|
||||||
// Add verse if present
|
// Add verse if present
|
||||||
if (this.verse !== undefined) {
|
if (this.verse !== undefined) {
|
||||||
reference += `:${this.verse}`;
|
reference += `:${this.verse}`;
|
||||||
|
|
||||||
// Add end verse if present (for ranges within same chapter)
|
// Add end verse if present (for ranges within same chapter)
|
||||||
if (this.endVerse !== undefined && this.endChapter === undefined) {
|
if (this.endVerse !== undefined && this.endChapter === undefined) {
|
||||||
reference += `-${this.endVerse}`;
|
reference += `-${this.endVerse}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle cross-chapter reference
|
// Handle cross-chapter reference
|
||||||
if (this.endChapter !== undefined) {
|
if (this.endChapter !== undefined) {
|
||||||
reference += `-${this.endChapter}`;
|
reference += `-${this.endChapter}`;
|
||||||
|
|
||||||
// Add end verse if present for a cross-chapter reference
|
// Add end verse if present for a cross-chapter reference
|
||||||
if (this.endVerse !== undefined) {
|
if (this.endVerse !== undefined) {
|
||||||
reference += `:${this.endVerse}`;
|
reference += `:${this.endVerse}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return reference;
|
return reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a deep copy of the reference
|
* Create a deep copy of the reference
|
||||||
*/
|
*/
|
||||||
public clone(): BibleReference {
|
public clone(): BibleReference {
|
||||||
return new BibleReference(
|
return new BibleReference(
|
||||||
this.book,
|
this.book,
|
||||||
this.chapter,
|
this.chapter,
|
||||||
this.verse,
|
this.verse,
|
||||||
this.endVerse,
|
this.endVerse,
|
||||||
this.endChapter
|
this.endChapter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if this reference is a range (spans multiple verses)
|
* Determine if this reference is a range (spans multiple verses)
|
||||||
*/
|
*/
|
||||||
public isRange(): boolean {
|
public isRange(): boolean {
|
||||||
return this.endVerse !== undefined || this.endChapter !== undefined;
|
return this.endVerse !== undefined || this.endChapter !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this is a chapter reference (no specific verse)
|
* Check if this is a chapter reference (no specific verse)
|
||||||
*/
|
*/
|
||||||
public isChapterReference(): boolean {
|
public isChapterReference(): boolean {
|
||||||
return this.verse === undefined;
|
return this.verse === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare this reference with another to check if they refer to the same passage
|
* Compare this reference with another to check if they refer to the same passage
|
||||||
*/
|
*/
|
||||||
public equals(other: BibleReference): boolean {
|
public equals(other: BibleReference): boolean {
|
||||||
return this.book === other.book &&
|
return this.book === other.book &&
|
||||||
this.chapter === other.chapter &&
|
this.chapter === other.chapter &&
|
||||||
this.verse === other.verse &&
|
this.verse === other.verse &&
|
||||||
this.endVerse === other.endVerse &&
|
this.endVerse === other.endVerse &&
|
||||||
this.endChapter === other.endChapter;
|
this.endChapter === other.endChapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference string suitable for display in UI
|
* Returns a reference string suitable for display in UI
|
||||||
*/
|
*/
|
||||||
public getDisplayReference(): string {
|
public getDisplayReference(): string {
|
||||||
// For UI display, we might want to abbreviate the book name
|
// For UI display, we might want to abbreviate the book name
|
||||||
// or format the reference in a specific way
|
// or format the reference in a specific way
|
||||||
return this.toString();
|
return this.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference string suitable for use as a key in the Bible data
|
* Returns a reference string suitable for use as a key in the Bible data
|
||||||
*/
|
*/
|
||||||
public getDataKey(): string {
|
public getDataKey(): string {
|
||||||
// For data keys, we often want to use a standardized format
|
// For data keys, we often want to use a standardized format
|
||||||
// This assumes the book name is already standardized
|
// This assumes the book name is already standardized
|
||||||
return `${this.book} ${this.chapter}${this.verse ? `:${this.verse}` : ''}`;
|
return `${this.book} ${this.chapter}${this.verse ? `:${this.verse}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a reference for the entire chapter containing this reference
|
* Creates a reference for the entire chapter containing this reference
|
||||||
*/
|
*/
|
||||||
public getChapterReference(): BibleReference {
|
public getChapterReference(): BibleReference {
|
||||||
// Create a new reference with the same book and chapter, but no verse info
|
// Create a new reference with the same book and chapter, but no verse info
|
||||||
return new BibleReference(this.book, this.chapter);
|
return new BibleReference(this.book, this.chapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,139 +1,143 @@
|
||||||
import { Plugin, MarkdownView, Notice } from 'obsidian';
|
import {Plugin, MarkdownView, Notice} from 'obsidian';
|
||||||
import { ESVApiService } from '../services/ESVApiService';
|
import {ESVApiService} from '../services/ESVApiService';
|
||||||
import { BibleContentService } from '../services/BibleContentService';
|
import {BibleContentService} from '../services/BibleContentService';
|
||||||
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
|
import {BibleReferenceRenderer} from '../components/BibleReferenceRenderer';
|
||||||
import { BibleStyles } from '../components/BibleStyles';
|
import {BibleStyles} from '../components/BibleStyles';
|
||||||
import { DisciplesJournalSettings, DEFAULT_SETTINGS, DisciplesJournalSettingsTab } from '../settings/DisciplesJournalSettings';
|
import {
|
||||||
import { BibleBookFiles } from 'src/services/BibleBookFiles';
|
DisciplesJournalSettings,
|
||||||
import { BibleMarkupProcessor } from './BibleMarkupProcessor';
|
DEFAULT_SETTINGS,
|
||||||
|
DisciplesJournalSettingsTab
|
||||||
|
} from '../settings/DisciplesJournalSettings';
|
||||||
|
import {BibleBookFiles} from 'src/services/BibleBookFiles';
|
||||||
|
import {BibleMarkupProcessor} from './BibleMarkupProcessor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disciples Journal Plugin for Obsidian
|
* Disciples Journal Plugin for Obsidian
|
||||||
* Enhances Bible references with hover previews and in-note embedding
|
* Enhances Bible references with hover previews and in-note embedding
|
||||||
*/
|
*/
|
||||||
export default class DisciplesJournalPlugin extends Plugin {
|
export default class DisciplesJournalPlugin extends Plugin {
|
||||||
settings: DisciplesJournalSettings;
|
settings: DisciplesJournalSettings;
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
private esvApiService: ESVApiService;
|
private esvApiService: ESVApiService;
|
||||||
private bibleContentService: BibleContentService;
|
private bibleContentService: BibleContentService;
|
||||||
private bibleBookFiles: BibleBookFiles;
|
private bibleBookFiles: BibleBookFiles;
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
private bibleStyles: BibleStyles;
|
private bibleStyles: BibleStyles;
|
||||||
private bibleReferenceRenderer: BibleReferenceRenderer;
|
private bibleReferenceRenderer: BibleReferenceRenderer;
|
||||||
private bibleMarkupProcessor: BibleMarkupProcessor;
|
private bibleMarkupProcessor: BibleMarkupProcessor;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
console.log('Loading Disciples Journal plugin');
|
console.log('Loading Disciples Journal plugin');
|
||||||
|
|
||||||
// Initialize settings
|
// Initialize settings
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
this.esvApiService = new ESVApiService(this);
|
this.esvApiService = new ESVApiService(this);
|
||||||
this.bibleContentService = new BibleContentService(this, this.esvApiService);
|
this.bibleContentService = new BibleContentService(this, this.esvApiService);
|
||||||
|
|
||||||
// Check if ESV API token is set and show a notice if it's not
|
// Check if ESV API token is set and show a notice if it's not
|
||||||
if (!this.settings.esvApiToken) {
|
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);
|
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
|
// Initialize components
|
||||||
this.bibleStyles = new BibleStyles();
|
this.bibleStyles = new BibleStyles();
|
||||||
this.bibleBookFiles = new BibleBookFiles(this, this.bibleContentService);
|
this.bibleBookFiles = new BibleBookFiles(this, this.bibleContentService);
|
||||||
|
|
||||||
this.bibleReferenceRenderer = new BibleReferenceRenderer(
|
this.bibleReferenceRenderer = new BibleReferenceRenderer(
|
||||||
this.bibleContentService,
|
this.bibleContentService,
|
||||||
this.bibleBookFiles,
|
this.bibleBookFiles,
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize markup processor
|
// Initialize markup processor
|
||||||
this.bibleMarkupProcessor = new BibleMarkupProcessor(this.bibleReferenceRenderer, this.settings);
|
this.bibleMarkupProcessor = new BibleMarkupProcessor(this.bibleReferenceRenderer, this.settings);
|
||||||
|
|
||||||
// Register bible reference processor
|
// Register bible reference processor
|
||||||
this.registerMarkdownCodeBlockProcessor('bible', this.bibleMarkupProcessor.processBibleCodeBlock.bind(this.bibleMarkupProcessor));
|
this.registerMarkdownCodeBlockProcessor('bible', this.bibleMarkupProcessor.processBibleCodeBlock.bind(this.bibleMarkupProcessor));
|
||||||
|
|
||||||
// Register markdown post processor for inline references
|
// Register markdown post processor for inline references
|
||||||
this.registerMarkdownPostProcessor(this.bibleMarkupProcessor.processInlineBibleReferences.bind(this.bibleMarkupProcessor));
|
this.registerMarkdownPostProcessor(this.bibleMarkupProcessor.processInlineBibleReferences.bind(this.bibleMarkupProcessor));
|
||||||
|
|
||||||
// Register settings tab
|
// Register settings tab
|
||||||
this.addSettingTab(new DisciplesJournalSettingsTab(this.app, this));
|
this.addSettingTab(new DisciplesJournalSettingsTab(this.app, this));
|
||||||
|
|
||||||
// Register active leaf change to update styles
|
// Register active leaf change to update styles
|
||||||
this.registerEvent(this.app.workspace.on('active-leaf-change', this.handleActiveLeafChange.bind(this)));
|
this.registerEvent(this.app.workspace.on('active-leaf-change', this.handleActiveLeafChange.bind(this)));
|
||||||
|
|
||||||
// Load Bible data
|
// Load Bible data
|
||||||
await this.loadBibleData();
|
await this.loadBibleData();
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.saveData(this.settings);
|
await this.saveData(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the Bible data from source
|
* Load the Bible data from source
|
||||||
*/
|
*/
|
||||||
async loadBibleData() {
|
async loadBibleData() {
|
||||||
try {
|
try {
|
||||||
console.log('Loading Bible data...');
|
console.log('Loading Bible data...');
|
||||||
await this.esvApiService.ensureBibleData();
|
await this.esvApiService.ensureBibleData();
|
||||||
console.log('Bible data loaded successfully');
|
console.log('Bible data loaded successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading Bible data:', error);
|
console.error('Error loading Bible data:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle leaf change event to check for verse references in the URL
|
* Handle leaf change event to check for verse references in the URL
|
||||||
*/
|
*/
|
||||||
handleActiveLeafChange() {
|
handleActiveLeafChange() {
|
||||||
// Refresh theme/styling when active leaf changes
|
// Refresh theme/styling when active leaf changes
|
||||||
this.updateBibleStyles();
|
this.updateBibleStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update Bible styles with current settings
|
* Update Bible styles with current settings
|
||||||
*/
|
*/
|
||||||
public updateBibleStyles(): void {
|
public updateBibleStyles(): void {
|
||||||
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView);
|
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
if (!activeLeaf) return;
|
if (!activeLeaf) return;
|
||||||
|
|
||||||
// Each popup window has a unique document, so we need to apply styles to each one
|
// Each popup window has a unique document, so we need to apply styles to each one
|
||||||
// See https://discord.com/channels/686053708261228577/840286264964022302/1362059117190975519
|
// 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
|
// Note: this doesnt completely work. When a new popout is created the style cars arent ported over
|
||||||
const docList: Document[] = [];
|
const docList: Document[] = [];
|
||||||
this.app.workspace.iterateAllLeaves(leaf => docList.push(leaf.getContainer().doc));
|
this.app.workspace.iterateAllLeaves(leaf => docList.push(leaf.getContainer().doc));
|
||||||
docList.unique().forEach(doc => {
|
docList.unique().forEach(doc => {
|
||||||
const isDarkMode = activeLeaf.containerEl.doc.body.classList.contains('theme-dark');
|
const isDarkMode = activeLeaf.containerEl.doc.body.classList.contains('theme-dark');
|
||||||
const theme = isDarkMode ? 'dark' : 'light';
|
const theme = isDarkMode ? 'dark' : 'light';
|
||||||
this.bibleStyles.applyStyles(
|
this.bibleStyles.applyStyles(
|
||||||
doc,
|
doc,
|
||||||
theme,
|
theme,
|
||||||
this.settings.stylePreset,
|
this.settings.stylePreset,
|
||||||
this.settings.bibleTextFontSize,
|
this.settings.bibleTextFontSize,
|
||||||
{
|
{
|
||||||
wordsOfChristColor: this.settings.wordsOfChristColor,
|
wordsOfChristColor: this.settings.wordsOfChristColor,
|
||||||
verseNumberColor: this.settings.verseNumberColor,
|
verseNumberColor: this.settings.verseNumberColor,
|
||||||
headingColor: this.settings.headingColor,
|
headingColor: this.settings.headingColor,
|
||||||
blockIndentation: this.settings.blockIndentation
|
blockIndentation: this.settings.blockIndentation
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open or create a chapter note (Public method for external access)
|
* Open or create a chapter note (Public method for external access)
|
||||||
*/
|
*/
|
||||||
public async openChapterNote(reference: string) {
|
public async openChapterNote(reference: string) {
|
||||||
return this.bibleBookFiles.openChapterNote(reference);
|
return this.bibleBookFiles.openChapterNote(reference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,110 @@
|
||||||
import { TFile } from 'obsidian';
|
import {TFile} from 'obsidian';
|
||||||
import { BibleContentService } from './BibleContentService';
|
import {BibleContentService} from './BibleContentService';
|
||||||
import { BibleReference } from '../core/BibleReference';
|
import {BibleReference} from '../core/BibleReference';
|
||||||
import { BibleFormatter } from '../utils/BibleFormatter';
|
import {BibleFormatter} from '../utils/BibleFormatter';
|
||||||
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for creating and opening Bible chapter notes
|
* Service for creating and opening Bible chapter notes
|
||||||
*/
|
*/
|
||||||
export class BibleBookFiles {
|
export class BibleBookFiles {
|
||||||
private plugin: DisciplesJournalPlugin;
|
private plugin: DisciplesJournalPlugin;
|
||||||
private bibleContentService: BibleContentService;
|
private bibleContentService: BibleContentService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
plugin: DisciplesJournalPlugin,
|
plugin: DisciplesJournalPlugin,
|
||||||
bibleContentService: BibleContentService,
|
bibleContentService: BibleContentService,
|
||||||
) {
|
) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.bibleContentService = bibleContentService;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get full content path including version
|
/**
|
||||||
const fullPath = this.getFullContentPath();
|
* Open or create a chapter note
|
||||||
const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`;
|
*/
|
||||||
|
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
|
// Get full content path including version
|
||||||
const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath);
|
const fullPath = this.getFullContentPath();
|
||||||
|
const chapterPath = `${fullPath}/${parsedRef.book}/${parsedRef.book} ${parsedRef.chapter}.md`;
|
||||||
|
|
||||||
if (!fileExists && this.plugin.settings.downloadOnDemand) {
|
// Check if note exists
|
||||||
// Create the note with content from the API
|
const fileExists = await this.plugin.app.vault.adapter.exists(chapterPath);
|
||||||
await this.createChapterNote(parsedRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try opening the note
|
if (!fileExists && this.plugin.settings.downloadOnDemand) {
|
||||||
const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath);
|
// Create the note with content from the API
|
||||||
|
await this.createChapterNote(parsedRef);
|
||||||
|
}
|
||||||
|
|
||||||
if (file && file instanceof TFile) {
|
// Try opening the note
|
||||||
const leaf = this.plugin.app.workspace.getLeaf(false);
|
const file = this.plugin.app.vault.getAbstractFileByPath(chapterPath);
|
||||||
await leaf.openFile(file);
|
|
||||||
|
|
||||||
// If there's a specific verse, scroll to it
|
if (file && file instanceof TFile) {
|
||||||
if (parsedRef.verse) {
|
const leaf = this.plugin.app.workspace.getLeaf(false);
|
||||||
setTimeout(() => {
|
await leaf.openFile(file);
|
||||||
// Find the verse element and scroll to it
|
|
||||||
const verseEl = leaf.getContainer().doc.querySelector(`.verse-${parsedRef.verse}`);
|
|
||||||
if (verseEl) {
|
|
||||||
verseEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
}
|
|
||||||
}, 300); // Give it a moment to load
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(`Could not find or create the chapter note: ${chapterPath}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error opening chapter note:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// If there's a specific verse, scroll to it
|
||||||
* Create a new chapter note
|
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) {
|
private async createChapterNote(reference: BibleReference) {
|
||||||
try {
|
try {
|
||||||
// Create a properly formatted reference string
|
// Create a properly formatted reference string
|
||||||
const referenceStr = reference.toString();
|
const referenceStr = reference.toString();
|
||||||
|
|
||||||
// Get the content from the Bible API and format it for a note
|
// Get the content from the Bible API and format it for a note
|
||||||
const passage = await this.bibleContentService.getBibleContent(referenceStr);
|
const passage = await this.bibleContentService.getBibleContent(referenceStr);
|
||||||
|
|
||||||
// Check if passage is null
|
// Check if passage is null
|
||||||
if (!passage) {
|
if (!passage) {
|
||||||
console.error(`Failed to get Bible content for ${referenceStr}`);
|
console.error(`Failed to get Bible content for ${referenceStr}`);
|
||||||
throw new 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
|
// Use the formatter utility to format the content
|
||||||
const content = BibleFormatter.formatChapterContent(passage);
|
const content = BibleFormatter.formatChapterContent(passage);
|
||||||
|
|
||||||
// Save the content to a note with the version path
|
// Save the content to a note with the version path
|
||||||
const fullPath = this.getFullContentPath();
|
const fullPath = this.getFullContentPath();
|
||||||
const bookPath = `${fullPath}/${reference.book}`;
|
const bookPath = `${fullPath}/${reference.book}`;
|
||||||
const chapterPath = `${fullPath}/${reference.book}/${reference.book} ${reference.chapter}.md`;
|
const chapterPath = `${fullPath}/${reference.book}/${reference.book} ${reference.chapter}.md`;
|
||||||
|
|
||||||
// Ensure the directory exists
|
// Ensure the directory exists
|
||||||
await this.plugin.app.vault.adapter.mkdir(bookPath);
|
await this.plugin.app.vault.adapter.mkdir(bookPath);
|
||||||
|
|
||||||
// Create the note
|
// Create the note
|
||||||
await this.plugin.app.vault.create(chapterPath, content);
|
await this.plugin.app.vault.create(chapterPath, content);
|
||||||
|
|
||||||
return chapterPath;
|
return chapterPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating chapter note:', error);
|
console.error('Error creating chapter note:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the full path with version
|
* Get the full path with version
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
import { BibleReference } from "../core/BibleReference";
|
import {BibleReference} from "../core/BibleReference";
|
||||||
import { BookNames } from "./BookNames";
|
import {BookNames} from "./BookNames";
|
||||||
import { ESVApiService } from "./ESVApiService";
|
import {ESVApiService} from "./ESVApiService";
|
||||||
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a single Bible verse
|
* Interface for a single Bible verse
|
||||||
*/
|
*/
|
||||||
export interface BibleVerse {
|
export interface BibleVerse {
|
||||||
book: string;
|
book: string;
|
||||||
chapter: number;
|
chapter: number;
|
||||||
verse: number;
|
verse: number;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for Bible passage content
|
* Interface for Bible passage content
|
||||||
*/
|
*/
|
||||||
export interface BiblePassage {
|
export interface BiblePassage {
|
||||||
reference: string;
|
reference: string;
|
||||||
verses: BibleVerse[];
|
verses: BibleVerse[];
|
||||||
htmlContent?: string;
|
htmlContent?: string;
|
||||||
missingToken?: boolean;
|
missingToken?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,8 +28,8 @@ export interface BiblePassage {
|
||||||
*/
|
*/
|
||||||
export class BibleContentService {
|
export class BibleContentService {
|
||||||
private plugin: DisciplesJournalPlugin;
|
private plugin: DisciplesJournalPlugin;
|
||||||
private bible: any = null;
|
private bible: any = null;
|
||||||
private esvApiService: ESVApiService;
|
private esvApiService: ESVApiService;
|
||||||
|
|
||||||
constructor(plugin: DisciplesJournalPlugin, esvApiService: ESVApiService) {
|
constructor(plugin: DisciplesJournalPlugin, esvApiService: ESVApiService) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
@ -37,127 +37,128 @@ export class BibleContentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single verse by reference
|
* Get a single verse by reference
|
||||||
*/
|
*/
|
||||||
public getVerse(book: string, chapter: number, verse: number): BibleVerse | null {
|
public getVerse(book: string, chapter: number, verse: number): BibleVerse | null {
|
||||||
if (!this.bible) return null;
|
if (!this.bible) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const normalizedBook = BookNames.normalize(book);
|
const normalizedBook = BookNames.normalize(book);
|
||||||
if (!normalizedBook) return null;
|
if (!normalizedBook) return null;
|
||||||
|
|
||||||
// Check if the book exists in the Bible data
|
// Check if the book exists in the Bible data
|
||||||
if (!this.bible[normalizedBook]) return null;
|
if (!this.bible[normalizedBook]) return null;
|
||||||
|
|
||||||
// Check if the chapter exists
|
// Check if the chapter exists
|
||||||
const chapterStr = chapter.toString();
|
const chapterStr = chapter.toString();
|
||||||
if (!this.bible[normalizedBook][chapterStr]) return null;
|
if (!this.bible[normalizedBook][chapterStr]) return null;
|
||||||
|
|
||||||
// Check if the verse exists
|
// Check if the verse exists
|
||||||
const verseStr = verse.toString();
|
const verseStr = verse.toString();
|
||||||
if (!this.bible[normalizedBook][chapterStr][verseStr]) return null;
|
if (!this.bible[normalizedBook][chapterStr][verseStr]) return null;
|
||||||
|
|
||||||
// Return the verse
|
// Return the verse
|
||||||
return {
|
return {
|
||||||
book: normalizedBook,
|
book: normalizedBook,
|
||||||
chapter: chapter,
|
chapter: chapter,
|
||||||
verse: verse,
|
verse: verse,
|
||||||
text: this.bible[normalizedBook][chapterStr][verseStr]
|
text: this.bible[normalizedBook][chapterStr][verseStr]
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting verse:', error);
|
console.error('Error getting verse:', error);
|
||||||
return null;
|
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)
|
* Get a passage by reference
|
||||||
*/
|
* This handles everything from single verses to full chapters
|
||||||
public async getBibleContent(referenceString: string): Promise<BiblePassage | null> {
|
*/
|
||||||
try {
|
public getPassage(reference: BibleReference): BiblePassage | null {
|
||||||
// Parse the reference
|
if (!this.bible) return null;
|
||||||
const parsedRef = BibleReference.parse(referenceString);
|
|
||||||
if (!parsedRef) {
|
try {
|
||||||
console.error(`Invalid reference: ${referenceString}`);
|
const normalizedBook = BookNames.normalize(reference.book);
|
||||||
return null;
|
if (!normalizedBook) return null;
|
||||||
}
|
|
||||||
|
// Prepare the result
|
||||||
// Try to get from local Bible data first
|
const result: BiblePassage = {
|
||||||
let passage = this.getPassage(parsedRef);
|
reference: reference.toString(),
|
||||||
|
verses: []
|
||||||
// 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
|
// Get the chapter
|
||||||
passage = await this.esvApiService.downloadFromESVApi(referenceString);
|
const chapterStr = reference.chapter.toString();
|
||||||
}
|
if (!this.bible[normalizedBook][chapterStr]) return null;
|
||||||
|
|
||||||
return passage;
|
// If verse is specified, get just that verse or verse range
|
||||||
} catch (error) {
|
if (reference.verse) {
|
||||||
console.error('Error getting Bible content:', error);
|
// Single verse
|
||||||
return null;
|
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
|
* Service for handling Bible book name standardization and mapping
|
||||||
*/
|
*/
|
||||||
export class BookNames {
|
export class BookNames {
|
||||||
/**
|
/**
|
||||||
* Standardize a book name to its canonical form
|
* Standardize a book name to its canonical form
|
||||||
*/
|
*/
|
||||||
public static normalize(bookName: string): string | null {
|
public static normalize(bookName: string): string | null {
|
||||||
if (!bookName) {
|
if (!bookName) {
|
||||||
return null;
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Clean up the book name
|
||||||
* Get the ordered list of book names
|
const cleanedName = bookName.trim();
|
||||||
*/
|
|
||||||
public static getBookOrder(): string[] {
|
// First try a direct lookup
|
||||||
return [...BookNames.bookOrder]; // Return a copy to prevent modification
|
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)
|
// Bible book structure (book name and chapter count)
|
||||||
private static bibleStructure: {[book: string]: number} = {
|
private static bibleStructure: { [book: string]: number } = {
|
||||||
// Old Testament
|
// Old Testament
|
||||||
"Genesis": 50,"Exodus": 40, "Leviticus": 27, "Numbers": 36, "Deuteronomy": 34,
|
"Genesis": 50, "Exodus": 40, "Leviticus": 27, "Numbers": 36, "Deuteronomy": 34,
|
||||||
"Joshua": 24, "Judges": 21, "Ruth": 4, "1 Samuel": 31, "2 Samuel": 24,
|
"Joshua": 24, "Judges": 21, "Ruth": 4, "1 Samuel": 31, "2 Samuel": 24,
|
||||||
"1 Kings": 22, "2 Kings": 25, "1 Chronicles": 29, "2 Chronicles": 36,
|
"1 Kings": 22, "2 Kings": 25, "1 Chronicles": 29, "2 Chronicles": 36,
|
||||||
"Ezra": 10, "Nehemiah": 13, "Esther": 10, "Job": 42, "Psalms": 150,
|
"Ezra": 10, "Nehemiah": 13, "Esther": 10, "Job": 42, "Psalms": 150,
|
||||||
"Proverbs": 31, "Ecclesiastes": 12, "Song of Solomon": 8, "Isaiah": 66,
|
"Proverbs": 31, "Ecclesiastes": 12, "Song of Solomon": 8, "Isaiah": 66,
|
||||||
"Jeremiah": 52, "Lamentations": 5, "Ezekiel": 48, "Daniel": 12, "Hosea": 14,
|
"Jeremiah": 52, "Lamentations": 5, "Ezekiel": 48, "Daniel": 12, "Hosea": 14,
|
||||||
"Joel": 3, "Amos": 9, "Obadiah": 1, "Jonah": 4, "Micah": 7,
|
"Joel": 3, "Amos": 9, "Obadiah": 1, "Jonah": 4, "Micah": 7,
|
||||||
"Nahum": 3, "Habakkuk": 3, "Zephaniah": 3, "Haggai": 2, "Zechariah": 14,
|
"Nahum": 3, "Habakkuk": 3, "Zephaniah": 3, "Haggai": 2, "Zechariah": 14,
|
||||||
"Malachi": 4,
|
"Malachi": 4,
|
||||||
// New Testament
|
// New Testament
|
||||||
"Matthew": 28, "Mark": 16, "Luke": 24, "John": 21, "Acts": 28,
|
"Matthew": 28, "Mark": 16, "Luke": 24, "John": 21, "Acts": 28,
|
||||||
"Romans": 16, "1 Corinthians": 16, "2 Corinthians": 13, "Galatians": 6,
|
"Romans": 16, "1 Corinthians": 16, "2 Corinthians": 13, "Galatians": 6,
|
||||||
"Ephesians": 6, "Philippians": 4, "Colossians": 4, "1 Thessalonians": 5,
|
"Ephesians": 6, "Philippians": 4, "Colossians": 4, "1 Thessalonians": 5,
|
||||||
"2 Thessalonians": 3, "1 Timothy": 6, "2 Timothy": 4, "Titus": 3,
|
"2 Thessalonians": 3, "1 Timothy": 6, "2 Timothy": 4, "Titus": 3,
|
||||||
"Philemon": 1, "Hebrews": 13, "James": 5, "1 Peter": 5, "2 Peter": 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
|
"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 _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() {
|
private bookNameMap: Map<string, string> = new Map();
|
||||||
this.initializeBookNameMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private constructor() {
|
||||||
* Initialize the map of standardized book names
|
this.initializeBookNameMap();
|
||||||
*/
|
}
|
||||||
private initializeBookNameMap(): void {
|
|
||||||
// Old Testament
|
|
||||||
this.addBookMapping("Genesis", ["Gen", "Ge", "Gn"]);
|
|
||||||
this.addBookMapping("Exodus", ["Exo", "Ex", "Exod"]);
|
|
||||||
this.addBookMapping("Leviticus", ["Lev", "Le", "Lv"]);
|
|
||||||
this.addBookMapping("Numbers", ["Num", "Nu", "Nm", "Nb"]);
|
|
||||||
this.addBookMapping("Deuteronomy", ["Deut", "De", "Dt"]);
|
|
||||||
this.addBookMapping("Joshua", ["Josh", "Jos", "Jsh"]);
|
|
||||||
this.addBookMapping("Judges", ["Judg", "Jdg", "Jg"]);
|
|
||||||
this.addBookMapping("Ruth", ["Rth", "Ru"]);
|
|
||||||
this.addBookMapping("1 Samuel", ["1 Sam", "1 Sa", "1S", "I Sa", "1Sam", "1st Samuel"]);
|
|
||||||
this.addBookMapping("2 Samuel", ["2 Sam", "2 Sa", "2S", "II Sa", "2Sam", "2nd Samuel"]);
|
|
||||||
this.addBookMapping("1 Kings", ["1 Ki", "1 K", "1K", "I K", "1Ki", "1st Kings"]);
|
|
||||||
this.addBookMapping("2 Kings", ["2 Ki", "2 K", "2K", "II K", "2Ki", "2nd Kings"]);
|
|
||||||
this.addBookMapping("1 Chronicles", ["1 Ch", "1 Chr", "I Ch", "1Ch", "1st Chronicles"]);
|
|
||||||
this.addBookMapping("2 Chronicles", ["2 Ch", "2 Chr", "II Ch", "2Ch", "2nd Chronicles"]);
|
|
||||||
this.addBookMapping("Ezra", ["Ezr", "Ez"]);
|
|
||||||
this.addBookMapping("Nehemiah", ["Neh", "Ne"]);
|
|
||||||
this.addBookMapping("Esther", ["Est", "Es"]);
|
|
||||||
this.addBookMapping("Job", ["Jb"]);
|
|
||||||
this.addBookMapping("Psalms", ["Ps", "Psa", "Psalm", "Pslm"]);
|
|
||||||
this.addBookMapping("Proverbs", ["Prov", "Pro", "Pr"]);
|
|
||||||
this.addBookMapping("Ecclesiastes", ["Eccl", "Ecc", "Ec", "Qoh"]);
|
|
||||||
this.addBookMapping("Song of Solomon", ["Song", "SoS", "Canticles", "Song of Songs", "Cant"]);
|
|
||||||
this.addBookMapping("Isaiah", ["Isa", "Is"]);
|
|
||||||
this.addBookMapping("Jeremiah", ["Jer", "Je"]);
|
|
||||||
this.addBookMapping("Lamentations", ["Lam", "La"]);
|
|
||||||
this.addBookMapping("Ezekiel", ["Ezek", "Eze", "Ezk"]);
|
|
||||||
this.addBookMapping("Daniel", ["Dan", "Da", "Dn"]);
|
|
||||||
this.addBookMapping("Hosea", ["Hos", "Ho"]);
|
|
||||||
this.addBookMapping("Joel", ["Jl"]);
|
|
||||||
this.addBookMapping("Amos", ["Am"]);
|
|
||||||
this.addBookMapping("Obadiah", ["Obad", "Ob"]);
|
|
||||||
this.addBookMapping("Jonah", ["Jon", "Jnh"]);
|
|
||||||
this.addBookMapping("Micah", ["Mic", "Mi"]);
|
|
||||||
this.addBookMapping("Nahum", ["Nah", "Na"]);
|
|
||||||
this.addBookMapping("Habakkuk", ["Hab", "Hb"]);
|
|
||||||
this.addBookMapping("Zephaniah", ["Zeph", "Zep", "Zp"]);
|
|
||||||
this.addBookMapping("Haggai", ["Hag", "Hg"]);
|
|
||||||
this.addBookMapping("Zechariah", ["Zech", "Zec", "Zc"]);
|
|
||||||
this.addBookMapping("Malachi", ["Mal", "Ml"]);
|
|
||||||
|
|
||||||
// New Testament
|
/**
|
||||||
this.addBookMapping("Matthew", ["Matt", "Mt"]);
|
* Initialize the map of standardized book names
|
||||||
this.addBookMapping("Mark", ["Mk", "Mr"]);
|
*/
|
||||||
this.addBookMapping("Luke", ["Lk", "Lu"]);
|
private initializeBookNameMap(): void {
|
||||||
this.addBookMapping("John", ["Jn", "Jhn"]);
|
// Old Testament
|
||||||
this.addBookMapping("Acts", ["Act", "Ac"]);
|
this.addBookMapping("Genesis", ["Gen", "Ge", "Gn"]);
|
||||||
this.addBookMapping("Romans", ["Rom", "Ro", "Rm"]);
|
this.addBookMapping("Exodus", ["Exo", "Ex", "Exod"]);
|
||||||
this.addBookMapping("1 Corinthians", ["1 Cor", "1 Co", "I Co", "1Cor", "1st Corinthians"]);
|
this.addBookMapping("Leviticus", ["Lev", "Le", "Lv"]);
|
||||||
this.addBookMapping("2 Corinthians", ["2 Cor", "2 Co", "II Co", "2Cor", "2nd Corinthians"]);
|
this.addBookMapping("Numbers", ["Num", "Nu", "Nm", "Nb"]);
|
||||||
this.addBookMapping("Galatians", ["Gal", "Ga"]);
|
this.addBookMapping("Deuteronomy", ["Deut", "De", "Dt"]);
|
||||||
this.addBookMapping("Ephesians", ["Eph", "Ep"]);
|
this.addBookMapping("Joshua", ["Josh", "Jos", "Jsh"]);
|
||||||
this.addBookMapping("Philippians", ["Phil", "Php", "Pp"]);
|
this.addBookMapping("Judges", ["Judg", "Jdg", "Jg"]);
|
||||||
this.addBookMapping("Colossians", ["Col", "Co"]);
|
this.addBookMapping("Ruth", ["Rth", "Ru"]);
|
||||||
this.addBookMapping("1 Thessalonians", ["1 Thess", "1 Th", "I Th", "1Thess", "1st Thessalonians"]);
|
this.addBookMapping("1 Samuel", ["1 Sam", "1 Sa", "1S", "I Sa", "1Sam", "1st Samuel"]);
|
||||||
this.addBookMapping("2 Thessalonians", ["2 Thess", "2 Th", "II Th", "2Thess", "2nd Thessalonians"]);
|
this.addBookMapping("2 Samuel", ["2 Sam", "2 Sa", "2S", "II Sa", "2Sam", "2nd Samuel"]);
|
||||||
this.addBookMapping("1 Timothy", ["1 Tim", "1 Ti", "I Ti", "1Tim", "1st Timothy"]);
|
this.addBookMapping("1 Kings", ["1 Ki", "1 K", "1K", "I K", "1Ki", "1st Kings"]);
|
||||||
this.addBookMapping("2 Timothy", ["2 Tim", "2 Ti", "II Ti", "2Tim", "2nd Timothy"]);
|
this.addBookMapping("2 Kings", ["2 Ki", "2 K", "2K", "II K", "2Ki", "2nd Kings"]);
|
||||||
this.addBookMapping("Titus", ["Tit", "Ti"]);
|
this.addBookMapping("1 Chronicles", ["1 Ch", "1 Chr", "I Ch", "1Ch", "1st Chronicles"]);
|
||||||
this.addBookMapping("Philemon", ["Phm", "Pm"]);
|
this.addBookMapping("2 Chronicles", ["2 Ch", "2 Chr", "II Ch", "2Ch", "2nd Chronicles"]);
|
||||||
this.addBookMapping("Hebrews", ["Heb", "He"]);
|
this.addBookMapping("Ezra", ["Ezr", "Ez"]);
|
||||||
this.addBookMapping("James", ["Jas", "Jm"]);
|
this.addBookMapping("Nehemiah", ["Neh", "Ne"]);
|
||||||
this.addBookMapping("1 Peter", ["1 Pet", "1 Pe", "I Pe", "1Pet", "1st Peter"]);
|
this.addBookMapping("Esther", ["Est", "Es"]);
|
||||||
this.addBookMapping("2 Peter", ["2 Pet", "2 Pe", "II Pe", "2Pet", "2nd Peter"]);
|
this.addBookMapping("Job", ["Jb"]);
|
||||||
this.addBookMapping("1 John", ["1 Jn", "I Jn", "1Jn", "1st John"]);
|
this.addBookMapping("Psalms", ["Ps", "Psa", "Psalm", "Pslm"]);
|
||||||
this.addBookMapping("2 John", ["2 Jn", "II Jn", "2Jn", "2nd John"]);
|
this.addBookMapping("Proverbs", ["Prov", "Pro", "Pr"]);
|
||||||
this.addBookMapping("3 John", ["3 Jn", "III Jn", "3Jn", "3rd John"]);
|
this.addBookMapping("Ecclesiastes", ["Eccl", "Ecc", "Ec", "Qoh"]);
|
||||||
this.addBookMapping("Jude", ["Jud", "Jd"]);
|
this.addBookMapping("Song of Solomon", ["Song", "SoS", "Canticles", "Song of Songs", "Cant"]);
|
||||||
this.addBookMapping("Revelation", ["Rev", "Re", "The Revelation"]);
|
this.addBookMapping("Isaiah", ["Isa", "Is"]);
|
||||||
}
|
this.addBookMapping("Jeremiah", ["Jer", "Je"]);
|
||||||
|
this.addBookMapping("Lamentations", ["Lam", "La"]);
|
||||||
|
this.addBookMapping("Ezekiel", ["Ezek", "Eze", "Ezk"]);
|
||||||
|
this.addBookMapping("Daniel", ["Dan", "Da", "Dn"]);
|
||||||
|
this.addBookMapping("Hosea", ["Hos", "Ho"]);
|
||||||
|
this.addBookMapping("Joel", ["Jl"]);
|
||||||
|
this.addBookMapping("Amos", ["Am"]);
|
||||||
|
this.addBookMapping("Obadiah", ["Obad", "Ob"]);
|
||||||
|
this.addBookMapping("Jonah", ["Jon", "Jnh"]);
|
||||||
|
this.addBookMapping("Micah", ["Mic", "Mi"]);
|
||||||
|
this.addBookMapping("Nahum", ["Nah", "Na"]);
|
||||||
|
this.addBookMapping("Habakkuk", ["Hab", "Hb"]);
|
||||||
|
this.addBookMapping("Zephaniah", ["Zeph", "Zep", "Zp"]);
|
||||||
|
this.addBookMapping("Haggai", ["Hag", "Hg"]);
|
||||||
|
this.addBookMapping("Zechariah", ["Zech", "Zec", "Zc"]);
|
||||||
|
this.addBookMapping("Malachi", ["Mal", "Ml"]);
|
||||||
|
|
||||||
/**
|
// New Testament
|
||||||
* Add a mapping for a book and its alternative names
|
this.addBookMapping("Matthew", ["Matt", "Mt"]);
|
||||||
*/
|
this.addBookMapping("Mark", ["Mk", "Mr"]);
|
||||||
private addBookMapping(standardName: string, alternateNames: string[]): void {
|
this.addBookMapping("Luke", ["Lk", "Lu"]);
|
||||||
// Add the standard name (lowercase for case-insensitive matching)
|
this.addBookMapping("John", ["Jn", "Jhn"]);
|
||||||
this.bookNameMap.set(standardName.toLowerCase(), standardName);
|
this.addBookMapping("Acts", ["Act", "Ac"]);
|
||||||
|
this.addBookMapping("Romans", ["Rom", "Ro", "Rm"]);
|
||||||
// Add alternate names
|
this.addBookMapping("1 Corinthians", ["1 Cor", "1 Co", "I Co", "1Cor", "1st Corinthians"]);
|
||||||
for (const alternateName of alternateNames) {
|
this.addBookMapping("2 Corinthians", ["2 Cor", "2 Co", "II Co", "2Cor", "2nd Corinthians"]);
|
||||||
this.bookNameMap.set(alternateName.toLowerCase(), standardName);
|
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 {requestUrl} from "obsidian";
|
||||||
import { BiblePassage } from "./BibleContentService";
|
import {BiblePassage} from "./BibleContentService";
|
||||||
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for ESV API Response
|
* Interface for ESV API Response
|
||||||
*/
|
*/
|
||||||
export interface ESVApiResponse {
|
export interface ESVApiResponse {
|
||||||
query: string;
|
query: string;
|
||||||
canonical: string;
|
canonical: string;
|
||||||
parsed: number[][];
|
parsed: number[][];
|
||||||
passage_meta: ESVPassageMeta[];
|
passage_meta: ESVPassageMeta[];
|
||||||
passages: string[];
|
passages: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for ESV Passage Metadata
|
* Interface for ESV Passage Metadata
|
||||||
*/
|
*/
|
||||||
export interface ESVPassageMeta {
|
export interface ESVPassageMeta {
|
||||||
canonical: string;
|
canonical: string;
|
||||||
chapter_start: number[];
|
chapter_start: number[];
|
||||||
chapter_end: number[];
|
chapter_end: number[];
|
||||||
prev_verse: number | null;
|
prev_verse: number | null;
|
||||||
next_verse: number | null;
|
next_verse: number | null;
|
||||||
prev_chapter: number | null;
|
prev_chapter: number | null;
|
||||||
next_chapter: number[] | null;
|
next_chapter: number[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for interacting with the ESV API
|
* Service for interacting with the ESV API
|
||||||
*/
|
*/
|
||||||
export class ESVApiService {
|
export class ESVApiService {
|
||||||
private plugin: DisciplesJournalPlugin;
|
private plugin: DisciplesJournalPlugin;
|
||||||
|
|
||||||
// Store HTML formatted Bible chapters
|
// Store HTML formatted Bible chapters
|
||||||
private htmlFormattedBible: {
|
private htmlFormattedBible: {
|
||||||
[reference: string]: {
|
[reference: string]: {
|
||||||
canonical: string;
|
canonical: string;
|
||||||
htmlContent: string;
|
htmlContent: string;
|
||||||
}
|
}
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
constructor(plugin: DisciplesJournalPlugin) {
|
constructor(plugin: DisciplesJournalPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the full vault path including version subdirectory
|
* Get the full vault path including version subdirectory
|
||||||
*/
|
*/
|
||||||
private getFullContentPath(): string {
|
private getFullContentPath(): string {
|
||||||
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
|
return `${this.plugin.settings.bibleContentVaultPath}/${this.plugin.settings.preferredBibleVersion}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get HTML formatted Bible content from in-memory storage
|
* Get HTML formatted Bible content from in-memory storage
|
||||||
*/
|
*/
|
||||||
public getHTMLContent(reference: string): BiblePassage | null {
|
public getHTMLContent(reference: string): BiblePassage | null {
|
||||||
if (this.htmlFormattedBible[reference]) {
|
if (this.htmlFormattedBible[reference]) {
|
||||||
return {
|
return {
|
||||||
reference: this.htmlFormattedBible[reference].canonical,
|
reference: this.htmlFormattedBible[reference].canonical,
|
||||||
verses: [], // Empty since we're using HTML
|
verses: [], // Empty since we're using HTML
|
||||||
htmlContent: this.htmlFormattedBible[reference].htmlContent
|
htmlContent: this.htmlFormattedBible[reference].htmlContent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a collection of HTML formatted Bible chapters from files in the vault
|
* Load a collection of HTML formatted Bible chapters from files in the vault
|
||||||
*/
|
*/
|
||||||
public async loadBibleChaptersFromVault(): Promise<void> {
|
public async loadBibleChaptersFromVault(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get the path where Bible content is stored
|
// Get the path where Bible content is stored
|
||||||
const fullPath = this.getFullContentPath();
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Check if path exists
|
||||||
* Check if data is in ESV API format
|
if (!(await this.plugin.app.vault.adapter.exists(fullPath))) {
|
||||||
*/
|
console.log(`Bible content directory ${fullPath} does not exist yet`);
|
||||||
public isESVApiFormat(data: any): boolean {
|
return;
|
||||||
return data &&
|
}
|
||||||
typeof data === 'object' &&
|
|
||||||
data.canonical !== undefined &&
|
|
||||||
data.passages !== undefined &&
|
|
||||||
Array.isArray(data.passages) &&
|
|
||||||
data.passages.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Get all book directories
|
||||||
* Create a DOM-friendly error message for missing token or API errors
|
const bookDirs = await this.plugin.app.vault.adapter.list(fullPath);
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Process each book directory
|
||||||
* Download Bible content from the ESV API
|
for (const bookDir of bookDirs.folders) {
|
||||||
*/
|
// Get all chapter files
|
||||||
public async downloadFromESVApi(reference: string): Promise<BiblePassage | null> {
|
const files = await this.plugin.app.vault.adapter.list(bookDir);
|
||||||
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 chapter file
|
||||||
* Save ESV API response to a file
|
for (const file of files.files) {
|
||||||
*/
|
if (file.endsWith('.json')) {
|
||||||
private async saveESVApiResponse(reference: string, data: ESVApiResponse): Promise<void> {
|
try {
|
||||||
try {
|
// Read and parse the file
|
||||||
// Extract book and chapter from the canonical reference
|
const content = await this.plugin.app.vault.adapter.read(file);
|
||||||
const parts = data.canonical.split(' ');
|
const data = JSON.parse(content);
|
||||||
if (parts.length < 2) return;
|
|
||||||
|
// 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(' ');
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Create the directory structure with version subdirectory
|
||||||
* Ensure vault directory exists, creating it if necessary
|
const fullPath = this.getFullContentPath();
|
||||||
*/
|
const bookPath = `${fullPath}/${book}`;
|
||||||
private async ensureVaultDirectoryExists(path: string): Promise<void> {
|
await this.ensureVaultDirectoryExists(fullPath);
|
||||||
const parts = path.split('/').filter(p => p.length > 0);
|
await this.ensureVaultDirectoryExists(bookPath);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Save the raw API response as JSON
|
||||||
* Ensures Bible data is loaded, downloading if necessary
|
const jsonPath = `${bookPath}/${data.canonical}.json`;
|
||||||
*/
|
await this.plugin.app.vault.adapter.write(jsonPath, JSON.stringify(data));
|
||||||
public async ensureBibleData(): Promise<void> {
|
} catch (error) {
|
||||||
try {
|
console.error('Error saving ESV API response:', error);
|
||||||
// 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...');
|
* Ensure vault directory exists, creating it if necessary
|
||||||
await this.loadBibleChaptersFromVault();
|
*/
|
||||||
} else {
|
private async ensureVaultDirectoryExists(path: string): Promise<void> {
|
||||||
console.log('Bible data already exists');
|
const parts = path.split('/').filter(p => p.length > 0);
|
||||||
}
|
let currentPath = '';
|
||||||
} catch (error) {
|
|
||||||
console.error('Error ensuring Bible data:', error);
|
for (const part of parts) {
|
||||||
throw error;
|
currentPath += (currentPath ? '/' : '') + part;
|
||||||
}
|
if (!(await this.plugin.app.vault.adapter.exists(currentPath))) {
|
||||||
}
|
await this.plugin.app.vault.adapter.mkdir(currentPath);
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* Check if Bible data exists in the vault
|
}
|
||||||
*/
|
|
||||||
private async checkBibleDataExists(): Promise<boolean> {
|
/**
|
||||||
try {
|
* Ensures Bible data is loaded, downloading if necessary
|
||||||
// Check for a common book like Genesis
|
*/
|
||||||
const genesisPath = `${this.getFullContentPath()}/Genesis/Genesis 1.md`;
|
public async ensureBibleData(): Promise<void> {
|
||||||
return await this.plugin.app.vault.adapter.exists(genesisPath);
|
try {
|
||||||
} catch (error) {
|
// Check if we have data by looking for specific files in the vault
|
||||||
console.error('Error checking if Bible data exists:', error);
|
const hasData = await this.checkBibleDataExists();
|
||||||
return false;
|
|
||||||
}
|
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 DisciplesJournalPlugin from '../core/DisciplesJournalPlugin';
|
||||||
import { THEME_PRESETS } from '../components/BibleStyles';
|
import {THEME_PRESETS} from '../components/BibleStyles';
|
||||||
|
|
||||||
export interface DisciplesJournalSettings {
|
export interface DisciplesJournalSettings {
|
||||||
displayInlineVerses: boolean;
|
displayInlineVerses: boolean;
|
||||||
displayFullPassages: boolean;
|
displayFullPassages: boolean;
|
||||||
bibleTextFontSize: string;
|
bibleTextFontSize: string;
|
||||||
wordsOfChristColor: string;
|
wordsOfChristColor: string;
|
||||||
verseNumberColor: string;
|
verseNumberColor: string;
|
||||||
headingColor: string;
|
headingColor: string;
|
||||||
blockIndentation: string;
|
blockIndentation: string;
|
||||||
preferredBibleVersion: string;
|
preferredBibleVersion: string;
|
||||||
esvApiToken: string;
|
esvApiToken: string;
|
||||||
downloadOnDemand: boolean;
|
downloadOnDemand: boolean;
|
||||||
bibleContentVaultPath: string;
|
bibleContentVaultPath: string;
|
||||||
stylePreset: string;
|
stylePreset: string;
|
||||||
showNavigationForVerses: boolean;
|
showNavigationForVerses: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: DisciplesJournalSettings = {
|
export const DEFAULT_SETTINGS: DisciplesJournalSettings = {
|
||||||
displayInlineVerses: true,
|
displayInlineVerses: true,
|
||||||
displayFullPassages: true,
|
displayFullPassages: true,
|
||||||
bibleTextFontSize: '100%',
|
bibleTextFontSize: '100%',
|
||||||
wordsOfChristColor: 'var(--text-accent)',
|
wordsOfChristColor: 'var(--text-accent)',
|
||||||
verseNumberColor: 'var(--text-accent)',
|
verseNumberColor: 'var(--text-accent)',
|
||||||
headingColor: 'var(--text-accent)',
|
headingColor: 'var(--text-accent)',
|
||||||
blockIndentation: '2em',
|
blockIndentation: '2em',
|
||||||
preferredBibleVersion: 'ESV',
|
preferredBibleVersion: 'ESV',
|
||||||
esvApiToken: '',
|
esvApiToken: '',
|
||||||
downloadOnDemand: true,
|
downloadOnDemand: true,
|
||||||
bibleContentVaultPath: 'Bible',
|
bibleContentVaultPath: 'Bible',
|
||||||
stylePreset: 'default',
|
stylePreset: 'default',
|
||||||
showNavigationForVerses: false
|
showNavigationForVerses: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
||||||
plugin: DisciplesJournalPlugin;
|
plugin: DisciplesJournalPlugin;
|
||||||
|
|
||||||
constructor(app: App, plugin: DisciplesJournalPlugin) {
|
constructor(app: App, plugin: DisciplesJournalPlugin) {
|
||||||
super(app, plugin);
|
super(app, plugin);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
display(): void {
|
display(): void {
|
||||||
const { containerEl } = this;
|
const {containerEl} = this;
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
new Setting(containerEl).setName('Display Customization').setHeading();
|
new Setting(containerEl).setName('Display Customization').setHeading();
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Display Inline Verses')
|
.setName('Display Inline Verses')
|
||||||
.setDesc('Enable rendering of inline Bible references (in `code blocks`).')
|
.setDesc('Enable rendering of inline Bible references (in `code blocks`).')
|
||||||
.addToggle(toggle => toggle
|
.addToggle(toggle => toggle
|
||||||
.setValue(this.plugin.settings.displayInlineVerses)
|
.setValue(this.plugin.settings.displayInlineVerses)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.displayInlineVerses = value;
|
this.plugin.settings.displayInlineVerses = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Display Full Passages')
|
.setName('Display Full Passages')
|
||||||
.setDesc('Enable rendering of full Bible passages (in ```bible code blocks).')
|
.setDesc('Enable rendering of full Bible passages (in ```bible code blocks).')
|
||||||
.addToggle(toggle => toggle
|
.addToggle(toggle => toggle
|
||||||
.setValue(this.plugin.settings.displayFullPassages)
|
.setValue(this.plugin.settings.displayFullPassages)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.displayFullPassages = value;
|
this.plugin.settings.displayFullPassages = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Show Navigation for Verses')
|
.setName('Show Navigation for Verses')
|
||||||
.setDesc('Show chapter navigation when displaying specific verses (by default, navigation only shows for full chapters).')
|
.setDesc('Show chapter navigation when displaying specific verses (by default, navigation only shows for full chapters).')
|
||||||
.addToggle(toggle => toggle
|
.addToggle(toggle => toggle
|
||||||
.setValue(this.plugin.settings.showNavigationForVerses)
|
.setValue(this.plugin.settings.showNavigationForVerses)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.showNavigationForVerses = value;
|
this.plugin.settings.showNavigationForVerses = value;
|
||||||
await this.plugin.saveSettings();
|
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)
|
new Setting(containerEl)
|
||||||
.setName('Bible Text Font Size')
|
.setName('Style Preset')
|
||||||
.setDesc('Set the font size for Bible verses and passages.')
|
.setDesc('Choose a predefined style preset for Bible content.')
|
||||||
.addDropdown(dropdown => dropdown
|
.addDropdown(dropdown => {
|
||||||
.addOption('80%', 'Smaller (80%)')
|
// Add all theme presets
|
||||||
.addOption('90%', 'Small (90%)')
|
Object.keys(THEME_PRESETS).forEach(preset => {
|
||||||
.addOption('100%', 'Normal (100%)')
|
dropdown.addOption(preset, preset.charAt(0).toUpperCase() + preset.slice(1));
|
||||||
.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)
|
dropdown.setValue(this.plugin.settings.stylePreset);
|
||||||
.setName('Words of Christ Color')
|
dropdown.onChange(async (value) => {
|
||||||
.setDesc('Set the color for Words of Christ (use `var(--text-normal)` for no special color).')
|
this.plugin.settings.stylePreset = value;
|
||||||
.addText(text => text
|
await this.plugin.saveSettings();
|
||||||
.setPlaceholder('var(--text-accent)')
|
this.plugin.updateBibleStyles();
|
||||||
.setValue(this.plugin.settings.wordsOfChristColor)
|
});
|
||||||
.onChange(async (value) => {
|
});
|
||||||
this.plugin.settings.wordsOfChristColor = value || 'var(--text-accent)';
|
|
||||||
await this.plugin.saveSettings();
|
new Setting(containerEl)
|
||||||
this.plugin.updateBibleStyles();
|
.setName('Bible Text Font Size')
|
||||||
}));
|
.setDesc('Set the font size for Bible verses and passages.')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
new Setting(containerEl)
|
.addOption('80%', 'Smaller (80%)')
|
||||||
.setName('Verse Number Color')
|
.addOption('90%', 'Small (90%)')
|
||||||
.setDesc('Set the color for verse numbers.')
|
.addOption('100%', 'Normal (100%)')
|
||||||
.addText(text => text
|
.addOption('110%', 'Large (110%)')
|
||||||
.setPlaceholder('var(--text-accent)')
|
.addOption('120%', 'Larger (120%)')
|
||||||
.setValue(this.plugin.settings.verseNumberColor)
|
.setValue(this.plugin.settings.bibleTextFontSize)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.verseNumberColor = value || 'var(--text-accent)';
|
this.plugin.settings.bibleTextFontSize = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
this.plugin.updateBibleStyles();
|
this.plugin.updateBibleStyles();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
new Setting(containerEl)
|
|
||||||
.setName('Heading Color')
|
new Setting(containerEl).setName('Text Styling').setHeading();
|
||||||
.setDesc('Set the color for Bible passage headings.')
|
|
||||||
.addText(text => text
|
new Setting(containerEl)
|
||||||
.setPlaceholder('var(--text-accent)')
|
.setName('Words of Christ Color')
|
||||||
.setValue(this.plugin.settings.headingColor)
|
.setDesc('Set the color for Words of Christ (use `var(--text-normal)` for no special color).')
|
||||||
.onChange(async (value) => {
|
.addText(text => text
|
||||||
this.plugin.settings.headingColor = value || 'var(--text-accent)';
|
.setPlaceholder('var(--text-accent)')
|
||||||
await this.plugin.saveSettings();
|
.setValue(this.plugin.settings.wordsOfChristColor)
|
||||||
this.plugin.updateBibleStyles();
|
.onChange(async (value) => {
|
||||||
}));
|
this.plugin.settings.wordsOfChristColor = value || 'var(--text-accent)';
|
||||||
|
await this.plugin.saveSettings();
|
||||||
new Setting(containerEl)
|
this.plugin.updateBibleStyles();
|
||||||
.setName('Block Indentation')
|
}));
|
||||||
.setDesc('Set the indentation for block sections.')
|
|
||||||
.addText(text => text
|
new Setting(containerEl)
|
||||||
.setPlaceholder('2em')
|
.setName('Verse Number Color')
|
||||||
.setValue(this.plugin.settings.blockIndentation)
|
.setDesc('Set the color for verse numbers.')
|
||||||
.onChange(async (value) => {
|
.addText(text => text
|
||||||
this.plugin.settings.blockIndentation = value || '2em';
|
.setPlaceholder('var(--text-accent)')
|
||||||
await this.plugin.saveSettings();
|
.setValue(this.plugin.settings.verseNumberColor)
|
||||||
this.plugin.updateBibleStyles();
|
.onChange(async (value) => {
|
||||||
}));
|
this.plugin.settings.verseNumberColor = value || 'var(--text-accent)';
|
||||||
|
await this.plugin.saveSettings();
|
||||||
new Setting(containerEl).setName('Bible').setHeading();
|
this.plugin.updateBibleStyles();
|
||||||
|
}));
|
||||||
new Setting(containerEl)
|
|
||||||
.setName('Preferred Bible Version')
|
new Setting(containerEl)
|
||||||
.setDesc('Select your preferred Bible version (only ESV currently supported).')
|
.setName('Heading Color')
|
||||||
.addDropdown(dropdown => dropdown
|
.setDesc('Set the color for Bible passage headings.')
|
||||||
.addOption('ESV', 'English Standard Version (ESV)')
|
.addText(text => text
|
||||||
.setValue(this.plugin.settings.preferredBibleVersion)
|
.setPlaceholder('var(--text-accent)')
|
||||||
.onChange(async (value) => {
|
.setValue(this.plugin.settings.headingColor)
|
||||||
this.plugin.settings.preferredBibleVersion = value;
|
.onChange(async (value) => {
|
||||||
await this.plugin.saveSettings();
|
this.plugin.settings.headingColor = value || 'var(--text-accent)';
|
||||||
})
|
await this.plugin.saveSettings();
|
||||||
);
|
this.plugin.updateBibleStyles();
|
||||||
|
}));
|
||||||
new Setting(containerEl)
|
|
||||||
.setName('Bible Content Vault Path')
|
new Setting(containerEl)
|
||||||
.setDesc('Root path in your vault where Bible content will be stored. Each translation will be stored in a subdirectory.')
|
.setName('Block Indentation')
|
||||||
.addText(text => text
|
.setDesc('Set the indentation for block sections.')
|
||||||
.setPlaceholder('Bible')
|
.addText(text => text
|
||||||
.setValue(this.plugin.settings.bibleContentVaultPath)
|
.setPlaceholder('2em')
|
||||||
.onChange(async (value) => {
|
.setValue(this.plugin.settings.blockIndentation)
|
||||||
this.plugin.settings.bibleContentVaultPath = value || 'Bible';
|
.onChange(async (value) => {
|
||||||
await this.plugin.saveSettings();
|
this.plugin.settings.blockIndentation = value || '2em';
|
||||||
}));
|
await this.plugin.saveSettings();
|
||||||
|
this.plugin.updateBibleStyles();
|
||||||
new Setting(containerEl).setName('ESV API').setHeading();
|
}));
|
||||||
|
|
||||||
const apiInfoDiv = containerEl.createDiv({ cls: 'disciples-journal-api-info' });
|
new Setting(containerEl).setName('Bible').setHeading();
|
||||||
|
|
||||||
apiInfoDiv.createEl('p', {
|
new Setting(containerEl)
|
||||||
text: 'The ESV API allows this plugin to download and display Bible passages from the ESV translation.'
|
.setName('Preferred Bible Version')
|
||||||
});
|
.setDesc('Select your preferred Bible version (only ESV currently supported).')
|
||||||
|
.addDropdown(dropdown => dropdown
|
||||||
apiInfoDiv.createEl('p', {
|
.addOption('ESV', 'English Standard Version (ESV)')
|
||||||
text: 'To get a free ESV API token:',
|
.setValue(this.plugin.settings.preferredBibleVersion)
|
||||||
attr: { style: 'font-weight: bold;' }
|
.onChange(async (value) => {
|
||||||
});
|
this.plugin.settings.preferredBibleVersion = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
const instructionsList = apiInfoDiv.createEl('ol');
|
})
|
||||||
|
);
|
||||||
const steps = [
|
|
||||||
'Visit api.esv.org',
|
new Setting(containerEl)
|
||||||
'Sign up for a free account',
|
.setName('Bible Content Vault Path')
|
||||||
'After logging in, go to "API Keys" in your account',
|
.setDesc('Root path in your vault where Bible content will be stored. Each translation will be stored in a subdirectory.')
|
||||||
'Create a new token and copy it here'
|
.addText(text => text
|
||||||
];
|
.setPlaceholder('Bible')
|
||||||
|
.setValue(this.plugin.settings.bibleContentVaultPath)
|
||||||
steps.forEach(step => {
|
.onChange(async (value) => {
|
||||||
if (step === 'Visit api.esv.org') {
|
this.plugin.settings.bibleContentVaultPath = value || 'Bible';
|
||||||
const listItem = instructionsList.createEl('li');
|
await this.plugin.saveSettings();
|
||||||
listItem.createEl('span', { text: 'Visit ' });
|
}));
|
||||||
listItem.createEl('a', {
|
|
||||||
text: 'api.esv.org',
|
new Setting(containerEl).setName('ESV API').setHeading();
|
||||||
href: 'https://api.esv.org/docs/',
|
|
||||||
attr: { target: '_blank' }
|
const apiInfoDiv = containerEl.createDiv({cls: 'disciples-journal-api-info'});
|
||||||
});
|
|
||||||
} else {
|
apiInfoDiv.createEl('p', {
|
||||||
instructionsList.createEl('li', { text: step });
|
text: 'The ESV API allows this plugin to download and display Bible passages from the ESV translation.'
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
apiInfoDiv.createEl('p', {
|
||||||
apiInfoDiv.createEl('p', {
|
text: 'To get a free ESV API token:',
|
||||||
text: 'With a valid token, the plugin can download and display Bible passages directly in your notes.'
|
attr: {style: 'font-weight: bold;'}
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
const instructionsList = apiInfoDiv.createEl('ol');
|
||||||
.setName('ESV API Token')
|
|
||||||
.setDesc('Enter your ESV API token from api.esv.org.')
|
const steps = [
|
||||||
.addText(text => text
|
'Visit api.esv.org',
|
||||||
.setPlaceholder('Enter your ESV API token')
|
'Sign up for a free account',
|
||||||
.setValue(this.plugin.settings.esvApiToken)
|
'After logging in, go to "API Keys" in your account',
|
||||||
.onChange(async (value) => {
|
'Create a new token and copy it here'
|
||||||
this.plugin.settings.esvApiToken = value;
|
];
|
||||||
await this.plugin.saveSettings();
|
|
||||||
if (value) {
|
steps.forEach(step => {
|
||||||
new Notice('ESV API token updated', 2000);
|
if (step === 'Visit api.esv.org') {
|
||||||
}
|
const listItem = instructionsList.createEl('li');
|
||||||
}));
|
listItem.createEl('span', {text: 'Visit '});
|
||||||
|
listItem.createEl('a', {
|
||||||
new Setting(containerEl)
|
text: 'api.esv.org',
|
||||||
.setName('Download on Demand')
|
href: 'https://api.esv.org/docs/',
|
||||||
.setDesc('Enable automatic downloading of Bible content when requested.')
|
attr: {target: '_blank'}
|
||||||
.addToggle(toggle => toggle
|
});
|
||||||
.setValue(this.plugin.settings.downloadOnDemand)
|
} else {
|
||||||
.onChange(async (value) => {
|
instructionsList.createEl('li', {text: step});
|
||||||
this.plugin.settings.downloadOnDemand = value;
|
}
|
||||||
await this.plugin.saveSettings();
|
});
|
||||||
}));
|
|
||||||
|
apiInfoDiv.createEl('p', {
|
||||||
|
text: 'With a valid token, the plugin can download and display Bible passages directly in your notes.'
|
||||||
new Setting(containerEl)
|
});
|
||||||
.setName('Bible Data Status')
|
|
||||||
.setDesc('Click to reload Bible data from source')
|
new Setting(containerEl)
|
||||||
.addButton(button => button
|
.setName('ESV API Token')
|
||||||
.setButtonText('Reload Bible Data')
|
.setDesc('Enter your ESV API token from api.esv.org.')
|
||||||
.onClick(async () => {
|
.addText(text => text
|
||||||
button.setButtonText('Loading...');
|
.setPlaceholder('Enter your ESV API token')
|
||||||
button.setDisabled(true);
|
.setValue(this.plugin.settings.esvApiToken)
|
||||||
await this.plugin.loadBibleData();
|
.onChange(async (value) => {
|
||||||
button.setButtonText('Reload Bible Data');
|
this.plugin.settings.esvApiToken = value;
|
||||||
button.setDisabled(false);
|
await this.plugin.saveSettings();
|
||||||
this.display(); // Refresh the settings panel
|
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' {
|
declare module '*.json' {
|
||||||
const value: any;
|
const value: any;
|
||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the structure of the Bible data for better type checking
|
// Define the structure of the Bible data for better type checking
|
||||||
interface ESVVerse {
|
interface ESVVerse {
|
||||||
pk: number;
|
pk: number;
|
||||||
translation: string;
|
translation: string;
|
||||||
book: number | string;
|
book: number | string;
|
||||||
chapter: number;
|
chapter: number;
|
||||||
verse: number;
|
verse: number;
|
||||||
text: string;
|
text: string;
|
||||||
comment?: 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
|
* Utility for formatting Bible content consistently across the application
|
||||||
*/
|
*/
|
||||||
export class BibleFormatter {
|
export class BibleFormatter {
|
||||||
/**
|
/**
|
||||||
* Format chapter content as Markdown
|
* Format chapter content as Markdown
|
||||||
*/
|
*/
|
||||||
public static formatChapterContent(passage: BiblePassage): string {
|
public static formatChapterContent(passage: BiblePassage): string {
|
||||||
if (!passage) return "# Error: No passage content\n\nThe requested passage could not be loaded.";
|
if (!passage) return "# Error: No passage content\n\nThe requested passage could not be loaded.";
|
||||||
|
|
||||||
let content = "";
|
let content = "";
|
||||||
|
|
||||||
// Add code block for rendering
|
// Add code block for rendering
|
||||||
content += "```bible\n";
|
content += "```bible\n";
|
||||||
content += passage.reference;
|
content += passage.reference;
|
||||||
content += "\n```\n\n";
|
content += "\n```\n\n";
|
||||||
|
|
||||||
// Alternatively, add each verse separately
|
// Alternatively, add each verse separately
|
||||||
if (passage.verses && passage.verses.length > 0) {
|
if (passage.verses && passage.verses.length > 0) {
|
||||||
for (const verse of passage.verses) {
|
for (const verse of passage.verses) {
|
||||||
content += `**${verse.verse}** ${verse.text}\n\n`;
|
content += `**${verse.verse}** ${verse.text}\n\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue