From ab12b922f2dd2e407ab5ecf91963a6dcd041ca61 Mon Sep 17 00:00:00 2001 From: Scott Tomaszewski Date: Tue, 2 Jun 2026 22:22:04 -0400 Subject: [PATCH] docs: design ethos, verse selection architecture, reference formats, changelog --- ARCHITECTURE.md | 34 +++++++++++++++++++++++++++++++++- CHANGELOG.md | 7 +++++++ CLAUDE.md | 8 ++++++++ docs/esv-api.md | 10 ++++++++++ docs/gotchas.md | 29 +++++++++++++++++++++++++++++ docs/reference-formats.md | 16 ++++++++++++++++ 6 files changed, 103 insertions(+), 1 deletion(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fc73c95..02f2850 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -52,6 +52,10 @@ Source is organized under `src/` by responsibility: 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. ### `services/` — content resolution and storage @@ -82,6 +86,18 @@ Source is organized under `src/` by responsibility: 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` note picker for "Append to note…". ### `utils/` — small helpers @@ -90,6 +106,9 @@ Source is organized under `src/` by responsibility: - **`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). - **`FrontmatterUtil.ts`** — apply user-configured custom frontmatter to Bible notes (`getCustomFrontmatterForReference`, `applyCustomFrontmatter`). - **`BibleCodeblockFormatter.ts`** — formatting helper for the `bible` code block. @@ -115,7 +134,20 @@ note renders ``` Full passages follow the same resolution path through `getBibleContent`, but render -the whole passage inline (`processFullBiblePassage`) with navigation + heading. +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. ## Storage model diff --git a/CHANGELOG.md b/CHANGELOG.md index 206f8af..ebc787e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ header text is **exactly** the release tag (no leading `v`). ## Unreleased +- Adds verse selection: tap verses in a rendered passage to select them (shift-click or + long-press-drag for a range), then copy or insert them as an inline reference, a `bible` + code block, or a blockquote of the verse text. A floating action bar (with configurable + split / toggle / submenu format choosers) and a desktop right-click "Insert … here" both + drive it; an optional "Append to note…" action can be enabled in settings. Adds a + *Verse selection* settings section and supports non-contiguous references like + `Genesis 1:2-3, 5` - Fixes chapter-range references (e.g. `Genesis 1-2`): `BibleReference.parse` was storing the end chapter in the `endVerse` field, so the range was dropped — it round-tripped to `Genesis 1` and was fetched/stored as a single chapter. The end diff --git a/CLAUDE.md b/CLAUDE.md index a0b19a7..1d30475 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,14 @@ passages inside notes (inline hover previews + full `bible` code blocks) and can download passages on demand from the ESV API. See `README.md` for the user-facing feature tour and `manifest.json` for plugin metadata. +## Design ethos + +**Non-intrusive by default.** People use this plugin during devotion, prayer, and small +group. Features must never distract, frustrate, or shake the user out of an intimate time +with God. Default to quiet: nothing new appears, moves, or interrupts until the user +deliberately asks for it. No text shifting, no surprise popups, no persistent chrome. When +in doubt, do less. + ## Where to look - **[ARCHITECTURE.md](ARCHITECTURE.md)** — how the plugin is wired: services, diff --git a/docs/esv-api.md b/docs/esv-api.md index bce30ef..2c82e64 100644 --- a/docs/esv-api.md +++ b/docs/esv-api.md @@ -82,3 +82,13 @@ across existing notes. All failures funnel through `BibleApiResponse.error(message, ErrorType)` (`ApiAuthentication`, `BadApiResponse`, `RequestsForbidden`, …). User-visible cases surface an Obsidian `Notice`; internal ones are logged to the console. + +## Attribution when reproducing verse text + +The verse-selection **blockquote** format (`VerseFormatter.formatBlockquote`) reproduces +ESV text into the user's note and appends a `— (ESV)` citation. The ESV API +[copyright/permissions terms](https://api.esv.org/) require attribution when ESV text is +quoted; the `(ESV)` suffix is the minimum, and larger reproductions carry a fuller +copyright-notice obligation. If the quoting story expands (e.g. exporting many passages), +revisit the notice requirement here. The other two formats (inline reference, `bible` +block) store only a reference, not text, so they don't trigger this. diff --git a/docs/gotchas.md b/docs/gotchas.md index 4607aee..4bfb7fb 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -39,3 +39,32 @@ uses a `MutationObserver` to scroll the instant the `.verse-N` element appears, with a **5000 ms** fallback that disconnects the observer if it never does (wrong ref, or an edit mode that produces no verse elements). Don't replace this with a fixed `setTimeout`. (`BibleFiles.ts:118-145`.) + +## Verse selection: wrapping, gestures, and bar lifecycle + +Verse selection is layered onto rendered passages by `VerseSelectionController` (one per +full `bible` passage, attached in `BibleReferenceRenderer.processFullBiblePassage`). A few +non-obvious things: + +- **Verses aren't pre-wrapped.** ESV HTML marks a verse only with a + ``; the verse's text then runs as loose + inline nodes until the next marker, sometimes across `

` boundaries. + `wrapPassageVerses` (`VerseWrapper.ts`) walks each block and wraps each verse's run in a + ``. It's **idempotent** (bails if a + `.dj-verse` already exists) and a verse split across paragraphs yields two spans sharing + the same `data-verse` — both highlight together. `parseVerseId` ignores the book digits; + the book comes from the passage's canonical reference. +- **Mobile selection vs. scroll.** Touch uses a long-press threshold (`LONG_PRESS_MS`): a + quick swipe scrolls normally; only a deliberate long-press engages drag-select (which + then calls `preventDefault` on `touchmove`). A short tap toggles a single verse. Don't + remove the threshold or you'll hijack scrolling. +- **One selection, globally.** `VerseSelectionService` (plugin-owned, `addChild`) holds a + single active selection + its owning controller. Only the owner renders highlight and the + action bar, so selecting in one passage clears another. +- **The action bar is per-`Document` and `fixed`-positioned.** It's appended to the + passage's own `doc.body` (popout-safe) and positioned from the passage's + `getBoundingClientRect`. Blockquote text is extracted from the owner's `sourceEl` (its + own document) for the same reason — never query the global `document`. +- **Selection clears when its passage unloads.** `controller.onunload` calls + `service.clearIfOwner(this)`, so a note re-render or pane close drops a stale selection + and its bar. diff --git a/docs/reference-formats.md b/docs/reference-formats.md index 7d1c71d..c4955c1 100644 --- a/docs/reference-formats.md +++ b/docs/reference-formats.md @@ -44,3 +44,19 @@ provides: These fields also drive note filenames — see [esv-api.md](esv-api.md) and `BibleFiles.pathForPassage`. + +## Non-contiguous (comma) lists + +Verse selection (see [gotchas.md](gotchas.md)) can produce non-contiguous references +like `Genesis 1:2-3, 5` or `Genesis 1:31, 2:1`. These are parsed by +`BibleReference.parseList`, **not** the single-range `parse`: + +- The first comma-separated item is a full reference (any format above). +- Later items may be a bare verse (`5`), a bare verse range (`5-7`) — both inheriting the + previous item's book **and** chapter — or `chapter:verse[-end]`, inheriting only the book. +- The result is a `BibleReference[]` (one per contiguous run); any invalid item makes the + whole list `null`. + +A rendered list resolves each run and concatenates the HTML +(`BibleContentService.getBibleContentList`); the passage heading/link uses the **first** +run.