docs: design ethos, verse selection architecture, reference formats, changelog

This commit is contained in:
Scott Tomaszewski 2026-06-02 22:22:04 -04:00
parent 973c9435f1
commit ab12b922f2
6 changed files with 103 additions and 1 deletions

View file

@ -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<TFile>` 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

View file

@ -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

View file

@ -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,

View file

@ -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 `— <ref> (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.

View file

@ -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
`<b class="verse-num|chapter-num" id="vBBCCCVVV-N">`; the verse's text then runs as loose
inline nodes until the next marker, sometimes across `<p>` boundaries.
`wrapPassageVerses` (`VerseWrapper.ts`) walks each block and wraps each verse's run in a
`<span class="dj-verse" data-chapter data-verse>`. 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.

View file

@ -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.