scotttomaszewski_obsidian-d.../ARCHITECTURE.md

172 lines
9.3 KiB
Markdown
Raw Permalink Normal View History

2026-06-01 01:22:09 +00:00
# Architecture
How Disciples Journal is put together. This is the orientation doc — read it before
making structural changes. For user-facing features see `README.md`; for narrow
topics see [docs/](docs/).
## Big picture
The plugin hooks into Obsidian's markdown pipeline in three places and turns Bible
references into rich content:
1. **Inline references** — an inline code span like `` `John 3:16` `` becomes a
styled span with a hover popup showing the verse text.
2. **Full passages** — a fenced ` ```bible ` code block renders the whole passage
inline, with navigation and a clickable heading.
3. **Chapter notes** — clicking a reference opens (or creates) a markdown note for
that chapter under the configured Bible content folder.
Content is resolved from local notes first, then downloaded from the **ESV API** on
demand (when enabled and a token is configured). Downloaded passages are persisted
as markdown notes whose frontmatter holds the raw API response, so they double as
the cache and the source of truth.
## Entry point and lifecycle
- `main.ts` is a thin shim that re-exports the real plugin class.
- `src/core/DisciplesJournalPlugin.ts` (`DisciplesJournalPlugin extends Plugin`) is
the composition root. `onload()`:
- loads settings (`loadSettings` merges saved data over `DEFAULT_SETTINGS`),
- constructs the services and components (wiring described below),
- registers the markdown processors, the Live Preview editor extension, the
`Open Bible` + `Update frontmatter` commands, a ribbon icon, the settings tab,
and an `active-leaf-change` handler that re-applies styles.
- The hover-preview event handler is added via `addChild(...)` so its global
document listeners/timers are unloaded with the plugin (avoids leaks).
## Layers
Source is organized under `src/` by responsibility:
### `core/` — plugin wiring and domain model
- **`DisciplesJournalPlugin.ts`** — the `Plugin` subclass / composition root
(settings, command registration, style refresh, `openChapterNote`,
`updateAllBibleNoteFrontmatter`).
- **`BibleReference.ts`** — the central value object: `{ book, chapter, verse?,
endVerse?, endChapter? }`. `BibleReference.parse(str)` understands the supported
formats; the constructor normalizes the book name via `BookNames` and throws on
an unknown book. See [docs/reference-formats.md](docs/reference-formats.md).
- **`BibleMarkupProcessor.ts`** — the two registered markdown callbacks
(`processBibleCodeBlock`, `processInlineBibleReferences`). It checks the relevant
display setting, then delegates to the renderer and handles errors.
- **`BibleEventHandlers.ts`** — the single, plugin-owned hover/popup lifecycle
(mouseover/mouseout, show/hide timing). Loaded as a child component.
- **`VerseSelection.ts`** — pure value object: a verse set within one book →
contiguous `BibleReference[]` runs + a display label (`Genesis 1:2-3, 5`).
- **`VerseSelectionService.ts`** — plugin-owned (`addChild`) holder of the *single*
active verse selection across panes + its owning controller; notifies subscribers.
2026-06-01 01:22:09 +00:00
### `services/` — content resolution and storage
- **`BibleContentService.ts`** — the resolution funnel. `getBibleContent(ref)`
returns a `BibleApiResponse`, trying in order: in-memory cache → local note
frontmatter → ESV API download (only if `downloadOnDemand`).
- **`ESVApiService.ts`** — talks to `api.esv.org` via Obsidian's `requestUrl`.
Converts raw API responses to `BibleApiResponse` (`toBibleApiResponse`, also
reused when re-reading a saved note) and persists responses as markdown notes
(`saveESVApiResponseAsMdNote`). Details in [docs/esv-api.md](docs/esv-api.md).
- **`BibleFiles.ts`** — path/file resolution for chapter & passage notes
(static helpers) plus opening/creating notes and scroll-to-verse behavior
(instance methods). Filenames: `Book N.md` for chapters, `Book NvV[-E].md` for
verse ranges, under `<bibleContentVaultPath>/<version>/<Book>/`.
- **`BookNames.ts`** — canonical book-name normalization and per-book chapter
counts (used e.g. to work around an ESV API single-chapter-book quirk).
### `components/` — rendering and UI
- **`BibleReferenceRenderer.ts`** — builds the DOM for inline references
(`processInlineCodeBlocks`), full passages (`processFullBiblePassage`), and the
hover popup (`showVersePreview`). HTML from the API is inserted via
`sanitizeHTMLToDom`, never raw `innerHTML`.
- **`BibleReferenceInlineExtension.ts`** — CodeMirror editor extension that makes
references work in Live Preview (edit mode), not just reading mode.
- **`BibleNavigation.ts`** — prev/next chapter navigation elements + navigation.
- **`BibleStyles.ts`** — injects/refreshes themed CSS per document (each popout
window has its own `Document`, so styles are applied per-doc).
- **`BookSuggest.ts`**, **`OpenBibleModal.ts`** — the `Open Bible` modal and its
book autocomplete.
- **`VerseWrapper.ts`** — `wrapPassageVerses(passageEl)`: wraps each rendered verse in a
`.dj-verse` span (idempotent) so verses are selectable/highlightable.
- **`VerseSelectionController.ts`** — one per rendered passage: wraps verses, binds
gestures (desktop tap/shift, mobile long-press-drag), reflects the service's selection
(highlight) and owns the action bar.
- **`VerseActionBar.ts`** — floating bar shown while verses are selected; renders the
Copy / Insert (and optional Append) actions with the configurable `split`/`toggle`/
`submenu` format chooser.
- **`VerseActions.ts`** — turns a selection + format into the payload and performs Copy
(clipboard), Insert (Editor API), or Append (`Vault.process`); extracts verse text for
the blockquote format from the passage's own document.
- **`InsertTargetModal.ts`** — `FuzzySuggestModal<TFile>` note picker for "Append to note…".
2026-06-01 01:22:09 +00:00
### `utils/` — small helpers
- **`BibleApiResponse.ts`** — result type wrapping either a `BiblePassage` or an
error with an `ErrorType` (auth, bad response, forbidden, …).
- **`BiblePassage.ts`** — `{ reference, html }` pairing for resolved content.
- **`BibleApiResponse`/`BiblePassage`** are what flow back out of the content
service to the renderer.
- **`VerseId.ts`** — `parseVerseId(id)`: ESV marker id (`v01001002-1`) → `{chapter, verse}`.
- **`VerseFormatter.ts`** — pure builders for the three insert formats (inline reference,
`bible` code block, blockquote-with-text + citation).
2026-06-01 01:22:09 +00:00
- **`FrontmatterUtil.ts`** — apply user-configured custom frontmatter to Bible
notes (`getCustomFrontmatterForReference`, `applyCustomFrontmatter`).
### `settings/`
- **`DisciplesJournalSettings.ts`** — the settings interface, `DEFAULT_SETTINGS`,
and the `DisciplesJournalSettingsTab` UI (display toggles, fonts/colors, content
vault path, ESV token + download-on-demand).
## Data flow: rendering an inline reference
```
note renders
→ BibleMarkupProcessor.processInlineBibleReferences (registered post-processor)
→ BibleReferenceRenderer.processInlineCodeBlocks
→ BibleReference.parse(text)
→ BibleContentService.getBibleContent(ref)
├─ in-memory passageCache? → return
├─ local note exists? read frontmatter→ ESVApiService.toBibleApiResponse
└─ downloadOnDemand? ESV API download → save note + cache
→ build span; on hover → BibleEventHandlers → showVersePreview popup
```
Full passages follow the same resolution path through `getBibleContent`, but render
the whole passage inline (`processFullBiblePassage`) with navigation + heading. Non-
contiguous references (`Genesis 1:2-3, 5`) resolve via `getBibleContentList`, which parses
the list with `BibleReference.parseList` and concatenates each run's HTML.
## Verse selection
After a full passage renders, `processFullBiblePassage` wraps its verses
(`wrapPassageVerses` → `.dj-verse` spans) and attaches a `VerseSelectionController`.
Selecting verses (tap/shift on desktop, long-press-drag on mobile) updates a per-passage
`VerseSelection` and pushes it to the plugin-owned `VerseSelectionService`, which holds the
single active selection. The owning controller highlights its verses and shows a
`VerseActionBar`; Copy / Insert / Append (and the editor `Insert … here` menu + commands)
run through `VerseActions`, formatting via `VerseFormatter`. See
[docs/gotchas.md](docs/gotchas.md) for the wrapping/gesture/bar-lifecycle details.
2026-06-01 01:22:09 +00:00
## Storage model
Downloaded passages are markdown notes whose **frontmatter holds the raw ESV API
JSON** (`query`, `canonical`, `parsed`, `passage_meta`, `passages`) plus any custom
frontmatter the user configured. The note body is a `bible` code block of the
canonical reference. Because the frontmatter is the cache, re-opening a chapter
reuses `toBibleApiResponse` on the stored data instead of re-hitting the API. See
[docs/esv-api.md](docs/esv-api.md).
2026-06-03 00:11:38 +00:00
## Tests
`test/` holds the test suites (Node's built-in `node:test` runner via `tsx`), one
2026-06-03 03:15:56 +00:00
`*.test.ts` per unit — currently `BibleReference`, `BookNames`, `VerseFormatter`,
`VerseId`, and `VerseSelection`. Run with `npm test` (or `devbox run test`); they
also gate `npm run build` and `just release`. See [docs/testing.md](docs/testing.md).
2026-06-03 00:11:38 +00:00
2026-06-01 01:22:09 +00:00
## Build
esbuild bundles `main.ts``main.js` (CJS, `es2018`); `obsidian`, `electron`, and
2026-06-03 00:11:38 +00:00
CodeMirror packages are externalized. `npm run build` type-checks and runs tests
first. See [docs/build-and-release.md](docs/build-and-release.md).