diff --git a/package-lock.json b/package-lock.json index d0dac39..7dd60db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "obsidian-sample-plugin", + "name": "obsidian-autoreference-scriptures", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "obsidian-sample-plugin", + "name": "obsidian-autoreference-scriptures", "version": "1.0.0", "license": "0-BSD", "dependencies": { diff --git a/src/scriptures/books.ts b/src/scriptures/books.ts index baec251..3c229a9 100644 --- a/src/scriptures/books.ts +++ b/src/scriptures/books.ts @@ -24,6 +24,15 @@ export interface ScriptureBook { * (e.g. `"chapter-"` turns chapter `1` into the path segment `chapter-1`). */ chapterPrefix?: string; + /** + * Optional map from a normalized section key to the URL path segment + * appended after `urlBase`. When present, the URL builder resolves the + * chapter via this map instead of the default `chapterPrefix + chapter` + * construction. Keys use the canonical forms returned by + * `normalizePmgSection` (e.g. `"message"`, `"intro"`, `"1"`, `"3.1"`, + * `"3.2"`, …). + */ + chapterMap?: Record; } export const SCRIPTURE_BOOKS: ScriptureBook[] = [ @@ -136,9 +145,29 @@ export const SCRIPTURE_BOOKS: ScriptureBook[] = [ { names: ['Preach My Gospel', 'PMG'], volume: 'manual', - slug: 'preach-my-gospel-a-guide-to-sharing-the-gospel-of-jesus-christ', - urlBase: - 'https://www.churchofjesuschrist.org/study/manual/preach-my-gospel-a-guide-to-sharing-the-gospel-of-jesus-christ', - chapterPrefix: 'chapter-', + slug: 'preach-my-gospel-2023', + urlBase: 'https://www.churchofjesuschrist.org/study/manual/preach-my-gospel-2023', + chapterMap: { + 'message': '01-first-presidency-message', + 'intro': '02-intro', + '1': '03-chapter-1', + '2': '04-chapter-2', + '3.1': '04-chapter-3/06-chapter-3-intro', + '3.2': '04-chapter-3/07-chapter-3-invite', + '3.3': '04-chapter-3/08-chapter-3-lesson-1', + '3.4': '04-chapter-3/09-chapter-3-lesson-2', + '3.5': '04-chapter-3/10-chapter-3-lesson-3', + '3.6': '04-chapter-3/11-chapter-3-lesson-4', + '4': '12-chapter-4', + '5': '13-chapter-5', + '6': '14-chapter-6', + '7': '15-chapter-7', + '8': '16-chapter-8', + '9': '17-chapter-9', + '10': '18-chapter-10', + '11': '19-chapter-11', + '12': '20-chapter-12', + '13': '21-chapter-13', + }, }, ]; diff --git a/src/scriptures/parser.ts b/src/scriptures/parser.ts index 15a0497..9aa6048 100644 --- a/src/scriptures/parser.ts +++ b/src/scriptures/parser.ts @@ -59,13 +59,17 @@ export const SCRIPTURE_REGEX: RegExp = (() => { // hyphen-separated additional digit groups (e.g. "1", "2-4", "9,11"). const versePart = String.raw`\d+(?:[-,]\d+)*`; - // After the book name, optionally allow "page N", "pg. N", or "#N" - // in addition to the bare "N" used by standard scripture references. - // This supports formats like "PMG page 18", "PMG pg. 18", "PMG #18". - const numberPrefix = String.raw`(?:(?:page|pg\.)\s+|#\s*)?`; + // Chapter / section identifier, tried in specificity order so that longer + // PMG-specific tokens are matched before the generic digit fallback: + // • PMG named sections: Message, Intro + // • PMG lesson shorthand: L1–L4 + // • PMG chapter-3 letter: 3B (= section 3.2, the "invite" page) + // • PMG chapter-3 decimal: 3.1–3.6 + // • Any book: optional "chapter"/"ch."/"#" prefix followed by digits + const chapterPart = String.raw`Message|Intro|L[1-4]|3B|3\.[1-6]|(?:(?:chapter|ch\.)\s+|#\s*)?\d+`; return new RegExp( - String.raw`\b(${bookAlt})\s+${numberPrefix}(\d+)(?::(${versePart}))?`, + String.raw`\b(${bookAlt})\s+(${chapterPart})(?::(${versePart}))?`, 'gi', ); })(); diff --git a/src/scriptures/urlBuilder.ts b/src/scriptures/urlBuilder.ts index bd8458d..7cae66d 100644 --- a/src/scriptures/urlBuilder.ts +++ b/src/scriptures/urlBuilder.ts @@ -23,10 +23,57 @@ function firstVerseAnchor(verseStr: string): string { return m ? `p${m[0]}` : 'p1'; } +/** + * Normalise a raw PMG section string (as captured by the regex) into the + * canonical key used in `ScriptureBook.chapterMap`. + * + * Supported inputs (case-insensitive): + * "Message" → "message" + * "Intro" → "intro" + * "L1"–"L4" → "3.3"–"3.6" + * "3B" → "3.2" + * "3.1"–"3.6" → "3.1"–"3.6" + * "3" / "chapter 3" / "ch. 3" / "#3" → "3.1" (chapter-3 intro) + * "1" / "chapter 1" / "ch. 1" / "#1" → "1" + */ +export function normalizePmgSection(raw: string): string | undefined { + const s = raw.toLowerCase().trim(); + + if (s === 'message') return 'message'; + if (s === 'intro') return 'intro'; + + // Lesson shorthand: L1–L4 map to chapter-3 sections 3.3–3.6. + // Offset 2 because: L1 → 1+2=3 → "3.3", L2 → 2+2=4 → "3.4", etc. + const lessonMatch = s.match(/^l([1-4])$/); + if (lessonMatch) return `3.${2 + parseInt(lessonMatch[1]!, 10)}`; + + // Letter shorthand: 3B = section 3.2 (the "invite" page). + if (s === '3b') return '3.2'; + + // Decimal notation 3.1–3.6: pass through as-is. + const decimalMatch = s.match(/^3\.([1-6])$/); + if (decimalMatch) return `3.${decimalMatch[1]}`; + + // Generic chapter reference (optional "chapter"/"ch."/"#" prefix + digits). + const chapterMatch = s.match(/^(?:(?:chapter|ch\.)\s+|#\s*)?(\d+)$/); + if (chapterMatch) { + const n = chapterMatch[1]; + // Bare "3" (or "chapter 3" etc.) means the chapter-3 intro page. + return n === '3' ? '3.1' : n; + } + + return undefined; +} + /** * Build a ChurchOfJesusChrist.org study URL for the given reference. * - * Examples: + * When `book.chapterMap` is present (e.g. Preach My Gospel), the chapter + * string is normalised via `normalizePmgSection` and looked up in the map to + * produce the correct path segment. Verse anchors are not applied for + * chapterMap books because PMG pages do not use paragraph-level anchors. + * + * Examples for standard scripture books: * book=gen, chapter="9", verses=undefined * → https://…/ot/gen/9?lang=eng * book=gen, chapter="9", verses="1" @@ -35,6 +82,14 @@ function firstVerseAnchor(verseStr: string): string { * → https://…/ot/gen/9?lang=eng&id=p2-p4#p2 * book=matt, chapter="5", verses="9,11" * → https://…/nt/matt/5?lang=eng&id=p9,p11#p9 + * + * Examples for Preach My Gospel: + * book=pmg, chapter="Message" + * → https://…/preach-my-gospel-2023/01-first-presidency-message?lang=eng + * book=pmg, chapter="3" (or "3.1") + * → https://…/preach-my-gospel-2023/04-chapter-3/06-chapter-3-intro?lang=eng + * book=pmg, chapter="L1" (or "3.3") + * → https://…/preach-my-gospel-2023/04-chapter-3/08-chapter-3-lesson-1?lang=eng */ export function buildUrl( book: ScriptureBook, @@ -42,6 +97,18 @@ export function buildUrl( verses: string | undefined, ): string { const base = book.urlBase ?? `${BASE_URL}/${book.volume}/${book.slug}`; + + // Books with a chapterMap (e.g. PMG) use a lookup table for URL paths. + if (book.chapterMap) { + const key = normalizePmgSection(chapter); + const path = key !== undefined ? book.chapterMap[key] : undefined; + if (path) { + return `${base}/${path}?lang=eng`; + } + // Unrecognised section: fall back to a best-effort URL. + return `${base}/${chapter}?lang=eng`; + } + const chapterSegment = `${book.chapterPrefix ?? ''}${chapter}`; const chapterUrl = `${base}/${chapterSegment}?lang=eng`;