bible-note 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1927 @@
1
+ # BibleNote — development guide
2
+
3
+ > Generated by `npm run pack` for **bible-note@0.5.0** from the hand-written topics in `knowledge/development/` and the package's own type declarations. Do not edit this file — edit the sources and re-run the pack. End-user help for the editor UI lives in `ui-rag.md` next to it.
4
+
5
+ ## Contents
6
+
7
+ 1. [Overview](#dev-overview) — What the package is, what it bundles, and what the host supplies.
8
+ 2. [Installation and stylesheets](#dev-installation-and-stylesheets) — Peer dependencies and the stylesheet order the editor needs.
9
+ 3. [Quick start](#dev-quick-start) — A complete minimal host, plus the optional hooks.
10
+ 4. [Datasets and lookup managers](#dev-datasets-and-lookup-managers) — Feeding name and location data, translated datasets, per-dataset styling, building your own lookup UI.
11
+ 5. [Persistence and storage](#dev-persistence-and-storage) — Where the note and the editor's own state are stored, and when.
12
+ 6. [Host integration](#dev-host-integration) — Fonts, media files, Excalidraw libraries, the settings panel, footer modes and the Electron file bridge.
13
+ 7. [Working on the editor UI](#dev-working-on-the-editor-ui) — For contributors — repository layout, the plugin tree, the dev loop, verifying changes in the browser.
14
+ 8. [Building and releasing the package](#dev-building-and-releasing-the-package) — What npm run pack does, the hand-maintained files, versioning and publishing.
15
+ 9. [API reference](#dev-api-reference) — Every export of the package, generated from its type declarations.
16
+
17
+ <a id="dev-overview"></a>
18
+
19
+ ## Overview
20
+
21
+ `bible-note` is a Bible-study note editor packaged as a drop-in component. It
22
+ extends the [Lexical playground](https://playground.lexical.dev/) editor with
23
+ Bible-specific features:
24
+
25
+ - person (`@`) and place (`#`) mentions backed by the host's datasets, with
26
+ hover cards and a draggable advanced panel;
27
+ - Bible verse mentions created by pasting verse text after `^`;
28
+ - a lookup panel for browsing both datasets;
29
+ - sticky notes, comments, Excalidraw drawings, equations, tables, column
30
+ layouts, collapsibles, media and embeds;
31
+ - a footer with Word (`.docx`) import and export, a lossless JSON backup, print
32
+ with page-size preview, read-only mode, and buttons the host adds itself.
33
+
34
+ The MIT license and Meta's copyright carry over from upstream Lexical.
35
+
36
+ ### The pieces
37
+
38
+ | Piece | What it is |
39
+ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `BibleNote` | The editor. `new BibleNote(props)` then `render(element)`. It mounts its own React root, so the host does not have to be a React app. |
41
+ | `NamesLookupManager`, `LocationsLookupManager` | The datasets behind every mention, hover card, panel and the lookup panel. They have no editor dependency — a host can use them on their own. |
42
+ | `BibleNoteSettings` | The optional settings panel (gear button), passed to `render`. |
43
+ | `bible-note/styles.css` | The one stylesheet: editor, KaTeX and Excalidraw rules. |
44
+ | `bible-note/knowledge/development.md` | This guide. |
45
+ | `bible-note/knowledge/ui-rag.md` | End-user help for the editor UI, chunked for a retrieval pipeline. |
46
+
47
+ ### Bundled vs. supplied by the host
48
+
49
+ React, React DOM, Bootstrap and bootstrap-icons are **peer dependencies**:
50
+
51
+ - React must be the host's copy so there is a single instance — a second copy
52
+ breaks hooks.
53
+ - Bootstrap and bootstrap-icons are global stylesheets. Shipping them inside the
54
+ package would fix their place in the host's cascade and pin a version the host
55
+ could not theme, so the host installs and imports them itself.
56
+
57
+ Everything else — Lexical, Excalidraw, KaTeX, Shiki, the Word converters — is
58
+ bundled. Lexical and Excalidraw are bundled on purpose: the editor is coupled to
59
+ their module instances, and a separately resolved copy could be a different
60
+ instance than the one the bundle talks to. `use-sync-external-store` is the one
61
+ runtime dependency, left external only so the consumer's bundler converts it
62
+ correctly.
63
+
64
+ The package ships **no datasets**. The host fetches the name and location maps
65
+ (and, optionally, verse text) and hands them to the editor.
66
+
67
+ ### Lifecycle in one paragraph
68
+
69
+ Construct `BibleNote` with the datasets, the storage callbacks and any optional
70
+ host hooks; call `render(container)`. Before the first render the editor reads
71
+ its own state (settings, comments, read-only mode, zoom) from `storageManager`
72
+ and the note itself from `loadData`. From then on every document change goes to
73
+ `saveData`, and every other state change to `storageManager`. Methods such as
74
+ `content`, `addText` and `searchText` may be called before `render`; writes are
75
+ queued until the editor mounts. `unmount()` tears the React root down; the
76
+ instance can be rendered again.
77
+
78
+ <a id="dev-installation-and-stylesheets"></a>
79
+
80
+ ## Installation and stylesheets
81
+
82
+ ```bash
83
+ npm install bible-note react react-dom bootstrap bootstrap-icons
84
+ ```
85
+
86
+ The peer ranges are those in the package's `package.json`: React and React DOM
87
+ `^19.2.8`, Bootstrap `^5.3.8`, bootstrap-icons `^1.13.1`.
88
+
89
+ ### Stylesheet order
90
+
91
+ Import three stylesheets, in this order — Bootstrap first, then the icon font,
92
+ then the editor's own stylesheet last so its overrides win:
93
+
94
+ ```ts
95
+ import 'bootstrap/dist/css/bootstrap.min.css';
96
+ import 'bootstrap-icons/font/bootstrap-icons.css';
97
+ import 'bible-note/styles.css';
98
+ ```
99
+
100
+ Skipping the first two does not throw — it renders a broken editor: the toolbar
101
+ and footer lose their layout, and with no `@font-face` for the icon font every
102
+ `bi bi-*` glyph renders as tofu. `bible-note/styles.css` needs no configuration.
103
+
104
+ The package's `sideEffects` lists `**/*.css`, so bundlers keep the
105
+ `bible-note/styles.css` import; if a custom build strips CSS side effects, the
106
+ editor renders unstyled.
107
+
108
+ ### Module formats and entry points
109
+
110
+ | Import | Resolves to |
111
+ | ----------------------------------------------------------------------- | ---------------------------------------------- |
112
+ | `import … from 'bible-note'` | ES module bundle, with TypeScript declarations |
113
+ | `require('bible-note')` | CommonJS bundle |
114
+ | `bible-note/styles.css` | the stylesheet |
115
+ | `bible-note/knowledge/development.md`, `bible-note/knowledge/ui-rag.md` | the knowledge files |
116
+
117
+ Shiki grammars, Excalidraw and KaTeX load lazily as separate chunks, so the
118
+ initial bundle stays small and those parts load the first time they are used.
119
+
120
+ ### Colour scheme
121
+
122
+ The editor themes itself through classes on `document.body`
123
+ (`playground-theme-light` / `playground-theme-dark`) and Bootstrap's
124
+ `data-bs-theme` attribute. Call `bibleNote.setColorScheme('dark')`, or let the
125
+ settings panel's Dark Mode switch do it. `applyBibleNoteColorScheme` and
126
+ `clearBibleNoteColorScheme` are the bare DOM steps for a host that manages the
127
+ body classes itself.
128
+
129
+ <a id="dev-quick-start"></a>
130
+
131
+ ## Quick start
132
+
133
+ A minimal host: the stylesheets, two datasets, browser storage, and a render.
134
+
135
+ ```tsx
136
+ import 'bootstrap/dist/css/bootstrap.min.css';
137
+ import 'bootstrap-icons/font/bootstrap-icons.css';
138
+ import 'bible-note/styles.css';
139
+
140
+ import {
141
+ BibleNote,
142
+ BibleNoteSettings,
143
+ createBrowserStorageManager,
144
+ EDITOR_STATE_LOCAL_STORAGE_KEY,
145
+ LocationsLookupManager,
146
+ NamesLookupManager,
147
+ } from 'bible-note';
148
+
149
+ const [namesMap, locationsMap] = await Promise.all([
150
+ fetch('data/KJV/namesMap.json').then((res) => res.json()),
151
+ fetch('data/KJV/locationsMap.json').then((res) => res.json()),
152
+ ]);
153
+
154
+ const storageManager = createBrowserStorageManager();
155
+
156
+ const bibleNote = new BibleNote({
157
+ namesLookupManager: NamesLookupManager.fromRawDataset({ en: namesMap }, 'en'),
158
+ locationsLookupManager: LocationsLookupManager.fromRawDataset(
159
+ { en: locationsMap },
160
+ 'en',
161
+ ),
162
+ getLangCode: (text) => (/[ក-៿]/u.test(text) ? 'km' : 'en'),
163
+ editorExtraFontFamilies: [],
164
+ stickyNoteExtraFontFamilies: [],
165
+ storageManager,
166
+ // The note itself: keep it wherever the host keeps documents.
167
+ loadData: async () =>
168
+ (await storageManager.getSetting<string>(EDITOR_STATE_LOCAL_STORAGE_KEY)) ??
169
+ '',
170
+ saveData: async (data) => {
171
+ if (data === '') {
172
+ await storageManager.deleteSetting(EDITOR_STATE_LOCAL_STORAGE_KEY);
173
+ } else {
174
+ await storageManager.setSetting(EDITOR_STATE_LOCAL_STORAGE_KEY, data);
175
+ }
176
+ },
177
+ });
178
+
179
+ bibleNote.render(document.getElementById('root')!, {
180
+ settings: (
181
+ <BibleNoteSettings
182
+ setColorScheme={(mode) => bibleNote.setColorScheme(mode)}
183
+ />
184
+ ),
185
+ });
186
+ ```
187
+
188
+ The `settings` option takes a React element, so a `.tsx` file is the natural
189
+ host. A host without JSX can pass
190
+ `React.createElement(BibleNoteSettings, { setColorScheme })` or leave `settings`
191
+ out — the editor then simply has no settings panel.
192
+
193
+ The required props are the two managers, `getLangCode`,
194
+ `editorExtraFontFamilies`, `stickyNoteExtraFontFamilies`, `storageManager`,
195
+ `loadData` and `saveData`. Everything else is optional and switches a feature
196
+ on; the API reference lists each prop.
197
+
198
+ ### Working with a mounted editor
199
+
200
+ ```ts
201
+ bibleNote.content; // serialized editor state (JSON string)
202
+ bibleNote.content = savedState; // replace the document; '' clears it
203
+ bibleNote.addText('^'); // insert as if pasted at the caret
204
+ bibleNote.addText('(KJV) Genesis 1:1\n(1): In the beginning God created…');
205
+ bibleNote.isReadOnly = true; // lock — the user cannot unlock it
206
+ bibleNote.searchText('Moses'); // open the find bar on a query; null closes it
207
+ bibleNote.getIsFocusing(); // is the caret in the editor right now?
208
+ BibleNote.getAllShortVersesFromText(bibleNote.content); // ['GEN 1:1', …]
209
+ ```
210
+
211
+ The two `addText` calls above create a Bible verse mention: verse text pasted
212
+ right after a `^` becomes a verse mention instead of a collapsible block.
213
+
214
+ ### Host footer buttons
215
+
216
+ ```tsx
217
+ bibleNote.prependFooterActionButton({
218
+ id: 'open-in-app',
219
+ description: 'Open in my app',
220
+ shortcutKey: 'Ctrl+Shift+K',
221
+ children: (
222
+ <button className="action-button" onClick={openInApp}>
223
+ <i className="bi bi-box-arrow-up-right" />
224
+ </button>
225
+ ),
226
+ });
227
+ bibleNote.removeFooterActionButton('open-in-app');
228
+ ```
229
+
230
+ `prepend…` places a button before the built-in footer buttons, `append…` after
231
+ them. Re-adding an `id` replaces that button in place. `description` becomes the
232
+ tooltip when the element has no `title`; `shortcutKey` clicks the button from
233
+ anywhere in the page and is shown in the tooltip. Give the button the
234
+ `action-button` class to match the built-in ones.
235
+
236
+ ### Mounting without auto-mount
237
+
238
+ `render` can be called any time after construction and again later — each call
239
+ unmounts the previous root first. Call `unmount()` when the host view goes away;
240
+ the instance keeps its props and footer buttons for the next `render`.
241
+
242
+ <a id="dev-datasets-and-lookup-managers"></a>
243
+
244
+ ## Datasets and lookup managers
245
+
246
+ The editor ships no data. Every person and place it can mention comes from two
247
+ managers the host builds:
248
+
249
+ ```ts
250
+ const names = NamesLookupManager.fromRawDataset({ en: namesMap }, 'en');
251
+ const locations = LocationsLookupManager.fromRawDataset(
252
+ { en: locationsMap },
253
+ 'en',
254
+ );
255
+ ```
256
+
257
+ `fromRawDataset` takes the generated dataset files exactly as fetched —
258
+ `namesMap.json` (`{ version, namesMap, childrenTree, versePersonsMap }`) and
259
+ `locationsMap.json` — keyed by a language code, and normalizes them. Records
260
+ can be an id-keyed object or an array; anything unrecognised is dropped rather
261
+ than trusted. Use the plain constructors only when you already hold normalized
262
+ `MentionNameMatch` / `MentionLocationMatch` records.
263
+
264
+ A manager may hold several languages (`{ en: …, km: … }`); `defaultLang` picks
265
+ the one the editor uses, and every query method takes an optional `lang`. The
266
+ constructor throws when the default language is missing from the map.
267
+
268
+ ### Records
269
+
270
+ A name record (`MentionNameMatch`) carries the name, a one-line `title`, a
271
+ `description`, family links (`parents`, `children`, `spouses`, `siblings`,
272
+ `cousin` — record ids), `locations` (location ids), `verses`, `links`,
273
+ `wikipedia`, `gender`, `age`, `years` and a `type` (`person`, `group`, `deity`,
274
+ `place`, `concept`, `month`, `supernatural`, `life`, `unknown`). A location
275
+ record (`MentionLocationMatch`) carries `type` (`city`, `region`, `mountain`, …),
276
+ `coordinates`, `modernIdentification`, `relatedLocations`, `uncertainty` and the
277
+ same text, verse and link fields.
278
+
279
+ `title` and `description` may contain inline reference links the UI renders as
280
+ clickable references:
281
+
282
+ | Link | Points to |
283
+ | ------------------------------------ | ----------------- |
284
+ | `[Amram](name-id://<id>)` | a name record |
285
+ | `[Egypt](location-id://<id>)` | a location record |
286
+ | `[Genesis 1:1](verse-key://GEN 1:1)` | a verse |
287
+ | `[Genesis](book-key://GEN)` | a book |
288
+ | `[Genesis 14](chapter-key://GEN 14)` | a chapter |
289
+
290
+ Verse keys are short keys — a book code plus chapter and verse (`GEN 1:1`,
291
+ `1SA 17:1-4`). Ranges are stored collapsed, so the length of `verses` is not a
292
+ verse count.
293
+
294
+ `matterRange` ranks importance: `0` is the most important record and larger is
295
+ less important; records without a rank sort last. Every listing and every tie in
296
+ a search is ordered by it, then alphabetically.
297
+
298
+ ### Searching
299
+
300
+ ```ts
301
+ names.searchNames('Mos', { limit: 20 }); // ranked MentionNameMatch[]
302
+ names.searchNames('', { type: 'deity' }); // filter applies before limit
303
+ names.searchNamesPagination({ query: 'Mos', page: 2, pageSize: 20 });
304
+ names.getRecordById(id);
305
+ names.getRecordsByIds(record.children);
306
+ names.getRecordsByName('Zechariah'); // names are not unique
307
+ names.searchByShortVerse('EXO 6:23'); // the names that verse prints
308
+ names.nameTypes; // types present, most common first
309
+ locations.searchLocations('Jer', { limit: 10 });
310
+ ```
311
+
312
+ Search is a case-insensitive substring match on the name, the KJV English name
313
+ and the former name (`oldName`), ranked exact, then prefix, then substring, then
314
+ by importance. An empty query lists everything in importance order. Indexes are
315
+ built lazily per language on first use and recent queries are cached.
316
+
317
+ `searchByShortVerse` needs the dataset's verse map (`versePersonsMap` /
318
+ `verseLocationsMap`, both included in the generated files).
319
+
320
+ ### Translated datasets
321
+
322
+ A record in a translated dataset (the Khmer and French mirrors of the KJV data)
323
+ carries its translated `name` plus the KJV English name in `kjvName`. The
324
+ managers index both, so the same manager answers a query in either language and
325
+ ranks an English hit exactly like a native one:
326
+
327
+ ```ts
328
+ const names = NamesLookupManager.fromRawDataset({ km: khmerNamesMap }, 'km');
329
+ names.searchNames('Genesis'); // finds the record named "លោកុប្បត្តិ"
330
+ ```
331
+
332
+ The built-in UI shows the English name beside the translated one in the lookup
333
+ panel, the mention typeahead, the hover card and the advanced panel.
334
+ `getMentionKjvName(record)` is the rule it uses: the English name, or `null`
335
+ when there is nothing to add. An untranslated dataset carries no `kjvName`, and
336
+ all of this is a no-op for it.
337
+
338
+ ### Styling a dataset
339
+
340
+ Both managers take an optional third argument: a React-style CSS object merged
341
+ into every surface that renders that dataset — the typeahead popup, the hover
342
+ card, the advanced panel and the lookup panel's list and search box. Use it to
343
+ give a dataset in another script its own font:
344
+
345
+ ```ts
346
+ const locations = LocationsLookupManager.fromRawDataset(
347
+ { km: khmerLocationsMap },
348
+ 'km',
349
+ { fontFamily: 'Battambang, serif' },
350
+ );
351
+ ```
352
+
353
+ The two managers are styled independently. `manager.style` can be read and
354
+ reassigned, but plugins read it as they render, so a change only shows up on the
355
+ next render. A hover card's own positioning always wins over the style.
356
+
357
+ ### Verse text
358
+
359
+ The package bundles no Bible text. Three optional props let the host supply it:
360
+
361
+ - `shortToVerseData(shortKey)` → `{ title, fullText, style? }` — labels verse
362
+ lists in the mention panels and fills the Word export's References appendix.
363
+ - `verseFullTextToListShorts(fullText)` → short keys — lets a verse's advanced
364
+ panel show the people and places that verse mentions.
365
+ - `changeBibleKey(event, fullText)` — makes the version label in a verse panel a
366
+ button that opens the same verse in another version.
367
+
368
+ `fullText` is the shape the editor accepts when Bible text is pasted after `^`:
369
+ a title line such as `(KJV) Genesis 1:1`, then one line per verse such as
370
+ `(1): In the beginning…`.
371
+
372
+ `BibleNote.getAllShortVersesFromText(text)` goes the other way: it lists every
373
+ verse a note refers to — verse mentions and references typed in the prose — as
374
+ short keys, in document order without duplicates. Ranges inside one chapter are
375
+ expanded verse by verse; a range crossing chapters keeps only its endpoints.
376
+
377
+ ### Your own lookup UI
378
+
379
+ The managers carry no editor dependency. To build a custom mention picker or a
380
+ search page, construct them and call their methods — no `BibleNote` import is
381
+ needed, and the results are exactly what the built-in UI shows.
382
+
383
+ <a id="dev-persistence-and-storage"></a>
384
+
385
+ ## Persistence and storage
386
+
387
+ The editor splits what it stores into two channels.
388
+
389
+ | Channel | Carries | Host hook |
390
+ | ------------ | ----------------------------------------------------- | ----------------------- |
391
+ | The note | the serialized Lexical editor state of the document | `loadData` / `saveData` |
392
+ | Editor state | settings, comments, read-only mode, zoom, panel state | `storageManager` |
393
+
394
+ ### The note: `loadData` and `saveData`
395
+
396
+ `saveData(data)` is called on every change to the document with the serialized
397
+ editor state — a JSON string, the same one `bibleNote.content` returns. It is
398
+ called with `''` when a stored note turns out to be unreadable and is discarded.
399
+ `loadData()` returns that string (or `null` / `''` for an empty editor) and may
400
+ be async. Store it wherever the host keeps documents: a file, a database row, a
401
+ server.
402
+
403
+ Because `saveData` runs on every change, debounce it on the host side if the
404
+ store is expensive. The string is also the lossless format: the footer's Export
405
+ button downloads it, Import reads it back, and assigning `bibleNote.content`
406
+ replaces the document with it.
407
+
408
+ ### Editor state: `storageManager`
409
+
410
+ A `BibleNoteStorageManager` is three async methods — `getSetting`, `setSetting`,
411
+ `deleteSetting` — over string values. `createBrowserStorageManager()` is the
412
+ `localStorage` implementation; an Electron or server-backed host can implement
413
+ the same three methods over its own store.
414
+
415
+ Before the editor first renders it reads every key in
416
+ `BIBLE_NOTE_STORAGE_MANAGER_KEYS` from the storage manager, then serves reads
417
+ from memory; every write goes straight through to `setSetting`, fire-and-forget.
418
+ The keys:
419
+
420
+ | Key | Holds |
421
+ | -------------------------------------------- | -------------------------------------------------------------------------- |
422
+ | `lexical-playground-settings` | the settings panel's switches |
423
+ | `lexical-playground-editor-state` | the note — routed to `loadData` / `saveData`, never to the storage manager |
424
+ | `lexical-playground-comments` | comment threads |
425
+ | `lexical-playground-comments-panel-open` | whether the comments panel is open |
426
+ | `lexical-playground-block-text-links` | the "link block text" connections |
427
+ | `lexical-playground-read-only-mode` | whether the user locked the note |
428
+ | `lexical-playground-insert-character-picker` | the Emoji & HTML Character picker's state |
429
+ | `lexical-playground-editor-zoom-percent` | the zoom level |
430
+
431
+ The `lexical-playground-` prefix is inherited from the upstream playground; keep
432
+ it when migrating stored data.
433
+
434
+ The editor-state key is exported as `EDITOR_STATE_LOCAL_STORAGE_KEY`. The quick
435
+ start reuses it to keep the note in the same store as everything else, which is
436
+ fine for a single-note host; a multi-note host keeps each note under its own key
437
+ or file and returns the right one from `loadData`.
438
+
439
+ ### Read-only mode
440
+
441
+ The user can lock a note from the footer, and that choice persists under
442
+ `lexical-playground-read-only-mode`. A host can force it: `bibleNote.isReadOnly
443
+ = true` locks the note, turns the footer button into **Read-Only Mode Locked**,
444
+ and the user cannot unlock it until the host assigns `false`.
445
+
446
+ ### Share links
447
+
448
+ Outside an app (`isOnApp` false), the footer's **Share as Link** button encodes
449
+ the note into the page URL's `editor-state` query parameter, optionally
450
+ password-protected; opening such a URL loads that note. Hosts that embed the
451
+ editor in an app set `isOnApp: true`, which hides the button.
452
+
453
+ <a id="dev-host-integration"></a>
454
+
455
+ ## Host integration
456
+
457
+ Every optional prop switches on one integration. This topic groups them by
458
+ feature; the API reference has the exact signatures.
459
+
460
+ ### Fonts and languages
461
+
462
+ - `editorExtraFontFamilies: [['Battambang', 'km'], …]` adds fonts to the
463
+ toolbar's font menu and to Excalidraw, each tagged with a language code. The
464
+ host loads the font faces itself (a `@font-face` or a webfont link).
465
+ - `getLangCode(text)` returns a language code for a piece of text. When it
466
+ returns something other than `'en'`, pasted Bible text and Excalidraw text
467
+ switch to the first extra font registered for that language — so Khmer verse
468
+ text renders in a Khmer font automatically.
469
+ - `stickyNoteExtraFontFamilies: ['"Fasthand"', …]` adds CSS font-family values
470
+ to the sticky-note font picker.
471
+ - A lookup manager's style object (`{ fontFamily }`) styles one dataset across
472
+ the mention UI; see the datasets topic.
473
+
474
+ ### Media files
475
+
476
+ By default a picked or dropped image, video, audio or PDF is embedded in the
477
+ note as a data URL. Two hooks change that:
478
+
479
+ - `resolveFilePath(file, { source })` returns a path or URL to store instead —
480
+ for example the file's location on disk in Electron, or an upload URL. `source`
481
+ is `'drop'` (dropped or pasted) or `'file-input'` (picked in the media dialog).
482
+ Return `null` to fall back to embedding.
483
+ - `revealFile(path)` shows a local file to the user (Electron:
484
+ `shell.showItemInFolder`). With it, a selected media item whose source is a
485
+ local path gets a reveal button.
486
+
487
+ ### Excalidraw libraries
488
+
489
+ The editor bundles a Christian-themed Excalidraw library. `excalidrawLibraryItems`
490
+ adds library items of the host's own. To persist libraries the user imports
491
+ inside Excalidraw, implement the trio:
492
+
493
+ - `excalidrawSaveLibrariesFile(json)` — store one serialized library;
494
+ - `excalidrawLoadLibrariesFileList()` — return the stored libraries, read when
495
+ the Excalidraw editor opens;
496
+ - `excalidrawClearLibrariesFileList()` — forget them all.
497
+
498
+ The demo host keeps the list as a JSON array in `localStorage`.
499
+
500
+ ### Settings panel
501
+
502
+ `render(el, { settings: <BibleNoteSettings setColorScheme={…} /> })` adds the
503
+ gear button and its switches. Without `settings` there is no settings panel and
504
+ every setting keeps its default. `settingsContainerElement` portals the panel
505
+ elsewhere in the page (the demo puts it in a fixed container of its own).
506
+ Settings persist through the storage manager. The switches, their keys and
507
+ defaults:
508
+
509
+ | Switch | Setting | Default |
510
+ | --- | --- | --- |
511
+ | Measure Perf | `measureTypingPerf` | Off |
512
+ | Debug View | `showTreeView` | Off |
513
+ | Dark Mode | `isDarkMode` | Off |
514
+ | Show invisible characters | `showInvisibleCharacters` | Off |
515
+ | Nested Editors Debug View | `showNestedEditorTreeView` | Off |
516
+ | Rich Text | `isRichText` | On |
517
+ | Nested Tables | `hasNestedTables` | Off |
518
+ | Char Limit | `isCharLimit` | Off |
519
+ | Char Limit (UTF-8) | `isCharLimitUtf8` | Off |
520
+ | Link Attributes | `hasLinkAttributes` | Off |
521
+ | Max Length | `isMaxLength` | Off |
522
+ | Autocomplete | `isAutocomplete` | On |
523
+ | Table Of Contents | `showTableOfContents` | Off |
524
+ | Use Lexical Context Menu | `shouldUseLexicalContextMenu` | Off |
525
+ | Preserve newlines in Markdown | `shouldPreserveNewLinesInMarkdown` | Off |
526
+ | Use Brackets for Highlighting | `shouldAllowHighlightingWithBrackets` | Off |
527
+ | Retain selection | `selectionAlwaysOnDisplay` | Off |
528
+ | Enable Code Highlighting | `isCodeHighlighted` | On |
529
+ | Use Shiki for Code Highlighting | `isCodeShiki` | Off |
530
+
531
+ Pass `setColorScheme={(mode) => bibleNote.setColorScheme(mode)}` so the
532
+ instance's `colorScheme` follows the Dark Mode switch.
533
+
534
+ ### Footer modes
535
+
536
+ - `isOnApp: true` — the editor runs inside an app, not a web page: hides
537
+ **Share as Link** and **Convert From Markdown**.
538
+ - `isMinimize: true` — compact footer: Import, Export and Editor Information
539
+ move into a **More actions** menu.
540
+ - `print()` — replaces the built-in printing (which lays the note out at the
541
+ selected page size and opens the print dialog).
542
+
543
+ Host buttons go before or after the built-in ones with
544
+ `prependFooterActionButton` / `appendFooterActionButton`.
545
+
546
+ ### Electron: native file dialogs
547
+
548
+ When the page exposes `window.bibleNoteDesktop`, the footer's Word import/export
549
+ and JSON Import/Export use native Open and Save dialogs instead of a browser
550
+ file picker and download. The contract (this repository's
551
+ `scripts/electron-preload.cjs` implements it with `contextBridge` and two
552
+ `ipcMain` handlers):
553
+
554
+ ```ts
555
+ window.bibleNoteDesktop = {
556
+ isElectron: true,
557
+ openFile(options: { filters: { name: string; extensions: string[] }[] }):
558
+ Promise<{ canceled: true } | { canceled: false; data: Uint8Array; fileName: string }>,
559
+ saveFile(options: {
560
+ data: Uint8Array | string;
561
+ defaultFileName: string;
562
+ filters: { name: string; extensions: string[] }[];
563
+ }): Promise<{ canceled: true } | { canceled: false; fileName: string }>,
564
+ };
565
+ ```
566
+
567
+ The bridge is detected by `isElectron === true`. It is independent of
568
+ `isOnApp` — the demo host sets `isOnApp` from whether the bridge exists, which
569
+ is a reasonable default.
570
+
571
+ ### Word round trips
572
+
573
+ The footer's Word buttons export a standard `.docx` that also embeds a private
574
+ BibleNote copy of the editor state; importing that file restores the full
575
+ editable document. A `.docx` from elsewhere (or one Word has stripped) is
576
+ converted from its Word content: text, headings, emphasis, lists, tables, links
577
+ and images survive, Word-only layout may be simplified.
578
+
579
+ Because a Word document has no hover cards, the export adds a **References**
580
+ appendix: every person, place and verse mention links to an entry with the
581
+ record's details and the verse text. Verse references are resolved through
582
+ `shortToVerseData` and related people and places through
583
+ `verseFullTextToListShorts`, so supply both for a complete appendix. Equations
584
+ become editable Word equations; sticky notes, drawings and embed thumbnails
585
+ become pictures.
586
+
587
+ <a id="dev-working-on-the-editor-ui"></a>
588
+
589
+ ## Working on the editor UI
590
+
591
+ This topic is for changing the editor itself, in its source repository.
592
+
593
+ ### Layout
594
+
595
+ | Path | What lives there |
596
+ | -------------------------------------------- | -------------------------------------------------------------------------------------- |
597
+ | `src/lib.ts` | the package's public entry — only re-exports; everything it reaches is library code |
598
+ | `src/index.tsx` | the demo host (fetches datasets, wires every prop, mounts) — not part of the package |
599
+ | `src/BibleNote.ts` | the `BibleNote` class: queued editor actions, footer buttons, colour scheme |
600
+ | `src/bibleNoteHelpers.tsx` | `BibleNoteProps`, the storage split, `renderEditor` |
601
+ | `src/AppComp.tsx` | providers, storage hydration, the editor controller behind `BibleNote`'s methods |
602
+ | `src/Editor.tsx` | the plugin tree, zoom and the full-view layout |
603
+ | `src/plugins/<Name>Plugin/` | one Lexical plugin per folder |
604
+ | `src/nodes/` | custom Lexical nodes (`PlaygroundNodes.ts` registers them) |
605
+ | `src/ui/` | shared UI: modals, dropdowns, colour picker, Excalidraw modal, equation editor |
606
+ | `src/context/` | React contexts: settings, toolbar state, lookup managers, verse actions, media actions |
607
+ | `src/themes/` | the Lexical editor themes and their SCSS |
608
+ | `src/LookupManager.ts`, `src/lookupTypes.ts` | the dataset managers and record types |
609
+ | `test-packages/` | a consumer app that installs the built package from `../packages/bible-note` |
610
+
611
+ ### The plugin tree
612
+
613
+ `src/Editor.tsx` renders every plugin. The table below is generated from it;
614
+ "mounted when" is the condition wrapping the element, usually a setting from the
615
+ settings panel.
616
+
617
+ | Plugin | Source | Mounted when |
618
+ | --- | --- | --- |
619
+ | `ToolbarPlugin` | `src/plugins/ToolbarPlugin` | `isRichText` |
620
+ | `ShortcutsPlugin` | `src/plugins/ShortcutsPlugin` | `isRichText` |
621
+ | `MaxLengthPlugin` | `src/plugins/MaxLengthPlugin` | `isMaxLength` |
622
+ | `DragDropPaste` | `src/plugins/DragDropPastePlugin` | always |
623
+ | `AutoFocusPlugin` | `@lexical/react/LexicalAutoFocusPlugin` | always |
624
+ | `EditorStatePersistencePlugin` | `src/plugins/EditorStatePersistencePlugin` | always |
625
+ | `SelectionAlwaysOnDisplay` | `@lexical/react/LexicalSelectionAlwaysOnDisplay` | `selectionAlwaysOnDisplay` |
626
+ | `ClearEditorPlugin` | `@lexical/react/LexicalClearEditorPlugin` | always |
627
+ | `FindPlugin` | `src/plugins/FindPlugin` | always |
628
+ | `ComponentPickerPlugin` | `src/plugins/ComponentPickerPlugin` | always |
629
+ | `EmojiPickerPlugin` | `src/plugins/EmojiPickerPlugin` | always |
630
+ | `AutoEmbedPlugin` | `src/plugins/AutoEmbedPlugin` | always |
631
+ | `MentionNamePlugin` | `src/plugins/MentionNamePlugin` | always |
632
+ | `MentionLocationsPlugin` | `src/plugins/MentionLocationsPlugin` | always |
633
+ | `MentionBibleVersesPlugin` | `src/plugins/MentionBibleVersesPlugin` | always |
634
+ | `EmojisPlugin` | `src/plugins/EmojisPlugin` | always |
635
+ | `KeywordsPlugin` | `src/plugins/KeywordsPlugin` | always |
636
+ | `AutoLinkPlugin` | `src/plugins/AutoLinkPlugin` | always |
637
+ | `DateTimePlugin` | `src/plugins/DateTimePlugin` | always |
638
+ | `CommentPlugin` | `src/plugins/CommentPlugin` | always |
639
+ | `LookupPlugin` | `src/plugins/LookupPlugin` | always |
640
+ | `HistoryPlugin` | `@lexical/react/LexicalHistoryPlugin` | `isRichText` |
641
+ | `RichTextPlugin` | `@lexical/react/LexicalRichTextPlugin` | `isRichText` |
642
+ | `StickyPlugin` | `src/plugins/StickyPlugin` | `isRichText` |
643
+ | `MarkdownShortcutPlugin` | `src/plugins/MarkdownShortcutPlugin` | `isRichText` |
644
+ | `CodeHighlightShikiPlugin` | `src/plugins/CodeHighlightShikiPlugin` | `isRichText` and `isCodeHighlighted` and `isCodeShiki` |
645
+ | `CodeHighlightPrismPlugin` | `src/plugins/CodeHighlightPrismPlugin` | `isRichText` and `isCodeHighlighted` and `!(isCodeShiki)` |
646
+ | `ListPlugin` | `@lexical/react/LexicalListPlugin` | `isRichText` |
647
+ | `CheckListPlugin` | `@lexical/react/LexicalCheckListPlugin` | `isRichText` |
648
+ | `TablePlugin` | `@lexical/react/LexicalTablePlugin` | `isRichText` |
649
+ | `TableCellResizer` | `src/plugins/TableCellResizer` | `isRichText` |
650
+ | `TableScrollShadowPlugin` | `src/plugins/TableScrollShadowPlugin` | `isRichText` |
651
+ | `MediaPlugin` | `src/plugins/MediaPlugin` | `isRichText` |
652
+ | `LinkPlugin` | `src/plugins/LinkPlugin` | `isRichText` |
653
+ | `PollPlugin` | `src/plugins/PollPlugin` | `isRichText` |
654
+ | `YouTubePlugin` | `src/plugins/YouTubePlugin` | `isRichText` |
655
+ | `WebsitePlugin` | `src/plugins/WebsitePlugin` | `isRichText` |
656
+ | `ClickableLinkPlugin` | `@lexical/react/LexicalClickableLinkPlugin` | `isRichText` |
657
+ | `HorizontalRulePlugin` | `@lexical/react/LexicalHorizontalRulePlugin` | `isRichText` |
658
+ | `EquationsPlugin` | `src/plugins/EquationsPlugin` | `isRichText` |
659
+ | `ExcalidrawPlugin` | `src/plugins/ExcalidrawPlugin` | `isRichText` |
660
+ | `TabFocusPlugin` | `src/plugins/TabFocusPlugin` | `isRichText` |
661
+ | `TabIndentationPlugin` | `@lexical/react/LexicalTabIndentationPlugin` | `isRichText` |
662
+ | `CollapsiblePlugin` | `src/plugins/CollapsiblePlugin` | `isRichText` |
663
+ | `PageBreakPlugin` | `src/plugins/PageBreakPlugin` | `isRichText` |
664
+ | `LayoutPlugin` | `src/plugins/LayoutPlugin/LayoutPlugin` | `isRichText` |
665
+ | `FloatingLinkEditorPlugin` | `src/plugins/FloatingLinkEditorPlugin` | `isRichText` and `floatingAnchorElem` |
666
+ | `BlockTextLinkPlugin` | `src/plugins/BlockTextLinkPlugin` | `isRichText` and `floatingAnchorElem` |
667
+ | `TableCellActionMenuPlugin` | `src/plugins/TableActionMenuPlugin` | `isRichText` and `floatingAnchorElem` |
668
+ | `FloatingTextFormatToolbarPlugin` | `src/plugins/FloatingTextFormatToolbarPlugin` | `isRichText` and `floatingAnchorElem` |
669
+ | `DraggableBlockPlugin` | `src/plugins/DraggableBlockPlugin` | `isRichText` and `floatingAnchorElem && hasPointerAffordances` |
670
+ | `CodeActionMenuPlugin` | `src/plugins/CodeActionMenuPlugin` | `isRichText` and `floatingAnchorElem && hasPointerAffordances` |
671
+ | `TableHoverActionsV2Plugin` | `src/plugins/TableHoverActionsV2Plugin` | `isRichText` and `floatingAnchorElem && hasPointerAffordances` |
672
+ | `PlainTextPlugin` | `@lexical/react/LexicalPlainTextPlugin` | `!(isRichText)` |
673
+ | `HistoryPlugin` | `@lexical/react/LexicalHistoryPlugin` | `!(isRichText)` |
674
+ | `InvisibleCharactersPlugin` | `src/plugins/InvisibleCharactersPlugin` | always |
675
+ | `CharacterLimitPlugin` | `@lexical/react/LexicalCharacterLimitPlugin` | `(isCharLimit \|\| isCharLimitUtf8)` |
676
+ | `AutocompletePlugin` | `src/plugins/AutocompletePlugin` | `isAutocomplete` |
677
+ | `PageSizePreviewPlugin` | `src/plugins/PageSizePreviewPlugin` | always |
678
+ | `PaperPageNavigationPlugin` | `src/plugins/PaperPageNavigationPlugin` | always |
679
+ | `TableOfContentsPlugin` | `src/plugins/TableOfContentsPlugin` | `showTableOfContents` |
680
+ | `ContextMenuPlugin` | `src/plugins/ContextMenuPlugin` | `shouldUseLexicalContextMenu` |
681
+ | `SpecialTextPlugin` | `src/plugins/SpecialTextPlugin` | `shouldAllowHighlightingWithBrackets` |
682
+ | `ActionsPlugin` | `src/plugins/ActionsPlugin` | always |
683
+ | `TreeViewPlugin` | `src/plugins/TreeViewPlugin` | `showTreeView` |
684
+
685
+ `isRichText` is on by default; `floatingAnchorElem` is the editor's scroll
686
+ container once it has mounted; `hasPointerAffordances` is true on a device with a
687
+ fine, hovering pointer (mouse or trackpad), so drag handles and hover menus never
688
+ appear on touch screens.
689
+
690
+ ### Adding a feature
691
+
692
+ A typical new block or inline feature touches:
693
+
694
+ 1. a node in `src/nodes/`, registered in `src/nodes/PlaygroundNodes.ts`;
695
+ 2. a plugin folder in `src/plugins/`, mounted in `src/Editor.tsx` (behind a
696
+ setting if it is optional — add the key to `DEFAULT_SETTINGS` in
697
+ `src/appSettings.ts` and a `<Switch>` in `src/Settings.tsx`);
698
+ 3. a slash-menu option in `getBaseOptions()` in
699
+ `src/plugins/ComponentPickerPlugin/index.tsx` and/or an item in the toolbar's
700
+ Insert dropdown;
701
+ 4. a shortcut in `SHORTCUTS` (`src/plugins/ShortcutsPlugin/shortcuts.ts`) and its
702
+ handler in the shortcuts plugin, if it has one;
703
+ 5. theme classes in `src/themes/PlaygroundEditorTheme.ts` and SCSS;
704
+ 6. any persisted state through `getBibleNoteStorage()` (`src/storageManager.ts`),
705
+ with the key added to `BIBLE_NOTE_STORAGE_MANAGER_KEYS` so it is hydrated
706
+ before the first render;
707
+ 7. Word export/import handling in `src/utils/docx*` if the node needs a Word form;
708
+ 8. the user-facing description in `knowledge/ui/` (see "The knowledge files"
709
+ below).
710
+
711
+ UI follows the existing conventions: Bootstrap utility classes, `bi bi-*` icons
712
+ from bootstrap-icons (a peer dependency — never import its CSS from library
713
+ code), and colours that work under both `playground-theme-light` and
714
+ `playground-theme-dark`.
715
+
716
+ ### Running it
717
+
718
+ - `npm run dev` — Vite on `http://localhost:3005` plus the Electron shell, with
719
+ remote debugging on `127.0.0.1:9225`.
720
+ - `npm run vite` — the web app alone on port 3005.
721
+ - If Electron dies at startup with a `BrowserWindow` export error, the shell
722
+ inherited `ELECTRON_RUN_AS_NODE=1` (VS Code's integrated terminal sets it);
723
+ clear it and run again.
724
+ - `npm run test:unit` — Vitest + jsdom over `src/**/*.test.ts(x)`.
725
+ - `npm run test:e2e` — Playwright.
726
+ - `npm run lint:ts`, `npm run lint:es`, `npm run lint:pre` — types, ESLint,
727
+ Prettier.
728
+
729
+ ### Verifying UI changes in the browser
730
+
731
+ Do not call a UI change done until it has been exercised in the running app.
732
+ The repository's Chrome DevTools MCP server attaches to the Electron debug port
733
+ (`9225`): select the `http://localhost:3005/` page, snapshot it, drive the flow
734
+ with clicks and key presses, watch the console, and screenshot the result. The
735
+ `bn-robot-test` skill runs a full regression battery this way — mentions, slash
736
+ menu, toolbar, markdown shortcuts, tables, undo, persistence — and backs up and
737
+ restores the open note around it.
738
+
739
+ ### The knowledge files
740
+
741
+ `knowledge/development/*.md` and `knowledge/ui/*.md` are the hand-written
742
+ sources of the two knowledge files; `npm run pack` builds them into
743
+ `packages/bible-note/knowledge/` (`npm run build:knowledge` rebuilds just them).
744
+ When a user-visible behaviour changes, update the matching `knowledge/ui/` topic;
745
+ when the public API changes, update its JSDoc — the API reference is generated
746
+ from the declarations. Catalogues such as the slash menu, shortcuts and the table
747
+ above are extracted from source; if `pack` fails in an extractor, the source moved
748
+ and `scripts/knowledge/extractUi.mjs` needs to follow it. `knowledge/README.md`
749
+ has the authoring rules.
750
+
751
+ <a id="dev-building-and-releasing-the-package"></a>
752
+
753
+ ## Building and releasing the package
754
+
755
+ ### `npm run pack`
756
+
757
+ `scripts/pack.mjs` builds `packages/bible-note/`:
758
+
759
+ 1. wipes the generated directories — `dist/`, `types/`, `knowledge/`;
760
+ 2. bundles `src/lib.ts` twice with `vite.lib.config.ts`, once as ESM (`.mjs`)
761
+ and once as CommonJS (`.cjs`), plus the single `dist/bible-note.css`;
762
+ 3. emits declarations with `tsc -p tsconfig.lib.json` and prunes them to what
763
+ `types/lib.d.ts` actually reaches;
764
+ 4. builds `knowledge/development.md` and `knowledge/ui-rag.md`;
765
+ 5. checks the static `package.json` against the build and the root manifest —
766
+ every advertised path exists, `files` and `sideEffects` are right, peer
767
+ ranges match the root `package.json`, external packages are declared;
768
+ 6. checks the entry surface (required exports present, no peer CSS bundled, no
769
+ throwing `require()` polyfill in the ES build) and type-checks the packaged
770
+ declarations from outside the repository, as a consumer would see them.
771
+
772
+ Any failure stops the run with the exact mismatch.
773
+
774
+ ### Hand-maintained files
775
+
776
+ `packages/bible-note/package.json`, `README.md` and `LICENSE` are the only files
777
+ in `packages/` that git tracks, and no script writes them:
778
+
779
+ - bump `version` whenever library code changes — patch for fixes and internal
780
+ changes, minor for new exports, options or published files, major for a
781
+ breaking change to the public surface;
782
+ - update the README when a change is visible to a consumer (a new export goes
783
+ in its Exports table);
784
+ - edit `package.json` by hand when peer ranges change in the root manifest or
785
+ entry points move — `pack` reports the exact drift.
786
+
787
+ ### Testing the built package
788
+
789
+ `test-packages/` installs `bible-note` from `../packages/bible-note`. After
790
+ `npm run pack`, run `npm i` and `npm run vite` (port 3007) there to use the
791
+ tarball contents exactly as a consumer would. `npm run copy-packages` mirrors
792
+ the publishable files into the sibling `../bible-note-dist` checkout
793
+ (`npm run pack:copy` does both).
794
+
795
+ ### Publishing
796
+
797
+ From a clean `main`, with the package built:
798
+
799
+ ```bash
800
+ npm pack --dry-run # check the file list
801
+ npm whoami # npm login if this fails
802
+ npm version patch --tag-version-prefix="" # or minor / major
803
+ npm publish
804
+ git push origin main --follow-tags
805
+ npm view bible-note version
806
+ ```
807
+
808
+ <a id="dev-api-reference"></a>
809
+
810
+ ## API reference
811
+
812
+ Generated from the package's own declarations (`types/lib.d.ts`), so it lists
813
+ exactly what `import … from 'bible-note'` provides. Descriptions are the JSDoc
814
+ on the source; a member without one shows its signature only.
815
+
816
+ The package's default export is [`BibleNote`](#api-biblenote); every name below is also a named export of `bible-note`.
817
+
818
+ ### Classes
819
+
820
+ <a id="api-biblenote"></a>
821
+
822
+ #### `BibleNote`
823
+
824
+ The BibleNote editor. Construct it with the host's datasets and storage
825
+ callbacks, then [`render`](#api-biblenote) it into an element.
826
+
827
+ Every editor-backed method (`content`, `addText`, `searchText`, …) can be
828
+ called before `render`: writes are queued and applied once the editor has
829
+ mounted, and reads return an empty value until then.
830
+
831
+ ```ts
832
+ new BibleNote(...)
833
+ constructor(props: BibleNoteProps);
834
+ ```
835
+
836
+ ##### `BibleNote.VERSION`
837
+
838
+ ```ts
839
+ static VERSION: string;
840
+ ```
841
+
842
+ The package version this bundle was built from.
843
+
844
+ ##### `BibleNote.getAllShortVersesFromText()`
845
+
846
+ ```ts
847
+ static getAllShortVersesFromText(lexicalText: string): Array<string>;
848
+ ```
849
+
850
+ Every short verse reference (`GEN 1:1`) the given note content mentions, in
851
+ document order and without duplicates. Takes what `content` returns — a
852
+ serialized editor state — or plain text, and reads both the Bible verse
853
+ mentions and any reference written in the prose. Ranges inside one chapter
854
+ are expanded verse by verse; a range crossing a chapter boundary keeps only
855
+ its endpoints, because per-chapter verse counts are not bundled here.
856
+
857
+ ##### `props`
858
+
859
+ ```ts
860
+ props: Readonly<BibleNoteProps>;
861
+ ```
862
+
863
+ The props the instance was constructed with.
864
+
865
+ ##### `colorScheme`
866
+
867
+ ```ts
868
+ colorScheme: BibleNoteColorScheme;
869
+ ```
870
+
871
+ The colour scheme last applied — by [`BibleNote.setColorScheme`](#api-biblenote) or the
872
+ settings panel's Dark Mode switch. Read it; change it with `setColorScheme`.
873
+
874
+ ##### `content`
875
+
876
+ ```ts
877
+ get content(): string;
878
+ set content(value: string);
879
+ ```
880
+
881
+ The note as a serialized Lexical editor state (a JSON string) — the same
882
+ string `saveData` receives. Reads `''` until the editor has mounted.
883
+ Assigning replaces the whole document; assigning `''` clears it.
884
+
885
+ ##### `isReadOnly`
886
+
887
+ ```ts
888
+ get isReadOnly(): boolean;
889
+ set isReadOnly(value: boolean);
890
+ ```
891
+
892
+ Whether the note is locked against editing. Assigning `true` forces
893
+ read-only mode: the footer's lock turns into **Read-Only Mode Locked** and
894
+ the user cannot unlock it. Assigning `false` releases that lock and makes
895
+ the note editable.
896
+
897
+ ##### `getIsFocusing()`
898
+
899
+ ```ts
900
+ getIsFocusing(): boolean;
901
+ ```
902
+
903
+ Whether the caret is in the editor right now: the window has focus, the
904
+ editor is the active element, and the selection is a collapsed caret inside
905
+ it. A host can use this to decide whether to send text to `addText`.
906
+
907
+ ##### `setColorScheme()`
908
+
909
+ ```ts
910
+ setColorScheme(mode: BibleNoteColorScheme): void;
911
+ ```
912
+
913
+ Switches the editor between light and dark. Applies the body classes at
914
+ once (see `applyBibleNoteColorScheme`) and keeps the settings panel's Dark
915
+ Mode switch in step.
916
+
917
+ ##### `addText()`
918
+
919
+ ```ts
920
+ addText(text: string): void;
921
+ ```
922
+
923
+ Inserts text at the caret, or at the end of the note when there is no
924
+ selection, exactly as if the user had pasted it as plain text — so the
925
+ paste rules apply: Bible text pasted right after a `^` becomes a verse
926
+ mention. Ignored while the note is read-only or when `text` is empty.
927
+
928
+ ##### `appendFooterActionButton()`
929
+
930
+ ```ts
931
+ appendFooterActionButton(actionButton: BibleNoteFooterActionButton): void;
932
+ ```
933
+
934
+ Adds a host button after the footer's built-in buttons. A button whose `id`
935
+ is already present is replaced in place, keeping its position.
936
+
937
+ ##### `prependFooterActionButton()`
938
+
939
+ ```ts
940
+ prependFooterActionButton(actionButton: BibleNoteFooterActionButton): void;
941
+ ```
942
+
943
+ Adds a host button before the footer's built-in buttons, ahead of any
944
+ prepended earlier. A button whose `id` is already present is replaced in
945
+ place, keeping its position.
946
+
947
+ ##### `getFooterActionButton()`
948
+
949
+ ```ts
950
+ getFooterActionButton(id: string): BibleNoteFooterActionButton | null;
951
+ ```
952
+
953
+ The host footer button with this `id`, or `null`.
954
+
955
+ ##### `removeFooterActionButton()`
956
+
957
+ ```ts
958
+ removeFooterActionButton(id: string): boolean;
959
+ ```
960
+
961
+ Removes the host footer button with this `id`; `false` when there was none.
962
+
963
+ ##### `searchText()`
964
+
965
+ ```ts
966
+ searchText(query: string | null): void;
967
+ ```
968
+
969
+ Drives the editor's find bar. A string opens the bar (or updates the query
970
+ if it is already open); `null` closes it. Queued until the editor mounts,
971
+ like the other editor-backed methods.
972
+
973
+ ##### `render()`
974
+
975
+ ```ts
976
+ render(containerElement: HTMLElement, options?: BibleNoteRenderOptions): void;
977
+ ```
978
+
979
+ Mounts the editor into `containerElement` with its own React root.
980
+ Rendering again unmounts the previous root first, so an instance is only
981
+ ever mounted once.
982
+
983
+ ##### `unmount()`
984
+
985
+ ```ts
986
+ unmount(): void;
987
+ ```
988
+
989
+ Unmounts the editor. The instance keeps its props and footer buttons and
990
+ can be rendered again.
991
+
992
+ <a id="api-locationslookupmanager"></a>
993
+
994
+ #### `LocationsLookupManager`
995
+
996
+ Owns the place/location dataset(s): ranked search, id and name lookup,
997
+ pagination and verse → locations lookup. Has no editor dependency, so a host
998
+ can use it to build its own UI. Indexes are built lazily per language on
999
+ first use.
1000
+
1001
+ ```ts
1002
+ new LocationsLookupManager(...)
1003
+ constructor(dataMap: LocationsLookupManagerDataMap, defaultLang: string, style?: LookupManagerStyle);
1004
+ ```
1005
+
1006
+ ##### `LocationsLookupManager.fromRawDataset()`
1007
+
1008
+ ```ts
1009
+ static fromRawDataset(rawDataMap: {
1010
+ [langCode: string]: unknown;
1011
+ }, defaultLang: string, style?: LookupManagerStyle): LocationsLookupManager;
1012
+ ```
1013
+
1014
+ Builds a manager straight from the generated dataset files, i.e. whatever
1015
+ `fetch('data/KJV/locationsMap.json')` resolved to, keyed by language code.
1016
+ Older generated files stored the records under `namesMap`, so both keys are
1017
+ accepted.
1018
+
1019
+ ##### `LocationsLookupManager.createEmpty()`
1020
+
1021
+ ```ts
1022
+ static createEmpty(): LocationsLookupManager;
1023
+ ```
1024
+
1025
+ An empty manager, used wherever a host has not supplied a dataset yet.
1026
+
1027
+ ##### `searchLocations()`
1028
+
1029
+ ```ts
1030
+ searchLocations(query: string, options?: LookupSearchOptions): Array<MentionLocationMatch>;
1031
+ ```
1032
+
1033
+ Locations matching `query`. Case-insensitive substring match against the name, the KJV English
1034
+ name (translated datasets) and the former name. Ranked exact, then prefix,
1035
+ then substring — the better of the two names counts — and by importance
1036
+ (`matterRange`, then alphabetical) within a tier. An empty query returns
1037
+ every record in importance order.
1038
+
1039
+ ##### `searchLocationsPagination()`
1040
+
1041
+ ```ts
1042
+ searchLocationsPagination(options?: LookupPaginationOptions): LookupPaginationResult<MentionLocationMatch>;
1043
+ ```
1044
+
1045
+ `searchLocations`, one page at a time (20 records per page by default).
1046
+
1047
+ ##### `searchByShortVerse()`
1048
+
1049
+ ```ts
1050
+ searchByShortVerse(shortVerse: string, lang?: string): Array<MentionLocationMatch>;
1051
+ ```
1052
+
1053
+ The locations a short verse key (e.g. `"EXO 6:23"`) mentions.
1054
+
1055
+ ##### `style`
1056
+
1057
+ ```ts
1058
+ get style(): LookupManagerStyle | undefined;
1059
+ set style(style: LookupManagerStyle | undefined);
1060
+ ```
1061
+
1062
+ The host-supplied style for this dataset, or `undefined` when it gave none.
1063
+ The UI merges it into the containers that render these records; like
1064
+ `defaultLang` it is read at render time, so a host that changes it after
1065
+ mounting must also make the editor re-render.
1066
+
1067
+ ##### `defaultLang`
1068
+
1069
+ ```ts
1070
+ get defaultLang(): string;
1071
+ set defaultLang(lang: string);
1072
+ ```
1073
+
1074
+ The language used when a call names none (or names one this manager does
1075
+ not hold). Assigning a language the data map lacks throws.
1076
+
1077
+ ##### `langs`
1078
+
1079
+ ```ts
1080
+ get langs(): ReadonlyArray<string>;
1081
+ ```
1082
+
1083
+ The language codes this manager holds data for.
1084
+
1085
+ ##### `getRecordCount()`
1086
+
1087
+ ```ts
1088
+ getRecordCount(lang?: string): number;
1089
+ ```
1090
+
1091
+ Total number of records in a language, ignoring any query.
1092
+
1093
+ ##### `getAllRecords()`
1094
+
1095
+ ```ts
1096
+ getAllRecords(lang?: string): Array<TRecord>;
1097
+ ```
1098
+
1099
+ Every record in a language, in importance order.
1100
+
1101
+ ##### `getRecordById()`
1102
+
1103
+ ```ts
1104
+ getRecordById(id: string, lang?: string): TRecord | null;
1105
+ ```
1106
+
1107
+ The record with this id (whitespace-trimmed), or `null`.
1108
+
1109
+ ##### `getRecordsByIds()`
1110
+
1111
+ ```ts
1112
+ getRecordsByIds(ids: ReadonlyArray<string>, lang?: string): Array<TRecord>;
1113
+ ```
1114
+
1115
+ The records for these ids, in the order given; unknown and repeated ids
1116
+ are skipped.
1117
+
1118
+ ##### `getRecordsByName()`
1119
+
1120
+ ```ts
1121
+ getRecordsByName(name: string, lang?: string): Array<TRecord>;
1122
+ ```
1123
+
1124
+ Every record carrying exactly this name. Names are not unique in the
1125
+ datasets, so callers that need certainty check for a single result.
1126
+
1127
+ <a id="api-nameslookupmanager"></a>
1128
+
1129
+ #### `NamesLookupManager`
1130
+
1131
+ Owns the person/name dataset(s): ranked search, id and name lookup,
1132
+ pagination, name-type counts and verse → names lookup. Has no editor
1133
+ dependency, so a host can use it to build its own UI. Indexes are built
1134
+ lazily per language on first use.
1135
+
1136
+ ```ts
1137
+ new NamesLookupManager(...)
1138
+ constructor(dataMap: NamesLookupManagerDataMap, defaultLang: string, style?: LookupManagerStyle);
1139
+ ```
1140
+
1141
+ ##### `NamesLookupManager.fromRawDataset()`
1142
+
1143
+ ```ts
1144
+ static fromRawDataset(rawDataMap: {
1145
+ [langCode: string]: unknown;
1146
+ }, defaultLang: string, style?: LookupManagerStyle): NamesLookupManager;
1147
+ ```
1148
+
1149
+ Builds a manager straight from the generated dataset files, i.e. whatever
1150
+ `fetch('data/KJV/namesMap.json')` resolved to, keyed by language code. Use this
1151
+ unless you already hold normalized [`MentionNameMatch`](#api-mentionnamematch) records.
1152
+
1153
+ ##### `NamesLookupManager.createEmpty()`
1154
+
1155
+ ```ts
1156
+ static createEmpty(): NamesLookupManager;
1157
+ ```
1158
+
1159
+ An empty manager, used wherever a host has not supplied a dataset yet.
1160
+
1161
+ ##### `getNameTypes()`
1162
+
1163
+ ```ts
1164
+ getNameTypes(lang?: string): ReadonlyArray<NameTypeCount>;
1165
+ ```
1166
+
1167
+ The name types actually present in the data, most common first, so a type
1168
+ filter only ever offers choices that would return results.
1169
+
1170
+ ##### `nameTypes`
1171
+
1172
+ ```ts
1173
+ get nameTypes(): ReadonlyArray<NameTypeCount>;
1174
+ ```
1175
+
1176
+ The name types present in the default language.
1177
+
1178
+ ##### `searchNames()`
1179
+
1180
+ ```ts
1181
+ searchNames(query: string, options?: NamesLookupSearchOptions): Array<MentionNameMatch>;
1182
+ ```
1183
+
1184
+ Names matching `query`. Case-insensitive substring match against the name, the KJV English
1185
+ name (translated datasets) and the former name. Ranked exact, then prefix,
1186
+ then substring — the better of the two names counts — and by importance
1187
+ (`matterRange`, then alphabetical) within a tier. An empty query returns
1188
+ every record in importance order.
1189
+
1190
+ ##### `searchNamesPagination()`
1191
+
1192
+ ```ts
1193
+ searchNamesPagination(options?: NamesLookupPaginationOptions): LookupPaginationResult<MentionNameMatch>;
1194
+ ```
1195
+
1196
+ `searchNames`, one page at a time (20 records per page by default).
1197
+
1198
+ ##### `searchByShortVerse()`
1199
+
1200
+ ```ts
1201
+ searchByShortVerse(shortVerse: string, lang?: string): Array<MentionNameMatch>;
1202
+ ```
1203
+
1204
+ The names a short verse key (e.g. `"EXO 6:23"`) mentions.
1205
+
1206
+ ##### `style`
1207
+
1208
+ ```ts
1209
+ get style(): LookupManagerStyle | undefined;
1210
+ set style(style: LookupManagerStyle | undefined);
1211
+ ```
1212
+
1213
+ The host-supplied style for this dataset, or `undefined` when it gave none.
1214
+ The UI merges it into the containers that render these records; like
1215
+ `defaultLang` it is read at render time, so a host that changes it after
1216
+ mounting must also make the editor re-render.
1217
+
1218
+ ##### `defaultLang`
1219
+
1220
+ ```ts
1221
+ get defaultLang(): string;
1222
+ set defaultLang(lang: string);
1223
+ ```
1224
+
1225
+ The language used when a call names none (or names one this manager does
1226
+ not hold). Assigning a language the data map lacks throws.
1227
+
1228
+ ##### `langs`
1229
+
1230
+ ```ts
1231
+ get langs(): ReadonlyArray<string>;
1232
+ ```
1233
+
1234
+ The language codes this manager holds data for.
1235
+
1236
+ ##### `getRecordCount()`
1237
+
1238
+ ```ts
1239
+ getRecordCount(lang?: string): number;
1240
+ ```
1241
+
1242
+ Total number of records in a language, ignoring any query.
1243
+
1244
+ ##### `getAllRecords()`
1245
+
1246
+ ```ts
1247
+ getAllRecords(lang?: string): Array<TRecord>;
1248
+ ```
1249
+
1250
+ Every record in a language, in importance order.
1251
+
1252
+ ##### `getRecordById()`
1253
+
1254
+ ```ts
1255
+ getRecordById(id: string, lang?: string): TRecord | null;
1256
+ ```
1257
+
1258
+ The record with this id (whitespace-trimmed), or `null`.
1259
+
1260
+ ##### `getRecordsByIds()`
1261
+
1262
+ ```ts
1263
+ getRecordsByIds(ids: ReadonlyArray<string>, lang?: string): Array<TRecord>;
1264
+ ```
1265
+
1266
+ The records for these ids, in the order given; unknown and repeated ids
1267
+ are skipped.
1268
+
1269
+ ##### `getRecordsByName()`
1270
+
1271
+ ```ts
1272
+ getRecordsByName(name: string, lang?: string): Array<TRecord>;
1273
+ ```
1274
+
1275
+ Every record carrying exactly this name. Names are not unique in the
1276
+ datasets, so callers that need certainty check for a single result.
1277
+
1278
+ ### Functions and components
1279
+
1280
+ <a id="api-applybiblenotecolorscheme"></a>
1281
+
1282
+ #### `applyBibleNoteColorScheme()`
1283
+
1284
+ Applies a colour scheme to `body` (default `document.body`): toggles the
1285
+ `playground-theme-dark` / `playground-theme-light` classes and sets
1286
+ `data-bs-theme`, so Bootstrap's own components follow. Prefer
1287
+ `BibleNote#setColorScheme` on a mounted editor; this is the bare DOM step.
1288
+
1289
+ ```ts
1290
+ function applyBibleNoteColorScheme(mode: BibleNoteColorScheme, body?: HTMLElement): void;
1291
+ ```
1292
+
1293
+ <a id="api-biblenotesettings"></a>
1294
+
1295
+ #### `BibleNoteSettings()`
1296
+
1297
+ The editor's settings panel — a gear button that opens the switches (Dark
1298
+ Mode, Table Of Contents, …). Pass it to `render` as `options.settings`;
1299
+ settings persist through the storage manager.
1300
+
1301
+ ```ts
1302
+ default function BibleNoteSettings({ setColorScheme, }: BibleNoteSettingsProps): JSX.Element;
1303
+ ```
1304
+
1305
+ <a id="api-clearbiblenotecolorscheme"></a>
1306
+
1307
+ #### `clearBibleNoteColorScheme()`
1308
+
1309
+ Removes the theme classes `applyBibleNoteColorScheme` added (leaves `data-bs-theme`).
1310
+
1311
+ ```ts
1312
+ function clearBibleNoteColorScheme(body?: HTMLElement): void;
1313
+ ```
1314
+
1315
+ <a id="api-createbrowserstoragemanager"></a>
1316
+
1317
+ #### `createBrowserStorageManager()`
1318
+
1319
+ A `BibleNoteStorageManager` backed by `window.localStorage`. Non-string
1320
+ values are stored as JSON; when `localStorage` is unavailable every call is
1321
+ a no-op.
1322
+
1323
+ ```ts
1324
+ function createBrowserStorageManager(): BibleNoteStorageManager;
1325
+ ```
1326
+
1327
+ <a id="api-getlookuprecordvalues"></a>
1328
+
1329
+ #### `getLookupRecordValues()`
1330
+
1331
+ The records of a raw dataset collection, whether it is an id-keyed object or
1332
+ a plain array; `[]` for anything else.
1333
+
1334
+ ```ts
1335
+ function getLookupRecordValues(value: unknown): Array<unknown>;
1336
+ ```
1337
+
1338
+ <a id="api-getmentionkjvname"></a>
1339
+
1340
+ #### `getMentionKjvName()`
1341
+
1342
+ The English name worth showing beside a record's own name, or `null` when
1343
+ there is nothing to add — an untranslated (KJV) dataset, or a record whose
1344
+ translated name is the English one anyway. Display it the way a Bible book
1345
+ reads in a translated UI: `លោកុប្បត្តិ (Genesis)`.
1346
+
1347
+ ```ts
1348
+ function getMentionKjvName(record: {
1349
+ name: string;
1350
+ kjvName: string | null;
1351
+ }): string | null;
1352
+ ```
1353
+
1354
+ <a id="api-normalizementionnametype"></a>
1355
+
1356
+ #### `normalizeMentionNameType()`
1357
+
1358
+ Normalizes a raw record `type` value to one of the known [`MentionNameType`](#api-mentionnametype)
1359
+ keys. Unrecognized or empty values fall back to `person`, the neutral default
1360
+ used for untyped name records.
1361
+
1362
+ ```ts
1363
+ function normalizeMentionNameType(type: string | null | undefined): MentionNameType;
1364
+ ```
1365
+
1366
+ <a id="api-parsementionlocationmatch"></a>
1367
+
1368
+ #### `parseMentionLocationMatch()`
1369
+
1370
+ Normalizes one raw dataset entry into a [`MentionLocationMatch`](#api-mentionlocationmatch), or
1371
+ returns `null` when it carries no usable identity.
1372
+
1373
+ ```ts
1374
+ function parseMentionLocationMatch(value: unknown): MentionLocationMatch | null;
1375
+ ```
1376
+
1377
+ <a id="api-parsementionlocationmatches"></a>
1378
+
1379
+ #### `parseMentionLocationMatches()`
1380
+
1381
+ Parses a whole dataset value (array or id-keyed object) into location records.
1382
+
1383
+ ```ts
1384
+ function parseMentionLocationMatches(value: unknown): Array<MentionLocationMatch>;
1385
+ ```
1386
+
1387
+ <a id="api-parsementionnamematch"></a>
1388
+
1389
+ #### `parseMentionNameMatch()`
1390
+
1391
+ Normalizes one raw dataset entry into a [`MentionNameMatch`](#api-mentionnamematch), or returns
1392
+ `null` when it carries no usable identity. The literal id `'id'` is rejected
1393
+ because some generated files carry a header row.
1394
+
1395
+ ```ts
1396
+ function parseMentionNameMatch(value: unknown): MentionNameMatch | null;
1397
+ ```
1398
+
1399
+ <a id="api-parsementionnamematches"></a>
1400
+
1401
+ #### `parseMentionNameMatches()`
1402
+
1403
+ Parses a whole dataset value (array or id-keyed object) into name records.
1404
+
1405
+ ```ts
1406
+ function parseMentionNameMatches(value: unknown): Array<MentionNameMatch>;
1407
+ ```
1408
+
1409
+ ### Constants
1410
+
1411
+ <a id="api-bible_note_storage_manager_keys"></a>
1412
+
1413
+ #### `BIBLE_NOTE_STORAGE_MANAGER_KEYS`
1414
+
1415
+ Every key the editor reads from its storage manager before it renders. A
1416
+ host-side store that needs to know its keys up front (to prefetch or migrate
1417
+ them) can use this list.
1418
+
1419
+ ```ts
1420
+ const BIBLE_NOTE_STORAGE_MANAGER_KEYS: readonly ["lexical-playground-settings", "lexical-playground-editor-state", "lexical-playground-comments", "lexical-playground-comments-panel-open", "lexical-playground-block-text-links", "lexical-playground-read-only-mode", "lexical-playground-insert-character-picker", "lexical-playground-editor-zoom-percent"]
1421
+ ```
1422
+
1423
+ <a id="api-editor_state_local_storage_key"></a>
1424
+
1425
+ #### `EDITOR_STATE_LOCAL_STORAGE_KEY`
1426
+
1427
+ The storage key of the note itself. The editor never hands this key to the
1428
+ host's `storageManager`: reads and writes of it go to `loadData` / `saveData`
1429
+ instead.
1430
+
1431
+ ```ts
1432
+ const EDITOR_STATE_LOCAL_STORAGE_KEY = "lexical-playground-editor-state"
1433
+ ```
1434
+
1435
+ ### Types
1436
+
1437
+ <a id="api-biblenotecolorscheme"></a>
1438
+
1439
+ #### `BibleNoteColorScheme`
1440
+
1441
+ The editor's two colour schemes.
1442
+
1443
+ ```ts
1444
+ type BibleNoteColorScheme = 'dark' | 'light';
1445
+ ```
1446
+
1447
+ <a id="api-biblenotefooteractionbutton"></a>
1448
+
1449
+ #### `BibleNoteFooterActionButton`
1450
+
1451
+ A host-supplied button in the editor's footer.
1452
+
1453
+ | Property | Type | Required | Description |
1454
+ | --- | --- | --- | --- |
1455
+ | `id` | `string` | yes | Unique per editor; adding a button with an existing id replaces it in place. |
1456
+ | `description` | `string` | no | What the button does. Used as the button's tooltip when the button element has no `title` of its own. |
1457
+ | `shortcutKey` | `string` | no | A keyboard shortcut that clicks the button from anywhere in the page, written as modifiers plus one key joined by `+`: `"Ctrl+Shift+K"`, `"Cmd+Shift+K"`, `"Alt+Shift+K"` (`Ctrl`/`Control`, `Cmd`/`Command`/`Meta`, `Alt`/`Option`, `Shift`). It is appended to the tooltip and set as `aria-keyshortcuts`. |
1458
+ | `children` | `ReactNode` | yes | The button itself, normally a single `<button>`. When it is a single plain element, the editor adds the tooltip and `aria-keyshortcuts` to it. |
1459
+
1460
+ <a id="api-biblenotefooteractionbuttonplacement"></a>
1461
+
1462
+ #### `BibleNoteFooterActionButtonPlacement`
1463
+
1464
+ Which side of the built-in footer buttons a host button sits on.
1465
+
1466
+ ```ts
1467
+ type BibleNoteFooterActionButtonPlacement = 'before' | 'after';
1468
+ ```
1469
+
1470
+ <a id="api-biblenotefooteractionbuttons"></a>
1471
+
1472
+ #### `BibleNoteFooterActionButtons`
1473
+
1474
+ The host footer buttons on each side of the built-in ones, in order.
1475
+
1476
+ ```ts
1477
+ type BibleNoteFooterActionButtons = Readonly<{
1478
+ before: ReadonlyArray<BibleNoteFooterActionButton>;
1479
+ after: ReadonlyArray<BibleNoteFooterActionButton>;
1480
+ }>;
1481
+ ```
1482
+
1483
+ <a id="api-biblenoteprops"></a>
1484
+
1485
+ #### `BibleNoteProps`
1486
+
1487
+ Everything `new BibleNote(props)` needs from the host application.
1488
+
1489
+ | Property | Type | Required | Description |
1490
+ | --- | --- | --- | --- |
1491
+ | `namesLookupManager` | `NamesLookupManager` | yes | The person dataset behind `@` mentions, hover cards, the advanced panel and the lookup panel. Build it with `NamesLookupManager.fromRawDataset`. |
1492
+ | `locationsLookupManager` | `LocationsLookupManager` | yes | The place dataset behind `#` mentions, hover cards, the advanced panel and the lookup panel. Build it with `LocationsLookupManager.fromRawDataset`. |
1493
+ | `getLangCode` | `(text: string) => string` | yes | Returns the language code of a piece of text (`'en'` for English). Used to pick a font for pasted Bible text and Excalidraw text: when it returns a code other than `'en'`, the editor switches to the first font in `editorExtraFontFamilies` registered for that language. |
1494
+ | `editorExtraFontFamilies` | `ReadonlyArray<ExtraFontFamilyOption>` | yes | Fonts added to the toolbar's font menu and to Excalidraw, as `[fontFamily, languageCode]` pairs. The host must load the font faces itself; pass `[]` for the built-in fonts only. |
1495
+ | `excalidrawLibraryItems` | `LibraryItems` | no | Extra Excalidraw library items, offered next to the bundled Christian library. |
1496
+ | `excalidrawClearLibrariesFileList` | `() => void` | no | Forgets every Excalidraw library file `excalidrawSaveLibrariesFile` stored. |
1497
+ | `excalidrawLoadLibrariesFileList` | `() => string[]` | no | Returns the Excalidraw library files saved earlier (each a serialized library JSON string); read each time the Excalidraw editor opens. |
1498
+ | `excalidrawSaveLibrariesFile` | `(librariesFile: string) => void` | no | Persists a library the user imported in Excalidraw (a serialized library JSON string) so `excalidrawLoadLibrariesFileList` can return it next time. |
1499
+ | `stickyNoteExtraFontFamilies` | `ReadonlyArray<string>` | yes | Extra CSS font-family values offered in the sticky-note font picker, e.g. `'"Fasthand"'`. Pass `[]` for none. |
1500
+ | `storageManager` | `BibleNoteStorageManager` | yes | Stores every piece of editor state except the note itself: settings, comments, read-only mode, zoom, panel state. `createBrowserStorageManager()` keeps them in `localStorage`. The keys it receives are listed in `BIBLE_NOTE_STORAGE_MANAGER_KEYS`. |
1501
+ | `saveData` | `(data: string) => Promise<void> \| void` | yes | Saves the note: called with the serialized editor state (a JSON string) on every change to the document — the same string `BibleNote#content` returns. Called with `''` when the stored note could not be parsed and is discarded. |
1502
+ | `loadData` | `() => Promise<string \| null> \| string \| null` | yes | Loads the note saved by `saveData`. Return `null` or `''` for an empty editor. |
1503
+ | `resolveFilePath` | `FilePathResolver` | no | Maps a media file the user dropped or picked to a path or URL to store in the note instead of embedding it as a data URL. Return `null` to fall back to embedding. In a browser there is usually no path to give; in Electron this is where a file's location on disk comes from. |
1504
+ | `revealFile` | `FileRevealer` | no | Shows a media file's location to the user (e.g. Electron's `shell.showItemInFolder`). When provided, a selected image, video, audio or PDF whose source is a local file path gets a reveal button. |
1505
+ | `print` | `() => void` | no | Replaces the footer Print button's built-in printing, which prints the note laid out at the selected page size. |
1506
+ | `isOnApp` | `boolean` | no | The editor is embedded in an app rather than a web page: hides the footer's **Share as Link** and **Convert From Markdown** buttons. Defaults to `false`. |
1507
+ | `isMinimize` | `boolean` | no | Compact footer: folds Import, Export and Editor Information into a **More actions** menu. Defaults to `false`. |
1508
+ | `shortToVerseData` | `(shortText: string) => Promise<MentionedBibleDataType \| null>` | no | Resolves a short verse key (`GEN 1:1`) to its displayed title and text. Used for verse lists in mention panels and in the Word export's References appendix. Without it, those lists show the bare key. |
1509
+ | `verseFullTextToListShorts` | `(fullText: string) => Promise<string[] \| null>` | no | Lists the short verse keys a verse mention covers, given its full text. Enables the people and places mentioned in a verse, shown in that verse's advanced panel and in the Word export. |
1510
+ | `changeBibleKey` | `(event: React.MouseEvent<HTMLButtonElement>, verseFullText: string) => Promise<MentionedBibleDataType \| null>` | no | Offers the same verse in another Bible version. When provided, the version label in a verse's advanced panel becomes a button; resolve to the verse in the chosen version to open it in a new panel, or `null` to do nothing. |
1511
+
1512
+ <a id="api-biblenoterenderoptions"></a>
1513
+
1514
+ #### `BibleNoteRenderOptions`
1515
+
1516
+ Options for `BibleNote#render`.
1517
+
1518
+ | Property | Type | Required | Description |
1519
+ | --- | --- | --- | --- |
1520
+ | `settings` | `ReactNode` | no | The settings panel to mount, normally `<BibleNoteSettings />`. Omit it and the editor has no settings panel. |
1521
+ | `settingsContainerElement` | `Element \| DocumentFragment` | no | Where to portal the `settings` panel. Without it the panel renders inside the editor's own layout. |
1522
+
1523
+ <a id="api-biblenotesettingsprops"></a>
1524
+
1525
+ #### `BibleNoteSettingsProps`
1526
+
1527
+ Props of `BibleNoteSettings`.
1528
+
1529
+ | Property | Type | Required | Description |
1530
+ | --- | --- | --- | --- |
1531
+ | `setColorScheme` | `(mode: BibleNoteColorScheme) => void` | yes | Called when the Dark Mode switch flips. Pass `(mode) => bibleNote.setColorScheme(mode)` so the instance stays in step. |
1532
+
1533
+ <a id="api-biblenotestoragemanager"></a>
1534
+
1535
+ #### `BibleNoteStorageManager`
1536
+
1537
+ Where the editor keeps its own state — settings, comments, read-only mode,
1538
+ zoom, panel state — as string values under the keys in
1539
+ `BIBLE_NOTE_STORAGE_MANAGER_KEYS`. The note itself goes through
1540
+ `loadData` / `saveData` instead. Every key is read once, before the editor
1541
+ first renders; writes are fire-and-forget.
1542
+
1543
+ | Property | Type | Required | Description |
1544
+ | --- | --- | --- | --- |
1545
+ | `setSetting` | `setSetting(key: string, value: unknown): Promise<void>` | yes | Stores `value` (a string, or a value to `JSON.stringify`) under `key`. |
1546
+ | `getSetting` | `getSetting<T = unknown>(key: string): Promise<T \| null>` | yes | The value stored under `key`, or `null` when there is none. |
1547
+ | `deleteSetting` | `deleteSetting(key: string): Promise<void>` | yes | Removes `key`. |
1548
+
1549
+ <a id="api-extrafontfamilyoption"></a>
1550
+
1551
+ #### `ExtraFontFamilyOption`
1552
+
1553
+ A host font added to the editor: `[fontFamily, languageCode]`, e.g.
1554
+ `['Battambang', 'km']`. The language code is what `getLangCode` returns
1555
+ for text that should use this font.
1556
+
1557
+ ```ts
1558
+ type ExtraFontFamilyOption = readonly [
1559
+ fontFamily: string,
1560
+ language: string
1561
+ ];
1562
+ ```
1563
+
1564
+ <a id="api-filepathresolver"></a>
1565
+
1566
+ #### `FilePathResolver`
1567
+
1568
+ Maps a media file to the path or URL the note should reference. Return
1569
+ `null` (or throw) to let the editor embed the file as a data URL instead.
1570
+
1571
+ ```ts
1572
+ type FilePathResolver = (file: File, context: FilePathResolverContext) => Promise<string | null> | string | null;
1573
+ ```
1574
+
1575
+ <a id="api-filepathresolvercontext"></a>
1576
+
1577
+ #### `FilePathResolverContext`
1578
+
1579
+ Context passed to a `FilePathResolver`.
1580
+
1581
+ ```ts
1582
+ type FilePathResolverContext = Readonly<{
1583
+ source: FilePathSource;
1584
+ }>;
1585
+ ```
1586
+
1587
+ <a id="api-filepathsource"></a>
1588
+
1589
+ #### `FilePathSource`
1590
+
1591
+ How a media file reached the editor: dropped/pasted, or picked in the media dialog.
1592
+
1593
+ ```ts
1594
+ type FilePathSource = 'drop' | 'file-input';
1595
+ ```
1596
+
1597
+ <a id="api-filerevealer"></a>
1598
+
1599
+ #### `FileRevealer`
1600
+
1601
+ Shows a local media file to the user, e.g. in the system file manager.
1602
+
1603
+ ```ts
1604
+ type FileRevealer = (filePath: string) => Promise<void> | void;
1605
+ ```
1606
+
1607
+ <a id="api-fontfamilyoption"></a>
1608
+
1609
+ #### `FontFamilyOption`
1610
+
1611
+ A font in the toolbar's font menu: `[fontFamily, label, languageCode]`.
1612
+
1613
+ ```ts
1614
+ type FontFamilyOption = readonly [
1615
+ fontFamily: string,
1616
+ label: string,
1617
+ language: string
1618
+ ];
1619
+ ```
1620
+
1621
+ <a id="api-locationslookupmanagerdatamap"></a>
1622
+
1623
+ #### `LocationsLookupManagerDataMap`
1624
+
1625
+ Location datasets keyed by language code, e.g. `{ en: …, km: … }`.
1626
+
1627
+ ```ts
1628
+ type LocationsLookupManagerDataMap = {
1629
+ [langCode: string]: LocationsLookupManagerLangData;
1630
+ };
1631
+ ```
1632
+
1633
+ <a id="api-locationslookupmanagerlangdata"></a>
1634
+
1635
+ #### `LocationsLookupManagerLangData`
1636
+
1637
+ One language's location dataset, as `LocationsLookupManager`'s constructor takes it.
1638
+
1639
+ | Property | Type | Required | Description |
1640
+ | --- | --- | --- | --- |
1641
+ | `records` | `ReadonlyArray<MentionLocationMatch>` | yes | The normalized records (see `parseMentionLocationMatches`). |
1642
+ | `verseLocationsMap` | `VerseRecordIdsMap` | no | Short verse key (e.g. `"EXO 6:23"`) to the ids of the locations it mentions. |
1643
+
1644
+ <a id="api-lookupmanagerstyle"></a>
1645
+
1646
+ #### `LookupManagerStyle`
1647
+
1648
+ Extra inline style the UI applies wherever it renders one manager's records —
1649
+ the mention popup and hover card, the advanced panel, and the lookup list.
1650
+ Its purpose is a dataset-wide `fontFamily` for a language the editor's own
1651
+ font does not cover; anything a `style` attribute accepts works.
1652
+
1653
+ ```ts
1654
+ type LookupManagerStyle = CSSProperties;
1655
+ ```
1656
+
1657
+ <a id="api-lookuppaginationoptions"></a>
1658
+
1659
+ #### `LookupPaginationOptions`
1660
+
1661
+ Options for the `…Pagination` searches.
1662
+
1663
+ | Property | Type | Required | Description |
1664
+ | --- | --- | --- | --- |
1665
+ | `query` | `string` | no | Search text; empty or omitted lists every record in importance order. |
1666
+ | `lang` | `string` | no | Language to search; falls back to `defaultLang` when absent or unknown. |
1667
+ | `page` | `number` | no | 1-based page number, clamped to the available pages. Defaults to 1. |
1668
+ | `pageSize` | `number` | no | Records per page (at least 1). Defaults to 20. |
1669
+
1670
+ <a id="api-lookuppaginationresult"></a>
1671
+
1672
+ #### `LookupPaginationResult`
1673
+
1674
+ One page of search results.
1675
+
1676
+ | Property | Type | Required | Description |
1677
+ | --- | --- | --- | --- |
1678
+ | `lang` | `string` | yes | The language actually searched. |
1679
+ | `totalRecords` | `number` | yes | Matches across all pages. |
1680
+ | `page` | `number` | yes | The page returned, after clamping. |
1681
+ | `totalPages` | `number` | yes | Number of pages; at least 1, even with no matches. |
1682
+ | `pageSize` | `number` | yes | |
1683
+ | `records` | `Array<TRecord>` | yes | This page's records. |
1684
+
1685
+ <a id="api-lookupsearchoptions"></a>
1686
+
1687
+ #### `LookupSearchOptions`
1688
+
1689
+ Options for `searchNames` / `searchLocations`.
1690
+
1691
+ | Property | Type | Required | Description |
1692
+ | --- | --- | --- | --- |
1693
+ | `lang` | `string` | no | Language to search; falls back to `defaultLang` when absent or unknown. |
1694
+ | `limit` | `number` | no | Maximum number of results; all matches when omitted. |
1695
+
1696
+ <a id="api-mentionlocationcoordinates"></a>
1697
+
1698
+ #### `MentionLocationCoordinates`
1699
+
1700
+ WGS 84 decimal degrees.
1701
+
1702
+ | Property | Type | Required | Description |
1703
+ | --- | --- | --- | --- |
1704
+ | `latitude` | `number` | yes | |
1705
+ | `longitude` | `number` | yes | |
1706
+
1707
+ <a id="api-mentionlocationlink"></a>
1708
+
1709
+ #### `MentionLocationLink`
1710
+
1711
+ An external reference about a location; same shape as [`MentionNameLink`](#api-mentionnamelink).
1712
+
1713
+ | Property | Type | Required | Description |
1714
+ | --- | --- | --- | --- |
1715
+ | `type` | `string` | yes | |
1716
+ | `url` | `string` | yes | |
1717
+
1718
+ <a id="api-mentionlocationmatch"></a>
1719
+
1720
+ #### `MentionLocationMatch`
1721
+
1722
+ One place/location record — what `LocationsLookupManager` searches and
1723
+ returns, and what a `#` mention shows.
1724
+
1725
+ | Property | Type | Required | Description |
1726
+ | --- | --- | --- | --- |
1727
+ | `id` | `string` | yes | Stable record id; mentions in saved notes refer to it. |
1728
+ | `name` | `string` | yes | The name as printed in this dataset's Bible text. |
1729
+ | `kjvName` | `string \| null` | yes | The KJV English name; see [`MentionNameMatch.kjvName`](#api-mentionnamematch). |
1730
+ | `oldName` | `string \| null` | yes | A former name, searchable like `name`; `null` when none. |
1731
+ | `title` | `string` | yes | A one-line identification, with inline reference links like [`MentionNameMatch.title`](#api-mentionnamematch). |
1732
+ | `description` | `string` | yes | A paragraph about the place, with the same inline links. |
1733
+ | `type` | `string` | yes | The kind of place, e.g. `city`, `region`, `mountain`. |
1734
+ | `relatedLocations` | `Array<string>` | yes | Ids of other location records. |
1735
+ | `modernIdentification` | `string \| null` | yes | The modern site or name, when known. |
1736
+ | `coordinates` | `MentionLocationCoordinates \| null` | yes | |
1737
+ | `verses` | `Array<string>` | yes | Short verse keys where the Bible prints the name; ranges are collapsed. |
1738
+ | `links` | `Array<MentionLocationLink>` | yes | External references (Wikipedia, Easton's, New Advent…). |
1739
+ | `wikipedia` | `string \| null` | yes | The record's Wikipedia URL, or `null`. |
1740
+ | `uncertainty` | `boolean` | yes | `true` when the identification or location is uncertain. |
1741
+ | `matterRange` | `number` | yes | Importance rank on the same scale as [`MentionNameMatch.matterRange`](#api-mentionnamematch). |
1742
+
1743
+ <a id="api-mentionlocationpreview"></a>
1744
+
1745
+ #### `MentionLocationPreview`
1746
+
1747
+ A location record as the mention UI displays it: the dataset record, or a
1748
+ fallback built from what a mention in the note carries. `id` is `null` when
1749
+ the mention has no record id.
1750
+
1751
+ ```ts
1752
+ type MentionLocationPreview = Omit<MentionLocationMatch, 'id'> & {
1753
+ id: string | null;
1754
+ };
1755
+ ```
1756
+
1757
+ <a id="api-mentionnamelink"></a>
1758
+
1759
+ #### `MentionNameLink`
1760
+
1761
+ An external reference about a record.
1762
+
1763
+ | Property | Type | Required | Description |
1764
+ | --- | --- | --- | --- |
1765
+ | `type` | `string` | yes | The source, e.g. `wikipedia`, `easton`, `newadvent`. |
1766
+ | `url` | `string` | yes | |
1767
+
1768
+ <a id="api-mentionnamematch"></a>
1769
+
1770
+ #### `MentionNameMatch`
1771
+
1772
+ One person/name record — what `NamesLookupManager` searches and returns, and
1773
+ what an `@` mention shows. Fields holding other records (`parents`,
1774
+ `locations`, …) hold their ids; resolve them with `getRecordsByIds`.
1775
+
1776
+ | Property | Type | Required | Description |
1777
+ | --- | --- | --- | --- |
1778
+ | `id` | `string` | yes | Stable record id; mentions in saved notes refer to it. |
1779
+ | `name` | `string` | yes | The name as printed in this dataset's Bible text. |
1780
+ | `kjvName` | `string \| null` | yes | The KJV English name, carried only by translated datasets (the km-BFBS mirror and any future one) where `name` holds the translated form. `null` on the KJV dataset itself, whose `name` already is the English one. The lookup managers index it alongside `name`, so a translated dataset stays searchable by its English name, and the UI shows it beside the translated name the way a Bible book reads "លោកុប្បត្តិ (Genesis)". |
1781
+ | `oldName` | `string \| null` | yes | A former name (e.g. Abram for Abraham), searchable like `name`; `null` when none. |
1782
+ | `title` | `string` | yes | A one-line identification ("prophet who led Israel out of Egypt"). Like `description`, it may carry inline reference links: `[Egypt](location-id://…)`, `[Amram](name-id://…)`, `[Genesis 1:1](verse-key://GEN 1:1)`, `[Genesis](book-key://GEN)`, `[Genesis 14](chapter-key://GEN 14)`. |
1783
+ | `description` | `string` | yes | A paragraph about the person, with the same inline links as `title`. |
1784
+ | `gender` | `string` | yes | `male`, `female` or `unknown` in the generated datasets; `''` when absent. |
1785
+ | `age` | `string` | yes | Age as stated in the text (e.g. `120 years`); `''` when unknown. |
1786
+ | `years` | `Array<MentionNameYearRange>` | yes | Dated spans such as a king's reign; usually empty. |
1787
+ | `locations` | `Array<string>` | yes | Ids of location records tied to the person. |
1788
+ | `spouses` | `Array<string>` | yes | Ids of name records. |
1789
+ | `children` | `Array<string>` | yes | Ids of name records. |
1790
+ | `parents` | `Array<string>` | yes | Ids of name records. |
1791
+ | `siblings` | `Array<string>` | yes | Ids of name records. |
1792
+ | `cousin` | `Array<string>` | yes | Ids of name records. |
1793
+ | `type` | `string` | yes | The raw type; `normalizeMentionNameType` maps it to a [`MentionNameType`](#api-mentionnametype). |
1794
+ | `verses` | `Array<string>` | yes | Short verse keys where the Bible prints the name, e.g. `GEN 1:1` or a collapsed range `1SA 17:1-4` (so the array length is not a verse count). |
1795
+ | `links` | `Array<MentionNameLink>` | yes | External references (Wikipedia, Easton's, New Advent…). |
1796
+ | `wikipedia` | `string \| null` | yes | The record's Wikipedia URL, or `null`. |
1797
+ | `matterRange` | `number` | yes | How much the record matters, ascending: 0 is the most important on this dataset's scale. Orders every listing and breaks ties in search. Records without a rank get `Infinity` and sort to the very end. |
1798
+
1799
+ <a id="api-mentionnamepreview"></a>
1800
+
1801
+ #### `MentionNamePreview`
1802
+
1803
+ A name record as the mention UI displays it: the dataset record, or a
1804
+ fallback built from what a mention in the note carries when the dataset has
1805
+ no such record. `id` is `null` when the mention has no record id.
1806
+
1807
+ ```ts
1808
+ type MentionNamePreview = Omit<MentionNameMatch, 'id'> & {
1809
+ id: string | null;
1810
+ };
1811
+ ```
1812
+
1813
+ <a id="api-mentionnametype"></a>
1814
+
1815
+ #### `MentionNameType`
1816
+
1817
+ The set of name "types" carried by biblical name records. Each type maps to a
1818
+ distinct icon so mentions can be told apart at a glance in the editor pills,
1819
+ the typeahead menu, the hover card, and the advanced panel.
1820
+
1821
+ When adding a new type here, add a matching `data-lexical-mention-name-type`
1822
+ mask rule in `AppComp.scss` so the editor pill icon stays in sync, and an
1823
+ entry in `NAME_TYPE_ICON_CLASS` in `plugins/MentionNamePlugin/nameTypeIcons`.
1824
+
1825
+ ```ts
1826
+ type MentionNameType = 'person' | 'concept' | 'place' | 'group' | 'deity' | 'month' | 'supernatural' | 'life' | 'unknown';
1827
+ ```
1828
+
1829
+ <a id="api-mentionnameyearrange"></a>
1830
+
1831
+ #### `MentionNameYearRange`
1832
+
1833
+ A dated span in a person's life, e.g. a reign.
1834
+
1835
+ | Property | Type | Required | Description |
1836
+ | --- | --- | --- | --- |
1837
+ | `type` | `string` | yes | What the span is, e.g. `reign` or `coregency`. |
1838
+ | `start` | `number` | yes | |
1839
+ | `end` | `number` | yes | |
1840
+
1841
+ <a id="api-nameslookupmanagerdatamap"></a>
1842
+
1843
+ #### `NamesLookupManagerDataMap`
1844
+
1845
+ Name datasets keyed by language code, e.g. `{ en: …, km: … }`.
1846
+
1847
+ ```ts
1848
+ type NamesLookupManagerDataMap = {
1849
+ [langCode: string]: NamesLookupManagerLangData;
1850
+ };
1851
+ ```
1852
+
1853
+ <a id="api-nameslookupmanagerlangdata"></a>
1854
+
1855
+ #### `NamesLookupManagerLangData`
1856
+
1857
+ One language's name dataset, as `NamesLookupManager`'s constructor takes it.
1858
+
1859
+ | Property | Type | Required | Description |
1860
+ | --- | --- | --- | --- |
1861
+ | `records` | `ReadonlyArray<MentionNameMatch>` | yes | The normalized records (see `parseMentionNameMatches`). |
1862
+ | `versePersonsMap` | `VerseRecordIdsMap` | no | Short verse key (e.g. `"EXO 6:23"`) to the ids of the names it mentions. |
1863
+
1864
+ <a id="api-nameslookuppaginationoptions"></a>
1865
+
1866
+ #### `NamesLookupPaginationOptions`
1867
+
1868
+ `LookupPaginationOptions` plus a name-type filter.
1869
+
1870
+ ```ts
1871
+ type NamesLookupPaginationOptions = LookupPaginationOptions & {
1872
+ /** Only records of this type. */
1873
+ type?: MentionNameType;
1874
+ };
1875
+ ```
1876
+
1877
+ <a id="api-nameslookupsearchoptions"></a>
1878
+
1879
+ #### `NamesLookupSearchOptions`
1880
+
1881
+ `LookupSearchOptions` plus a name-type filter.
1882
+
1883
+ ```ts
1884
+ type NamesLookupSearchOptions = LookupSearchOptions & {
1885
+ /** Only records of this type; applied before `limit`. */
1886
+ type?: MentionNameType;
1887
+ };
1888
+ ```
1889
+
1890
+ <a id="api-nametypecount"></a>
1891
+
1892
+ #### `NameTypeCount`
1893
+
1894
+ How many records of one name type a dataset holds.
1895
+
1896
+ | Property | Type | Required | Description |
1897
+ | --- | --- | --- | --- |
1898
+ | `type` | `MentionNameType` | yes | |
1899
+ | `count` | `number` | yes | |
1900
+
1901
+ <a id="api-verserecordidsmap"></a>
1902
+
1903
+ #### `VerseRecordIdsMap`
1904
+
1905
+ ```ts
1906
+ type VerseRecordIdsMap = Record<string, ReadonlyArray<string>>;
1907
+ ```
1908
+
1909
+ ### Supporting types
1910
+
1911
+ Not exported, but used by the public signatures above — shown so every shape a host has to produce or consume is spelled out.
1912
+
1913
+ #### `LibraryItems`
1914
+
1915
+ ```ts
1916
+ type LibraryItems = readonly any[];
1917
+ ```
1918
+
1919
+ #### `MentionedBibleDataType`
1920
+
1921
+ A Bible verse as the host renders it: a title line plus the full text.
1922
+
1923
+ | Property | Type | Required | Description |
1924
+ | --- | --- | --- | --- |
1925
+ | `title` | `string` | yes | The verse reference as displayed, e.g. `(KJV) Genesis 1:1`. |
1926
+ | `fullText` | `string` | yes | The title line followed by the verse lines, in the same shape the editor accepts when Bible text is pasted after `^`: `(KJV) Genesis 1:1\n(1): In the beginning…`. |
1927
+ | `style` | `CSSProperties` | no | Extra CSS for this verse's text, e.g. a font for a non-Latin version. |