From 7bf0f1b8170c110af95850bfc4e81c89382b3cb9 Mon Sep 17 00:00:00 2001 From: Ira Hopkinson Date: Sat, 26 Sep 2026 18:51:25 +1200 Subject: [PATCH 1/3] Add EditorOptions.copyLimit to limit how much text can be copied When `copyLimit` is set, `CopyLimitPlugin` limits copying and cutting out of the editor: - A copy or cut over the limit shortens the selection (its start is kept, its end dropped) and copies or cuts exactly what remains, silently. The end never splits a character cluster (including across text nodes), a marker, a token node, a note, or a table, figure or sidebar. In Standard and Markers view the selection keeps shortening until its USFM clipboard form fits, so what is copied or removed is exactly what stays selected. A copy may take less than the limit, possibly nothing. - A limit of 0 copies and cuts nothing and leaves the clipboard unchanged. - A page-wide copy guard shortens browser copies that include editor text but that the editor didn't handle; when several editors guard one copy, the smallest limit wins. - The Select All shortcut is blocked while a limit is set, except in another text field. The macOS Edit menu's Select All is native and not blocked; a copy after it is still shortened. - Under a limit, copies from Formatted view (and Markers-view selections that can't be mapped to USFM) go to the clipboard as plain text. - Undefined leaves copy, cut and Select All unaffected. Character boundaries come from `unicode-segmenter` (`^0.17.3`), a new dependency that is imported, not bundled. Rebuilds the committed platform dist and API report, and adds docs/clipboard-semantics.md. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/clipboard-semantics.md | 6 + .../plugins/usj/OpaqueBlockGuardPlugin.tsx | 18 +- packages/platform/CHANGELOG.md | 11 + packages/platform/dist/index.d.ts | 23 + packages/platform/dist/index.js | 13614 ++++++++-------- packages/platform/dist/index.js.map | 2 +- packages/platform/etc/platform-editor.api.md | 1 + packages/platform/package.json | 3 +- packages/platform/src/editor/Editor.test.tsx | 730 + packages/platform/src/editor/Editor.tsx | 12 +- .../src/editor/MarkersViewCopyPlugin.tsx | 63 +- .../editor/copyLimit/CopyLimitPlugin.test.tsx | 852 + .../src/editor/copyLimit/CopyLimitPlugin.tsx | 236 + .../editor/copyLimit/copyLimit.utils.test.tsx | 513 + .../src/editor/copyLimit/copyLimit.utils.ts | 341 + packages/platform/src/editor/editor.model.ts | 23 + .../editor/markerEdit/MarkerEditPlugin.tsx | 59 +- .../whitespaceDisplay.plugin.utils.test.tsx | 151 + .../whitespaceDisplay.plugin.utils.ts | 107 +- packages/platform/vite.config.mts | 3 + pnpm-lock.yaml | 8 + 21 files changed, 10077 insertions(+), 6699 deletions(-) create mode 100644 packages/platform/src/editor/copyLimit/CopyLimitPlugin.test.tsx create mode 100644 packages/platform/src/editor/copyLimit/CopyLimitPlugin.tsx create mode 100644 packages/platform/src/editor/copyLimit/copyLimit.utils.test.tsx create mode 100644 packages/platform/src/editor/copyLimit/copyLimit.utils.ts diff --git a/docs/clipboard-semantics.md b/docs/clipboard-semantics.md index 8a5bfbf05..8b59f5e27 100644 --- a/docs/clipboard-semantics.md +++ b/docs/clipboard-semantics.md @@ -30,6 +30,8 @@ Already ~true because markers are real text nodes; this plan closes the exceptio - **An opaque construct copies out on ONE line:** the walk's ordinary rule puts a `\n` at every non-inline element boundary it crosses, which would spread a single construct over several lines — a sidebar's nested `\p`, and every row and cell of a table, are real block-level nodes. Pasting that text replays each `\n` as a paragraph split, and Tier 2 re-tokenizes strictly one paragraph at a time, so the `\esb`/`\esbe` and `\tr`/`\th`/`\tc` assembly the fragment tokenizer already implements never saw the whole construct in one pass. `$startsBlockLine` (`whitespaceDisplay.plugin.utils.ts`) therefore suppresses the line break for a block boundary with an opaque ancestor ABOVE it — the construct's own outermost node still starts its own line, so a table stays separated from the prose around it. USFM markers self-delimit, so the one-line form is valid USFM of the same document; it is simply not the line-per-marker layout a USFM writer emits, which is the deliberate trade for a paste that survives. Pinned by the corpus sweep's now-unskipped `"sidebar (esb)"` and `"table with header and cells"` fixtures and by `unknownClipboardFidelity.test.tsx`. A `\periph` peripheral division needs the rule just as much, for the same reason and with nothing periph-specific about it: the division marker carries no closing bytes at all, so what ends its attribute run and what opens its content are the SAME thing — the next marker — and both spellings tokenize identically (pinned in `usfmFragmentToUsj.test.ts`). It is the paste's line splitting, not the byte layout, that decides whether one re-tokenization pass ever sees the division together with the blocks inside it; measured both ways and pinned in `unknownClipboardFidelity.test.tsx`. Tables needed one more thing on the paste side: `usfm.sty` classifies `\th1`/`\tc1`/… as CHARACTER styles, so with a real project stylesheet a cell marker reached the fragment tokenizer as a char token and its table-cell assembly — which only ran for paragraph tokens — never saw it, turning every cell into a loose `char` span inside an empty row. ParatextData derives a cell from the marker NAME, so the assembly now recognizes a cell marker on either token kind, guarded on an open `\tr` row. Pinned sheet-backed (`createMarkerLookup(defaultStyleInfo)`, where the cell markers really are Character) in `usfmFragmentToUsj.test.ts`'s `"cell markers under a real stylesheet"` describe, including the align-infix names (`\thc3` → center, `\thr5` → end) and both guards — a cell-named span with no open row stays an ordinary char span, and a nested `\+tc1` is never a cell. +- **A copy limit caps both readable flavors:** when the host sets `EditorOptions.copyLimit`, a copy or cut over the limit first shortens the selection (`$limitSelectionLength`): the start is kept and the end moves back until the selected text fits, never ending partway through a user-perceived character (a letter and its combining marks, or both halves of a surrogate pair), and keeping a marker glyph, a verse glyph or a token-mode node whole or leaving it out, and likewise a note or an opaque construct (a table, a figure, a sidebar) that the selection runs into; the one a selection starts inside, if any, is not kept whole, so the selection keeps its start. A letter whose marks start the next text node is kept with them too. A node selection (a selected verse marker) is copied, or cut, whole or not at all. **Copies and cuts are fitted to what they write:** the USFM written for a selection can be longer than the selected text (a collapsed note's caller and `\cat` span, a figure the Markers view shows no characters for), so in Standard view (`MarkerEditPlugin`) and the Markers view (`MarkersViewCopyPlugin`) a copy's or cut's selection is shortened further, searching for the longest one whose USFM fits the limit, before anything is written. What stays selected is then exactly what is copied, a copy never ends partway through a marker, and a cut removes exactly what it copies. In the other views the selected text is what is copied. As a backstop for a caller that did not fit the selection, `$writeCopyPayload` fits it itself and rebuilds the caller's full payload from the fitted selection; only if that rebuilt payload still overshoots does it cut `text/plain` to the limit, rebuild `text/html` from the shortened text, and write no `application/x-lexical-editor` flavor. A copy or cut can therefore take less than the limit, possibly nothing. When nothing fits, the copy or cut writes and removes nothing, as at a limit of 0. A cut in a read-only editor removes nothing, so it copies like a copy. Applies in Standard view and the Markers view (S5). Pinned in `Editor.test.tsx` (`options.copyLimit`), `CopyLimitPlugin.test.tsx` and `copyLimit.utils.test.tsx`. + ### S2. Paste (Standard view, internal) — same-namespace `application/x-lexical-editor` payload reconstructs the exact node tree (existing) **Fixed gap (2026-08-13, extended to every kind 2026-09-10), lexical-JSON flavor only:** the "exact node tree" promise did not hold for a selection containing an `UnknownNode` (figure, sidebar, periph, ref, optbreak). `@lexical/clipboard`'s JSON generator (`$appendNodesToJSON`, the function behind the `application/x-lexical-editor` flavor) computes node exclusion from `currentNode.excludeFromCopy('html')` — the literal string `'html'`, hardcoded for EVERY copy-out destination `$appendNodesToJSON` itself handles, not only actual HTML generation (`'clone'` is never passed by any Lexical-shipped code path in the installed version). This change touches ONLY that flavor, and doubly so: Standard view's `text/html` is not a DOM export at all (S1 — it renders the copy walker's own USFM), and even where Lexical's exporter does run, `$appendNodesToHTML` (`@lexical/html`) computes the same `excludeFromCopy('html')` value but returns early on `UnknownNode.exportDOM()`'s unconditional `{element: null}` BEFORE ever consulting it. @@ -105,6 +107,10 @@ Under S3 this is semantically identical to a normal external paste in Standard v - **Copy-out is prose** (no marker text) — existing, gets a gate test protecting Standard-view handlers from leaking there. Their `text/html` is Lexical's own `$generateHtmlFromNodes` export, unchanged by this work, so it drops every `UnknownNode` and `ImmutableNoteCallerNode` subtree — the one-set-of-bytes guarantee (S1) is Standard view's alone. +- **With a copy limit, these views copy plain text only:** when the host sets `EditorOptions.copyLimit`, a copy or cut that no view-specific handler claims (these views, and a Markers-view range that cannot be mapped) is written by `CopyLimitPlugin` as the shortened selection's `text/plain` alone. Lexical's own export would also write `text/html` and `application/x-lexical-editor`, and both can carry text the selection shows no characters for — a footnote's whole text rides behind its caller. The cost is that a limited copy from these views loses its rich HTML, and a paste of it back into the editor is a plain-text paste. Pinned in `CopyLimitPlugin.test.tsx`. + +Unformatted view is not a hidden-marker view, but it has no view-specific clipboard claim either, so whenever a copy limit is set, `CopyLimitPlugin`'s plain-text copy writes its copies and cuts too. + - **The paste gate** (swallow anything containing `\` **or `/`**) is over-broad — it eats URLs, dates, "and/or" (live-confirmed 2026-08-07). **Pre-existing upstream behavior** (`CommandMenuPlugin` on `origin/main`, predates the standard-view branches) — per TJ, NOT fixed here; recorded in the semantics doc's deferred list. Structural apply-markers-on-paste in formatted views likewise out of scope. ### S6. Cut = copy + `removeText()` (existing) diff --git a/libs/shared-react/src/plugins/usj/OpaqueBlockGuardPlugin.tsx b/libs/shared-react/src/plugins/usj/OpaqueBlockGuardPlugin.tsx index 62174cd5b..54c5b6c52 100644 --- a/libs/shared-react/src/plugins/usj/OpaqueBlockGuardPlugin.tsx +++ b/libs/shared-react/src/plugins/usj/OpaqueBlockGuardPlugin.tsx @@ -177,12 +177,18 @@ export function isEditingKey(event: KeyboardEvent): boolean { * lands, this predicate is where it comes back out. */ export function $opaqueBlockAncestor(node: LexicalNode): LexicalNode | undefined { - return ( - $findMatchingParent( - node, - (current) => $isUnknownNode(current) || $isImmutableTableNode(current), - ) ?? undefined - ); + return $findMatchingParent(node, $isOpaqueBlockNode) ?? undefined; +} + +/** + * Read-only: safe inside `editor.getEditorState().read()`, an `editor.update()`, or a command + * handler. + * + * Whether `node` itself is an opaque construct — the test {@link $opaqueBlockAncestor} applies at + * each ancestor, for a caller that already walks the ancestors itself. + */ +export function $isOpaqueBlockNode(node: LexicalNode | null | undefined): boolean { + return $isUnknownNode(node) || $isImmutableTableNode(node); } /** diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md index 48a9b43b3..49fd46604 100644 --- a/packages/platform/CHANGELOG.md +++ b/packages/platform/CHANGELOG.md @@ -26,6 +26,17 @@ refused. The public surface grew substantially; nothing was removed. - `defaultStyleInfo` and the `StyleInfo` / `MarkerStyleInfo` / `StyleType` types. - `EditorOptions.styleInfo`, `EditorOptions.markerSettleDelayMs`, `ViewOptions.showParaMarkerPrefixes`, and the caller/separator fields on `UsjNodeOptions`. +- `EditorOptions.copyLimit`: limit how much text can be copied or cut at once, counted in UTF-16 + code units. A copy or cut over the limit shortens the selection, keeping its start and dropping + its end, then copies or cuts it, so a cut removes exactly what it copies. The end never falls + inside a character or a marker; a note, table, figure or sidebar the selection runs into is kept + whole or left out (one it starts inside keeps its start); and a selected marker is copied or cut + whole or not at all. So a copy or cut may take less than the limit, possibly nothing. While the + limit is set, the Select All keyboard shortcut is blocked unless focus is in another text field. + Select All from the macOS Edit menu is not blocked, but a copy after it is still shortened. `0` + blocks copying and cutting; undefined leaves copying, cutting and Select All unchanged. +- Dependency on `unicode-segmenter` (`^0.17.3`), which keeps a shortened copy on character + boundaries whatever the runtime's own segmenter does. It is imported, not bundled. - **Ctrl+Space removes character formatting from the selection.** On macOS this is ⌃Space rather than ⌘Space, which is Spotlight. It can collide with the macOS input-source switcher and with some IME on/off toggles; the handler declines while a composition is active. diff --git a/packages/platform/dist/index.d.ts b/packages/platform/dist/index.d.ts index f73b7303d..4de2a5bf7 100644 --- a/packages/platform/dist/index.d.ts +++ b/packages/platform/dist/index.d.ts @@ -291,6 +291,29 @@ export declare interface EditorOptions { * Falls back to the bundled default stylesheet data when absent. */ styleInfo?: StyleInfo; + /** + * Maximum amount of text that may be copied or cut from the editor at once, as a non-negative + * integer (a fraction rounds down; a negative or `NaN` value counts as `0`). It is counted in + * UTF-16 code units, as JavaScript's `String.length` counts: most characters count as one, a + * character outside the Basic Multilingual Plane counts as two, and each combining mark counts + * on its own. + * + * When undefined, copying, cutting and the Select All keyboard shortcut behave normally. + * + * When set, a copy or cut over the limit is shortened silently: the selection keeps its start and + * loses its end, then is copied or cut, so a cut removes exactly what it copies. The end never + * falls partway through a character, between a letter and its marks, or inside a marker, and a + * note, table, figure or sidebar the selection runs into is kept whole or left out (one the + * selection starts inside keeps its start). A selected marker is copied or cut whole, or not at + * all. Where the copied text is longer than the text shown (Standard view and the Markers view + * copy USFM), the selection is shortened until its copied text fits. So a copy or cut may take + * less than the limit, possibly nothing. In a read-only editor a cut removes nothing and copies + * like a copy. The Select All keyboard shortcut is blocked on the page unless focus is in another + * text field. Select All from the macOS Edit menu is a native menu command and is not blocked, + * but a copy after it is still shortened. `0` blocks all three: nothing can be copied or cut, and + * the Select All shortcut is blocked. + */ + copyLimit?: number; /** * EXPERIMENTAL: Delay in milliseconds before pending marker edits settle in place while the * user is idle (Paratext 9's debounced-reformat cadence), in editable marker modes. Defaults diff --git a/packages/platform/dist/index.js b/packages/platform/dist/index.js index aac3a371a..81e583cca 100644 --- a/packages/platform/dist/index.js +++ b/packages/platform/dist/index.js @@ -1,34 +1,35 @@ -import { jsx as C, jsxs as Te, Fragment as gn } from "react/jsx-runtime"; -import { forwardRef as En, useState as de, useRef as Z, useCallback as he, useEffect as z, useMemo as ze, memo as ey, createContext as gf, useContext as mf, Children as ty, isValidElement as ry, cloneElement as ny, useImperativeHandle as gc, useLayoutEffect as cs } from "react"; -import { assertSafeKey as He, isValidBookCode as iy, MARKER_OBJECT_PROPS as sy, USJ_VERSION as br, USJ_TYPE as kr, isUsjTextContentLocation as oy, indexesFromUsjJsonPath as yf, isUsjAttributeKeyLocation as ay, isUsjAttributeMarkerLocation as cy, isUsjClosingAttributeMarkerLocation as ly, isUsjMarkerLocation as uy, isUsjClosingMarkerLocation as dy, isUsjPropertyValueLocation as fy, getUsjDocumentLocationTypeName as py, usjJsonPathFromIndexes as on, EMPTY_USJ as bf } from "@eten-tech-foundation/scripture-utilities"; -import { $applyNodeReplacement as je, $parseSerializedNode as ao, DecoratorNode as ls, ElementNode as Zt, isHTMLElement as An, createState as co, $getState as ne, $setState as bt, $isRangeSelection as N, $isElementNode as F, $isTextNode as v, $getSelection as w, $isNodeSelection as mc, ParagraphNode as yc, TextNode as Ke, $createTextNode as pe, $getCommonAncestor as hy, $isLineBreakNode as us, NODE_STATE_KEY as ds, $getEditor as Zn, $hasUpdateTag as gy, $getNodeByKey as se, $getRoot as Fe, $createRangeSelection as bc, $createPoint as su, $getCharacterOffsets as kc, KEY_DOWN_COMMAND as vr, COMMAND_PRIORITY_HIGH as Ne, HISTORY_MERGE_TAG as kf, CLICK_COMMAND as lo, COMMAND_PRIORITY_EDITOR as mn, isDOMNode as Tf, $getNearestNodeFromDOMNode as li, CONTROLLED_TEXT_INSERTION_COMMAND as Tc, PASTE_COMMAND as yr, COMMAND_PRIORITY_CRITICAL as Wt, CUT_COMMAND as yn, DROP_COMMAND as xc, DELETE_CHARACTER_COMMAND as my, DELETE_WORD_COMMAND as yy, DELETE_LINE_COMMAND as by, $isDecoratorNode as uo, COPY_COMMAND as fo, COMMAND_PRIORITY_LOW as Tt, COMMAND_PRIORITY_NORMAL as Jn, SELECTION_CHANGE_COMMAND as ur, getDOMSelection as ky, isSelectionWithinEditor as Ty, $createRangeSelectionFromDom as xy, $setSelection as ei, isDOMTextNode as _y, BLUR_COMMAND as _c, $addUpdateTag as jr, SKIP_DOM_SELECTION_TAG as Cy, CLEAR_HISTORY_COMMAND as Sy, $getPreviousSelection as vy, $isRootOrShadowRoot as My, CAN_UNDO_COMMAND as Ey, CAN_REDO_COMMAND as Ay, DRAGSTART_COMMAND as Py, $createNodeSelection as xf, getDOMSelectionFromTarget as Ny, $onUpdate as Oy, KEY_ENTER_COMMAND as _f, LineBreakNode as Cf, $copyNode as wy, FOCUS_COMMAND as qy, $isRootNode as Ry, KEY_ESCAPE_COMMAND as Sf, INSERT_PARAGRAPH_COMMAND as Rs, createCommand as vf, HISTORIC_TAG as Cc, createEditor as $y, UNDO_COMMAND as Mf, REDO_COMMAND as Ef, CLEAR_EDITOR_COMMAND as Iy } from "lexical"; -import { addClassNamesToElement as Un, removeClassNamesFromElement as Ho, $findMatchingParent as nt, $dfsIterator as Af, $dfs as ui, mergeRegister as Ve, registerNestedElementResolver as Pf, $unwrapNode as _a, IS_APPLE as $s } from "@lexical/utils"; -import { useLexicalNodeSelection as Ly } from "@lexical/react/useLexicalNodeSelection"; -import { deepEqual as wt } from "fast-equals"; -import qi from "quill-delta"; +import { jsx as v, jsxs as xe, Fragment as bn } from "react/jsx-runtime"; +import { forwardRef as Nn, useState as fe, useRef as Q, useCallback as ge, useEffect as F, useMemo as je, memo as py, createContext as Ef, useContext as Af, Children as hy, isValidElement as gy, cloneElement as my, useImperativeHandle as _c, useLayoutEffect as ps } from "react"; +import { assertSafeKey as Ge, isValidBookCode as yy, MARKER_OBJECT_PROPS as by, USJ_VERSION as Cr, USJ_TYPE as vr, isUsjTextContentLocation as ky, indexesFromUsjJsonPath as Pf, isUsjAttributeKeyLocation as Ty, isUsjAttributeMarkerLocation as xy, isUsjClosingAttributeMarkerLocation as _y, isUsjMarkerLocation as Cy, isUsjClosingMarkerLocation as vy, isUsjPropertyValueLocation as Sy, getUsjDocumentLocationTypeName as My, usjJsonPathFromIndexes as ln, EMPTY_USJ as Nf } from "@eten-tech-foundation/scripture-utilities"; +import { $applyNodeReplacement as Ve, $parseSerializedNode as po, DecoratorNode as hs, ElementNode as Zt, isHTMLElement as On, createState as ho, $getState as ie, $setState as kt, $isRangeSelection as P, $isElementNode as L, $isTextNode as S, $getSelection as w, $isNodeSelection as go, ParagraphNode as Cc, TextNode as Be, $createTextNode as he, $getCommonAncestor as Ey, $isLineBreakNode as di, NODE_STATE_KEY as gs, $getEditor as ti, $hasUpdateTag as Ay, $getNodeByKey as oe, $getRoot as Ke, $createRangeSelection as vc, $createPoint as pu, $getCharacterOffsets as Sc, KEY_DOWN_COMMAND as Nr, COMMAND_PRIORITY_HIGH as qe, HISTORY_MERGE_TAG as Of, CLICK_COMMAND as mo, COMMAND_PRIORITY_EDITOR as kn, isDOMNode as wf, $getNearestNodeFromDOMNode as fi, CONTROLLED_TEXT_INSERTION_COMMAND as Mc, PASTE_COMMAND as _r, COMMAND_PRIORITY_CRITICAL as tt, CUT_COMMAND as Wt, DROP_COMMAND as Ec, DELETE_CHARACTER_COMMAND as Py, DELETE_WORD_COMMAND as Ny, DELETE_LINE_COMMAND as Oy, $isDecoratorNode as ms, COPY_COMMAND as Hr, COMMAND_PRIORITY_LOW as xt, COMMAND_PRIORITY_NORMAL as Wr, SELECTION_CHANGE_COMMAND as dr, getDOMSelection as wy, isSelectionWithinEditor as qy, $createRangeSelectionFromDom as Ry, $setSelection as Tn, isDOMTextNode as $y, BLUR_COMMAND as Ac, $addUpdateTag as Gr, SKIP_DOM_SELECTION_TAG as Ly, CLEAR_HISTORY_COMMAND as Iy, $getPreviousSelection as Dy, $isRootOrShadowRoot as Uy, CAN_UNDO_COMMAND as Fy, CAN_REDO_COMMAND as zy, DRAGSTART_COMMAND as Ky, $createNodeSelection as qf, getDOMSelectionFromTarget as jy, $onUpdate as By, KEY_ENTER_COMMAND as Rf, LineBreakNode as $f, $copyNode as Vy, FOCUS_COMMAND as Wy, INSERT_PARAGRAPH_COMMAND as Us, SELECT_ALL_COMMAND as Hy, isExactShortcutMatch as Gy, $isRootNode as Jy, KEY_ESCAPE_COMMAND as Lf, createCommand as If, HISTORIC_TAG as Pc, createEditor as Yy, UNDO_COMMAND as Df, REDO_COMMAND as Uf, CLEAR_EDITOR_COMMAND as Xy } from "lexical"; +import { addClassNamesToElement as Kn, removeClassNamesFromElement as Qo, $findMatchingParent as rt, $dfsIterator as Ff, $dfs as pi, mergeRegister as Fe, registerNestedElementResolver as zf, $unwrapNode as Aa, IS_APPLE as ri } from "@lexical/utils"; +import { useLexicalNodeSelection as Qy } from "@lexical/react/useLexicalNodeSelection"; +import { deepEqual as qt } from "fast-equals"; +import Ii from "quill-delta"; import { useLexicalComposerContext as ce } from "@lexical/react/LexicalComposerContext"; -import { $getLexicalContent as Dy, copyToClipboard as Uy } from "@lexical/clipboard"; -import { TreeView as Fy } from "@lexical/react/LexicalTreeView"; -import * as zy from "react-dom"; -import { createPortal as hn } from "react-dom"; -import { LexicalComposer as Nf } from "@lexical/react/LexicalComposer"; -import { ContentEditable as Of } from "@lexical/react/LexicalContentEditable"; -import { EditorRefPlugin as wf } from "@lexical/react/LexicalEditorRefPlugin"; -import { LexicalErrorBoundary as qf } from "@lexical/react/LexicalErrorBoundary"; -import { HistoryPlugin as Rf } from "@lexical/react/LexicalHistoryPlugin"; -import { RichTextPlugin as Ky } from "@lexical/react/LexicalRichTextPlugin"; -import { $setBlocksType as jy, createDOMRange as By, createRectsFromDOMRange as Vy } from "@lexical/selection"; -import { autoUpdate as Wy, computePosition as Hy, shift as Gy, flip as Jy } from "@floating-ui/dom"; -import { $generateNodesFromDOM as Yy } from "@lexical/html"; -import { AutoFocusPlugin as Xy } from "@lexical/react/LexicalAutoFocusPlugin"; -import { ClearEditorPlugin as Qy } from "@lexical/react/LexicalClearEditorPlugin"; -import { useCollaborationContext as $f, LexicalCollaboration as Zy } from "@lexical/react/LexicalCollaborationContext"; -import { OnChangePlugin as eb } from "@lexical/react/LexicalOnChangePlugin"; -import { PlainTextPlugin as tb } from "@lexical/react/LexicalPlainTextPlugin"; -import { $rootTextContent as rb, $isRootTextContentEmpty as nb } from "@lexical/text"; -import { TOGGLE_CONNECT_COMMAND as ib } from "@lexical/yjs"; -import { Array as ou, Map as au, YArrayEvent as sb } from "yjs"; -const Go = (e) => je(ao(e)), ob = { +import { graphemeSegments as Zy } from "unicode-segmenter/grapheme"; +import { copyToClipboard as eb, $getLexicalContent as tb } from "@lexical/clipboard"; +import { TreeView as rb } from "@lexical/react/LexicalTreeView"; +import * as nb from "react-dom"; +import { createPortal as yn } from "react-dom"; +import { LexicalComposer as Kf } from "@lexical/react/LexicalComposer"; +import { ContentEditable as jf } from "@lexical/react/LexicalContentEditable"; +import { EditorRefPlugin as Bf } from "@lexical/react/LexicalEditorRefPlugin"; +import { LexicalErrorBoundary as Vf } from "@lexical/react/LexicalErrorBoundary"; +import { HistoryPlugin as Wf } from "@lexical/react/LexicalHistoryPlugin"; +import { RichTextPlugin as ib } from "@lexical/react/LexicalRichTextPlugin"; +import { $setBlocksType as sb, createDOMRange as ob, createRectsFromDOMRange as ab } from "@lexical/selection"; +import { autoUpdate as cb, computePosition as lb, shift as ub, flip as db } from "@floating-ui/dom"; +import { $generateNodesFromDOM as fb } from "@lexical/html"; +import { AutoFocusPlugin as pb } from "@lexical/react/LexicalAutoFocusPlugin"; +import { ClearEditorPlugin as hb } from "@lexical/react/LexicalClearEditorPlugin"; +import { useCollaborationContext as Hf, LexicalCollaboration as gb } from "@lexical/react/LexicalCollaborationContext"; +import { OnChangePlugin as mb } from "@lexical/react/LexicalOnChangePlugin"; +import { PlainTextPlugin as yb } from "@lexical/react/LexicalPlainTextPlugin"; +import { $rootTextContent as bb, $isRootTextContentEmpty as kb } from "@lexical/text"; +import { TOGGLE_CONNECT_COMMAND as Tb } from "@lexical/yjs"; +import { Array as hu, Map as gu, YArrayEvent as xb } from "yjs"; +const Zo = (e) => Ve(po(e)), _b = { c: ["number"], ef: ["caller"], efe: ["caller"], @@ -39,16 +40,16 @@ const Go = (e) => je(ao(e)), ob = { v: ["number"], x: ["caller"] }; -function If(e) { - return ob[e]; -} -const I = " ", Is = "​", Dt = I, Sc = `${I}|`, cr = "p", Bi = "+", Lf = "-", Ls = "chapter", Ca = "verse", cu = "invalid", ab = "text-spacing", cb = "formatted-font", lb = "marker-", Df = "external-usj-mutation", Uf = "selection-change", Br = "cursor-change", Sa = "annotation-change", Vi = "delta-change", Ff = "marker-settle", ub = [ - Df, - Uf, - Br, - Sa, - Vi -], bn = "zmsc-s", Yn = "zmsc-e", db = [bn, Yn], fb = [ +function Gf(e) { + return _b[e]; +} +const R = " ", Fs = "​", Ut = R, Nc = `${R}|`, lr = "p", Gi = "+", Jf = "-", zs = "chapter", Pa = "verse", mu = "invalid", Cb = "text-spacing", vb = "formatted-font", Sb = "marker-", Yf = "external-usj-mutation", Xf = "selection-change", Jr = "cursor-change", Na = "annotation-change", Ji = "delta-change", Qf = "marker-settle", Mb = [ + Yf, + Xf, + Jr, + Na, + Ji +], xn = "zmsc-s", Qn = "zmsc-e", Eb = [xn, Qn], Ab = [ "ts-s", "ts-e", "t-s", @@ -67,16 +68,16 @@ const I = " ", Is = "​", Dt = I, Sc = `${I}|`, cr = "p", Bi = "+", Lf = "-", "qt-s", "qt-e", // custom markers used for annotations - bn, - Yn -], zf = 1, vc = [ + xn, + Qn +], Zf = 1, Oc = [ "type", "marker", "sid", "eid", "content" -], pb = vc.filter((e) => e !== "sid" && e !== "eid"); -class Jt extends ls { +], Pb = Oc.filter((e) => e !== "sid" && e !== "eid"); +class Jt extends hs { __marker; __sid; __eid; @@ -98,10 +99,10 @@ class Jt extends ls { return new Jt(r, n, i, s, a, o); } static importJSON(t) { - return jf().updateFromJSON(t); + return tp().updateFromJSON(t); } static isValidMarker(t, r) { - return t !== void 0 && (fb.includes(t) || t.startsWith("z") || (r?.includes(t) ?? !1)); + return t !== void 0 && (Ab.includes(t) || t.startsWith("z") || (r?.includes(t) ?? !1)); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setSid(t.sid).setEid(t.eid).setUnknownAttributes(t.unknownAttributes).setAttributeOrder(t.attributeOrder); @@ -185,7 +186,7 @@ class Jt extends ls { eid: this.getEid(), unknownAttributes: this.getUnknownAttributes(), attributeOrder: this.getAttributeOrder(), - version: zf + version: Zf }; } // Mutation @@ -193,18 +194,18 @@ class Jt extends ls { return !1; } } -function Kf(e) { - return db.includes(e); +function ep(e) { + return Eb.includes(e); } -function jf(e, t, r, n, i) { - return je(new Jt(e, t, r, n, void 0, i)); +function tp(e, t, r, n, i) { + return Ve(new Jt(e, t, r, n, void 0, i)); } -function Be(e) { +function He(e) { return e instanceof Jt; } -const Mc = "f", hb = [ +const wc = "f", Nb = [ // Footnote - Mc, + wc, "fe", "ef", "efe", @@ -212,45 +213,45 @@ const Mc = "f", hb = [ "x", "ex" ]; -function Ri(e) { +function Di(e) { return e.startsWith("f") || e.startsWith("ef") ? "footnote" : "crossref"; } -const gb = [ +const Ob = [ "type", "marker", "caller", "category", "content" -], Bf = 1; -class Ee extends Zt { +], rp = 1; +class Pe extends Zt { __marker; __caller; __isCollapsed; __category; __unknownAttributes; - constructor(t = Mc, r, n = !0, i, s, o) { - super(o), this.__marker = t, this.__caller = r ?? (Ri(t) === "crossref" ? Lf : Bi), this.__isCollapsed = n, this.__category = i, this.__unknownAttributes = s; + constructor(t = wc, r, n = !0, i, s, o) { + super(o), this.__marker = t, this.__caller = r ?? (Di(t) === "crossref" ? Jf : Gi), this.__isCollapsed = n, this.__category = i, this.__unknownAttributes = s; } static getType() { return "note"; } static clone(t) { const { __marker: r, __caller: n, __isCollapsed: i, __category: s, __unknownAttributes: o, __key: a } = t; - return new Ee(r, n, i, s, o, a); + return new Pe(r, n, i, s, o, a); } static importDOM() { return { - span: (t) => yb(t) ? { - conversion: mb, + span: (t) => qb(t) ? { + conversion: wb, priority: 1 } : null }; } static importJSON(t) { - return Ec().updateFromJSON(t); + return qc().updateFromJSON(t); } static isValidMarker(t, r) { - return t !== void 0 && (hb.includes(t) || (r?.includes(t) ?? !1)); + return t !== void 0 && (Nb.includes(t) || (r?.includes(t) ?? !1)); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setCaller(t.caller).setIsCollapsed(t.isCollapsed).setCategory(t.category).setUnknownAttributes(t.unknownAttributes); @@ -304,14 +305,14 @@ class Ee extends Zt { } createDOM() { const t = document.createElement("span"); - return t.setAttribute("data-marker", this.__marker), t.classList.add(this.__type, `usfm_${this.__marker}`, this.__isCollapsed ? "collapsed" : "expanded"), t.setAttribute("data-caller", this.__caller), t.setAttribute("data-note-kind", Ri(this.__marker)), t; + return t.setAttribute("data-marker", this.__marker), t.classList.add(this.__type, `usfm_${this.__marker}`, this.__isCollapsed ? "collapsed" : "expanded"), t.setAttribute("data-caller", this.__caller), t.setAttribute("data-note-kind", Di(this.__marker)), t; } updateDOM(t, r) { - return t.__isCollapsed !== this.__isCollapsed ? !0 : (t.__marker !== this.__marker && (r.setAttribute("data-marker", this.__marker), r.classList.remove(`usfm_${t.__marker}`), r.classList.add(`usfm_${this.__marker}`), r.setAttribute("data-note-kind", Ri(this.__marker))), t.__caller !== this.__caller && r.setAttribute("data-caller", this.__caller), !1); + return t.__isCollapsed !== this.__isCollapsed ? !0 : (t.__marker !== this.__marker && (r.setAttribute("data-marker", this.__marker), r.classList.remove(`usfm_${t.__marker}`), r.classList.add(`usfm_${this.__marker}`), r.setAttribute("data-note-kind", Di(this.__marker))), t.__caller !== this.__caller && r.setAttribute("data-caller", this.__caller), !1); } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`, this.getIsCollapsed() ? "collapsed" : "expanded"), r.setAttribute("data-caller", this.getCaller()), r.setAttribute("data-note-kind", Ri(this.getMarker()))), { element: r }; + return r && On(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`, this.getIsCollapsed() ? "collapsed" : "expanded"), r.setAttribute("data-caller", this.getCaller()), r.setAttribute("data-note-kind", Di(this.getMarker()))), { element: r }; } exportJSON() { return { @@ -322,7 +323,7 @@ class Ee extends Zt { isCollapsed: this.getIsCollapsed(), category: this.getCategory(), unknownAttributes: this.getUnknownAttributes(), - version: Bf + version: rp }; } // Mutation @@ -333,21 +334,21 @@ class Ee extends Zt { return !0; } } -function mb(e) { +function wb(e) { const t = e.getAttribute("data-marker") ?? "f", r = e.getAttribute("data-caller") ?? "", n = e.classList.contains("collapsed"); - return { node: Ec(t, r, n) }; + return { node: qc(t, r, n) }; } -function Ec(e, t, r, n, i) { - return je(new Ee(e, t, r, n, i)); +function qc(e, t, r, n, i) { + return Ve(new Pe(e, t, r, n, i)); } -function yb(e) { +function qb(e) { if (!e) return !1; const t = e.getAttribute("data-marker") ?? ""; - return Ee.isValidMarker(t) && e.classList.contains(Ee.getType()); + return Pe.isValidMarker(t) && e.classList.contains(Pe.getType()); } -function j(e) { - return e instanceof Ee; +function K(e) { + return e instanceof Pe; } var T; (function(e) { @@ -357,7 +358,7 @@ var b; (function(e) { e.Paragraph = "Paragraph", e.Character = "Character", e.Note = "Note", e.Milestone = "Milestone", e.Unknown = "Unknown"; })(b || (b = {})); -const va = { +const Oa = { id: { category: T.FileIdentification, type: b.Paragraph, @@ -3267,7 +3268,7 @@ const va = { hasEndMarker: !1, children: void 0 } -}, an = { +}, un = { DivisionMarks: { add: ["v", "c"], remove: [] }, Paragraphs: { add: ["p"], remove: [] }, Poetry: { add: ["q", "q1", "q2", "q3", "q4", "b"], remove: [] }, @@ -3294,14 +3295,14 @@ const va = { ], remove: [] } -}, lu = { - p: { children: an }, - q: { children: an }, - q1: { children: an }, - q2: { children: an }, - q3: { children: an }, - q4: { children: an }, - b: { children: an }, +}, yu = { + p: { children: un }, + q: { children: un }, + q1: { children: un }, + q2: { children: un }, + q3: { children: un }, + q4: { children: un }, + b: { children: un }, qm: { children: { Paragraphs: { add: ["p"], remove: [] } @@ -3347,8 +3348,8 @@ const va = { hasEndMarker: !0 } }; -function lr(e) { - const t = Object.hasOwn(va, e) ? va[e] : void 0, r = Object.hasOwn(lu, e) ? lu[e] : void 0; +function ur(e) { + const t = Object.hasOwn(Oa, e) ? Oa[e] : void 0, r = Object.hasOwn(yu, e) ? yu[e] : void 0; if (!t) return r?.category !== void 0 && r.type !== void 0 && r.description !== void 0 && r.hasEndMarker !== void 0 ? { ...r } : void 0; if (!r) @@ -3373,16 +3374,16 @@ function lr(e) { children: n }; } -const Vf = "v", Wf = "c", cn = "fig", uu = "tr", Ma = "esb", Hf = "esbe", du = "periph", fu = "alt", pu = /^t[hc]([rc]?)(\d+)(?:-(\d+))?$/, bb = { +const np = "v", ip = "c", dn = "fig", bu = "tr", wa = "esb", sp = "esbe", ku = "periph", Tu = "alt", xu = /^t[hc]([rc]?)(\d+)(?:-(\d+))?$/, Rb = { "": "start", c: "center", r: "end" }; -function hu(e) { +function _u(e) { const [, , t, r] = e; return r ? /^[1-5]$/.test(t) && /^[2-5]$/.test(r) && Number(r) > Number(t) : /^(?:[1-9]|1[0-2])$/.test(t); } -function gu(e) { +function Cu(e) { const t = e.charCodeAt(0); return t >= 9 && t <= 13 ? !0 : t === 32 || // SPACE t === 133 || // NEXT LINE @@ -3395,24 +3396,24 @@ function gu(e) { t === 8287 || // MEDIUM MATHEMATICAL SPACE t === 8203; } -const kb = /[\u200D\u2003\u2002\u0020\u00A0\u202F\u2009\u200A\u3000\u200B\u200C\u2060\u200E\u200F]/; -function Tb(e) { +const $b = /[\u200D\u2003\u2002\u0020\u00A0\u202F\u2009\u200A\u3000\u200B\u200C\u2060\u200E\u200F]/; +function Lb(e) { let t = "", r = !1, n = "\0", i = -1; for (let s = 0; s < e.length; s += 1) { const o = e[s]; - o.charCodeAt(0) < 32 ? (r || (i = t.length, t += " "), r = !0) : !r && o === Is && s + 1 < e.length && gu(e[s + 1]) || (gu(o) ? (r || (i = t.length, t += o), r = !0) : kb.test(o) && o === n || (t += o, r = !1, i = -1)), (o === ` + o.charCodeAt(0) < 32 ? (r || (i = t.length, t += " "), r = !0) : !r && o === Fs && s + 1 < e.length && Cu(e[s + 1]) || (Cu(o) ? (r || (i = t.length, t += o), r = !0) : $b.test(o) && o === n || (t += o, r = !1, i = -1)), (o === ` ` || o === "\r") && i >= 0 && (t = `${t.slice(0, i)} ${t.slice(i + 1)}`), n = o; } return t; } -function xb(e) { +function Ib(e) { if (e.length === 0) return !0; const t = e[0]; return t === "*" ? !1 : !!(t === "\\" || t === "|" || /[\s\u200B]/.test(t)); } -function _b(e, t) { +function Db(e, t) { let r = t; for (; r < e.length; ) { const n = e[r]; @@ -3428,11 +3429,11 @@ function _b(e, t) { } return { name: e.slice(t, r), next: r }; } -const Cb = /^(?:qt[1-5]?|ts)-[se]$/; -function Ac(e) { - return Cb.test(e) || Kf(e); +const Ub = /^(?:qt[1-5]?|ts)-[se]$/; +function Rc(e) { + return Ub.test(e) || ep(e); } -function Jo(e, t) { +function ea(e, t) { let r = t; for (; r < e.length && /[\s\u00A0\u200B]/.test(e[r]); ) r++; @@ -3444,7 +3445,7 @@ function Jo(e, t) { r++; return { word: i, next: r }; } -function Sb(e, t, r) { +function Fb(e, t, r) { const n = []; let i = 0, s; const o = (c) => { @@ -3453,18 +3454,18 @@ function Sb(e, t, r) { const l = n[n.length - 1]; l?.kind === "text" ? l.text += c : n.push({ kind: "text", text: c }); }, a = (c) => { - c.split("//").forEach((u, d) => { - d > 0 && n.push({ kind: "optbreak" }), o(u); + c.split("//").forEach((d, u) => { + u > 0 && n.push({ kind: "optbreak" }), o(d); }); }; for (; i < e.length; ) { if (e[i] !== "\\") { - const g = e.indexOf("\\", i), y = g === -1 ? e.length : g; - a(Tb(e.slice(i, y))), i = y; + const h = e.indexOf("\\", i), y = h === -1 ? e.length : h; + a(Lb(e.slice(i, y))), i = y; continue; } - const c = i, { name: l, next: u } = _b(e, i + 1); - if (i = u, l === "") { + const c = i, { name: l, next: d } = Db(e, i + 1); + if (i = d, l === "") { o(e.slice(c, i)); continue; } @@ -3476,64 +3477,64 @@ function Sb(e, t, r) { l.slice(0, -1) === s && (s = void 0), n.push({ kind: "end", marker: l.slice(0, -1) }); continue; } - const d = () => { + const u = () => { for (; i < e.length && /[\s\u00A0\u200B]/.test(e[i]); ) i++; }; - if (l === Vf) { - const { word: g, next: y } = Jo(e, i); - i = y, n.push({ kind: "verse", number: g }); + if (l === np) { + const { word: h, next: y } = ea(e, i); + i = y, n.push({ kind: "verse", number: h }); continue; } - if (l === Wf) { - const { word: g, next: y } = Jo(e, i); - i = y, s = void 0, n.push({ kind: "chapter", number: g }); + if (l === ip) { + const { word: h, next: y } = ea(e, i); + i = y, s = void 0, n.push({ kind: "chapter", number: h }); continue; } - const f = l.startsWith("+"), p = f ? l.slice(1) : l, m = t(p)?.type; - if (m === b.Note || m === void 0 && Ee.isValidMarker(l)) { - const { word: g, next: y } = Jo(e, i); - i = y, s = l, n.push({ kind: "note", marker: l, caller: g || "+" }); + const p = l.startsWith("+"), f = p ? l.slice(1) : l, g = t(f)?.type; + if (g === b.Note || g === void 0 && Pe.isValidMarker(l)) { + const { word: h, next: y } = ea(e, i); + i = y, s = l, n.push({ kind: "note", marker: l, caller: h || "+" }); continue; } - if (m === b.Milestone || m === void 0 && Ac(l)) { - const g = wb(e, c, l, i); - if (g) - n.push(g.token), g.ejectedText && o(g.ejectedText), i = g.next; + if (g === b.Milestone || g === void 0 && Rc(l)) { + const h = Gb(e, c, l, i); + if (h) + n.push(h.token), h.ejectedText && o(h.ejectedText), i = h.next; else { const y = e.indexOf("\\", i), k = y === -1 ? e.length : y; o(e.slice(c, k)), i = k; } continue; } - m === b.Paragraph ? (d(), n.push({ kind: "para", marker: l })) : m === b.Character ? (d(), n.push({ kind: "charOpen", marker: p, isNested: f })) : Ds(p) ? (d(), Ds(p)?.shape === "para" ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f })) : (d(), !(r || s !== void 0) || l === Ma || l === Hf ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f })); + g === b.Paragraph ? (u(), n.push({ kind: "para", marker: l })) : g === b.Character ? (u(), n.push({ kind: "charOpen", marker: f, isNested: p })) : Ks(f) ? (u(), Ks(f)?.shape === "para" ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: f, isNested: p })) : (u(), !(r || s !== void 0) || l === wa || l === sp ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: f, isNested: p })); } return n; } -const mu = { +const vu = { ca: { attrName: "altnumber", targetTypes: ["chapter"], shape: "char" }, cp: { attrName: "pubnumber", targetTypes: ["chapter"], shape: "para" }, va: { attrName: "altnumber", targetTypes: ["verse"], shape: "char" }, vp: { attrName: "pubnumber", targetTypes: ["verse"], shape: "char" }, cat: { attrName: "category", targetTypes: ["note", "sidebar"], shape: "char" } }; -function Ds(e) { - return Object.hasOwn(mu, e) ? mu[e] : void 0; +function Ks(e) { + return Object.hasOwn(vu, e) ? vu[e] : void 0; } -function vb(e) { - return Ds(e) !== void 0; +function zb(e) { + return Ks(e) !== void 0; } -const Mb = /([-\w]+)\s*=\s*"(.*?)"/g, Eb = /[\s\u200B]*[\n\r][\s\u200B]*/g, Gf = { +const Kb = /([-\w]+)\s*=\s*"(.*?)"/g, jb = /[\s\u200B]*[\n\r][\s\u200B]*/g, op = { w: "lemma", rb: "gloss", xt: "link-href", jmp: "link-href" }; -function po(e) { - return Gf[e]; +function yo(e) { + return op[e]; } -const Ab = /* @__PURE__ */ new Set(["type", "marker", "content"]); -function Pb(e, t) { +const Bb = /* @__PURE__ */ new Set(["type", "marker", "content"]); +function Vb(e, t) { let r = 0; for (const n of t) { const i = n.index; @@ -3543,26 +3544,26 @@ function Pb(e, t) { } return e.slice(r).trim() === ""; } -function Wi(e, t, r = Gf[t]) { - const n = e.replace(Eb, " "), i = /* @__PURE__ */ Object.create(null), s = [...n.matchAll(Mb)]; +function Yi(e, t, r = op[t]) { + const n = e.replace(jb, " "), i = /* @__PURE__ */ Object.create(null), s = [...n.matchAll(Kb)]; if (s.length > 0) { - if (!Pb(n, s) || s.some((o) => o[2] === "")) + if (!Vb(n, s) || s.some((o) => o[2] === "")) return; for (const [, o, a] of s) - Ab.has(o) || (i[o] = a); + Bb.has(o) || (i[o] = a); return Object.keys(i).length > 0 ? i : void 0; } if (n.trim() && r) return { [r]: n }; } -function ho(e) { +function bo(e) { return e.endsWith("-e") ? "eid" : e.startsWith("qt") ? "who" : "sid"; } -function Nb(e) { - const t = Mr(e)[0], r = typeof t == "object" && "content" in t ? t.content : void 0; +function Wb(e) { + const t = Or(e)[0], r = typeof t == "object" && "content" in t ? t.content : void 0; return r ? r.some((n, i) => typeof n != "object" || n.type !== "ms" || typeof r[i + 1] != "string" ? !1 : r.slice(i + 2).some((s) => typeof s != "string" && s.type === "unmatched" && s.marker === "*")) : !1; } -function Ob(e, t, r) { +function Hb(e, t, r) { let n = t; for (; n < e.length && /[\s\u00A0\u200B]/.test(e[n]); ) n++; @@ -3571,22 +3572,22 @@ function Ob(e, t, r) { const i = e.indexOf("\\", n); if (i === -1 || e.slice(i, i + 2) !== "\\*") return; - const s = Wi(e.slice(n + 1, i), r, ho(r)); + const s = Yi(e.slice(n + 1, i), r, bo(r)); if (s) return { attributes: s, next: i + 2 }; } -function wb(e, t, r, n) { +function Gb(e, t, r, n) { const i = e.indexOf("\\", n); if (i === -1 || e.slice(i, i + 2) !== "\\*") return; const s = e.slice(n, i), o = s.indexOf("|"); let a; - if (o >= 0 && (a = Wi(s.slice(o + 1), r, ho(r)), !a && s.slice(o + 1).trim() !== "")) { - const u = s.slice(0, o); + if (o >= 0 && (a = Yi(s.slice(o + 1), r, bo(r)), !a && s.slice(o + 1).trim() !== "")) { + const d = s.slice(0, o); return { token: { kind: "milestone", marker: r }, next: n + o, - ejectedText: u.trim() !== "" ? u.replace(/^[ \u00A0]/, "") : void 0 + ejectedText: d.trim() !== "" ? d.replace(/^[ \u00A0]/, "") : void 0 }; } const c = o >= 0 ? s.slice(0, o) : s; @@ -3596,7 +3597,7 @@ function wb(e, t, r, n) { next: i, ejectedText: c.replace(/^[ \u00A0]/, "") }; - const l = Ob(e, i + 2, r); + const l = Hb(e, i + 2, r); return l ? { token: { kind: "milestone", @@ -3606,301 +3607,301 @@ function wb(e, t, r, n) { next: l.next } : { token: { kind: "milestone", marker: r, attributes: a }, next: i + 2 }; } -function or(e) { +function ar(e) { return e.replaceAll(` -`, " ").replaceAll("~", I); +`, " ").replaceAll("~", R); } -function Ir(e) { +function Ur(e) { return e.content || (e.content = []), e.content; } -function Mr(e, t) { +function Or(e, t) { const r = [], n = t?.isNoteContext ?? !1; let i, s; const o = []; - let a = 0, c, l, u, d; - const f = () => u ? Ir(u) : d ? Ir(d) : r; - let p = !1; - const m = () => { + let a = 0, c, l, d, u; + const p = () => d ? Ur(d) : u ? Ur(u) : r; + let f = !1; + const g = () => { if (s) - return o.length > a ? Ir(o[o.length - 1].object) : Ir(s); + return o.length > a ? Ur(o[o.length - 1].object) : Ur(s); if (o.length > 0) - return Ir(o[o.length - 1].object); + return Ur(o[o.length - 1].object); if (!i) { - if (p && !n) - return f(); - i = { type: "para", marker: cr, content: [] }, f().push(i); - } - return Ir(i); - }, g = (te) => { - const E = m(); - typeof te == "string" && typeof E[E.length - 1] == "string" ? E[E.length - 1] = E[E.length - 1] + te : E.push(te); - }, y = (te) => { - for (let E = te; E < o.length; E += 1) { - const Y = o[E].object; - Y.closed = "false"; + if (f && !n) + return p(); + i = { type: "para", marker: lr, content: [] }, p().push(i); + } + return Ur(i); + }, h = (ne) => { + const A = g(); + typeof ne == "string" && typeof A[A.length - 1] == "string" ? A[A.length - 1] = A[A.length - 1] + ne : A.push(ne); + }, y = (ne) => { + for (let A = ne; A < o.length; A += 1) { + const G = o[A].object; + G.closed = "false"; } }, k = () => { y(0), o.length = 0; - }, _ = (te) => { - s && (o.length > a && (y(a), o.length = a), a = 0, te || (s.closed = "false"), s = void 0); - }, S = () => { + }, _ = (ne) => { + s && (o.length > a && (y(a), o.length = a), a = 0, ne || (s.closed = "false"), s = void 0); + }, C = () => { c = void 0, l = void 0; - }, P = (te, E, Y) => { + }, M = (ne, A, G) => { k(); - const [, le, W, xe] = Y, ht = { + const [, de, Ee, ee] = G, Se = { type: "table:cell", - marker: xe ? E.slice(0, E.indexOf("-")) : E, - align: bb[le], + marker: ee ? A.slice(0, A.indexOf("-")) : A, + align: Rb[de], content: [] }; - xe && (ht.colspan = String(Number(xe) + 1 - Number(W))), Ir(te).push(ht), i = ht; - }, A = (te) => { - u && (te || (u.closed = "false"), u = void 0); - }, B = () => { - d = void 0; + ee && (Se.colspan = String(Number(ee) + 1 - Number(Ee))), Ur(ne).push(Se), i = Se; + }, E = (ne) => { + d && (ne || (d.closed = "false"), d = void 0); + }, z = () => { + u = void 0; }; - let M, q = "", $; - const J = () => { - q && g(or(q)), q = ""; - }, Q = (te = !1) => { - M?.type === "sidebar" ? q = "" : te && q.endsWith(` -`) && (q = q.slice(0, -1)), M = void 0, J(); - }, Me = () => { - if (!$) + let W, j = "", U; + const N = () => { + j && h(ar(j)), j = ""; + }, B = (ne = !1) => { + W?.type === "sidebar" ? j = "" : ne && j.endsWith(` +`) && (j = j.slice(0, -1)), W = void 0, N(); + }, re = () => { + if (!U) return; - const te = { type: "char", marker: $.marker, content: [] }; - $.value && (te.content = [or($.value)]), m().push(te), o.push({ object: te }), $ = void 0; - }, re = (te, E) => { - p = !1, S(), k(), _(!1), i = { type: "para", marker: te, content: [] }, E && (i.content = [or(E)]), f().push(i); - }, qe = () => { - $ && (re($.marker, $.value), $ = void 0); + const ne = { type: "char", marker: U.marker, content: [] }; + U.value && (ne.content = [ar(U.value)]), g().push(ne), o.push({ object: ne }), U = void 0; + }, X = (ne, A) => { + f = !1, C(), k(), _(!1), i = { type: "para", marker: ne, content: [] }, A && (i.content = [ar(A)]), p().push(i); + }, ye = () => { + U && (X(U.marker, U.value), U = void 0); }; - let be; - const tr = (te) => { - if (!be) + let ke; + const tr = (ne) => { + if (!ke) return; - let { value: E } = be; - be = void 0, te && E.endsWith(` -`) && (E = E.slice(0, -1)); - const Y = E.indexOf("|"), le = Y >= 0 ? Wi(E.slice(Y + 1), du) : void 0, W = Y >= 0 ? E.slice(0, Y) : E, xe = Y >= 0 && (!le || !!W && !!le[fu]), ht = xe ? void 0 : le, zt = xe ? E : W, gt = { + let { value: A } = ke; + ke = void 0, ne && A.endsWith(` +`) && (A = A.slice(0, -1)); + const G = A.indexOf("|"), de = G >= 0 ? Yi(A.slice(G + 1), ku) : void 0, Ee = G >= 0 ? A.slice(0, G) : A, ee = G >= 0 && (!de || !!Ee && !!de[Tu]), Se = ee ? void 0 : de, yr = ee ? A : Ee, wt = { type: "periph", - ...zt ? { [fu]: or(zt) } : {}, - ...ht + ...yr ? { [Tu]: ar(yr) } : {}, + ...Se }; - gt.content = [], f().push(gt), d = gt, i = void 0; + wt.content = [], p().push(wt), u = wt, i = void 0; }; - let Re; - const en = () => { - if (Re) { - if (Re.shape === "para") - re(cn, Re.value); + let $e; + const sn = () => { + if ($e) { + if ($e.shape === "para") + X(dn, $e.value); else { - const te = { type: "char", marker: cn, content: [] }; - Re.value && (te.content = [or(Re.value)]), m().push(te), o.push({ object: te }); + const ne = { type: "char", marker: dn, content: [] }; + $e.value && (ne.content = [ar($e.value)]), g().push(ne), o.push({ object: ne }); } - Re = void 0; - } - }, gr = Sb(e, t?.getMarker ?? lr, n); - for (let te = 0; te < gr.length; te++) { - const E = gr[te]; - if ($) { - if (E.kind === "text") { - $.value += E.text; + $e = void 0; + } + }, mr = Fb(e, t?.getMarker ?? ur, n); + for (let ne = 0; ne < mr.length; ne++) { + const A = mr[ne]; + if (U) { + if (A.kind === "text") { + U.value += A.text; continue; } - if ($.shape === "char" && E.kind === "end" && E.marker.replace(/^\+/, "") === $.marker) { - if ($.value.trim() === "") { - m().push({ type: "char", marker: $.marker, content: [] }), $ = void 0, Q(); + if (U.shape === "char" && A.kind === "end" && A.marker.replace(/^\+/, "") === U.marker) { + if (U.value.trim() === "") { + g().push({ type: "char", marker: U.marker, content: [] }), U = void 0, B(); continue; } - Object.assign($.target, { - [$.attrName]: or($.value.trim()) + Object.assign(U.target, { + [U.attrName]: ar(U.value.trim()) }); - const Y = $.marker; - if ($ = void 0, Y === "ca") { - const le = gr[te + 1]; - le?.kind === "text" && /^[\s\u200B]*$/.test(le.text) && te++; + const G = U.marker; + if (U = void 0, G === "ca") { + const de = mr[ne + 1]; + de?.kind === "text" && /^[\s\u200B]*$/.test(de.text) && ne++; } continue; } - if ($.shape === "para" && (E.kind === "para" || E.kind === "chapter")) { - const Y = $.value.replace(/[\s\u200B]+$/, ""); - Y === "" ? (re($.marker), $ = void 0) : (Object.assign($.target, { [$.attrName]: or(Y) }), $ = void 0); + if (U.shape === "para" && (A.kind === "para" || A.kind === "chapter")) { + const G = U.value.replace(/[\s\u200B]+$/, ""); + G === "" ? (X(U.marker), U = void 0) : (Object.assign(U.target, { [U.attrName]: ar(G) }), U = void 0); } else { - M = void 0, (E.kind === "para" || E.kind === "chapter") && $.value.endsWith(` -`) && ($.value = $.value.slice(0, -1)), $.shape === "para" ? qe() : Me(), te--; + W = void 0, (A.kind === "para" || A.kind === "chapter") && U.value.endsWith(` +`) && (U.value = U.value.slice(0, -1)), U.shape === "para" ? ye() : re(), ne--; continue; } } - if (be) { - if (E.kind === "text" || E.kind === "optbreak") { - be.value += E.kind === "text" ? E.text : "//"; + if (ke) { + if (A.kind === "text" || A.kind === "optbreak") { + ke.value += A.kind === "text" ? A.text : "//"; continue; } - tr(E.kind === "para" || E.kind === "chapter"), te--; + tr(A.kind === "para" || A.kind === "chapter"), ne--; continue; } - if (Re) { - if (E.kind === "text" || E.kind === "optbreak") { - Re.value += E.kind === "text" ? E.text : "//"; + if ($e) { + if (A.kind === "text" || A.kind === "optbreak") { + $e.value += A.kind === "text" ? A.text : "//"; continue; } - if (E.kind === "end" && E.marker.replace(/^\+/, "") === cn) { - const Y = Re.value.indexOf("|"), le = Y >= 0 ? Wi(Re.value.slice(Y + 1), cn) : void 0; - if (le) { - const W = {}; - for (const [zt, gt] of Object.entries(le)) - W[zt === "src" ? "file" : zt] = gt; - const xe = { + if (A.kind === "end" && A.marker.replace(/^\+/, "") === dn) { + const G = $e.value.indexOf("|"), de = G >= 0 ? Yi($e.value.slice(G + 1), dn) : void 0; + if (de) { + const Ee = {}; + for (const [yr, wt] of Object.entries(de)) + Ee[yr === "src" ? "file" : yr] = wt; + const ee = { type: "figure", - marker: cn, - ...W - }, ht = Re.value.slice(0, Y); - ht && (xe.content = [or(ht)]), g(xe), Re = void 0; + marker: dn, + ...Ee + }, Se = $e.value.slice(0, G); + Se && (ee.content = [ar(Se)]), h(ee), $e = void 0; continue; } } - en(), te--; + sn(), ne--; continue; } - if (M) - if (E.kind === "text") { - if (E.text.includes(` -`) && /^[\s\u200B]*$/.test(E.text)) { - q += E.text; + if (W) + if (A.kind === "text") { + if (A.text.includes(` +`) && /^[\s\u200B]*$/.test(A.text)) { + j += A.text; continue; } - Q(); - } else if (E.kind === "charOpen" || E.kind === "para") { - const Y = E.kind === "para" || !E.isNested ? Ds(E.marker) : void 0; - if (Y && Y.targetTypes.includes(M.type)) { - q = "", $ = { - target: M, - attrName: Y.attrName, - marker: E.marker, - shape: Y.shape, + B(); + } else if (A.kind === "charOpen" || A.kind === "para") { + const G = A.kind === "para" || !A.isNested ? Ks(A.marker) : void 0; + if (G && G.targetTypes.includes(W.type)) { + j = "", U = { + target: W, + attrName: G.attrName, + marker: A.marker, + shape: G.shape, value: "" }; continue; } - Q(E.kind === "para"); + B(A.kind === "para"); } else - Q(E.kind === "chapter"); - if (!s && !n && (E.kind === "charOpen" && !E.isNested && E.marker === cn || E.kind === "para" && E.marker === cn)) { - k(), Re = { shape: E.kind === "charOpen" ? "char" : "para", value: "" }; + B(A.kind === "chapter"); + if (!s && !n && (A.kind === "charOpen" && !A.isNested && A.marker === dn || A.kind === "para" && A.marker === dn)) { + k(), $e = { shape: A.kind === "charOpen" ? "char" : "para", value: "" }; continue; } - switch (E.kind) { + switch (A.kind) { case "text": { - let Y = E.text; - if (!s && Y.endsWith(` + let G = A.text; + if (!s && G.endsWith(` `)) { - const le = gr[te + 1]; - (le === void 0 || le.kind === "para" || le.kind === "chapter") && (Y = Y.slice(0, -1)); + const de = mr[ne + 1]; + (de === void 0 || de.kind === "para" || de.kind === "chapter") && (G = G.slice(0, -1)); } - Y && g(or(Y)); + G && h(ar(G)); break; } case "para": { - const Y = !s && !n; - if (Y && E.marker === uu) { - k(), c || (c = { type: "table", content: [] }, f().push(c)), l = { type: "table:row", marker: uu, content: [] }, Ir(c).push(l), i = l, p = !1; + const G = !s && !n; + if (G && A.marker === bu) { + k(), c || (c = { type: "table", content: [] }, p().push(c)), l = { type: "table:row", marker: bu, content: [] }, Ur(c).push(l), i = l, f = !1; break; } - if (Y && l) { - const le = pu.exec(E.marker); - if (le && hu(le)) { - P(l, E.marker, le); + if (G && l) { + const de = xu.exec(A.marker); + if (de && _u(de)) { + M(l, A.marker, de); break; } } - if (S(), !n && E.marker === Ma) { - k(), _(!1), A(!1); - const le = { + if (C(), !n && A.marker === wa) { + k(), _(!1), E(!1); + const de = { type: "sidebar", - marker: Ma, + marker: wa, content: [] }; - f().push(le), u = le, i = void 0, M = u, p = !1; + p().push(de), d = de, i = void 0, W = d, f = !1; break; } - if (E.marker === Hf && u) { - k(), _(!1), A(!0), i = void 0; + if (A.marker === sp && d) { + k(), _(!1), E(!0), i = void 0; break; } - if (!n && E.marker === du) { - k(), _(!1), A(!1), B(), be = { value: "" }, i = void 0, p = !1; + if (!n && A.marker === ku) { + k(), _(!1), E(!1), z(), ke = { value: "" }, i = void 0, f = !1; break; } - re(E.marker); + X(A.marker); break; } case "verse": { _(!1); - const Y = { type: "verse", marker: Vf, number: E.number }; - g(Y), M = Y; + const G = { type: "verse", marker: np, number: A.number }; + h(G), W = G; break; } case "chapter": { - k(), _(!1), S(), A(!1), B(), i = void 0; - const Y = { + k(), _(!1), C(), E(!1), z(), i = void 0; + const G = { type: "chapter", - marker: Wf, - number: E.number + marker: ip, + number: A.number }; - r.push(Y), M = Y, p = !0; + r.push(G), W = G, f = !0; break; } case "note": { _(!1); - const Y = m(); - s = { type: "note", marker: E.marker, caller: E.caller, content: [] }, a = o.length, Y.push(s), M = s; + const G = g(); + s = { type: "note", marker: A.marker, caller: A.caller, content: [] }, a = o.length, G.push(s), W = s; break; } case "charOpen": { - if (!s && !n && l && !E.isNested) { - const W = pu.exec(E.marker); - if (W && hu(W)) { - P(l, E.marker, W); + if (!s && !n && l && !A.isNested) { + const Ee = xu.exec(A.marker); + if (Ee && _u(Ee)) { + M(l, A.marker, Ee); break; } } - if (!E.isNested) { - const W = s ? a : 0; - y(W), o.length = W; + if (!A.isNested) { + const Ee = s ? a : 0; + y(Ee), o.length = Ee; } - const Y = m(), le = { type: "char", marker: E.marker, content: [] }; - Y.push(le), o.push({ object: le }); + const G = g(), de = { type: "char", marker: A.marker, content: [] }; + G.push(de), o.push({ object: de }); break; } case "end": { - const Y = E.marker.replace(/^\+/, ""), le = s ? a : 0, W = o.findLastIndex((xe, ht) => ht >= le && xe.object.marker === Y); - W >= 0 ? (qb(o[W].object), y(W + 1), o.length = W) : s && s.marker === Y ? _(!0) : (y(le), o.length = le, g({ type: "unmatched", marker: `${E.marker}*` })); + const G = A.marker.replace(/^\+/, ""), de = s ? a : 0, Ee = o.findLastIndex((ee, Se) => Se >= de && ee.object.marker === G); + Ee >= 0 ? (Jb(o[Ee].object), y(Ee + 1), o.length = Ee) : s && s.marker === G ? _(!0) : (y(de), o.length = de, h({ type: "unmatched", marker: `${A.marker}*` })); break; } case "milestone": - g({ type: "ms", marker: E.marker, ...E.attributes }); + h({ type: "ms", marker: A.marker, ...A.attributes }); break; case "optbreak": - g({ type: "optbreak" }); + h({ type: "optbreak" }); break; } } - if (be && tr(!0), Re && en(), $) - if ($.shape === "para") { - const te = $.value.replace(/[\s\u200B]+$/, ""); - te === "" ? re($.marker) : Object.assign($.target, { [$.attrName]: or(te) }), $ = void 0; + if (ke && tr(!0), $e && sn(), U) + if (U.shape === "para") { + const ne = U.value.replace(/[\s\u200B]+$/, ""); + ne === "" ? X(U.marker) : Object.assign(U.target, { [U.attrName]: ar(ne) }), U = void 0; } else - $.value.endsWith(` -`) && ($.value = $.value.slice(0, -1)), Me(); - k(), _(!1), A(!1); - const vt = (te) => { - for (const E of te) - typeof E != "string" && E.content && (vt(E.content), E.content.length === 0 && delete E.content); + U.value.endsWith(` +`) && (U.value = U.value.slice(0, -1)), re(); + k(), _(!1), E(!1); + const Mt = (ne) => { + for (const A of ne) + typeof A != "string" && A.content && (Mt(A.content), A.content.length === 0 && delete A.content); }; - return vt(r), r; + return Mt(r), r; } -function qb(e) { +function Jb(e) { const t = e.content; if (!t || t.length === 0) return; @@ -3914,22 +3915,22 @@ function qb(e) { const n = t.findIndex((c, l) => l >= r && typeof c == "string" && c.includes("|")); if (n < 0) return; - const i = t.slice(n).map((c) => typeof c == "string" ? c : "//").join(""), s = i.indexOf("|"), o = Wi(i.slice(s + 1), e.marker ?? ""); + const i = t.slice(n).map((c) => typeof c == "string" ? c : "//").join(""), s = i.indexOf("|"), o = Yi(i.slice(s + 1), e.marker ?? ""); if (!o) return; const a = i.slice(0, s); t.length = n, a && t.push(a), Object.assign(e, o); } -const kn = co("cid", { +const _n = ho("cid", { parse: (e) => typeof e == "string" ? e : void 0 -}), Vr = co("segment", { +}), Yr = ho("segment", { parse: (e) => typeof e == "string" ? e : void 0 -}), oe = co("textType", { +}), ae = ho("textType", { parse: (e) => typeof e == "string" ? e : void 0 -}), fr = "marker-trailing-space", Jf = 1, Rb = "marker", Pc = co("isGutterMarker", { +}), pr = "marker-trailing-space", ap = 1, Yb = "marker", $c = ho("isGutterMarker", { parse: (e) => e === !0 }); -class Er extends ls { +class wr extends hs { __textType; __text; constructor(t = "", r = "", n) { @@ -3940,18 +3941,18 @@ class Er extends ls { } static clone(t) { const { __textType: r, __text: n, __key: i } = t; - return new Er(r, n, i); + return new wr(r, n, i); } static importDOM() { return { - span: (t) => Db(t) ? { - conversion: $b, + span: (t) => ek(t) ? { + conversion: Xb, priority: 1 } : null }; } static importJSON(t) { - return Tr().updateFromJSON(t); + return Sr().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setTextType(t.textType).setTextContent(t.text); @@ -3983,7 +3984,7 @@ class Er extends ls { } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && r.setAttribute("data-text-type", this.getTextType()), { element: r }; + return r && On(r) && r.setAttribute("data-text-type", this.getTextType()), { element: r }; } /** * No decorator payload: the glyph bytes are rendered by {@link createDOM} instead. @@ -4019,7 +4020,7 @@ class Er extends ls { type: this.getType(), textType: this.getTextType(), text: this.getTextContent(), - version: Jf + version: ap }; } // Mutation @@ -4027,30 +4028,30 @@ class Er extends ls { return !1; } } -function $b(e) { +function Xb(e) { const t = e.getAttribute("data-text-type") ?? "", r = e.textContent ?? ""; - return { node: Tr(t, r) }; + return { node: Sr(t, r) }; } -function Tr(e, t) { - return je(new Er(e, t)); +function Sr(e, t) { + return Ve(new wr(e, t)); } -function Ib(e) { - return bt(Tr(Rb, e), Pc, !0); +function Qb(e) { + return kt(Sr(Yb, e), $c, !0); } -function Lb(e) { - return Yt(e) && ne(e, Pc); +function Zb(e) { + return Yt(e) && ie(e, $c); } -function Db(e) { +function ek(e) { return e?.tagName === "span"; } function Yt(e) { - return e instanceof Er; + return e instanceof wr; } -function Yf(e) { - return e?.type === Er.getType(); +function cp(e) { + return e?.type === wr.getType(); } -const Kr = "internal-comment", Ub = [Kr], Xf = Object.freeze({}), Ea = Object.freeze({}), Aa = Object.freeze({}), Pa = Object.freeze({}), Na = Object.freeze({}), Fb = 1, Fn = /* @__PURE__ */ new Map(), vi = /* @__PURE__ */ new Map(), zn = /* @__PURE__ */ new Map(), Kn = /* @__PURE__ */ new Map(); -class Qe extends Zt { +const Vr = "internal-comment", tk = [Vr], lp = Object.freeze({}), qa = Object.freeze({}), Ra = Object.freeze({}), $a = Object.freeze({}), La = Object.freeze({}), rk = 1, jn = /* @__PURE__ */ new Map(), Pi = /* @__PURE__ */ new Map(), Bn = /* @__PURE__ */ new Map(), Vn = /* @__PURE__ */ new Map(); +class et extends Zt { __typedIDs; __typedOnClicks; __typedOnRemoves; @@ -4060,39 +4061,39 @@ class Qe extends Zt { __domOnMouseEnterListener; __domOnMouseLeaveListener; __suppressOnRemoveCallbacks; - constructor(t = Xf, r, n, i, s, o) { - super(o), this.__typedIDs = Ss(t), this.__typedOnClicks = Yo(r), this.__typedOnRemoves = Xo(n), this.__typedOnMouseEnters = Qo(i), this.__typedOnMouseLeaves = Zo(s), this.pruneTypedOnClicks(), this.pruneTypedOnRemoves(), this.pruneTypedOnMouseEnters(), this.pruneTypedOnMouseLeaves(), this.syncTypedOnClicksToRegistry(), this.syncTypedOnRemovesToRegistry(), this.syncTypedOnMouseEntersToRegistry(), this.syncTypedOnMouseLeavesToRegistry(); + constructor(t = lp, r, n, i, s, o) { + super(o), this.__typedIDs = Ps(t), this.__typedOnClicks = ta(r), this.__typedOnRemoves = ra(n), this.__typedOnMouseEnters = na(i), this.__typedOnMouseLeaves = ia(s), this.pruneTypedOnClicks(), this.pruneTypedOnRemoves(), this.pruneTypedOnMouseEnters(), this.pruneTypedOnMouseLeaves(), this.syncTypedOnClicksToRegistry(), this.syncTypedOnRemovesToRegistry(), this.syncTypedOnMouseEntersToRegistry(), this.syncTypedOnMouseLeavesToRegistry(); } static getType() { return "typed-mark"; } static clone(t) { - const r = Ss(t.__typedIDs), n = Yo(t.__typedOnClicks), i = Xo(t.__typedOnRemoves), s = Qo(t.__typedOnMouseEnters), o = Zo(t.__typedOnMouseLeaves); - return new Qe(r, n, i, s, o, t.__key); + const r = Ps(t.__typedIDs), n = ta(t.__typedOnClicks), i = ra(t.__typedOnRemoves), s = na(t.__typedOnMouseEnters), o = ia(t.__typedOnMouseLeaves); + return new et(r, n, i, s, o, t.__key); } static isReservedType(t) { - return Ub.includes(t); + return tk.includes(t); } static importDOM() { return null; } static importJSON(t) { - return Hi().updateFromJSON(t); + return Xi().updateFromJSON(t); } exportJSON() { return { ...super.exportJSON(), type: this.getType(), typedIDs: this.getTypedIDs(), - version: Fb + version: rk }; } createDOM(t, r) { const n = document.createElement("mark"); for (const [a, c] of Object.entries(this.__typedIDs)) { - Un(n, ln(t.theme.typedMark, a)), c.length > 1 && Un(n, ln(t.theme.typedMarkOverlap, a)); + Kn(n, fn(t.theme.typedMark, a)), c.length > 1 && Kn(n, fn(t.theme.typedMarkOverlap, a)); for (const l of c) - Un(n, ln("annotationId", l)); + Kn(n, fn("annotationId", l)); } const i = this.getOrCreateDOMClickListener(r); n.addEventListener("click", i); @@ -4107,13 +4108,13 @@ class Qe extends Zt { ...Object.keys(this.__typedIDs ?? {}) ]); for (const s of i) { - const o = t.__typedIDs[s] ?? [], a = this.__typedIDs[s] ?? [], c = o.length, l = a.length, u = ln(n.theme.typedMark, s), d = ln(n.theme.typedMarkOverlap, s); - c !== l && (c === 0 ? l === 1 && Un(r, u) : l === 0 && Ho(r, u), c === 1 ? l === 2 && Un(r, d) : l === 1 && Ho(r, d)); - const f = new Set(o), p = new Set(a); - for (const m of o) - p.has(m) || Ho(r, ln("annotationId", m)); - for (const m of a) - f.has(m) || Un(r, ln("annotationId", m)); + const o = t.__typedIDs[s] ?? [], a = this.__typedIDs[s] ?? [], c = o.length, l = a.length, d = fn(n.theme.typedMark, s), u = fn(n.theme.typedMarkOverlap, s); + c !== l && (c === 0 ? l === 1 && Kn(r, d) : l === 0 && Qo(r, d), c === 1 ? l === 2 && Kn(r, u) : l === 1 && Qo(r, u)); + const p = new Set(o), f = new Set(a); + for (const g of o) + f.has(g) || Qo(r, fn("annotationId", g)); + for (const g of a) + p.has(g) || Kn(r, fn("annotationId", g)); } return !1; } @@ -4134,48 +4135,48 @@ class Qe extends Zt { return Ce(t) ? t.__typedIDs : {}; } setTypedIDs(t) { - const r = this.getWritable(), n = Ss(r.__typedIDs); - r.__typedIDs = Ss(t), r.dispatchRemovedIDs(n, r.__typedIDs, "removed"), r.pruneTypedOnClicks(), r.pruneTypedOnRemoves(), r.pruneTypedOnMouseEnters(), r.pruneTypedOnMouseLeaves(), r.syncTypedOnClicksToRegistry(), r.syncTypedOnRemovesToRegistry(), r.syncTypedOnMouseEntersToRegistry(), r.syncTypedOnMouseLeavesToRegistry(); + const r = this.getWritable(), n = Ps(r.__typedIDs); + r.__typedIDs = Ps(t), r.dispatchRemovedIDs(n, r.__typedIDs, "removed"), r.pruneTypedOnClicks(), r.pruneTypedOnRemoves(), r.pruneTypedOnMouseEnters(), r.pruneTypedOnMouseLeaves(), r.syncTypedOnClicksToRegistry(), r.syncTypedOnRemovesToRegistry(), r.syncTypedOnMouseEntersToRegistry(), r.syncTypedOnMouseLeavesToRegistry(); const i = r.mergeWithAdjacentTypedMarks(); - return i.hasNoIDsForEveryType() && i.getParent() !== null && Us(i), i; + return i.hasNoIDsForEveryType() && i.getParent() !== null && js(i), i; } setTypedOnClicks(t) { const r = this.getWritable(); - return r.__typedOnClicks = Yo(t), r.pruneTypedOnClicks(), r.syncTypedOnClicksToRegistry(), r; + return r.__typedOnClicks = ta(t), r.pruneTypedOnClicks(), r.syncTypedOnClicksToRegistry(), r; } getTypedOnClicks() { const t = this.getLatest(); - return Ce(t) ? Fn.get(t.getKey()) ?? {} : {}; + return Ce(t) ? jn.get(t.getKey()) ?? {} : {}; } setTypedOnRemoves(t) { const r = this.getWritable(); - return r.__typedOnRemoves = Xo(t), r.pruneTypedOnRemoves(), r.syncTypedOnRemovesToRegistry(), r; + return r.__typedOnRemoves = ra(t), r.pruneTypedOnRemoves(), r.syncTypedOnRemovesToRegistry(), r; } getTypedOnRemoves() { const t = this.getLatest(); - return Ce(t) ? vi.get(t.getKey()) ?? {} : {}; + return Ce(t) ? Pi.get(t.getKey()) ?? {} : {}; } setTypedOnMouseEnters(t) { const r = this.getWritable(); - return r.__typedOnMouseEnters = Qo(t), r.pruneTypedOnMouseEnters(), r.syncTypedOnMouseEntersToRegistry(), r; + return r.__typedOnMouseEnters = na(t), r.pruneTypedOnMouseEnters(), r.syncTypedOnMouseEntersToRegistry(), r; } getTypedOnMouseEnters() { const t = this.getLatest(); - return Ce(t) ? zn.get(t.getKey()) ?? {} : {}; + return Ce(t) ? Bn.get(t.getKey()) ?? {} : {}; } setTypedOnMouseLeaves(t) { const r = this.getWritable(); - return r.__typedOnMouseLeaves = Zo(t), r.pruneTypedOnMouseLeaves(), r.syncTypedOnMouseLeavesToRegistry(), r; + return r.__typedOnMouseLeaves = ia(t), r.pruneTypedOnMouseLeaves(), r.syncTypedOnMouseLeavesToRegistry(), r; } getTypedOnMouseLeaves() { const t = this.getLatest(); - return Ce(t) ? Kn.get(t.getKey()) ?? {} : {}; + return Ce(t) ? Vn.get(t.getKey()) ?? {} : {}; } addID(t, r, n, i, s, o) { const a = this.getWritable(); if (!Ce(a)) return; - He(t), He(r); + Ge(t), Ge(r); let c = a.__typedIDs[t]; c || (c = [], a.__typedIDs[t] = c); for (const l of c) @@ -4199,13 +4200,13 @@ class Qe extends Zt { } n.removeOnClickFor(t, r), n.removeOnRemoveFor(t, r), n.removeOnMouseEnterFor(t, r), n.removeOnMouseLeaveFor(t, r), n.pruneTypedOnClicks(), n.pruneTypedOnRemoves(), n.pruneTypedOnMouseEnters(), n.pruneTypedOnMouseLeaves(); const s = n.mergeWithAdjacentTypedMarks(); - s.hasNoIDsForEveryType() && s.getParent() !== null && Us(s); + s.hasNoIDsForEveryType() && s.getParent() !== null && js(s); } hasNoIDsForEveryType() { return Object.values(this.getTypedIDs()).every((t) => t === void 0 || t.length === 0); } insertNewAfter(t, r = !0) { - const n = Hi(this.__typedIDs, this.getTypedOnClicks()); + const n = Xi(this.__typedIDs, this.getTypedOnClicks()); return this.insertAfter(n, r), n; } canInsertTextBefore() { @@ -4221,7 +4222,7 @@ class Qe extends Zt { return !0; } extractWithChild(t, r, n) { - if (!N(r) || n === "html") + if (!P(r) || n === "html") return !1; const i = r.anchor, s = r.focus, o = i.getNode(), a = s.getNode(), l = r.isBackward() ? i.offset - s.offset : s.offset - i.offset; return this.isParentOf(o) && this.isParentOf(a) && this.getTextContent().length === l; @@ -4231,7 +4232,7 @@ class Qe extends Zt { } remove(t) { const r = this.getWritable(), n = this.getTypedIDs(); - r.__suppressOnRemoveCallbacks ? r.__suppressOnRemoveCallbacks = void 0 : r.dispatchOnRemoveForTypedIDs(n, "destroyed"), Fn.delete(r.getKey()), vi.delete(r.getKey()), zn.delete(r.getKey()), Kn.delete(r.getKey()), r.__typedOnClicks = void 0, r.__typedOnRemoves = void 0, r.__typedOnMouseEnters = void 0, r.__typedOnMouseLeaves = void 0, super.remove.call(r, t); + r.__suppressOnRemoveCallbacks ? r.__suppressOnRemoveCallbacks = void 0 : r.dispatchOnRemoveForTypedIDs(n, "destroyed"), jn.delete(r.getKey()), Pi.delete(r.getKey()), Bn.delete(r.getKey()), Vn.delete(r.getKey()), r.__typedOnClicks = void 0, r.__typedOnRemoves = void 0, r.__typedOnMouseEnters = void 0, r.__typedOnMouseLeaves = void 0, super.remove.call(r, t); } getOrCreateDOMClickListener(t) { return this.__domOnClickListener || (this.__domOnClickListener = (r) => { @@ -4239,7 +4240,7 @@ class Qe extends Zt { }), this.__domOnClickListener; } handleDOMClick(t, r) { - const n = Fn.get(this.getKey()); + const n = jn.get(this.getKey()); if (!n) return; const i = []; @@ -4258,7 +4259,7 @@ class Qe extends Zt { }), this.__domOnMouseEnterListener; } handleDOMMouseEnter(t, r) { - const n = zn.get(this.getKey()); + const n = Bn.get(this.getKey()); if (!n) return; const i = []; @@ -4277,7 +4278,7 @@ class Qe extends Zt { }), this.__domOnMouseLeaveListener; } handleDOMMouseLeave(t, r) { - const n = Kn.get(this.getKey()); + const n = Vn.get(this.getKey()); if (!n) return; const i = []; @@ -4291,21 +4292,21 @@ class Qe extends Zt { o(t, a, c, s); } ensureOnClickMapMutable() { - if (this.__typedOnClicks === void 0 || this.__typedOnClicks === Ea) { - const t = Fn.get(this.getKey()); + if (this.__typedOnClicks === void 0 || this.__typedOnClicks === qa) { + const t = jn.get(this.getKey()); this.__typedOnClicks = t ?? {}; } return this.__typedOnClicks; } syncTypedOnClicksToRegistry() { if (!this.__typedOnClicks || Object.keys(this.__typedOnClicks).length === 0) { - Fn.delete(this.getKey()), this.__typedOnClicks && Object.keys(this.__typedOnClicks).length === 0 && (this.__typedOnClicks = void 0); + jn.delete(this.getKey()), this.__typedOnClicks && Object.keys(this.__typedOnClicks).length === 0 && (this.__typedOnClicks = void 0); return; } - Fn.set(this.getKey(), this.__typedOnClicks); + jn.set(this.getKey(), this.__typedOnClicks); } setOnClickFor(t, r, n) { - He(t), He(r); + Ge(t), Ge(r); const i = this.ensureOnClickMapMutable(), s = i[t] ?? (i[t] = {}); s[r] = n, this.syncTypedOnClicksToRegistry(); } @@ -4315,20 +4316,20 @@ class Qe extends Zt { const n = this.__typedOnClicks[t]; if (!n) return; - const i = Lr(n, r); + const i = Fr(n, r); if (Object.keys(i).length > 0) this.__typedOnClicks = { ...this.__typedOnClicks, [t]: i }; else { - const o = Lr(this.__typedOnClicks, t); + const o = Fr(this.__typedOnClicks, t); this.__typedOnClicks = Object.keys(o).length > 0 ? o : void 0; } this.syncTypedOnClicksToRegistry(); } pruneTypedOnClicks() { - if (!this.__typedOnClicks || this.__typedOnClicks === Ea) { + if (!this.__typedOnClicks || this.__typedOnClicks === qa) { this.__typedOnClicks = void 0, this.syncTypedOnClicksToRegistry(); return; } @@ -4345,21 +4346,21 @@ class Qe extends Zt { this.__typedOnClicks = Object.keys(t).length > 0 ? t : void 0, this.syncTypedOnClicksToRegistry(); } ensureOnRemoveMapMutable() { - if (this.__typedOnRemoves === void 0 || this.__typedOnRemoves === Aa) { - const t = vi.get(this.getKey()); + if (this.__typedOnRemoves === void 0 || this.__typedOnRemoves === Ra) { + const t = Pi.get(this.getKey()); this.__typedOnRemoves = t ?? {}; } return this.__typedOnRemoves; } syncTypedOnRemovesToRegistry() { if (!this.__typedOnRemoves || Object.keys(this.__typedOnRemoves).length === 0) { - vi.delete(this.getKey()), this.__typedOnRemoves && Object.keys(this.__typedOnRemoves).length === 0 && (this.__typedOnRemoves = void 0); + Pi.delete(this.getKey()), this.__typedOnRemoves && Object.keys(this.__typedOnRemoves).length === 0 && (this.__typedOnRemoves = void 0); return; } - vi.set(this.getKey(), this.__typedOnRemoves); + Pi.set(this.getKey(), this.__typedOnRemoves); } setOnRemoveFor(t, r, n) { - He(t), He(r); + Ge(t), Ge(r); const i = this.ensureOnRemoveMapMutable(), s = i[t] ?? (i[t] = {}); s[r] = n, this.syncTypedOnRemovesToRegistry(); } @@ -4369,20 +4370,20 @@ class Qe extends Zt { const n = this.__typedOnRemoves[t]; if (!n) return; - const i = Lr(n, r); + const i = Fr(n, r); if (Object.keys(i).length > 0) this.__typedOnRemoves = { ...this.__typedOnRemoves, [t]: i }; else { - const o = Lr(this.__typedOnRemoves, t); + const o = Fr(this.__typedOnRemoves, t); this.__typedOnRemoves = Object.keys(o).length > 0 ? o : void 0; } this.syncTypedOnRemovesToRegistry(); } pruneTypedOnRemoves() { - if (!this.__typedOnRemoves || this.__typedOnRemoves === Aa) { + if (!this.__typedOnRemoves || this.__typedOnRemoves === Ra) { this.__typedOnRemoves = void 0, this.syncTypedOnRemovesToRegistry(); return; } @@ -4399,21 +4400,21 @@ class Qe extends Zt { this.__typedOnRemoves = Object.keys(t).length > 0 ? t : void 0, this.syncTypedOnRemovesToRegistry(); } ensureOnMouseEnterMapMutable() { - if (this.__typedOnMouseEnters === void 0 || this.__typedOnMouseEnters === Pa) { - const t = zn.get(this.getKey()); + if (this.__typedOnMouseEnters === void 0 || this.__typedOnMouseEnters === $a) { + const t = Bn.get(this.getKey()); this.__typedOnMouseEnters = t ?? {}; } return this.__typedOnMouseEnters; } syncTypedOnMouseEntersToRegistry() { if (!this.__typedOnMouseEnters || Object.keys(this.__typedOnMouseEnters).length === 0) { - zn.delete(this.getKey()), this.__typedOnMouseEnters && Object.keys(this.__typedOnMouseEnters).length === 0 && (this.__typedOnMouseEnters = void 0); + Bn.delete(this.getKey()), this.__typedOnMouseEnters && Object.keys(this.__typedOnMouseEnters).length === 0 && (this.__typedOnMouseEnters = void 0); return; } - zn.set(this.getKey(), this.__typedOnMouseEnters); + Bn.set(this.getKey(), this.__typedOnMouseEnters); } setOnMouseEnterFor(t, r, n) { - He(t), He(r); + Ge(t), Ge(r); const i = this.ensureOnMouseEnterMapMutable(), s = i[t] ?? (i[t] = {}); s[r] = n, this.syncTypedOnMouseEntersToRegistry(); } @@ -4423,20 +4424,20 @@ class Qe extends Zt { const n = this.__typedOnMouseEnters[t]; if (!n) return; - const i = Lr(n, r); + const i = Fr(n, r); if (Object.keys(i).length > 0) this.__typedOnMouseEnters = { ...this.__typedOnMouseEnters, [t]: i }; else { - const o = Lr(this.__typedOnMouseEnters, t); + const o = Fr(this.__typedOnMouseEnters, t); this.__typedOnMouseEnters = Object.keys(o).length > 0 ? o : void 0; } this.syncTypedOnMouseEntersToRegistry(); } pruneTypedOnMouseEnters() { - if (!this.__typedOnMouseEnters || this.__typedOnMouseEnters === Pa) { + if (!this.__typedOnMouseEnters || this.__typedOnMouseEnters === $a) { this.__typedOnMouseEnters = void 0, this.syncTypedOnMouseEntersToRegistry(); return; } @@ -4453,21 +4454,21 @@ class Qe extends Zt { this.__typedOnMouseEnters = Object.keys(t).length > 0 ? t : void 0, this.syncTypedOnMouseEntersToRegistry(); } ensureOnMouseLeaveMapMutable() { - if (this.__typedOnMouseLeaves === void 0 || this.__typedOnMouseLeaves === Na) { - const t = Kn.get(this.getKey()); + if (this.__typedOnMouseLeaves === void 0 || this.__typedOnMouseLeaves === La) { + const t = Vn.get(this.getKey()); this.__typedOnMouseLeaves = t ?? {}; } return this.__typedOnMouseLeaves; } syncTypedOnMouseLeavesToRegistry() { if (!this.__typedOnMouseLeaves || Object.keys(this.__typedOnMouseLeaves).length === 0) { - Kn.delete(this.getKey()), this.__typedOnMouseLeaves && Object.keys(this.__typedOnMouseLeaves).length === 0 && (this.__typedOnMouseLeaves = void 0); + Vn.delete(this.getKey()), this.__typedOnMouseLeaves && Object.keys(this.__typedOnMouseLeaves).length === 0 && (this.__typedOnMouseLeaves = void 0); return; } - Kn.set(this.getKey(), this.__typedOnMouseLeaves); + Vn.set(this.getKey(), this.__typedOnMouseLeaves); } setOnMouseLeaveFor(t, r, n) { - He(t), He(r); + Ge(t), Ge(r); const i = this.ensureOnMouseLeaveMapMutable(), s = i[t] ?? (i[t] = {}); s[r] = n, this.syncTypedOnMouseLeavesToRegistry(); } @@ -4477,20 +4478,20 @@ class Qe extends Zt { const n = this.__typedOnMouseLeaves[t]; if (!n) return; - const i = Lr(n, r); + const i = Fr(n, r); if (Object.keys(i).length > 0) this.__typedOnMouseLeaves = { ...this.__typedOnMouseLeaves, [t]: i }; else { - const o = Lr(this.__typedOnMouseLeaves, t); + const o = Fr(this.__typedOnMouseLeaves, t); this.__typedOnMouseLeaves = Object.keys(o).length > 0 ? o : void 0; } this.syncTypedOnMouseLeavesToRegistry(); } pruneTypedOnMouseLeaves() { - if (!this.__typedOnMouseLeaves || this.__typedOnMouseLeaves === Na) { + if (!this.__typedOnMouseLeaves || this.__typedOnMouseLeaves === La) { this.__typedOnMouseLeaves = void 0, this.syncTypedOnMouseLeavesToRegistry(); return; } @@ -4511,7 +4512,7 @@ class Qe extends Zt { s && (s(t, r, n, this.getTextContent()), this.removeOnRemoveFor(t, r)); } dispatchRemovedIDs(t, r, n) { - const i = zb(t, r); + const i = nk(t, r); if (i.length !== 0) for (const [s, o] of i) this.invokeOnRemove(s, o, n); @@ -4526,10 +4527,10 @@ class Qe extends Zt { if (this.hasNoIDsForEveryType()) return this; let t = this.getPreviousSibling(); - for (; Ce(t) && bu(t.getTypedIDs(), this.getTypedIDs()); ) + for (; Ce(t) && Mu(t.getTypedIDs(), this.getTypedIDs()); ) this.mergeWithPreviousTypedMark(t), t = this.getPreviousSibling(); let r = this.getNextSibling(); - for (; Ce(r) && bu(this.getTypedIDs(), r.getTypedIDs()); ) + for (; Ce(r) && Mu(this.getTypedIDs(), r.getTypedIDs()); ) this.mergeWithNextTypedMark(r), r = this.getNextSibling(); return this; } @@ -4546,107 +4547,107 @@ class Qe extends Zt { mergeOnClicksFrom(t) { if (!t || Object.keys(t).length === 0) return; - const r = Kb(this.getTypedOnClicks(), t); + const r = ik(this.getTypedOnClicks(), t); Object.keys(r).length !== 0 && this.setTypedOnClicks(r); } mergeOnRemovesFrom(t) { if (!t || Object.keys(t).length === 0) return; - const r = jb(this.getTypedOnRemoves(), t); + const r = sk(this.getTypedOnRemoves(), t); Object.keys(r).length !== 0 && this.setTypedOnRemoves(r); } mergeOnMouseEntersFrom(t) { if (!t || Object.keys(t).length === 0) return; - const r = Bb(this.getTypedOnMouseEnters(), t); + const r = ok(this.getTypedOnMouseEnters(), t); Object.keys(r).length !== 0 && this.setTypedOnMouseEnters(r); } mergeOnMouseLeavesFrom(t) { if (!t || Object.keys(t).length === 0) return; - const r = Vb(this.getTypedOnMouseLeaves(), t); + const r = ak(this.getTypedOnMouseLeaves(), t); Object.keys(r).length !== 0 && this.setTypedOnMouseLeaves(r); } } -function Ss(e = Xf) { +function Ps(e = lp) { const t = {}; for (const [r, n] of Object.entries(e)) { - if (He(r), !Array.isArray(n)) { + if (Ge(r), !Array.isArray(n)) { t[r] = []; continue; } const i = []; for (const s of n) - He(s), i.push(s); + Ge(s), i.push(s); t[r] = i; } return t; } -function Yo(e) { - if (!e || e === Ea) +function ta(e) { + if (!e || e === qa) return; const t = {}; for (const [r, n] of Object.entries(e)) { - He(r); + Ge(r); const i = {}; for (const [s, o] of Object.entries(n)) - He(s), i[s] = o; + Ge(s), i[s] = o; Object.keys(i).length > 0 && (t[r] = i); } return Object.keys(t).length > 0 ? t : void 0; } -function Xo(e) { - if (!e || e === Aa) +function ra(e) { + if (!e || e === Ra) return; const t = {}; for (const [r, n] of Object.entries(e)) { - He(r); + Ge(r); const i = {}; for (const [s, o] of Object.entries(n)) - He(s), i[s] = o; + Ge(s), i[s] = o; Object.keys(i).length > 0 && (t[r] = i); } return Object.keys(t).length > 0 ? t : void 0; } -function Qo(e) { - if (!e || e === Pa) +function na(e) { + if (!e || e === $a) return; const t = {}; for (const [r, n] of Object.entries(e)) { - He(r); + Ge(r); const i = {}; for (const [s, o] of Object.entries(n)) - He(s), i[s] = o; + Ge(s), i[s] = o; Object.keys(i).length > 0 && (t[r] = i); } return Object.keys(t).length > 0 ? t : void 0; } -function Zo(e) { - if (!e || e === Na) +function ia(e) { + if (!e || e === La) return; const t = {}; for (const [r, n] of Object.entries(e)) { - He(r); + Ge(r); const i = {}; for (const [s, o] of Object.entries(n)) - He(s), i[s] = o; + Ge(s), i[s] = o; Object.keys(i).length > 0 && (t[r] = i); } return Object.keys(t).length > 0 ? t : void 0; } -function Lr(e, t) { +function Fr(e, t) { const r = {}; for (const [n, i] of Object.entries(e)) n !== t && (r[n] = i); return r; } -function yu(e) { +function Su(e) { const t = {}; for (const [r, n] of Object.entries(e)) !n || n.length === 0 || (t[r] = [...n].sort()); return t; } -function zb(e, t) { +function nk(e, t) { const r = []; for (const [n, i] of Object.entries(e)) { const s = new Set(t[n] ?? []); @@ -4655,8 +4656,8 @@ function zb(e, t) { } return r; } -function bu(e, t) { - const r = yu(e), n = yu(t), i = Object.keys(r).sort(), s = Object.keys(n).sort(); +function Mu(e, t) { + const r = Su(e), n = Su(t), i = Object.keys(r).sort(), s = Object.keys(n).sort(); if (i.length !== s.length) return !1; for (let o = 0; o < i.length; o++) { @@ -4666,118 +4667,118 @@ function bu(e, t) { const c = r[a], l = n[a]; if (!c || !l || c.length !== l.length) return !1; - for (let u = 0; u < c.length; u++) - if (c[u] !== l[u]) + for (let d = 0; d < c.length; d++) + if (c[d] !== l[d]) return !1; } return !0; } -function Kb(e, t) { +function ik(e, t) { const r = {}, n = /* @__PURE__ */ new Set([...Object.keys(e), ...Object.keys(t)]); for (const i of n) { const s = e[i] ?? {}, o = t[i] ?? {}, a = /* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(o)]), c = {}; for (const l of a) { - const u = s[l] ?? o[l]; - u && (c[l] = u); + const d = s[l] ?? o[l]; + d && (c[l] = d); } Object.keys(c).length > 0 && (r[i] = c); } return r; } -function jb(e, t) { +function sk(e, t) { const r = {}, n = /* @__PURE__ */ new Set([...Object.keys(e), ...Object.keys(t)]); for (const i of n) { const s = e[i] ?? {}, o = t[i] ?? {}, a = /* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(o)]), c = {}; for (const l of a) { - const u = s[l] ?? o[l]; - u && (c[l] = u); + const d = s[l] ?? o[l]; + d && (c[l] = d); } Object.keys(c).length > 0 && (r[i] = c); } return r; } -function Bb(e, t) { +function ok(e, t) { const r = {}, n = /* @__PURE__ */ new Set([...Object.keys(e), ...Object.keys(t)]); for (const i of n) { const s = e[i] ?? {}, o = t[i] ?? {}, a = /* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(o)]), c = {}; for (const l of a) { - const u = s[l] ?? o[l]; - u && (c[l] = u); + const d = s[l] ?? o[l]; + d && (c[l] = d); } Object.keys(c).length > 0 && (r[i] = c); } return r; } -function Vb(e, t) { +function ak(e, t) { const r = {}, n = /* @__PURE__ */ new Set([...Object.keys(e), ...Object.keys(t)]); for (const i of n) { const s = e[i] ?? {}, o = t[i] ?? {}, a = /* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(o)]), c = {}; for (const l of a) { - const u = s[l] ?? o[l]; - u && (c[l] = u); + const d = s[l] ?? o[l]; + d && (c[l] = d); } Object.keys(c).length > 0 && (r[i] = c); } return r; } -function ln(e, t) { +function fn(e, t) { return `${e}-${t}`; } -function ku(e) { +function Eu(e) { return `external-${e}`; } -function Hi(e, t, r, n, i) { - return je(new Qe(e, t, r, n, i)); +function Xi(e, t, r, n, i) { + return Ve(new et(e, t, r, n, i)); } function Ce(e) { - return e instanceof Qe; + return e instanceof et; } -function Qf(e) { - return e?.type === Qe.getType(); +function up(e) { + return e?.type === et.getType(); } -function Us(e) { +function js(e) { const t = e.getChildren(); let r = null; for (const n of t) r === null ? e.insertBefore(n) : r.insertAfter(n), r = n; e.remove(); } -function Zf(e, t, r, n, i, s, o) { - const a = e.getNodes(), c = e.anchor.offset, l = e.focus.offset, u = a.length, d = e.isBackward(), f = d ? l : c, p = d ? c : l; - let m, g; - for (let y = 0; y < u; y++) { +function dp(e, t, r, n, i, s, o) { + const a = e.getNodes(), c = e.anchor.offset, l = e.focus.offset, d = a.length, u = e.isBackward(), p = u ? l : c, f = u ? c : l; + let g, h; + for (let y = 0; y < d; y++) { const k = a[y]; - if (F(g) && g.isParentOf(k)) + if (L(h) && h.isParentOf(k)) continue; - const _ = y === 0, S = y === u - 1; - let P = null; - if (v(k)) { - const A = k.getTextContentSize(), B = _ ? f : 0, M = S ? p : A; - if (B === 0 && M === 0) + const _ = y === 0, C = y === d - 1; + let M = null; + if (S(k)) { + const E = k.getTextContentSize(), z = _ ? p : 0, W = C ? f : E; + if (z === 0 && W === 0) continue; - const q = k.splitText(B, M); - P = q.length > 1 && (q.length === 3 || _ && !S || M === A) ? q[1] : q[0]; + const j = k.splitText(z, W); + M = j.length > 1 && (j.length === 3 || _ && !C || W === E) ? j[1] : j[0]; } else { if (Ce(k)) continue; - F(k) && k.isInline() && (P = k); + L(k) && k.isInline() && (M = k); } - if (P !== null) { - if (P && P.is(m)) + if (M !== null) { + if (M && M.is(g)) continue; - const A = P.getParent(); - (A == null || !A.is(m)) && (g = void 0), m = A, g === void 0 && (g = Hi(), g.addID(t, r, n, i, s, o), P.insertBefore(g)), g.append(P); + const E = M.getParent(); + (E == null || !E.is(g)) && (h = void 0), g = E, h === void 0 && (h = Xi(), h.addID(t, r, n, i, s, o), M.insertBefore(h)), h.append(M); } else - m = void 0, g = void 0; + g = void 0, h = void 0; } - t === Kr && F(g) && (d ? g.selectStart() : g.selectEnd()); + t === Vr && L(h) && (u ? h.selectStart() : h.selectEnd()); } -function Wb(e, t, r) { +function ck(e, t, r) { let n = e; for (; n !== null; ) { if (Ce(n)) return n.getTypedIDs()[t]; - if (v(n) && r === n.getTextContentSize()) { + if (S(n) && r === n.getTextContentSize()) { const i = n.getNextSibling(); if (Ce(i)) return i.getTypedIDs()[t]; @@ -4785,8 +4786,8 @@ function Wb(e, t, r) { n = n.getParent(); } } -const Hb = ["type", "marker", "content"], Oa = "unknown", ep = 1, Gb = /* @__PURE__ */ new Set(["optbreak", "ref"]); -class Pn extends Zt { +const lk = ["type", "marker", "content"], Ia = "unknown", fp = 1, uk = /* @__PURE__ */ new Set(["optbreak", "ref"]); +class wn extends Zt { __tag; __marker; __unknownAttributes; @@ -4798,18 +4799,18 @@ class Pn extends Zt { } static clone(t) { const { __tag: r, __marker: n, __unknownAttributes: i, __key: s } = t; - return new Pn(r, n, i, s); + return new wn(r, n, i, s); } static importDOM() { return { - [Oa]: (t) => Yb(t) ? { - conversion: Jb, + [Ia]: (t) => fk(t) ? { + conversion: dk, priority: 1 } : null }; } static importJSON(t) { - return Nc().updateFromJSON(t); + return Lc().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setTag(t.tag).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes); @@ -4830,7 +4831,7 @@ class Pn extends Zt { * callers must not add or strip spaces next to them. */ isInlineTag() { - return Gb.has(this.getTag()); + return uk.has(this.getTag()); } setMarker(t) { if (this.__marker === t) @@ -4849,7 +4850,7 @@ class Pn extends Zt { return this.getLatest().__unknownAttributes; } createDOM() { - const t = document.createElement(Oa); + const t = document.createElement(Ia); return t.setAttribute("data-tag", this.getTag()), t.setAttribute("data-marker", this.getMarker() ?? ""), t.classList.add(this.isInlineTag() ? "unknown-inline" : "unknown-block"), t.contentEditable = "false", t; } updateDOM(t, r) { @@ -4870,7 +4871,7 @@ class Pn extends Zt { tag: this.getTag(), marker: this.getMarker(), unknownAttributes: this.getUnknownAttributes(), - version: ep + version: fp }; } // Mutation @@ -4963,7 +4964,7 @@ class Pn extends Zt { const r = t ?? w(); if (!r) return !1; - if (mc(r) && super.isSelected(r)) + if (go(r) && super.isSelected(r)) return !0; if (r.isCollapsed()) return !1; @@ -4971,24 +4972,24 @@ class Pn extends Zt { return this.getChildren().some((i) => n.some((s) => s.is(i))); } } -function Jb(e) { +function dk(e) { const t = e.getAttribute("data-tag") ?? "", r = e.getAttribute("data-marker") ?? ""; - return { node: Nc(t, r) }; + return { node: Lc(t, r) }; } -function Nc(e, t, r) { - return je(new Pn(e, t, r)); +function Lc(e, t, r) { + return Ve(new wn(e, t, r)); } -function Yb(e) { - return e?.tagName.toLowerCase() === Oa; +function fk(e) { + return e?.tagName.toLowerCase() === Ia; } -function De(e) { - return e instanceof Pn; +function Ue(e) { + return e instanceof wn; } -const tp = 1, Xb = "attribute-run"; -function ea(e) { +const pp = 1, pk = "attribute-run"; +function sa(e) { return e === "va" || e === "vp" || e === "ca" || e === "cp" ? `usfm_${e}` : void 0; } -class Ar extends Zt { +class qr extends Zt { __runKind; constructor(t, r) { super(r), this.__runKind = t; @@ -4998,10 +4999,10 @@ class Ar extends Zt { } static clone(t) { const { __runKind: r, __key: n } = t; - return new Ar(r, n); + return new qr(r, n); } static importJSON(t) { - return rp(t.runKind).updateFromJSON(t); + return hp(t.runKind).updateFromJSON(t); } // No HTML shape ever round-trips: `exportDOM` below contributes no wrapper element of its own // (a DocumentFragment leaves no markup behind), so there is nothing for a paste to hand back @@ -5026,15 +5027,15 @@ class Ar extends Zt { } createDOM() { const t = document.createElement("span"); - t.classList.add(Xb); - const r = ea(this.__runKind); + t.classList.add(pk); + const r = sa(this.__runKind); return r !== void 0 && t.classList.add(r), t; } updateDOM(t, r) { if (t.__runKind !== this.__runKind) { - const n = ea(t.__runKind); + const n = sa(t.__runKind); n !== void 0 && r.classList.remove(n); - const i = ea(this.__runKind); + const i = sa(this.__runKind); i !== void 0 && r.classList.add(i); } return !1; @@ -5047,7 +5048,7 @@ class Ar extends Zt { ...super.exportJSON(), type: this.getType(), runKind: this.getRunKind(), - version: tp + version: pp }; } // Mutation @@ -5058,20 +5059,20 @@ class Ar extends Zt { return !0; } } -function rp(e) { - return je(new Ar(e)); +function hp(e) { + return Ve(new qr(e)); } -function Le(e) { - return e instanceof Ar; +function De(e) { + return e instanceof qr; } -const Gi = "id", np = 1, Qb = [ +const Qi = "id", gp = 1, hk = [ "type", "marker", "code", "content" ]; -class Ut extends Zt { - __marker = Gi; +class Ft extends Zt { + __marker = Qi; __code; __unknownAttributes; constructor(t = "", r, n) { @@ -5082,14 +5083,14 @@ class Ut extends Zt { } static clone(t) { const { __code: r, __unknownAttributes: n, __key: i } = t; - return new Ut(r, n, i); + return new Ft(r, n, i); } static importJSON(t) { const { code: r } = t; - return ip(r).updateFromJSON(t); + return mp(r).updateFromJSON(t); } static isValidBookCode(t) { - return iy(t); + return yy(t); } updateFromJSON(t) { return super.updateFromJSON(t).setCode(t.code).setUnknownAttributes(t.unknownAttributes); @@ -5131,20 +5132,20 @@ class Ut extends Zt { marker: this.getMarker(), code: this.getCode(), unknownAttributes: this.getUnknownAttributes(), - version: np + version: gp }; } } -function ip(e, t) { - return je(new Ut(e, t)); +function mp(e, t) { + return Ve(new Ft(e, t)); } -function pt(e) { - return e instanceof Ut; +function ht(e) { + return e instanceof Ft; } -function sp(e) { - return e?.type === Ut.getType(); +function yp(e) { + return e?.type === Ft.getType(); } -const Fs = "c", op = 1, Zb = [ +const Bs = "c", bp = 1, gk = [ "type", "marker", "number", @@ -5153,7 +5154,7 @@ const Fs = "c", op = 1, Zb = [ "pubnumber", "content" ]; -class Nt extends Zt { +class Ot extends Zt { __marker; __number; __sid; @@ -5161,17 +5162,17 @@ class Nt extends Zt { __pubnumber; __unknownAttributes; constructor(t = "", r, n, i, s, o) { - super(o), this.__marker = Fs, this.__number = t, this.__sid = r, this.__altnumber = n, this.__pubnumber = i, this.__unknownAttributes = s; + super(o), this.__marker = Bs, this.__number = t, this.__sid = r, this.__altnumber = n, this.__pubnumber = i, this.__unknownAttributes = s; } static getType() { return "chapter"; } static clone(t) { const { __number: r, __sid: n, __altnumber: i, __pubnumber: s, __unknownAttributes: o, __key: a } = t; - return new Nt(r, n, i, s, o, a); + return new Ot(r, n, i, s, o, a); } static importJSON(t) { - return ap().updateFromJSON(t); + return kp().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setNumber(t.number).setSid(t.sid).setAltnumber(t.altnumber).setPubnumber(t.pubnumber).setUnknownAttributes(t.unknownAttributes); @@ -5230,7 +5231,7 @@ class Nt extends Zt { } createDOM() { const t = document.createElement("p"); - return t.setAttribute("data-marker", this.__marker), t.classList.add(Ls, `usfm_${this.__marker}`), t.setAttribute("data-number", this.__number), t; + return t.setAttribute("data-marker", this.__marker), t.classList.add(zs, `usfm_${this.__marker}`), t.setAttribute("data-number", this.__number), t; } updateDOM(t, r) { return t.__number !== this.__number && r.setAttribute("data-number", this.__number), !1; @@ -5245,20 +5246,20 @@ class Nt extends Zt { altnumber: this.getAltnumber(), pubnumber: this.getPubnumber(), unknownAttributes: this.getUnknownAttributes(), - version: op + version: bp }; } } -function ap(e, t, r, n, i) { - return je(new Nt(e, t, r, n, i)); +function kp(e, t, r, n, i) { + return Ve(new Ot(e, t, r, n, i)); } -function Pe(e) { - return e instanceof Nt; +function we(e) { + return e instanceof Ot; } -function ek(e) { - return e?.type === Nt.getType(); +function mk(e) { + return e?.type === Ot.getType(); } -const cp = [ +const Tp = [ "fr", "fq", "fqa", @@ -5271,7 +5272,7 @@ const cp = [ "fm", "fdc" // Deprecated marker. -], lp = [ +], xp = [ "xo", "xop", "xk", @@ -5282,7 +5283,7 @@ const cp = [ "xnt", "xdc" // Deprecated marker. -], tk = [ +], yk = [ // Chapter & Verse "ca", "cp", @@ -5337,10 +5338,10 @@ const cp = [ "liv3", "liv4", "liv5", - ...cp, - ...lp -], up = 1, rk = ["type", "marker", "content"]; -class me extends Zt { + ...Tp, + ...xp +], _p = 1, bk = ["type", "marker", "content"]; +class be extends Zt { __marker; __unknownAttributes; constructor(t = "", r, n) { @@ -5351,16 +5352,16 @@ class me extends Zt { } static clone(t) { const { __marker: r, __unknownAttributes: n, __key: i } = t; - return new me(r, n, i); + return new be(r, n, i); } static isValidMarker(t, r) { - return t !== void 0 && (tk.includes(t) || (r?.includes(t) ?? !1)); + return t !== void 0 && (yk.includes(t) || (r?.includes(t) ?? !1)); } static isValidFootnoteMarker(t) { - return t !== void 0 && cp.includes(t); + return t !== void 0 && Tp.includes(t); } static isValidCrossReferenceMarker(t) { - return t !== void 0 && lp.includes(t); + return t !== void 0 && xp.includes(t); } /** * Whether a character marker belongs to the note-content families - footnote or cross-reference. @@ -5374,18 +5375,18 @@ class me extends Zt { * @returns `true` if the marker is a footnote or cross-reference marker, `false` otherwise. */ static isNoteContentMarker(t) { - return me.isValidFootnoteMarker(t) || me.isValidCrossReferenceMarker(t); + return be.isValidFootnoteMarker(t) || be.isValidCrossReferenceMarker(t); } static importDOM() { return { - span: (t) => ik(t) ? { - conversion: nk, + span: (t) => Tk(t) ? { + conversion: kk, priority: 1 } : null }; } static importJSON(t) { - return xr().updateFromJSON(t); + return Mr().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes); @@ -5408,14 +5409,14 @@ class me extends Zt { } createDOM(t) { const r = document.createElement("span"); - return Tu(r, this.__marker, t), r.classList.add(this.__type), r; + return Au(r, this.__marker, t), r.classList.add(this.__type), r; } updateDOM(t, r, n) { - return t.__marker !== this.__marker && (r.classList.remove(`usfm_${t.__marker}`), Tu(r, this.__marker, n)), !1; + return t.__marker !== this.__marker && (r.classList.remove(`usfm_${t.__marker}`), Au(r, this.__marker, n)), !1; } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r }; + return r && On(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r }; } exportJSON() { return { @@ -5423,12 +5424,12 @@ class me extends Zt { type: this.getType(), marker: this.getMarker(), unknownAttributes: this.getUnknownAttributes(), - version: up + version: _p }; } // Mutation insertNewAfter(t, r) { - const n = this.getUnknownAttributes()?.closed === "false", i = xr(this.getMarker(), n ? { closed: "false" } : void 0); + const n = this.getUnknownAttributes()?.closed === "false", i = Mr(this.getMarker(), n ? { closed: "false" } : void 0); return i.setDirection(this.getDirection()), i.setFormat(this.getFormatType()), i.setStyle(this.getTextStyle()), this.insertAfter(i, r), i; } canBeEmpty() { @@ -5438,30 +5439,30 @@ class me extends Zt { return !0; } } -function Tu(e, t, r) { +function Au(e, t, r) { e.setAttribute("data-marker", t), r.theme?.showCharMarkerTitles !== !1 ? e.setAttribute("title", t) : e.removeAttribute("title"), e.classList.add(`usfm_${t}`); } -function nk(e) { +function kk(e) { const t = e.getAttribute("data-marker") ?? "f"; - return { node: xr(t) }; + return { node: Mr(t) }; } -function xr(e, t) { - return je(new me(e, t)); +function Mr(e, t) { + return Ve(new be(e, t)); } -function ik(e) { +function Tk(e) { if (!e) return !1; const t = e.getAttribute("data-marker") ?? ""; - return me.isValidMarker(t) && e.classList.contains(me.getType()); + return be.isValidMarker(t) && e.classList.contains(be.getType()); } -function D(e) { - return e instanceof me; +function I(e) { + return e instanceof be; } -function sk(e) { - return e?.type === me.getType(); +function xk(e) { + return e?.type === be.getType(); } -const dp = 1, ok = "c", fp = "span"; -class pr extends ls { +const Cp = 1, _k = "c", vp = "span"; +class hr extends hs { __marker; __number; __showMarker; @@ -5470,25 +5471,25 @@ class pr extends ls { __pubnumber; __unknownAttributes; constructor(t = "", r = !1, n, i, s, o, a) { - super(a), this.__marker = ok, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; + super(a), this.__marker = _k, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; } static getType() { return "immutable-chapter"; } static clone(t) { const { __number: r, __showMarker: n, __sid: i, __altnumber: s, __pubnumber: o, __unknownAttributes: a, __key: c } = t; - return new pr(r, n, i, s, o, a, c); + return new hr(r, n, i, s, o, a, c); } static importDOM() { return { - span: (t) => pp(t) ? { - conversion: ak, + span: (t) => Sp(t) ? { + conversion: Ck, priority: 1 } : null }; } static importJSON(t) { - return Oc().updateFromJSON(t); + return Ic().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setNumber(t.number).setShowMarker(t.showMarker).setSid(t.sid).setAltnumber(t.altnumber).setPubnumber(t.pubnumber).setUnknownAttributes(t.unknownAttributes); @@ -5555,15 +5556,15 @@ class pr extends ls { return this.getLatest().__unknownAttributes; } createDOM() { - const t = document.createElement(fp); - return t.setAttribute("data-marker", this.__marker), t.classList.add(Ls, `usfm_${this.__marker}`), this.__showMarker && t.classList.add("marker"), t.setAttribute("data-number", this.__number), t; + const t = document.createElement(vp); + return t.setAttribute("data-marker", this.__marker), t.classList.add(zs, `usfm_${this.__marker}`), this.__showMarker && t.classList.add("marker"), t.setAttribute("data-number", this.__number), t; } updateDOM() { return !1; } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(Ls, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r }; + return r && On(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(zs, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r }; } /** * VISIBLE bytes with STABLE IDENTITY, which is only safe because of where this node lives. @@ -5583,7 +5584,7 @@ class pr extends ls { * these bytes from `createDOM` instead, the way `ImmutableTypedTextNode` does. */ decorate() { - return this.getShowMarker() ? Lt(this.getMarker(), this.getNumber()) : this.getNumber(); + return this.getShowMarker() ? Dt(this.getMarker(), this.getNumber()) : this.getNumber(); } exportJSON() { return { @@ -5595,7 +5596,7 @@ class pr extends ls { altnumber: this.getAltnumber(), pubnumber: this.getPubnumber(), unknownAttributes: this.getUnknownAttributes(), - version: dp + version: Cp }; } // Mutation @@ -5606,41 +5607,41 @@ class pr extends ls { return !1; } } -function ak(e) { +function Ck(e) { const t = e.getAttribute("data-number") ?? "0"; - return { node: Oc(t) }; + return { node: Ic(t) }; } -function Oc(e, t, r, n, i, s) { - return je(new pr(e, t, r, n, i, s)); +function Ic(e, t, r, n, i, s) { + return Ve(new hr(e, t, r, n, i, s)); } -function pp(e) { - return e ? e.classList.contains(Ls) && e.tagName.toLowerCase() === fp : !1; +function Sp(e) { + return e ? e.classList.contains(zs) && e.tagName.toLowerCase() === vp : !1; } -function fs(e) { - return e instanceof pr; +function ys(e) { + return e instanceof hr; } -function ck(e) { - return e?.type === pr.getType(); +function vk(e) { + return e?.type === hr.getType(); } -const hp = 1; -class Wr extends yc { +const Mp = 1; +class Xr extends Cc { static getType() { return "implied-para"; } static clone(t) { - return new Wr(t.__key); + return new Xr(t.__key); } static importJSON(t) { return Ht().updateFromJSON(t); } getMarker() { - return cr; + return lr; } exportJSON() { return { ...super.exportJSON(), type: this.getType(), - version: hp + version: Mp }; } // Mutation @@ -5650,15 +5651,15 @@ class Wr extends yc { } } function Ht() { - return je(new Wr()); + return Ve(new Xr()); } -function dr(e) { - return e instanceof Wr; +function fr(e) { + return e instanceof Xr; } -function go(e) { - return e?.type === Wr.getType(); +function ko(e) { + return e?.type === Xr.getType(); } -const lk = [ +const Sk = [ // Identification "ide", "sts", @@ -5736,7 +5737,7 @@ const lk = [ "sd3", "sd4", // Body Paragraphs - cr, + lr, "m", "po", "cls", @@ -5791,11 +5792,11 @@ const lk = [ "lim4", // Breaks - see https://docs.usfm.bible/usfm/3.1/char/breaks/pb.html "pb" -], gp = 1, uk = ["type", "marker", "content"]; -class Ze extends yc { +], Ep = 1, Mk = ["type", "marker", "content"]; +class nt extends Cc { __marker; __unknownAttributes; - constructor(t = cr, r, n) { + constructor(t = lr, r, n) { super(n), this.__marker = t, this.__unknownAttributes = r; } static getType() { @@ -5803,21 +5804,21 @@ class Ze extends yc { } static clone(t) { const { __marker: r, __unknownAttributes: n, __key: i } = t; - return new Ze(r, n, i); + return new nt(r, n, i); } static isValidMarker(t, r) { - return t !== void 0 && (lk.includes(t) || (r?.includes(t) ?? !1)); + return t !== void 0 && (Sk.includes(t) || (r?.includes(t) ?? !1)); } static importDOM() { return { p: () => ({ - conversion: dk, + conversion: Ek, priority: 1 }) }; } static importJSON(t) { - return Ji().updateFromJSON(t); + return Zi().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes); @@ -5848,7 +5849,7 @@ class Ze extends yc { } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r }; + return r && On(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r }; } exportJSON() { return { @@ -5856,17 +5857,17 @@ class Ze extends yc { type: this.getType(), marker: this.getMarker(), unknownAttributes: this.getUnknownAttributes(), - version: gp + version: Ep }; } // Mutation insertNewAfter(t, r) { - const n = Ji(this.getMarker()); + const n = Zi(this.getMarker()); return n.setTextFormat(t.format), n.setTextStyle(t.style), n.setDirection(this.getDirection()), n.setFormat(this.getFormatType()), n.setStyle(this.getTextStyle()), this.insertAfter(n, r), n; } } -function dk(e) { - const t = e.getAttribute("data-marker") ?? void 0, r = Ji(t); +function Ek(e) { + const t = e.getAttribute("data-marker") ?? void 0, r = Zi(t); if (e.style) { r.setFormat(e.style.textAlign); const n = parseInt(e.style.textIndent, 10) / 20; @@ -5874,16 +5875,16 @@ function dk(e) { } return { node: r }; } -function Ji(e, t) { - return je(new Ze(e, t)); +function Zi(e, t) { + return Ve(new nt(e, t)); } -function ae(e) { - return e instanceof Ze; +function le(e) { + return e instanceof nt; } -function wc(e) { - return e?.type === Ze.getType(); +function Dc(e) { + return e?.type === nt.getType(); } -const zs = "v", mp = 1, fk = [ +const Vs = "v", Ap = 1, Ak = [ "type", "marker", "number", @@ -5892,7 +5893,7 @@ const zs = "v", mp = 1, fk = [ "pubnumber", "content" ]; -class dt extends Ke { +class ft extends Be { __marker; __number; __sid; @@ -5900,17 +5901,17 @@ class dt extends Ke { __pubnumber; __unknownAttributes; constructor(t = "", r, n, i, s, o, a) { - super(r ?? t, a), this.__marker = zs, this.__number = t, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; + super(r ?? t, a), this.__marker = Vs, this.__number = t, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; } static getType() { return "verse"; } static clone(t) { const { __number: r, __text: n, __sid: i, __altnumber: s, __pubnumber: o, __unknownAttributes: a, __key: c } = t; - return new dt(r, n, i, s, o, a, c); + return new ft(r, n, i, s, o, a, c); } static importJSON(t) { - return yp().updateFromJSON(t); + return Pp().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setNumber(t.number).setSid(t.sid).setAltnumber(t.altnumber).setPubnumber(t.pubnumber).setUnknownAttributes(t.unknownAttributes); @@ -5969,7 +5970,7 @@ class dt extends Ke { } createDOM(t) { const r = super.createDOM(t); - return r.setAttribute("data-marker", this.__marker), r.classList.add(Ca, `usfm_${this.__marker}`), r.setAttribute("data-number", this.__number), r; + return r.setAttribute("data-marker", this.__marker), r.classList.add(Pa, `usfm_${this.__marker}`), r.setAttribute("data-number", this.__number), r; } updateDOM(t, r, n) { const i = super.updateDOM(t, r, n); @@ -5985,54 +5986,54 @@ class dt extends Ke { altnumber: this.getAltnumber(), pubnumber: this.getPubnumber(), unknownAttributes: this.getUnknownAttributes(), - version: mp + version: Ap }; } } -function yp(e, t, r, n, i, s) { - return je(new dt(e, t, r, n, i, s)); +function Pp(e, t, r, n, i, s) { + return Ve(new ft(e, t, r, n, i, s)); } -function Oe(e) { - return e instanceof dt; +function Ae(e) { + return e instanceof ft; } -function bp(e) { - return e?.type === dt.getType(); +function Np(e) { + return e?.type === ft.getType(); } -const pk = "​", ti = pk; -var xu; +const Pk = "​", ni = Pk; +var Pu; (function(e) { e.LEFT = "left", e.RIGHT = "right"; -})(xu || (xu = {})); -var _u; +})(Pu || (Pu = {})); +var Nu; (function(e) { e[e.BEFORE = 0] = "BEFORE", e[e.AFTER = 1] = "AFTER"; -})(_u || (_u = {})); -function hk() { - return pe(ti); +})(Nu || (Nu = {})); +function Nk() { + return he(ni); } -function gk(e) { +function Ok(e) { const t = e.getTextContent(); - e.setTextContent(t.replaceAll(ti, "")); + e.setTextContent(t.replaceAll(ni, "")); } -function ps(e) { - return e.length > 0 && e.includes(ti) && e.replaceAll(ti, "") === ""; +function bs(e) { + return e.length > 0 && e.includes(ni) && e.replaceAll(ni, "") === ""; } -function qc(e) { - return v(e) && ps(e.getTextContent()); +function Uc(e) { + return S(e) && bs(e.getTextContent()); } -function kp(e) { - return ek(e) || ck(e); +function Op(e) { + return mk(e) || vk(e); } -function Ge(e) { - return Pe(e) || fs(e); +function Je(e) { + return we(e) || ys(e); } -function Tp(e, t) { - return e.find((r) => Ge(r) && r.getNumber() === t.toString()); +function wp(e, t) { + return e.find((r) => Je(r) && r.getNumber() === t.toString()); } -function mk(e, t = !1) { - return e.find((r, n) => (!t || n > 0) && Ge(r)); +function wk(e, t = !1) { + return e.find((r, n) => (!t || n > 0) && Je(r)); } -function Cu(e) { +function Ou(e) { let t = e; for (; t && t.getParent() !== null; ) { const r = t.getPreviousSibling(); @@ -6041,24 +6042,24 @@ function Cu(e) { t = t.getParent(); } } -function xp(e) { +function qp(e) { if (!e) return; - if (Ge(e)) + if (Je(e)) return e; let t = e.getTopLevelElement()?.getPreviousSibling(); - for (; t && !Ge(t); ) + for (; t && !Je(t); ) t = t.getPreviousSibling(); - if (t && Ge(t)) + if (t && Je(t)) return t; } function Xt(e) { - return nt(e, j) ?? void 0; + return rt(e, K) ?? void 0; } -function yk(e) { - return pt(e) || Pe(e) || D(e) || fs(e) || dr(e) || Be(e) || ae(e) || j(e) || Oe(e) || De(e); +function qk(e) { + return ht(e) || we(e) || I(e) || ys(e) || fr(e) || He(e) || le(e) || K(e) || Ae(e) || Ue(e); } -function _p(e) { +function Rp(e) { if (e.anchor.type === "element") { const r = e.anchor.getNode(), n = e.anchor.offset; if (n < r.getChildrenSize()) @@ -6067,23 +6068,23 @@ function _p(e) { const t = e.anchor.getNode(); return t.getNextSibling() ?? t.getParent()?.getNextSibling() ?? null; } -function bk(e) { +function Rk(e) { const t = e.anchor.offset; if (e.anchor.type === "element" && t > 0) return e.anchor.getNode().getChildAtIndex(t - 1); const r = e.anchor.getNode(); return r.getPreviousSibling() ?? r.getParent()?.getPreviousSibling() ?? null; } -function Et(e) { - return Se(e) || pt(e); +function At(e) { + return ve(e) || ht(e); } -function Se(e) { - return ae(e) || dr(e); +function ve(e) { + return le(e) || fr(e); } -function kk(e) { - return wc(e) || go(e); +function $k(e) { + return Dc(e) || ko(e); } -function Ks(e, t) { +function Ws(e, t) { let r = e.getParent(); for (; r; ) { if (r.getKey() === t) @@ -6092,141 +6093,141 @@ function Ks(e, t) { } return !1; } -function Tn(e, t) { - const r = ne(t, kn), n = !!(e.cid && r), i = !e.cid && !r; +function Cn(e, t) { + const r = ie(t, _n), n = !!(e.cid && r), i = !e.cid && !r; return e.style === t.getMarker() && (i || n && e.cid === r); } -function Tk(e, t) { - const r = F(e) ? e : e.getParent(), n = F(t) ? t : t.getParent(), i = r && n ? hy(r, n) : void 0; +function Lk(e, t) { + const r = L(e) ? e : e.getParent(), n = L(t) ? t : t.getParent(), i = r && n ? Ey(r, n) : void 0; return i ? i.commonAncestor : void 0; } -function xk(e) { +function Ik(e) { const t = e.getStartEndPoints(); if (!t) return; const [r, n] = t, i = e.isBackward() ? r : n; e.focus.set(i.key, i.offset, i.type), e.anchor.set(i.key, i.offset, i.type); } -function ri(e) { - return e?.type === Ke.getType(); +function ii(e) { + return e?.type === Be.getType(); } -function _k(e, t) { +function Dk(e, t) { if (!t) return; const r = e.findIndex((n) => n === t); r && (e.length = r); } -function Ck(e, t) { +function Uk(e, t) { if (!t) return e; const r = t.getIndexWithinParent(); return e.splice(r + 1, e.length - r - 1); } -function we(e, t = !1) { +function Re(e, t = !1) { return `\\${t ? "+" : ""}${e}`; } -function rt(e, t = !1) { +function ot(e, t = !1) { return `\\${t ? "+" : ""}${e}*`; } -function Cp(e, t, r) { - const n = we(e); +function $p(e, t, r) { + const n = Re(e); if (t?.startsWith(n)) { const i = t.slice(n.length).replace(/^[\s ]+/, ""), s = /^([^ \u00A0\\]+)/.exec(i); s && (r = s[1]); } return r; } -function Lt(e, t) { - let r = we(e); - return t && (r += `${I}${t}`), r += " ", r; +function Dt(e, t) { + let r = Re(e); + return t && (r += `${R}${t}`), r += " ", r; } -function Sk(e) { - const t = e[ds]; +function Fk(e) { + const t = e[gs]; if (t && typeof t == "object" && "textType" in t) { const r = t.textType; if (typeof r == "string") return r; } } -function Sp(e) { - return Dc(e) || Yf(e) && e.textType === "marker" || ri(e) && Sk(e) === "attribute" ? "" : ri(e) && e.text !== I ? e.text : sk(e) ? e.children.map((t) => Sp(t)).join("") : ""; +function Lp(e) { + return Bc(e) || cp(e) && e.textType === "marker" || ii(e) && Fk(e) === "attribute" ? "" : ii(e) && e.text !== R ? e.text : xk(e) ? e.children.map((t) => Lp(t)).join("") : ""; } -function vk(e) { - return e.map((r) => Sp(r)).filter((r) => r.length > 0).join(" ").trim(); +function zk(e) { + return e.map((r) => Lp(r)).filter((r) => r.length > 0).join(" ").trim(); } -function At(e) { - return " " + e + I; +function Pt(e) { + return " " + e + R; } -function Rc(e) { +function Fc(e) { const t = []; for (const r of e) { - if (!D(r)) + if (!I(r)) continue; - const n = vp(r); - n !== Dt && n.length > 0 && t.push(n); + const n = Ip(r); + n !== Ut && n.length > 0 && t.push(n); } return t.join(" ").trim(); } -function vp(e) { - return O(e) || Pr(e) || v(e) && ne(e, oe) === "attribute" ? "" : v(e) ? e.getTextContent() : F(e) ? e.getChildren().map((t) => vp(t)).join("") : ""; +function Ip(e) { + return O(e) || Rr(e) || S(e) && ie(e, ae) === "attribute" ? "" : S(e) ? e.getTextContent() : L(e) ? e.getChildren().map((t) => Ip(t)).join("") : ""; } -function Pr(e) { +function Rr(e) { return Yt(e) && e.getTextType() === "marker"; } -function Ft(e) { - return O(e) || Pr(e); +function zt(e) { + return O(e) || Rr(e); } -function Su(e, t) { - Mk(e, t), e.setMarker(t); +function wu(e, t) { + Kk(e, t), e.setMarker(t); } -function Mk(e, t) { - const r = e.getMarker(), n = we(r), i = we(r, !0), s = rt(r), o = rt(r, !0), a = me.isNoteContentMarker(t); +function Kk(e, t) { + const r = e.getMarker(), n = Re(r), i = Re(r, !0), s = ot(r), o = ot(r, !0), a = be.isNoteContentMarker(t); e.getChildren().forEach((c) => { - if (!Ft(c)) + if (!zt(c)) return; - const l = c.getTextContent(), u = l === n || l === i, d = !u && (l === s || l === o); - if (!(!u && !d)) { - if (d && a) { + const l = c.getTextContent(), d = l === n || l === i, u = !d && (l === s || l === o); + if (!(!d && !u)) { + if (u && a) { c.remove(); return; } if (O(c)) c.setMarker(t); - else if (Pr(c)) { - const f = l.startsWith(we("", !0)); - c.setTextContent(u ? we(t, f) : rt(t, f)); + else if (Rr(c)) { + const p = l.startsWith(Re("", !0)); + c.setTextContent(d ? Re(t, p) : ot(t, p)); } } }); } -function Ue(e, t = sy) { +function ze(e, t = by) { const r = { ...e }; return t.forEach((n) => { Reflect.deleteProperty(r, n); }), Object.keys(r).length === 0 ? void 0 : r; } -function Ae(e) { +function Ne(e) { return Object.fromEntries(Object.entries(e).filter(([, t]) => t !== void 0)); } -function Mp(e) { +function Dp(e) { const t = e instanceof Error ? e.message : String(e); return t.includes("$caretFromPoint") && (t.includes("does not inherit from ElementNode") || t.includes("does not inherit from TextNode")); } -function $c(e) { - if (!N(e)) - return vu(e); +function zc(e) { + if (!P(e)) + return qu(e); const t = e.anchor.getNode(); - if (t && (e.anchor.type === "element" && !F(t) || e.anchor.type === "text" && !v(t))) + if (t && (e.anchor.type === "element" && !L(t) || e.anchor.type === "text" && !S(t))) return t ?? void 0; try { - return vu(e) ?? t ?? void 0; + return qu(e) ?? t ?? void 0; } catch (n) { - if (Mp(n)) + if (Dp(n)) return t ?? void 0; throw n; } } -function Ek(e, t) { +function jk(e, t) { if (!t) return (e + 1).toString(); const r = t.split("-"); @@ -6238,7 +6239,7 @@ function Ek(e, t) { const i = String.fromCharCode(n[2].charCodeAt(0) + 1); return `${n[1]}${i}`; } -function Ic(e, t) { +function Kc(e, t) { if (!t) return !1; const r = t.split("-").map((n) => parseInt(n)); @@ -6246,52 +6247,52 @@ function Ic(e, t) { throw new Error("isVerseInRange: invalid range"); return r.length === 1 ? e === r[0] : r.length === 2 && isNaN(r[1]) ? e >= r[0] : (r.length === 2 && isNaN(r[0]) || e >= r[0]) && e <= r[1]; } -function Ep(e) { +function Up(e) { return !!e && e.includes("-"); } -function Ap(e) { +function Fp(e) { const t = e.split("-"), r = parseInt(t[0], 10), n = t.length > 1 ? parseInt(t[t.length - 1], 10) : r; return { start: r, end: n }; } -function vu(e) { +function qu(e) { if (!e) return; const t = e.getNodes(); if (t.length > 0) return e.isBackward() ? t[t.length - 1] : t[0]; } -function Lc(e) { +function jc(e) { if (!e) return !1; - if (us(e) || O(e) || Pr(e) || Le(e) || Yt(e) && e.getTextType() === "attribute") + if (di(e) || O(e) || Rr(e) || De(e) || Yt(e) && e.getTextType() === "attribute") return !0; - if (v(e)) { - const t = ne(e, oe); - if (t === fr || t === "attribute") + if (S(e)) { + const t = ie(e, ae); + if (t === pr || t === "attribute") return !0; const r = e.getTextContent(); - if (r === "" || r === I || ps(r)) + if (r === "" || r === R || bs(r)) return !0; } return !1; } -function mo() { - const e = pe(I); - return bt(e, oe, fr), e.setMode("token"), e; +function To() { + const e = he(R); + return kt(e, ae, pr), e.setMode("token"), e; } -function Ak(e) { +function Bk(e) { const t = e.getTextContent(); - t.startsWith(I) || e.setTextContent(I + t); + t.startsWith(R) || e.setTextContent(R + t); } -function Nn(e) { - return v(e) && ne(e, oe) === fr; +function qn(e) { + return S(e) && ie(e, ae) === pr; } -function Pp(e) { +function zp(e) { const t = e.getFirstChild(); - if (!Ft(t) || t === null || Nn(t.getNextSibling())) + if (!zt(t) || t === null || qn(t.getNextSibling())) return !1; const r = w(); - if (!N(r) || !r.isCollapsed()) + if (!P(r) || !r.isCollapsed()) return !1; const n = r.anchor.getNode(); if (n.is(t) || n.is(e)) @@ -6299,18 +6300,18 @@ function Pp(e) { const i = t.getNextSibling(); return i !== null && n.is(i) && r.anchor.offset === 0; } -function di(e) { +function hi(e) { const t = []; let r; const n = () => { r && (t.push({ type: "text", segments: r.segments, length: r.length }), r = void 0); }, i = (s) => { - if (!Lc(s)) { + if (!jc(s)) { if (Ce(s)) { s.getChildren().forEach(i); return; } - if (v(s) && s.getType() === Ke.getType()) { + if (S(s) && s.getType() === Be.getType()) { r ??= { segments: [], length: 0 }, r.segments.push({ node: s, start: r.length }), r.length += s.getTextContentSize(); return; } @@ -6319,20 +6320,20 @@ function di(e) { }; return e.getChildren().forEach(i), n(), t; } -function yo(e) { +function xo(e) { let t = e.getParent(); for (; t && Ce(t); ) t = t.getParent(); return t; } -function Pk(e, t) { - return di(e).findIndex((r) => r.type === "element" ? r.node.is(t) : r.segments.some((n) => n.node.is(t))); +function Vk(e, t) { + return hi(e).findIndex((r) => r.type === "element" ? r.node.is(t) : r.segments.some((n) => n.node.is(t))); } -function Nk(e, t) { - const r = yo(e); +function Wk(e, t) { + const r = xo(e); if (!r) return; - const n = di(r); + const n = hi(r); for (let i = 0; i < n.length; i++) { const s = n[i]; if (s.type !== "text") @@ -6342,7 +6343,7 @@ function Nk(e, t) { return { parent: r, index: i, offset: o.start + t }; } } -function Ok(e, t) { +function Hk(e, t) { if (t < 0 || t > e.length) return; for (const n of e.segments) { @@ -6354,35 +6355,35 @@ function Ok(e, t) { if (r) return [r.node, t - r.start]; } -function Np(e, t) { - const r = di(e), n = e.getChildAtIndex(t); +function Kp(e, t) { + const r = hi(e), n = e.getChildAtIndex(t); if (!n) return { type: "index", index: r.length }; - if (Lc(n)) - return Np(e, t + 1); + if (jc(n)) + return Kp(e, t + 1); for (let i = 0; i < r.length; i++) { const s = r[i]; if (s.type === "element") { - if (s.node.is(n) || Ks(s.node, n.getKey())) + if (s.node.is(n) || Ws(s.node, n.getKey())) return { type: "index", index: i }; continue; } for (const o of s.segments) - if (o.node.is(n) || Ks(o.node, n.getKey())) + if (o.node.is(n) || Ws(o.node, n.getKey())) return o.start === 0 ? { type: "index", index: i } : { type: "text", index: i, offset: o.start }; } return { type: "index", index: r.length }; } -function wk(e, t) { +function Gk(e, t) { if (t <= 0) return 0; - const r = di(e); + const r = hi(e); if (r.length === 0 || t > r.length) return e.getChildrenSize(); - const n = r[t - 1], i = n.type === "element" ? n.node : n.segments[n.segments.length - 1]?.node, s = i ? qk(e, i) : void 0; + const n = r[t - 1], i = n.type === "element" ? n.node : n.segments[n.segments.length - 1]?.node, s = i ? Jk(e, i) : void 0; return s ? s.getIndexWithinParent() + 1 : e.getChildrenSize(); } -function qk(e, t) { +function Jk(e, t) { let r = t; for (; r; ) { const n = r.getParent(); @@ -6391,8 +6392,8 @@ function qk(e, t) { r = n; } } -const Rk = 1; -class hr extends Ke { +const Yk = 1; +class gr extends Be { __marker; __markerSyntax; __nested; @@ -6401,16 +6402,16 @@ class hr extends Ke { // slotting a new field ahead of it would silently reinterpret an existing 3-argument call's // `NodeKey` as this flag. constructor(t = "", r = "opening", n, i = !1) { - super(fn(t, r, i), n), this.__marker = t, this.__markerSyntax = r, this.__nested = i; + super(gn(t, r, i), n), this.__marker = t, this.__markerSyntax = r, this.__nested = i; } static getType() { return "marker"; } static clone(t) { - return new hr(t.__marker, t.__markerSyntax, t.__key, t.__nested); + return new gr(t.__marker, t.__markerSyntax, t.__key, t.__nested); } static importJSON(t) { - return ot().updateFromJSON(t); + return lt().updateFromJSON(t); } updateFromJSON(t) { const { marker: r, markerSyntax: n = "opening", nested: i = !1 } = t, o = super.updateFromJSON({ @@ -6418,7 +6419,7 @@ class hr extends Ke { // An EMPTY serialized text is the "build canonical bytes" sentinel — the adaptor's // createMarker serializes glyphs with `text: ""` and relies on the import deriving them. // Any non-empty text is the glyph's actual displayed bytes and is kept verbatim. - text: t.text || fn(r, n, i) + text: t.text || gn(r, n, i) }).getWritable(); return o.__marker = r, o.__markerSyntax = n, o.__nested = i, o; } @@ -6426,7 +6427,7 @@ class hr extends Ke { if (this.__marker === t) return this; const r = this.getWritable(); - return r.__marker = t, r.__text = fn(t, r.__markerSyntax, r.__nested), r; + return r.__marker = t, r.__text = gn(t, r.__markerSyntax, r.__nested), r; } getMarker() { return this.getLatest().__marker; @@ -6435,7 +6436,7 @@ class hr extends Ke { if (this.__markerSyntax === t) return this; const r = this.getWritable(); - return r.__markerSyntax = t, r.__text = fn(r.__marker, t, r.__nested), r; + return r.__markerSyntax = t, r.__text = gn(r.__marker, t, r.__nested), r; } getMarkerSyntax() { return this.getLatest().__markerSyntax; @@ -6444,7 +6445,7 @@ class hr extends Ke { if (this.__nested === t) return this; const r = this.getWritable(); - return r.__nested = t, r.__text = fn(r.__marker, r.__markerSyntax, t), r; + return r.__nested = t, r.__text = gn(r.__marker, r.__markerSyntax, t), r; } getNested() { return this.getLatest().__nested; @@ -6467,34 +6468,34 @@ class hr extends Ke { // Only serialize the flag for genuinely nested glyphs; absence means non-nested, so // existing states (and the overwhelmingly common non-nested markers) stay unchanged. ...this.getNested() ? { nested: !0 } : {}, - version: Rk + version: Yk }; } } -function ot(e, t, r) { - return je(new hr(e, t, void 0, r)); +function lt(e, t, r) { + return Ve(new gr(e, t, void 0, r)); } function O(e) { - return e instanceof hr; + return e instanceof gr; } -function Dc(e) { - return e?.type === hr.getType(); +function Bc(e) { + return e?.type === gr.getType(); } -function Xr(e) { - return e.getTextContent() === fn(e.getMarker(), e.getMarkerSyntax(), e.getNested()); +function rn(e) { + return e.getTextContent() === gn(e.getMarker(), e.getMarkerSyntax(), e.getNested()); } -function $k(e) { - e.setTextContent(fn(e.getMarker(), e.getMarkerSyntax(), e.getNested())); +function Xk(e) { + e.setTextContent(gn(e.getMarker(), e.getMarkerSyntax(), e.getNested())); } -function fn(e, t, r = !1) { - return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : we(e, r); +function gn(e, t, r = !1) { + return t === "closing" ? ot(e, r) : t === "selfClosing" ? ot("") : Re(e, r); } -const Ik = /* @__PURE__ */ new Set(["closed"]); -function ar(e, t) { - const r = Object.entries(e).filter(([n, i]) => i !== void 0 && !Ik.has(n)); +const Qk = /* @__PURE__ */ new Set(["closed"]); +function cr(e, t) { + const r = Object.entries(e).filter(([n, i]) => i !== void 0 && !Qk.has(n)); return r.length === 0 ? "" : r.length === 1 && r[0][0] === t && r[0][1] !== "" ? `|${r[0][1]}` : `|${r.map(([n, i]) => `${n}="${i}"`).join(" ")}`; } -function Op(e, t) { +function jp(e, t) { if (!t || t.length === 0) return e; const r = {}; @@ -6504,16 +6505,16 @@ function Op(e, t) { Object.hasOwn(r, n) || (r[n] = i); }), r; } -function wp(e) { - const t = Object.keys(e).filter((n) => !pb.includes(n)), r = [ +function Bp(e) { + const t = Object.keys(e).filter((n) => !Pb.includes(n)), r = [ ...t.filter((n) => n === "sid"), ...t.filter((n) => n === "eid"), ...t.filter((n) => n !== "sid" && n !== "eid") ]; return t.every((n, i) => n === r[i]) ? void 0 : t; } -function qp(e, t, r, n) { - return Op( +function Vp(e, t, r, n) { + return jp( // Presence, not truthiness: an authored `sid=""` is a byte the document holds, and folding it // out here deletes it from the displayed run — which a settle then re-derives node state from, // so the empty value would be gone from the file. Matches `orderedAttributes`' own `in` test @@ -6526,35 +6527,35 @@ function qp(e, t, r, n) { n ); } -function Di(e) { +function Ki(e) { return e.getChildren().find((t) => O(t) && t.getMarkerSyntax() === "closing" && t.getMarker() === e.getMarker()); } -function Lk(e) { +function Zk(e) { const t = e.getUnknownAttributes(); - return !t || !Object.keys(t).some((n) => n !== "closed") ? !1 : Di(e) === void 0 && Rp(e) === void 0; + return !t || !Object.keys(t).some((n) => n !== "closed") ? !1 : Ki(e) === void 0 && Wp(e) === void 0; } -function Rp(e) { - return e.getChildren().find((t) => v(t) && ne(t, oe) === "attribute"); +function Wp(e) { + return e.getChildren().find((t) => S(t) && ie(t, ae) === "attribute"); } -function Yi(e, t) { - return hs(e.getNextSibling(), t); +function es(e, t) { + return ks(e.getNextSibling(), t); } -const Dk = /^[ \u00A0]+$/; -function Uc(e) { - if (Xr(e)) +const eT = /^[ \u00A0]+$/; +function Vc(e) { + if (rn(e)) return !0; if (e.getMarkerSyntax() !== "opening") return !1; - const t = we(e.getMarker(), e.getNested()), r = e.getTextContent(); - return r.startsWith(t) && Dk.test(r.slice(t.length)); + const t = Re(e.getMarker(), e.getNested()), r = e.getTextContent(); + return r.startsWith(t) && eT.test(r.slice(t.length)); } -function hs(e, t) { +function ks(e, t) { let r, n, i, s; - return Le(e) && e.getRunKind() === t && (s = e, e = e.getFirstChild()), O(e) && e.getMarkerSyntax() === "opening" && e.getMarker() === t && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the + return De(e) && e.getRunKind() === t && (s = e, e = e.getFirstChild()), O(e) && e.getMarkerSyntax() === "opening" && e.getMarker() === t && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the // opener is at rest, not byte damage. - Uc(e) && (r = e, e = e.getNextSibling()), v(e) && ne(e, oe) === "attribute" && (n = e, e = e.getNextSibling()), O(e) && e.getMarkerSyntax() === "closing" && e.getMarker() === t && Xr(e) && (i = e), { opener: r, value: n, closer: i, wrapper: s }; + Vc(e) && (r = e, e = e.getNextSibling()), S(e) && ie(e, ae) === "attribute" && (n = e, e = e.getNextSibling()), O(e) && e.getMarkerSyntax() === "closing" && e.getMarker() === t && rn(e) && (i = e), { opener: r, value: n, closer: i, wrapper: s }; } -function Xi(e) { +function ts(e) { const t = e.getChildren(); let r = 0; for (; r < t.length; ) { @@ -6564,89 +6565,89 @@ function Xi(e) { r++; } const n = t[r]; - if (v(n) && n.getTextContent() === At(e.getCaller())) + if (S(n) && n.getTextContent() === Pt(e.getCaller())) return n; } -function $p(e) { - const t = Xi(e); - return t ? hs(t.getNextSibling(), "cat") : {}; +function Hp(e) { + const t = ts(e); + return t ? ks(t.getNextSibling(), "cat") : {}; } -function bo(e) { +function _o(e) { const t = e.getFirstChild(); - if (!(!v(t) || O(t)) && ne(t, oe) !== "attribute") + if (!(!S(t) || O(t)) && ie(t, ae) !== "attribute") return t; } -function Ip(e) { - const t = bo(e); - return t ? hs(t.getNextSibling(), "ca") : {}; +function Gp(e) { + const t = _o(e); + return t ? ks(t.getNextSibling(), "ca") : {}; } -function Lp(e) { - const t = bo(e); +function Jp(e) { + const t = _o(e); if (!t) return; - const r = hs(t.getNextSibling(), "ca"); + const r = ks(t.getNextSibling(), "ca"); return r.wrapper ?? r.closer ?? t; } -function Dp(e) { - const t = Lp(e); - return t ? hs(t.getNextSibling(), "cp") : {}; +function Yp(e) { + const t = Jp(e); + return t ? ks(t.getNextSibling(), "cp") : {}; } -function Up(e) { +function Xp(e) { const t = e.getParent(); - if (!D(t)) + if (!I(t)) return; const r = t.getMarker(); if (!(r !== "va" && r !== "vp")) for (let n = t.getPreviousSibling(); n; n = n.getPreviousSibling()) { - if (Oe(n)) + if (Ae(n)) return n; - if (!(O(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || v(n) && ne(n, oe) === "attribute" || D(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || Le(n))) + if (!(O(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || S(n) && ie(n, ae) === "attribute" || I(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || De(n))) return; } } -function ko(e) { +function Co(e) { let t, r, n, i, s = e.getNextSibling(); - return Le(s) && s.getRunKind() === "milestone" && (i = s, s = s.getFirstChild()), O(s) && s.getMarkerSyntax() === "opening" && s.getMarker() === e.getMarker() && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the + return De(s) && s.getRunKind() === "milestone" && (i = s, s = s.getFirstChild()), O(s) && s.getMarkerSyntax() === "opening" && s.getMarker() === e.getMarker() && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the // opener is at rest, not byte damage. - Uc(s) && (t = s, s = s.getNextSibling()), v(s) && ne(s, oe) === "attribute" && (r = s, s = s.getNextSibling()), O(s) && s.getMarkerSyntax() === "selfClosing" && Xr(s) && (n = s), { opening: t, attribute: r, closing: n, wrapper: i }; + Vc(s) && (t = s, s = s.getNextSibling()), S(s) && ie(s, ae) === "attribute" && (r = s, s = s.getNextSibling()), O(s) && s.getMarkerSyntax() === "selfClosing" && rn(s) && (n = s), { opening: t, attribute: r, closing: n, wrapper: i }; } -function Fc(e) { - return D(yo(e)); +function Wc(e) { + return I(xo(e)); } -function wa(e, t) { +function Da(e, t) { if (e.getMarkerSyntax() === "selfClosing") return; const r = e.getMarker(); - return r === t.getMarker() ? Fc(t) : t.getChildren().some((i) => D(i) && i.getMarker() === r) ? !0 : void 0; + return r === t.getMarker() ? Wc(t) : t.getChildren().some((i) => I(i) && i.getMarker() === r) ? !0 : void 0; } -function Uk(e) { +function tT(e) { e.isAttached() && e.getChildren().forEach((t) => { if (!O(t)) return; - const r = wa(t, e); + const r = Da(t, e); r !== void 0 && t.setNested(r); }); } -function To(e) { - return v(e) && e.getType() === Ke.getType() && ne(e, oe) !== "attribute"; +function vo(e) { + return S(e) && e.getType() === Be.getType() && ie(e, ae) !== "attribute"; } -function zc(e, t) { - if (e.getMarkerSyntax() !== "opening" || wa(e, t) === void 0) +function Hc(e, t) { + if (e.getMarkerSyntax() !== "opening" || Da(e, t) === void 0) return; const r = e.getNextSibling(); if (r !== null) - return O(r) ? wa(r, t) === !0 ? "spacer" : void 0 : To(r) ? r.getTextContent().startsWith(I) ? void 0 : "prefix" : "spacer"; + return O(r) ? Da(r, t) === !0 ? "spacer" : void 0 : vo(r) ? r.getTextContent().startsWith(R) ? void 0 : "prefix" : "spacer"; } -function Fk(e) { +function rT(e) { if (e.isAttached()) { for (const t of e.getChildren()) - if (O(t) && zc(t, e) !== void 0) + if (O(t) && Hc(t, e) !== void 0) return t.getNextSibling()?.getTextContent() ?? ""; } } -function Fp(e, t) { +function Qp(e, t) { const r = w(); - if (!N(r) || !r.isCollapsed()) + if (!P(r) || !r.isCollapsed()) return !1; const n = r.anchor.getNode(); if (n.is(e) || n.is(t)) @@ -6654,33 +6655,33 @@ function Fp(e, t) { const i = e.getNextSibling(); return i !== null && n.is(i) && r.anchor.offset === 0; } -function zp(e) { +function Zp(e) { e.isAttached() && e.getChildren().forEach((t) => { if (!O(t)) return; - const r = zc(t, e); - if (r !== void 0 && !Fp(t, e)) + const r = Hc(t, e); + if (r !== void 0 && !Qp(t, e)) if (r === "prefix") { const n = t.getNextSibling(); - v(n) && n.setTextContent(I + n.getTextContent()); + S(n) && n.setTextContent(R + n.getTextContent()); } else - t.insertAfter(pe(I)); + t.insertAfter(he(R)); }); } -function Kp(e) { - return e.isAttached() ? e.getChildren().some((t) => O(t) && zc(t, e) !== void 0 && Fp(t, e)) : !1; +function eh(e) { + return e.isAttached() ? e.getChildren().some((t) => O(t) && Hc(t, e) !== void 0 && Qp(t, e)) : !1; } -const zk = "file", Kk = "src", jk = "colspan", Bk = "category", Vk = "alt", Wk = "closed", Hk = "false"; -function Gk(e) { - return e[Wk] !== Hk; +const nT = "file", iT = "src", sT = "colspan", oT = "category", aT = "alt", cT = "closed", lT = "false"; +function uT(e) { + return e[cT] !== lT; } -function Jk(e) { +function dT(e) { return Object.fromEntries(Object.entries(e).map(([t, r]) => [ - t === zk ? Kk : t, + t === nT ? iT : t, r ])); } -function jp(e, t) { +function th(e, t) { if (e === void 0) return; const r = Number(t); @@ -6695,8 +6696,8 @@ function jp(e, t) { } return n === e.length ? e : `${e}-${Number(e.slice(n)) + r - 1}`; } -function Bp(e, t, r) { - const n = r ?? {}, i = Gk(n); +function rh(e, t, r) { + const n = r ?? {}, i = uT(n); switch (e) { case "optbreak": return { opening: "//", attributes: "", closingAttributes: "", closing: "" }; @@ -6707,7 +6708,7 @@ function Bp(e, t, r) { return { opening: `\\${t} `, attributes: "", closingAttributes: "", closing: "" }; case "table:cell": return { - opening: `\\${jp(t, n[jk])} `, + opening: `\\${th(t, n[sT])} `, attributes: "", closingAttributes: "", closing: "" @@ -6716,23 +6717,23 @@ function Bp(e, t, r) { return { opening: `\\${t} `, attributes: "", - closingAttributes: ar(Jk(n), void 0), + closingAttributes: cr(dT(n), void 0), closing: i ? `\\${t}*` : "" }; case "sidebar": { - const { [Bk]: s, ...o } = n; + const { [oT]: s, ...o } = n; return { opening: "\\esb", - attributes: (s === void 0 ? "" : ` \\cat ${s}\\cat*`) + ar(o, void 0), + attributes: (s === void 0 ? "" : ` \\cat ${s}\\cat*`) + cr(o, void 0), closingAttributes: "", closing: i ? "\\esbe" : "" }; } case "periph": { - const { [Vk]: s, ...o } = n; + const { [aT]: s, ...o } = n; return { opening: `\\periph ${s ?? ""}`, - attributes: ar(o, void 0), + attributes: cr(o, void 0), closingAttributes: "", closing: "" }; @@ -6741,26 +6742,26 @@ function Bp(e, t, r) { return { opening: `\\${t} `, attributes: "", - closingAttributes: ar(n, void 0), + closingAttributes: cr(n, void 0), closing: i ? `\\${t}*` : "" }; } } -const xt = { wantsRun: !1, valueText: void 0 }, Nr = {}; -function ta(e, t) { +const _t = { wantsRun: !1, valueText: void 0 }, $r = {}; +function oa(e, t) { if (t === "va") return e; - const r = Yi(e, "va"); + const r = es(e, "va"); return r.wrapper ?? r.closer ?? e; } -function Kc(e) { +function Gc(e) { const t = w(); - if (!N(t) || !t.isCollapsed()) + if (!P(t) || !t.isCollapsed()) return !1; const r = t.anchor.getNode(); if (r.is(e) && t.anchor.offset === e.getTextContentSize()) return !0; - if (F(e)) { + if (L(e)) { const i = e.getLastDescendant(); if (i !== null && r.is(i) && t.anchor.offset === i.getTextContentSize()) return !0; @@ -6768,25 +6769,25 @@ function Kc(e) { const n = e.getNextSibling(); return n !== null && r.is(n) && t.anchor.offset === 0; } -function xo(e) { +function So(e) { const { opener: t, closer: r } = e; if (!t) return !1; const n = w(); - if (!N(n) || !n.isCollapsed()) + if (!P(n) || !n.isCollapsed()) return !1; const i = n.anchor.getNode(), s = i.is(t) && n.anchor.offset === t.getTextContentSize(); return r ? s || i.is(r) : s; } -function Yk(e) { - return Le(e) ? e.getRunKind() === "va" || e.getRunKind() === "vp" : O(e) ? e.getMarker() === "va" || e.getMarker() === "vp" : v(e) && ne(e, oe) === "attribute"; +function fT(e) { + return De(e) ? e.getRunKind() === "va" || e.getRunKind() === "vp" : O(e) ? e.getMarker() === "va" || e.getMarker() === "vp" : S(e) && ie(e, ae) === "attribute"; } -function Xk(e) { +function pT(e) { if (O(e)) { const n = e.getMarker(); return n === "va" || n === "vp" ? n : void 0; } - if (!v(e) || ne(e, oe) !== "attribute") + if (!S(e) || ie(e, ae) !== "attribute") return; const t = e.getPreviousSibling(); if (!O(t)) @@ -6794,32 +6795,32 @@ function Xk(e) { const r = t.getMarker(); return r === "va" || r === "vp" ? r : void 0; } -function ra(e) { +function aa(e) { for (let t = e.getPreviousSibling(); t; t = t.getPreviousSibling()) { - if (Oe(t)) + if (Ae(t)) return t; - if (!Yk(t)) + if (!fT(t)) return; } } -function Mu(e) { +function Ru(e) { return { kind: e, - ownerPredicate: (t) => Oe(t), + ownerPredicate: (t) => Ae(t), ownerOf: (t) => { - if (Le(t)) - return t.getRunKind() === e ? ra(t) : void 0; + if (De(t)) + return t.getRunKind() === e ? aa(t) : void 0; const r = t.getParent(); - return Le(r) ? r.getRunKind() === e ? ra(r) : void 0 : Xk(t) === e ? ra(t) : void 0; + return De(r) ? r.getRunKind() === e ? aa(r) : void 0 : pT(t) === e ? aa(t) : void 0; }, expectedPieces: (t) => { - if (!Oe(t)) - return xt; + if (!Ae(t)) + return _t; const r = e === "va" ? t.getAltnumber() : t.getPubnumber(); - return r === void 0 ? xt : { wantsRun: !0, valueText: I + r }; + return r === void 0 ? _t : { wantsRun: !0, valueText: R + r }; }, - scanPieces: (t) => Oe(t) ? Yi(ta(t, e), e) : Nr, - graceSite: (t, r) => Oe(t) ? !r.opener && !r.closer ? Kc(ta(t, e)) : xo(r) : !1, + scanPieces: (t) => Ae(t) ? es(oa(t, e), e) : $r, + graceSite: (t, r) => Ae(t) ? !r.opener && !r.closer ? Gc(oa(t, e)) : So(r) : !1, settleScope: "owner", deletionPolicy: "retokenize", byteFormat: { @@ -6828,48 +6829,48 @@ function Mu(e) { glyphs: "with-value", glyphMarker: () => e, closerSyntax: "closing", - insertRunAfter: (t) => Oe(t) ? ta(t, e) : void 0 + insertRunAfter: (t) => Ae(t) ? oa(t, e) : void 0 } }; } -const Qk = { +const hT = { kind: "separator", // The NBSP a char span shows after its opening glyph. Its "deletion" is a TEXT mutation (an NBSP // prefix edit), not node destruction, so it has no owner walk and no destruction pend — its // caret-grace path is what settles it, exactly as before joining the registry. - ownerPredicate: (e) => D(e), + ownerPredicate: (e) => I(e), ownerOf: () => { }, - expectedPieces: () => xt, - scanPieces: () => Nr, - graceSite: (e) => D(e) && Kp(e), + expectedPieces: () => _t, + scanPieces: () => $r, + graceSite: (e) => I(e) && eh(e), settleScope: "owner", deletionPolicy: "retokenize", byteFormat: { writer: "kind-owned", glyphs: "none" } -}, Zk = { +}, gT = { kind: "char", - ownerPredicate: (e) => D(e), + ownerPredicate: (e) => I(e), ownerOf: (e) => { - if (!v(e) || ne(e, oe) !== "attribute") + if (!S(e) || ie(e, ae) !== "attribute") return; const t = e.getParent(); - return D(t) ? t : void 0; + return I(t) ? t : void 0; }, expectedPieces: (e) => { - if (!D(e) || Di(e) === void 0) - return xt; - const t = ar(e.getUnknownAttributes() ?? {}, po(e.getMarker())); - return t === "" ? xt : { wantsRun: !0, valueText: t }; + if (!I(e) || Ki(e) === void 0) + return _t; + const t = cr(e.getUnknownAttributes() ?? {}, yo(e.getMarker())); + return t === "" ? _t : { wantsRun: !0, valueText: t }; }, - scanPieces: (e) => D(e) ? { value: Rp(e) } : Nr, + scanPieces: (e) => I(e) ? { value: Wp(e) } : $r, graceSite: (e, t) => { - if (!D(e) || t.value) + if (!I(e) || t.value) return !1; - const r = Di(e); + const r = Ki(e); if (!r) return !1; const n = w(); - if (!N(n) || !n.isCollapsed()) + if (!P(n) || !n.isCollapsed()) return !1; const i = n.anchor.getNode(); if (i.is(r)) @@ -6882,54 +6883,54 @@ const Qk = { byteFormat: { writer: "owner-children", glyphs: "none", - insertRunBefore: (e) => D(e) ? Di(e) : void 0 + insertRunBefore: (e) => I(e) ? Ki(e) : void 0 } }; -function Vp(e) { +function nh(e) { if (O(e)) return e.getMarker() === "cat"; - if (!v(e) || ne(e, oe) !== "attribute") + if (!S(e) || ie(e, ae) !== "attribute") return !1; const t = e.getPreviousSibling(); return O(t) && t.getMarker() === "cat"; } -function eT(e) { +function mT(e) { const t = e.getParent(); - if (!j(t)) + if (!K(t)) return; - const r = Xi(t); + const r = ts(t); if (r) for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) { if (n.is(r)) return t; - if (!Vp(n)) + if (!nh(n)) return; } } -const tT = { +const yT = { kind: "cat", - ownerPredicate: (e) => j(e), + ownerPredicate: (e) => K(e), ownerOf: (e) => { - if (Le(e)) - return e.getRunKind() === "cat" && j(e.getParent()) ? e.getParent() ?? void 0 : void 0; + if (De(e)) + return e.getRunKind() === "cat" && K(e.getParent()) ? e.getParent() ?? void 0 : void 0; const t = e.getParent(); - return Le(t) ? t.getRunKind() === "cat" && j(t.getParent()) ? t.getParent() ?? void 0 : void 0 : Vp(e) ? eT(e) : void 0; + return De(t) ? t.getRunKind() === "cat" && K(t.getParent()) ? t.getParent() ?? void 0 : void 0 : nh(e) ? mT(e) : void 0; }, expectedPieces: (e) => { - if (!j(e) || e.getIsCollapsed() !== !1) - return xt; + if (!K(e) || e.getIsCollapsed() !== !1) + return _t; const t = e.getCategory(); - return t === void 0 ? xt : { wantsRun: !0, valueText: I + t }; + return t === void 0 ? _t : { wantsRun: !0, valueText: R + t }; }, - scanPieces: (e) => j(e) ? $p(e) : Nr, + scanPieces: (e) => K(e) ? Hp(e) : $r, graceSite: (e, t) => { - if (!j(e)) + if (!K(e)) return !1; if (!t.opener && !t.closer) { - const r = Xi(e); - return r !== void 0 && Kc(r); + const r = ts(e); + return r !== void 0 && Gc(r); } - return xo(t); + return So(t); }, settleScope: "owner", deletionPolicy: "retokenize", @@ -6939,18 +6940,18 @@ const tT = { glyphs: "with-value", glyphMarker: () => "cat", closerSyntax: "closing", - insertRunAfter: (e) => j(e) ? Xi(e) : void 0 + insertRunAfter: (e) => K(e) ? ts(e) : void 0 } }; -function rT(e) { - return Le(e) ? e.getRunKind() === "ca" || e.getRunKind() === "cp" : O(e) ? e.getMarker() === "ca" || e.getMarker() === "cp" : v(e) && ne(e, oe) === "attribute"; +function bT(e) { + return De(e) ? e.getRunKind() === "ca" || e.getRunKind() === "cp" : O(e) ? e.getMarker() === "ca" || e.getMarker() === "cp" : S(e) && ie(e, ae) === "attribute"; } -function nT(e) { +function kT(e) { if (O(e)) { const n = e.getMarker(); return n === "ca" || n === "cp" ? n : void 0; } - if (!v(e) || ne(e, oe) !== "attribute") + if (!S(e) || ie(e, ae) !== "attribute") return; const t = e.getPreviousSibling(); if (!O(t)) @@ -6958,45 +6959,45 @@ function nT(e) { const r = t.getMarker(); return r === "ca" || r === "cp" ? r : void 0; } -function iT(e) { +function TT(e) { const t = e.getParent(); - if (!Pe(t)) + if (!we(t)) return; - const r = bo(t); + const r = _o(t); if (r) for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) { if (n.is(r)) return t; - if (!rT(n)) + if (!bT(n)) return; } } -function Eu(e) { - const t = (r) => Pe(r) ? e === "ca" ? bo(r) : Lp(r) : void 0; +function $u(e) { + const t = (r) => we(r) ? e === "ca" ? _o(r) : Jp(r) : void 0; return { kind: e, - ownerPredicate: (r) => Pe(r), + ownerPredicate: (r) => we(r), ownerOf: (r) => { - if (Le(r)) - return r.getRunKind() === e && Pe(r.getParent()) ? r.getParent() ?? void 0 : void 0; + if (De(r)) + return r.getRunKind() === e && we(r.getParent()) ? r.getParent() ?? void 0 : void 0; const n = r.getParent(); - return Le(n) ? n.getRunKind() === e && Pe(n.getParent()) ? n.getParent() ?? void 0 : void 0 : nT(r) === e ? iT(r) : void 0; + return De(n) ? n.getRunKind() === e && we(n.getParent()) ? n.getParent() ?? void 0 : void 0 : kT(r) === e ? TT(r) : void 0; }, expectedPieces: (r) => { - if (!Pe(r)) - return xt; + if (!we(r)) + return _t; const n = e === "ca" ? r.getAltnumber() : r.getPubnumber(); - return n === void 0 ? xt : { wantsRun: !0, valueText: I + n }; + return n === void 0 ? _t : { wantsRun: !0, valueText: R + n }; }, - scanPieces: (r) => Pe(r) ? e === "ca" ? Ip(r) : Dp(r) : Nr, + scanPieces: (r) => we(r) ? e === "ca" ? Gp(r) : Yp(r) : $r, graceSite: (r, n) => { - if (!Pe(r)) + if (!we(r)) return !1; if (!n.opener && !n.closer) { const i = t(r); - return i !== void 0 && Kc(i); + return i !== void 0 && Gc(i); } - return xo(n); + return So(n); }, settleScope: "owner", deletionPolicy: "retokenize", @@ -7010,51 +7011,51 @@ function Eu(e) { } }; } -function Wp(e) { +function ih(e) { if (O(e)) { const t = e.getMarkerSyntax(); return t === "selfClosing" || t === "opening"; } - return v(e) && ne(e, oe) === "attribute"; + return S(e) && ie(e, ae) === "attribute"; } -function sT(e) { +function xT(e) { for (let t = e.getPreviousSibling(); t; t = t.getPreviousSibling()) { - if (Be(t)) { + if (He(t)) { const r = O(e) && e.getMarkerSyntax() === "opening" ? e : void 0; return !r || r.getMarker() === t.getMarker() ? t : void 0; } - if (!Wp(t)) + if (!ih(t)) return; } } -const oT = { +const _T = { kind: "milestone", - ownerPredicate: (e) => Be(e), + ownerPredicate: (e) => He(e), ownerOf: (e) => { - const t = Le(e) ? e.getRunKind() === "milestone" ? e : void 0 : Le(e.getParent()) ? e.getParent() : Wp(e) ? e : void 0; - if (!t || Le(t) && t.getRunKind() !== "milestone") + const t = De(e) ? e.getRunKind() === "milestone" ? e : void 0 : De(e.getParent()) ? e.getParent() : ih(e) ? e : void 0; + if (!t || De(t) && t.getRunKind() !== "milestone") return; const r = t.getPreviousSibling(); - return Le(t) ? Be(r) ? r : void 0 : sT(t); + return De(t) ? He(r) ? r : void 0 : xT(t); }, expectedPieces: (e) => { - if (!Be(e)) - return xt; - const t = qp(e.getSid(), e.getEid(), e.getUnknownAttributes(), e.getAttributeOrder()), r = ar(t, ho(e.getMarker())); - return { wantsRun: !0, valueText: r === "" ? void 0 : I + r }; + if (!He(e)) + return _t; + const t = Vp(e.getSid(), e.getEid(), e.getUnknownAttributes(), e.getAttributeOrder()), r = cr(t, bo(e.getMarker())); + return { wantsRun: !0, valueText: r === "" ? void 0 : R + r }; }, scanPieces: (e) => { - if (!Be(e)) - return Nr; - const { opening: t, attribute: r, closing: n, wrapper: i } = ko(e); + if (!He(e)) + return $r; + const { opening: t, attribute: r, closing: n, wrapper: i } = Co(e); return { opener: t, value: r, closer: n, wrapper: i }; }, graceSite: (e, t) => { - if (!Be(e)) + if (!He(e)) return !1; if (!t.opener && !t.closer) { const r = w(); - if (!N(r) || !r.isCollapsed()) + if (!P(r) || !r.isCollapsed()) return !1; const n = r.anchor.getNode(), i = e.getPreviousSibling(); if (i !== null && n.is(i) && r.anchor.offset === i.getTextContentSize()) @@ -7062,7 +7063,7 @@ const oT = { const s = e.getNextSibling(); return s !== null && n.is(s) && r.anchor.offset === 0; } - return xo(t); + return So(t); }, settleScope: "owner", deletionPolicy: "remove-owner", @@ -7070,17 +7071,17 @@ const oT = { writer: "wrapper", runKind: "milestone", glyphs: "unconditional", - glyphMarker: (e) => Be(e) ? e.getMarker() : "", + glyphMarker: (e) => He(e) ? e.getMarker() : "", closerSyntax: "selfClosing", insertRunAfter: (e) => e } -}, aT = Bp("optbreak", void 0, void 0).opening, cT = { +}, CT = rh("optbreak", void 0, void 0).opening, vT = { kind: "optbreak", - ownerPredicate: (e) => De(e) && e.getTag() === "optbreak", + ownerPredicate: (e) => Ue(e) && e.getTag() === "optbreak", ownerOf: (e) => { const t = e.getParent(); - if (!(!De(t) || t.getTag() !== "optbreak")) - return v(e) || Yt(e) ? t : void 0; + if (!(!Ue(t) || t.getTag() !== "optbreak")) + return S(e) || Yt(e) ? t : void 0; }, // `valueText` is the RENDERED BYTES the kind owes — so `$runDiverges`'s value-byte comparison // classifies the scanned token by what it actually spells: a canonical `//` is at rest, a @@ -7089,13 +7090,13 @@ const oT = { // CANONICAL optbreak read as diverged while a GUTTED one read as at rest. Nothing ever WRITES // from this (the `"read-only"` writer returns before any sync write), so the value is purely // classificatory. - expectedPieces: () => ({ wantsRun: !0, valueText: aT }), - scanPieces: (e) => De(e) ? { value: e.getFirstChild() ?? void 0 } : Nr, + expectedPieces: () => ({ wantsRun: !0, valueText: CT }), + scanPieces: (e) => Ue(e) ? { value: e.getFirstChild() ?? void 0 } : $r, graceSite: () => !1, settleScope: "owner", deletionPolicy: "remove-owner", byteFormat: { writer: "read-only", glyphs: "none" } -}, lT = { +}, ST = { kind: "opaqueUnknown", // Scope is every UnknownNode kind EXCEPT optbreak — `ownerPredicate` excludes it explicitly, so // `optbreakDescriptor` above is the sole owner of that kind. A non-optbreak UnknownNode is a @@ -7105,83 +7106,83 @@ const oT = { // `remove-owner` shape — i.e. isn't entirely absent — falls through unhandled by either // descriptor instead; harmlessly inert, since `$settleScopeForNode` refuses every `UnknownNode` // outright, so the caller's `$requestTier2ForNode` fallback always bails on it too.) - ownerPredicate: (e) => De(e) && e.getTag() !== "optbreak", + ownerPredicate: (e) => Ue(e) && e.getTag() !== "optbreak", ownerOf: () => { }, - expectedPieces: () => xt, - scanPieces: () => Nr, + expectedPieces: () => _t, + scanPieces: () => $r, graceSite: () => !1, settleScope: "owner", deletionPolicy: "none", byteFormat: { writer: "read-only", glyphs: "none" } -}, uT = { +}, MT = { kind: "nestedGlyph", // The `+` on a nested span's glyphs. Purely tree-derived and rewritten in place by its own sync; // there is no state a user edit can leave half-finished, so it owes no pend or deletion duty. - ownerPredicate: (e) => D(e), + ownerPredicate: (e) => I(e), ownerOf: () => { }, - expectedPieces: () => xt, - scanPieces: () => Nr, + expectedPieces: () => _t, + scanPieces: () => $r, graceSite: () => !1, settleScope: "none", deletionPolicy: "none", byteFormat: { writer: "kind-owned", glyphs: "none" } -}, Qi = [ - Qk, - Zk, - Mu("va"), - Mu("vp"), - tT, - Eu("ca"), - Eu("cp"), - oT, - cT, - lT, - uT -], dT = new Map(Qi.map((e) => [e.kind, e])); -function xn(e) { - const t = dT.get(e); +}, rs = [ + hT, + gT, + Ru("va"), + Ru("vp"), + yT, + $u("ca"), + $u("cp"), + _T, + vT, + ST, + MT +], ET = new Map(rs.map((e) => [e.kind, e])); +function vn(e) { + const t = ET.get(e); if (!t) throw new Error(`No display-run descriptor registered for kind "${e}"`); return t; } -function _n(e) { - for (const t of Qi) { +function Sn(e) { + for (const t of rs) { const r = t.ownerOf(e); if (r) return { owner: r, kind: t.kind }; } } -function Hp(e) { - return _n(e) !== void 0; +function sh(e) { + return Sn(e) !== void 0; } -const js = "unmatched", Gp = 2; -function Ui(e) { +const Hs = "unmatched", oh = 2; +function ji(e) { return `\\${e}`; } -class Or extends Ke { +class Lr extends Be { __marker; constructor(t = "", r) { - super(Ui(t), r), this.__marker = t, this.__mode = 1; + super(ji(t), r), this.__marker = t, this.__mode = 1; } static getType() { return "unmatched"; } static clone(t) { const { __marker: r, __key: n } = t; - return new Or(r, n); + return new Lr(r, n); } static importDOM() { return { - [js]: (t) => pT(t) ? { - conversion: fT, + [Hs]: (t) => PT(t) ? { + conversion: AT, priority: 1 } : null }; } static importJSON(t) { - return jc().updateFromJSON(t); + return Jc().updateFromJSON(t); } updateFromJSON(t) { const r = t.marker ?? "", i = super.updateFromJSON({ @@ -7190,7 +7191,7 @@ class Or extends Ke { format: t.format ?? 0, mode: t.mode ?? "token", style: t.style ?? "", - text: t.text ?? Ui(r) + text: t.text ?? ji(r) }).getWritable(); return i.__marker = r, i; } @@ -7198,29 +7199,29 @@ class Or extends Ke { if (this.__marker === t) return this; const r = this.getWritable(); - return r.__marker = t, r.__text = Ui(t), r; + return r.__marker = t, r.__text = ji(t), r; } getMarker() { return this.getLatest().__marker; } createDOM(t) { const r = super.createDOM(t); - return r.setAttribute("data-marker", this.__marker), r.classList.add(cu), r.title = Au(this.__marker), r; + return r.setAttribute("data-marker", this.__marker), r.classList.add(mu), r.title = Lu(this.__marker), r; } updateDOM(t, r, n) { const i = super.updateDOM(t, r, n); - return t.__marker !== this.__marker && (r.setAttribute("data-marker", this.__marker), r.title = Au(this.__marker)), i; + return t.__marker !== this.__marker && (r.setAttribute("data-marker", this.__marker), r.title = Lu(this.__marker)), i; } exportDOM() { - const t = document.createElement(js); - return t.setAttribute("data-marker", this.getMarker()), t.classList.add(cu), t.textContent = this.getTextContent(), { element: t }; + const t = document.createElement(Hs); + return t.setAttribute("data-marker", this.getMarker()), t.classList.add(mu), t.textContent = this.getTextContent(), { element: t }; } exportJSON() { return { ...super.exportJSON(), type: this.getType(), marker: this.getMarker(), - version: Gp + version: oh }; } canInsertTextBefore() { @@ -7230,39 +7231,39 @@ class Or extends Ke { return !1; } } -function Jp(e) { - return e.getTextContent() === Ui(e.getMarker()); +function ah(e) { + return e.getTextContent() === ji(e.getMarker()); } -function Au(e) { +function Lu(e) { return e.endsWith("*") ? "This closing marker has no matching opening marker!" : "This opening marker has no matching closing marker!"; } -function fT(e) { +function AT(e) { const t = e.getAttribute("data-marker") ?? ""; - return { node: jc(t) }; + return { node: Jc(t) }; } -function jc(e) { - return je(new Or(e)); +function Jc(e) { + return Ve(new Lr(e)); } -function pT(e) { - return e?.tagName.toLowerCase() === js; +function PT(e) { + return e?.tagName.toLowerCase() === Hs; } -function Qr(e) { - return e instanceof Or; +function Ir(e) { + return e instanceof Lr; } -const Yp = "table", qa = "immutable-table", Xp = 1, hT = ["type", "marker", "content"]; -class On extends Zt { +const ch = "table", Ua = "immutable-table", lh = 1, NT = ["type", "marker", "content"]; +class Rn extends Zt { __unknownAttributes; constructor(t, r) { super(r), this.__unknownAttributes = t; } static getType() { - return qa; + return Ua; } static clone(t) { - return new On(t.__unknownAttributes, t.__key); + return new Rn(t.__unknownAttributes, t.__key); } static importJSON(t) { - return gT().updateFromJSON(t); + return OT().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setUnknownAttributes(t.unknownAttributes); @@ -7285,9 +7286,9 @@ class On extends Zt { const t = this.getUnknownAttributes(); return { ...super.exportJSON(), - type: qa, + type: Ua, ...t !== void 0 && { unknownAttributes: t }, - version: Xp + version: lh }; } // Shadow root: isolate selection so content doesn't merge across the table boundary. @@ -7295,33 +7296,33 @@ class On extends Zt { return !0; } } -function gT(e) { - return je(new On(e)); +function OT(e) { + return Ve(new Rn(e)); } -function Qp(e) { - return e instanceof On; +function uh(e) { + return e instanceof Rn; } -function mT(e) { - return e?.type === qa; +function wT(e) { + return e?.type === Ua; } -const Zp = "table:row", Pu = "immutable-table-row", eh = 1, Ra = "tr", yT = ["type", "marker", "content"]; -class fi extends Zt { +const dh = "table:row", Iu = "immutable-table-row", fh = 1, Fa = "tr", qT = ["type", "marker", "content"]; +class gi extends Zt { __marker; __unknownAttributes; - constructor(t = Ra, r, n) { + constructor(t = Fa, r, n) { super(n), this.__marker = t, this.__unknownAttributes = r; } static getType() { - return Pu; + return Iu; } static clone(t) { - return new fi(t.__marker, t.__unknownAttributes, t.__key); + return new gi(t.__marker, t.__unknownAttributes, t.__key); } static importJSON(t) { - return bT().updateFromJSON(t); + return RT().updateFromJSON(t); } updateFromJSON(t) { - return super.updateFromJSON(t).setMarker(t.marker ?? Ra).setUnknownAttributes(t.unknownAttributes); + return super.updateFromJSON(t).setMarker(t.marker ?? Fa).setUnknownAttributes(t.unknownAttributes); } setMarker(t) { if (this.__marker === t) @@ -7350,46 +7351,46 @@ class fi extends Zt { const t = this.getUnknownAttributes(); return { ...super.exportJSON(), - type: Pu, + type: Iu, marker: this.getMarker(), ...t !== void 0 && { unknownAttributes: t }, - version: eh + version: fh }; } } -function bT(e, t) { - return je(new fi(e, t)); +function RT(e, t) { + return Ve(new gi(e, t)); } -const th = "table:cell", Nu = "immutable-table-cell", rh = 1, $a = "tc1", kT = [ +const ph = "table:cell", Du = "immutable-table-cell", hh = 1, za = "tc1", $T = [ "type", "marker", "align", "colspan", "content" ]; -function TT(e) { +function LT(e) { return e === "start" || e === "center" || e === "end" ? e : void 0; } -class pi extends Zt { +class mi extends Zt { __marker; __align; __colspan; __unknownAttributes; - constructor(t = $a, r, n, i, s) { + constructor(t = za, r, n, i, s) { super(s), this.__marker = t, this.__align = r, this.__colspan = n, this.__unknownAttributes = i; } static getType() { - return Nu; + return Du; } static clone(t) { const { __marker: r, __align: n, __colspan: i, __unknownAttributes: s, __key: o } = t; - return new pi(r, n, i, s, o); + return new mi(r, n, i, s, o); } static importJSON(t) { - return xT().updateFromJSON(t); + return IT().updateFromJSON(t); } updateFromJSON(t) { - return super.updateFromJSON(t).setMarker(t.marker ?? $a).setAlign(t.align).setColspan(t.colspan).setUnknownAttributes(t.unknownAttributes); + return super.updateFromJSON(t).setMarker(t.marker ?? za).setAlign(t.align).setColspan(t.colspan).setUnknownAttributes(t.unknownAttributes); } setMarker(t) { if (this.__marker === t) @@ -7428,7 +7429,7 @@ class pi extends Zt { createDOM() { const t = this.__marker.startsWith("th"), r = document.createElement(t ? "th" : "td"); r.setAttribute("data-marker", this.__marker), r.classList.add("table-cell", `usfm_${this.__marker}`); - const n = TT(this.__align); + const n = LT(this.__align); return n && (r.style.textAlign = n), this.__colspan && r.setAttribute("colspan", this.__colspan), r; } updateDOM(t) { @@ -7438,324 +7439,324 @@ class pi extends Zt { const t = this.getAlign(), r = this.getColspan(), n = this.getUnknownAttributes(); return { ...super.exportJSON(), - type: Nu, + type: Du, marker: this.getMarker(), ...t !== void 0 && { align: t }, ...r !== void 0 && { colspan: r }, ...n !== void 0 && { unknownAttributes: n }, - version: rh + version: hh }; } } -function xT(e, t, r, n) { - return je(new pi(e, t, r, n)); +function IT(e, t, r, n) { + return Ve(new mi(e, t, r, n)); } -function _o(e, t) { +function Mo(e, t) { const r = e.getChildAtIndex(t); - return v(r) ? r : void 0; + return S(r) ? r : void 0; } function Qt(e, t) { - const r = _o(e, t); + const r = Mo(e, t); r ? r.select(0, 0) : e.select(t, t); } -function Zi(e) { +function ns(e) { return e.getUnknownAttributes()?.closed !== "false"; } -function _T(e) { +function DT(e) { return e.getChildren().some((t) => O(t) && t.getMarkerSyntax() === "closing"); } -function CT(e) { - return Zi(e) ? void 0 : { closed: "false" }; +function UT(e) { + return ns(e) ? void 0 : { closed: "false" }; } -function ST(e, t, r, n) { - const i = t.getMarker(), s = Fc(t), o = _T(t); +function FT(e, t, r, n) { + const i = t.getMarker(), s = Wc(t), o = DT(t); if (n) { - e.append(ot(i, "opening", s)); + e.append(lt(i, "opening", s)); const [a] = r; - To(a) && !a.getTextContent().startsWith(I) && a.setTextContent(I + a.getTextContent()); + vo(a) && !a.getTextContent().startsWith(R) && a.setTextContent(R + a.getTextContent()); } - e.append(...r), o && e.append(ot(i, "closing", s)); + e.append(...r), o && e.append(lt(i, "closing", s)); } -function Cn(e) { - return nt(e, D) ?? void 0; +function Mn(e) { + return rt(e, I) ?? void 0; } -function Bc(e) { +function Yc(e) { let t = e.getParent(); - for (; D(t); ) + for (; I(t); ) t = t.getParent(); return t; } -function Ia(e) { - const t = nh(e); - return e.getChildren().every((r) => O(r) || t && ne(r, oe) === "attribute" || v(r) && r.getTextContent().replaceAll(I, "") === ""); +function Ka(e) { + const t = gh(e); + return e.getChildren().every((r) => O(r) || t && ie(r, ae) === "attribute" || S(r) && r.getTextContent().replaceAll(R, "") === ""); } -function nh(e) { - return Zi(e); +function gh(e) { + return ns(e); } -function vT(e, t) { - const r = e.getUnknownAttributes(), n = r ? ar(r, po(e.getMarker())) : ""; - n !== "" && t.insertAfter(pe(n)), e.remove(); +function zT(e, t) { + const r = e.getUnknownAttributes(), n = r ? cr(r, yo(e.getMarker())) : ""; + n !== "" && t.insertAfter(he(n)), e.remove(); } -function MT(e, t) { - if (Zi(e)) +function KT(e, t) { + if (ns(e)) return; const r = e.getUnknownAttributes(); if (r) { const n = { ...r }; delete n.closed, e.setUnknownAttributes(Object.keys(n).length > 0 ? n : void 0); } - t && e.append(ot(e.getMarker(), "closing", Fc(e))); + t && e.append(lt(e.getMarker(), "closing", Wc(e))); } -function ET(e, t) { - return D(e) && !Zi(e) && !Zi(t); +function jT(e, t) { + return I(e) && !ns(e) && !ns(t); } -function AT(e, t, r) { - Ia(e) && e.getChildren().forEach((i) => { +function BT(e, t, r) { + Ka(e) && e.getChildren().forEach((i) => { O(i) || i.remove(); }); const [n] = t; - r && To(n) && !n.getTextContent().startsWith(I) && n.setTextContent(I + n.getTextContent()), e.append(...t); + r && vo(n) && !n.getTextContent().startsWith(R) && n.setTextContent(R + n.getTextContent()), e.append(...t); } -function PT(e, t, r) { - const { renderGlyphs: n, closeImplicitSpans: i = !1 } = r, s = nh(t), o = []; +function VT(e, t, r) { + const { renderGlyphs: n, closeImplicitSpans: i = !1 } = r, s = gh(t), o = []; for (let l = e.getNextSibling(); l; ) { - const u = l.getNextSibling(), d = O(l) && l.getMarkerSyntax() === "closing", f = s && ne(l, oe) === "attribute"; - !d && !f && o.push(l), l = u; + const d = l.getNextSibling(), u = O(l) && l.getMarkerSyntax() === "closing", p = s && ie(l, ae) === "attribute"; + !u && !p && o.push(l), l = d; } - const a = ET(e, t); + const a = jT(e, t); t.insertAfter(e); let c = e; if (o.length > 0) if (a) - AT(e, o, n); + BT(e, o, n); else { - const l = xr(t.getMarker(), CT(t)); - ST(l, t, o, n), e.insertAfter(l), Ia(l) ? l.remove() : c = l; + const l = Mr(t.getMarker(), UT(t)); + FT(l, t, o, n), e.insertAfter(l), Ka(l) ? l.remove() : c = l; } - i && !a && MT(t, n), Ia(t) && vT(t, c); + i && !a && KT(t, n), Ka(t) && zT(t, c); } -function ni(e, t) { +function si(e, t) { let r = e.getParent(); - for (; D(r); ) - PT(e, r, t), r = e.getParent(); + for (; I(r); ) + VT(e, r, t), r = e.getParent(); } -function Vc(e) { - if (v(e) && !O(e)) { - const t = e.getTextContent().startsWith(I) ? 1 : 0; +function Xc(e) { + if (S(e) && !O(e)) { + const t = e.getTextContent().startsWith(R) ? 1 : 0; e.select(t, t); return; } - if (F(e)) { + if (L(e)) { const t = e.getChildren().find((r) => !O(r)); if (t) { - Vc(t); + Xc(t); return; } e.selectEnd(); } } -const Xn = /* @__PURE__ */ new WeakMap(); -function NT(e, t) { - return Xn.set(e, t), () => { - Xn.get(e) === t && Xn.delete(e); +const Zn = /* @__PURE__ */ new WeakMap(); +function WT(e, t) { + return Zn.set(e, t), () => { + Zn.get(e) === t && Zn.delete(e); }; } -function Ou(e) { - return Xn.get(e); +function Uu(e) { + return Zn.get(e); } -function OT(e) { - return Xn.get(Zn())?.has(e.getKey()) ?? !1; +function HT(e) { + return Zn.get(ti())?.has(e.getKey()) ?? !1; } -function wT(e) { - Xn.get(Zn())?.add(e.getKey()); +function GT(e) { + Zn.get(ti())?.add(e.getKey()); } -function qT(e) { +function JT(e) { return !!(e.opener || e.value || e.closer || e.wrapper); } -function La(e) { +function ja(e) { return !!(e.opener || e.value || e.closer); } -function wu(e) { +function Fu(e) { return /^\s/.test(e); } -function Wc(e, t) { +function Qc(e, t) { if (e === t) return !1; - if (e === void 0 || t === void 0 || !wu(t) || !wu(e)) + if (e === void 0 || t === void 0 || !Fu(t) || !Fu(e)) return !0; const r = t.trim(); return r === "" || e.trim() !== r; } -function Co(e, t, r) { - return r.wantsRun ? Wc(t.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!t.opener || e.byteFormat.closerSyntax !== "none" && !t.closer) ? !0 : e.byteFormat.writer === "wrapper" && t.wrapper === void 0 : qT(t); +function Eo(e, t, r) { + return r.wantsRun ? Qc(t.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!t.opener || e.byteFormat.closerSyntax !== "none" && !t.closer) ? !0 : e.byteFormat.writer === "wrapper" && t.wrapper === void 0 : JT(t); } -function RT(e, t) { +function YT(e, t) { if (e.byteFormat.writer !== "wrapper") return !1; const r = e.expectedPieces(t); if (!r.wantsRun) return !1; const n = e.scanPieces(t); - return Wc(n.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!n.opener || e.byteFormat.closerSyntax !== "none" && !n.closer) ? !1 : n.wrapper === void 0; + return Qc(n.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!n.opener || e.byteFormat.closerSyntax !== "none" && !n.closer) ? !1 : n.wrapper === void 0; } -function ih(e, t) { - return !La(e.scanPieces(t)); +function mh(e, t) { + return !ja(e.scanPieces(t)); } -function gs(e, t) { +function Ts(e, t) { if (!t.isAttached()) return !1; if (e.byteFormat.writer === "kind-owned") return e.graceSite(t, {}); const r = e.expectedPieces(t), n = e.scanPieces(t); - if (!Co(e, n, r)) + if (!Eo(e, n, r)) return !1; const i = w(); - if (!N(i) || !i.isCollapsed()) + if (!P(i) || !i.isCollapsed()) return !1; const s = i.anchor.getNode(), { wrapper: o, value: a } = n; - return o && (s.is(o) || Ks(s, o.getKey())) ? !0 : a ? s.is(a) : e.graceSite(t, n); + return o && (s.is(o) || Ws(s, o.getKey())) ? !0 : a ? s.is(a) : e.graceSite(t, n); } -function $T(e, t, r, n) { - return !r.wantsRun || La(n) || gy(Vi) ? !1 : Zn().getEditorState().read(() => { - const i = se(t.getKey()); - return !i || !e.ownerPredicate(i) ? !1 : La(e.scanPieces(i)); +function XT(e, t, r, n) { + return !r.wantsRun || ja(n) || Ay(Ji) ? !1 : ti().getEditorState().read(() => { + const i = oe(t.getKey()); + return !i || !e.ownerPredicate(i) ? !1 : ja(e.scanPieces(i)); }); } -function IT(e) { +function QT(e) { e.opener?.remove(), e.value?.remove(), e.closer?.remove(); } -function qu(e) { - const t = pe(e); - return bt(t, oe, "attribute"), t; +function zu(e) { + const t = he(e); + return kt(t, ae, "attribute"), t; } -function LT(e, t, r) { +function ZT(e, t, r) { if (r.wrapper) return r.wrapper; const { runKind: n, insertRunAfter: i } = e.byteFormat, s = i?.(t); if (!n || !s) return; - const o = rp(n); + const o = hp(n); return s.insertAfter(o), r.opener && o.append(r.opener), r.value && o.append(r.value), r.closer && o.append(r.closer), o; } -function DT(e, t, r, n) { +function ex(e, t, r, n) { const { writer: i, glyphs: s, glyphMarker: o, closerSyntax: a, insertRunBefore: c } = e.byteFormat; if (i === "owner-children") { - const f = c?.(t); - if (!f || n.valueText === void 0) + const p = c?.(t); + if (!p || n.valueText === void 0) return; - v(r.value) ? r.value.setTextContent(n.valueText) : f.insertBefore(qu(n.valueText)); + S(r.value) ? r.value.setTextContent(n.valueText) : p.insertBefore(zu(n.valueText)); return; } - const l = LT(e, t, r); + const l = ZT(e, t, r); if (!l || s === "none" || !o || !a) return; - const u = r.opener ?? (() => { - const f = ot(o(t), "opening"), p = l.getFirstChild(); - return p ? p.insertBefore(f) : l.append(f), f; + const d = r.opener ?? (() => { + const p = lt(o(t), "opening"), f = l.getFirstChild(); + return f ? f.insertBefore(p) : l.append(p), p; })(); - let d = r.value; - n.valueText === void 0 ? (d?.remove(), d = void 0) : v(d) ? Wc(d.getTextContent(), n.valueText) && d.setTextContent(n.valueText) : (d = qu(n.valueText), u.insertAfter(d)), a !== "none" && !r.closer && (d ?? u).insertAfter(ot(a === "selfClosing" ? "" : o(t), a)); + let u = r.value; + n.valueText === void 0 ? (u?.remove(), u = void 0) : S(u) ? Qc(u.getTextContent(), n.valueText) && u.setTextContent(n.valueText) : (u = zu(n.valueText), d.insertAfter(u)), a !== "none" && !r.closer && (u ?? d).insertAfter(lt(a === "selfClosing" ? "" : o(t), a)); } -function es(e, t) { +function is(e, t) { const { writer: r } = e.byteFormat; if (r === "kind-owned" || r === "read-only" || !t.isAttached()) return; const n = e.expectedPieces(t), i = e.scanPieces(t); - if (Co(e, i, n) && !OT(t)) { - if ($T(e, t, n, i)) { - wT(t); + if (Eo(e, i, n) && !HT(t)) { + if (XT(e, t, n, i)) { + GT(t); return; } - if (!gs(e, t)) { + if (!Ts(e, t)) { if (!n.wantsRun) { - IT(i); + QT(i); return; } - DT(e, t, i, n); + ex(e, t, i, n); } } } -function UT(e, t, r) { - es(e, t), t.isAttached() && gs(e, t) && r.add(t.getKey()); +function tx(e, t, r) { + is(e, t), t.isAttached() && Ts(e, t) && r.add(t.getKey()); } -function sh(e) { - if (!v(e)) +function yh(e) { + if (!S(e)) return !1; - if (O(e) || Oe(e) || Qr(e)) + if (O(e) || Ae(e) || Ir(e)) return !0; - const t = ne(e, oe); - return t === "attribute" || t === fr; + const t = ie(e, ae); + return t === "attribute" || t === pr; } -function Hc(e, t) { - return O(e) ? !(t === e.getTextContentSize() && e.getMarkerSyntax() !== "opening" && Xr(e) && D(e.getParent())) : !1; +function Zc(e, t) { + return O(e) ? !(t === e.getTextContentSize() && e.getMarkerSyntax() !== "opening" && rn(e) && I(e.getParent())) : !1; } -function FT() { +function rx() { const e = w(); - return N(e) ? Hc(e.focus.getNode(), e.focus.offset) : !1; + return P(e) ? Zc(e.focus.getNode(), e.focus.offset) : !1; } -function oh(e) { +function bh(e) { if (e.type !== "text") return; const t = e.getNode(); - return v(t) && sh(t) ? t : void 0; + return S(t) && yh(t) ? t : void 0; } -function zT(e) { - const t = oh(e); +function nx(e) { + const t = bh(e); if (t) return e.offset > 0 && e.offset < t.getTextContentSize() ? t : void 0; } -function KT(e) { - const t = oh(e); +function ix(e) { + const t = bh(e); if (t) return e.offset === 0 || e.offset === t.getTextContentSize() ? t : void 0; } -function Ru(e) { +function Ku(e) { return { key: e.key, offset: e.offset, type: e.type }; } -function $u(e, t) { +function ju(e, t) { e.set(t.key, t.offset, t.type); } -function jT(e, t) { - let r = KT(e); +function sx(e, t) { + let r = ix(e); for (; r; ) { const n = t === "next" ? r.getNextSibling() : r.getPreviousSibling(); - if (!v(n)) + if (!S(n)) return; - if (!sh(n)) + if (!yh(n)) return { node: n, offset: t === "next" ? 0 : n.getTextContentSize() }; r = n; } } -function Iu(e, t) { - const r = jT(e, t); +function Bu(e, t) { + const r = sx(e, t); return r ? (e.set(r.node.getKey(), r.offset, "text"), !0) : !1; } -function ah(e) { +function kh(e) { if (e.isCollapsed()) { - const a = zT(e.anchor); + const a = nx(e.anchor); if (!a) return !1; const c = a.getTextContentSize(); return e.anchor.set(a.getKey(), c, "text"), e.focus.set(a.getKey(), c, "text"), !0; } - const t = e.isBackward(), r = t ? e.focus : e.anchor, n = t ? e.anchor : e.focus, i = [Ru(r), Ru(n)], s = Iu(r, "next"), o = Iu(n, "previous"); - return !s && !o ? !1 : e.isCollapsed() || e.isBackward() !== t ? ($u(r, i[0]), $u(n, i[1]), !1) : !0; + const t = e.isBackward(), r = t ? e.focus : e.anchor, n = t ? e.anchor : e.focus, i = [Ku(r), Ku(n)], s = Bu(r, "next"), o = Bu(n, "previous"); + return !s && !o ? !1 : e.isCollapsed() || e.isBackward() !== t ? (ju(r, i[0]), ju(n, i[1]), !1) : !0; } -const Bs = "verse-block", ch = 1, BT = "verse-block"; -class hi extends Zt { +const Gs = "verse-block", Th = 1, ox = "verse-block"; +class yi extends Zt { /** The verse marker verbatim. Authoritative: the range is derived from it, never stored. */ __number; constructor(t = "", r) { super(r), this.__number = t; } static getType() { - return Bs; + return Gs; } static clone(t) { - return new hi(t.__number, t.__key); + return new yi(t.__number, t.__key); } static importJSON(t) { - return VT().updateFromJSON(t); + return ax().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setNumber(t.number); @@ -7771,14 +7772,14 @@ class hi extends Zt { } /** The first and last verse numbers this block covers. A bridge covers more than one. */ getRange() { - return Ap(this.getNumber()); + return Fp(this.getNumber()); } createDOM() { const t = document.createElement("div"); - return t.classList.add(BT), Lu(t, this.__number), t; + return t.classList.add(ox), Vu(t, this.__number), t; } updateDOM(t, r) { - return t.__number !== this.__number && Lu(r, this.__number), !1; + return t.__number !== this.__number && Vu(r, this.__number), !1; } // No `exportDOM`/`importDOM`: Lexical's default export already emits `createDOM`'s element, so // the HTML flavor of a copied passage carries these wrappers, which an ordinary paste unwraps @@ -7800,48 +7801,48 @@ class hi extends Zt { exportJSON() { return { ...super.exportJSON(), - type: Bs, + type: Gs, number: this.getNumber(), - version: ch + version: Th }; } canBeEmpty() { return !1; } } -function Lu(e, t) { - const { start: r, end: n } = Ap(t), i = !isNaN(r) && !isNaN(n) && r <= n; - e.setAttribute("data-verse-number", t), Du(e, "data-verse-start", i ? r : NaN), Du(e, "data-verse-end", i ? n : NaN); +function Vu(e, t) { + const { start: r, end: n } = Fp(t), i = !isNaN(r) && !isNaN(n) && r <= n; + e.setAttribute("data-verse-number", t), Wu(e, "data-verse-start", i ? r : NaN), Wu(e, "data-verse-end", i ? n : NaN); } -function Du(e, t, r) { +function Wu(e, t, r) { isNaN(r) ? e.removeAttribute(t) : e.setAttribute(t, r.toString()); } -function VT(e) { - return je(new hi(e)); +function ax(e) { + return Ve(new yi(e)); } -function ts(e) { - return e instanceof hi; -} -function WT(e) { - return e?.type === Bs; -} -const HT = [ - Ut, - pr, - Nt, - dt, - me, - Ee, - Jt, +function ss(e) { + return e instanceof yi; +} +function cx(e) { + return e?.type === Gs; +} +const lx = [ + Ft, hr, - Pn, - Er, - Or, - Ze, - Wr, - On, - fi, - pi, + Ot, + ft, + be, + Pe, + Jt, + gr, + wn, + wr, + Lr, + nt, + Xr, + Rn, + gi, + mi, // The forward adaptor (usj-editor.adaptor.ts, platform) serializes editable-mode verse/milestone // display runs as AttributeRunNode wrappers, and this package's own self-healing sync // (displayRunSync.utils.ts's shared $syncDisplayRun driver, parameterized by each kind's own @@ -7849,13 +7850,13 @@ const HT = [ // every USJ-shaped editor needs the class registered, not only shared-react's (a non-react host, // e.g. packages/scribe's NoteEditor, builds its editor straight from usjBaseNodes with no // react-specific node list). - Ar, + qr, { - replace: yc, + replace: Cc, with: () => Ht(), - withKlass: Wr + withKlass: Xr } -], Vs = { +], Js = { markers: { id: { marker: "id", @@ -14895,15 +14896,15 @@ const HT = [ fontSize: 12 } } -}, GT = { +}, ux = { paragraph: b.Paragraph, character: b.Character, note: b.Note, milestone: b.Milestone }; -function JT(e) { +function dx(e) { if (!e) - return lr; + return ur; const t = /* @__PURE__ */ new Map(); return (r) => { if (t.has(r)) @@ -14912,25 +14913,25 @@ function JT(e) { // Through `getMarker`, not the raw generated table: `usfmMarkersOverwrites` supplies // markers the generated data lacks (`w`, `rb`, `jmp`), and reading the table directly // demoted exactly those to Uncategorized whenever a project StyleInfo was active. - category: lr(r)?.category ?? T.Uncategorized, - type: GT[n.styleType] ?? b.Unknown, + category: ur(r)?.category ?? T.Uncategorized, + type: ux[n.styleType] ?? b.Unknown, description: n.description ?? "", hasEndMarker: !!n.endMarker, - children: lr(r)?.children + children: ur(r)?.children } : void 0; return t.set(r, i), i; }; } -function Uu(e, t, r) { +function Hu(e, t, r) { const n = { - type: kr, - version: br, + type: vr, + version: Cr, content: e }, i = t.serializeEditorState(n, r); - return go(i.root.children[0]) ? i.root.children[0].children[0] : i.root.children[0]; + return ko(i.root.children[0]) ? i.root.children[0].children[0] : i.root.children[0]; } -const lh = "v", uh = 1, YT = "verse-selected"; -class Ct extends ls { +const xh = "v", _h = 1, fx = "verse-selected"; +class vt extends hs { __marker; __number; __showMarker; @@ -14939,25 +14940,25 @@ class Ct extends ls { __pubnumber; __unknownAttributes; constructor(t = "", r = !1, n, i, s, o, a) { - super(a), this.__marker = lh, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; + super(a), this.__marker = xh, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o; } static getType() { return "immutable-verse"; } static clone(t) { const { __number: r, __showMarker: n, __sid: i, __altnumber: s, __pubnumber: o, __unknownAttributes: a, __key: c } = t; - return new Ct(r, n, i, s, o, a, c); + return new vt(r, n, i, s, o, a, c); } static importDOM() { return { - span: (t) => ZT(t) ? { - conversion: QT, + span: (t) => gx(t) ? { + conversion: hx, priority: 1 } : null }; } static importJSON(t) { - return Gc().updateFromJSON(t); + return el().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setMarker(t.marker).setNumber(t.number).setShowMarker(t.showMarker).setSid(t.sid).setAltnumber(t.altnumber).setPubnumber(t.pubnumber).setUnknownAttributes(t.unknownAttributes); @@ -15025,21 +15026,21 @@ class Ct extends ls { } createDOM() { const t = document.createElement("span"); - return t.setAttribute("data-marker", this.__marker), t.classList.add(Ca, `usfm_${this.__marker}`), this.__showMarker && t.classList.add("marker"), t.setAttribute("data-number", this.__number), t; + return t.setAttribute("data-marker", this.__marker), t.classList.add(Pa, `usfm_${this.__marker}`), this.__showMarker && t.classList.add("marker"), t.setAttribute("data-number", this.__number), t; } updateDOM() { return !1; } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(Ca, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r }; + return r && On(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(Pa, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r }; } decorate() { - const t = this.getShowMarker() ? Lt(this.getMarker(), this.getNumber()) : ( + const t = this.getShowMarker() ? Dt(this.getMarker(), this.getNumber()) : ( // ZWSP added so double click word selection works without including this number. - Is + this.getNumber() + Is + Fs + this.getNumber() + Fs ); - return C(XT, { nodeKey: this.getKey(), text: t }); + return v(px, { nodeKey: this.getKey(), text: t }); } exportJSON() { return { @@ -15051,14 +15052,14 @@ class Ct extends ls { altnumber: this.getAltnumber(), pubnumber: this.getPubnumber(), unknownAttributes: this.getUnknownAttributes(), - version: uh + version: _h }; } isSelected(t) { try { return super.isSelected(t); } catch (r) { - if (Mp(r)) + if (Dp(r)) return !1; throw r; } @@ -15068,204 +15069,204 @@ class Ct extends ls { return !1; } } -function XT({ nodeKey: e, text: t }) { - const [r] = Ly(e); - return C("span", { className: r ? YT : void 0, children: t }); +function px({ nodeKey: e, text: t }) { + const [r] = Qy(e); + return v("span", { className: r ? fx : void 0, children: t }); } -function QT(e) { +function hx(e) { const t = e.getAttribute("data-number") ?? "0"; - return { node: Gc(t) }; + return { node: el(t) }; } -function Gc(e, t, r, n, i, s) { - return je(new Ct(e, t, r, n, i, s)); +function el(e, t, r, n, i, s) { + return Ve(new vt(e, t, r, n, i, s)); } -function ZT(e) { - return (e?.getAttribute("data-marker") ?? void 0) === lh; +function gx(e) { + return (e?.getAttribute("data-marker") ?? void 0) === xh; } -function wn(e) { - return e instanceof Ct; +function $n(e) { + return e instanceof vt; } -function ex(e) { - return e?.type === Ct.getType(); +function mx(e) { + return e?.type === vt.getType(); } -function ge(e) { - return Oe(e) || wn(e); +function me(e) { + return Ae(e) || $n(e); } -function dh(e) { - return bp(e) || ex(e); +function Ch(e) { + return Np(e) || mx(e); } -function tx(e) { - return rx(e).find((t) => ae(t)); +function yx(e) { + return bx(e).find((t) => le(t)); } -function rx(e) { - return e.some(ts) ? e.flatMap((t) => ts(t) ? t.getChildren() : t) : e; +function bx(e) { + return e.some(ss) ? e.flatMap((t) => ss(t) ? t.getChildren() : t) : e; } -function So(e) { - return F(e) ? ts(e) ? e.getChildren().flatMap(So) : e.getChildren() : []; +function Ao(e) { + return L(e) ? ss(e) ? e.getChildren().flatMap(Ao) : e.getChildren() : []; } -function nx(e, t) { - return So(e).find((i) => ge(i) && Ic(t, i.getNumber())); +function kx(e, t) { + return Ao(e).find((i) => me(i) && Kc(t, i.getNumber())); } -function ix(e, t) { - return t === 0 ? tx(e) : e.map((r) => nx(r, t)).filter((r) => r)[0]; +function Tx(e, t) { + return t === 0 ? yx(e) : e.map((r) => kx(r, t)).filter((r) => r)[0]; } -function Ws(e) { - return So(e).find((r) => ge(r)); +function Ys(e) { + return Ao(e).find((r) => me(r)); } -function fh(e, t) { - if (!F(e) || t <= 0) +function vh(e, t) { + if (!L(e) || t <= 0) return; const r = e.getChildren(); for (let n = t - 1; n >= 0; n--) { const i = r[n]; - if (ge(i)) + if (me(i)) return i; } } -function sx(e) { +function xx(e) { const t = e.getParent(); - if (t && F(t)) { + if (t && L(t)) { const n = t.getChildren(); for (let i = e.getIndexWithinParent() + 1; i < n.length; i++) { const s = n[i]; - if (ge(s)) + if (me(s)) return s; } } let r = t?.getNextSibling(); - for (; r && !Ge(r); ) { - const n = Ws(r); + for (; r && !Je(r); ) { + const n = Ys(r); if (n) return n; r = r.getNextSibling(); } } -function Da(e) { - return So(e).findLast((t) => ge(t)); +function Ba(e) { + return Ao(e).findLast((t) => me(t)); } -function ox(e) { - if (!Oe(e)) +function _x(e) { + if (!Ae(e)) return 0; const t = e.getNumber(); return e.getTextContent().startsWith(t) ? t.length : 0; } -function ax(e, t, r) { +function Cx(e, t, r) { if (!r) return !1; const n = t.getParent(); - if (r === n && F(r)) { + if (r === n && L(r)) { const i = t.getIndexWithinParent(); return e.anchor.offset <= i; } return r.getNextSibling() === t; } -function cx(e, t) { +function vx(e, t) { const r = t.anchor.getNode(); if (r !== e) - return ax(t, e, r); - if (v(e)) { - const n = ox(e); + return Cx(t, e, r); + if (S(e)) { + const n = _x(e); return t.anchor.offset < n; } return !0; } -function Fu(e) { +function Gu(e) { const t = e.getNumber(), r = Number.parseInt(t ?? "0", 10); return { verseNum: r, verse: t != null && r.toString() !== t ? t : void 0 }; } -function lx(e, t) { +function Sx(e, t) { if (!e) return { verseNum: 0 }; - if (!N(t)) - return Fu(e); + if (!P(t)) + return Gu(e); const r = Number.parseInt(e.getNumber() ?? "0", 10), n = r <= 1 ? 0 : r - 1; - return cx(e, t) ? { verseNum: n } : Fu(e); + return vx(e, t) ? { verseNum: n } : Gu(e); } -function ux(e) { - return yk(e) || wn(e); +function Mx(e) { + return qk(e) || $n(e); } -function Jc(e) { - if (v(e)) { +function tl(e) { + if (S(e)) { const t = e.getTextContent(); - !t.endsWith(" ") && !t.endsWith(I) && e.setTextContent(`${t} `); + !t.endsWith(" ") && !t.endsWith(R) && e.setTextContent(`${t} `); } } -function ph(e) { - if (v(e)) { +function Sh(e) { + if (S(e)) { const t = e.getTextContent(); t.startsWith(" ") && e.setTextContent(t.trimStart()); } } -function Ua(e, t) { - return e.getEditorState().read(() => !se(t)); +function Va(e, t) { + return e.getEditorState().read(() => !oe(t)); } -function dx(e) { +function Ex(e) { if (!e.isCollapsed()) return !1; - const t = e.anchor.getNode(), r = Yc(t, e); + const t = e.anchor.getNode(), r = rl(t, e); let n; if (r) { const i = r.getParent(); - if (i && F(i) && F(t) && t === i && e.anchor.offset < r.getIndexWithinParent() && (n = r), !n && i && F(i)) { + if (i && L(i) && L(t) && t === i && e.anchor.offset < r.getIndexWithinParent() && (n = r), !n && i && L(i)) { const s = i.getChildren(), o = r.getIndexWithinParent(); for (let a = o + 1; a < s.length; a++) { const c = s[a]; - if (ge(c)) { + if (me(c)) { n = c; break; } } } if (!n && i) { - let s = zu(i); - for (; s && !Ge(s); ) { - const o = Ws(s); + let s = Ju(i); + for (; s && !Je(s); ) { + const o = Ys(s); if (o) { n = o; break; } - s = zu(s); + s = Ju(s); } } } else { let s = t.getTopLevelElement() ?? t; for (; s; ) { - const o = Ws(s); + const o = Ys(s); if (o) { n = o; break; } - if (s = s.getNextSibling(), s && Ge(s)) + if (s = s.getNextSibling(), s && Je(s)) break; } } return n ? (n.selectNext(0, 0), !0) : !1; } -function fx(e) { +function Ax(e) { if (!e.isCollapsed()) return !1; - const t = e.anchor.getNode(), r = Yc(t, e); + const t = e.anchor.getNode(), r = rl(t, e); let n; if (r) { const i = r.getParent(), s = t.getTopLevelElement(); - if (i && s && s !== i.getTopLevelElement() && (n = r), !n && i && F(i) && (n = fh(i, r.getIndexWithinParent())), !n && i) { - let o = Ku(i); - for (; o && !Ge(o); ) { - const a = Da(o); + if (i && s && s !== i.getTopLevelElement() && (n = r), !n && i && L(i) && (n = vh(i, r.getIndexWithinParent())), !n && i) { + let o = Yu(i); + for (; o && !Je(o); ) { + const a = Ba(o); if (a) { n = a; break; } - o = Ku(o); + o = Yu(o); } } } else { let s = t.getTopLevelElement()?.getPreviousSibling() ?? null; - for (; s && !Ge(s); ) { - const o = Da(s); + for (; s && !Je(s); ) { + const o = Ba(s); if (o) { n = o; break; @@ -15275,69 +15276,69 @@ function fx(e) { } return n ? (n.selectNext(0, 0), !0) : !1; } -function zu(e) { +function Ju(e) { const t = e.getNextSibling(); if (t) return t; const r = e.getTopLevelElement(); return r && r !== e ? r.getNextSibling() : null; } -function Ku(e) { +function Yu(e) { const t = e.getPreviousSibling(); if (t) return t; const r = e.getTopLevelElement(); return r && r !== e ? r.getPreviousSibling() : null; } -function Yc(e, t) { - if (F(e) && N(t) && t.anchor.key === e.getKey()) { +function rl(e, t) { + if (L(e) && P(t) && t.anchor.key === e.getKey()) { const n = e.getChildAtIndex(t.anchor.offset); - if (n && ge(n)) + if (n && me(n)) return n; - const i = fh(e, t.anchor.offset); + const i = vh(e, t.anchor.offset); if (i) return i; - const s = Ws(e); + const s = Ys(e); if (s) return s; } - return Xc(e); + return nl(e); } -function Xc(e) { - if (!e || Ge(e)) +function nl(e) { + if (!e || Je(e)) return; - if (ge(e)) + if (me(e)) return e; - let t = Cu(e); + let t = Ou(e); for (; t; ) { - if (Ge(t)) + if (Je(t)) return; - if (ge(t)) + if (me(t)) return t; - const r = Da(t); + const r = Ba(t); if (r) return r; - t = Cu(t); + t = Ou(t); } } -const px = ["style"], hx = ["style", "code"], Hs = ["style", "cid"], gx = [ +const Px = ["style"], Nx = ["style", "code"], Xs = ["style", "cid"], Ox = [ "style", "number", "sid", "altnumber", "pubnumber" -], mx = [ +], wx = [ "style", "number", "sid", "altnumber", "pubnumber" -], yx = [ +], qx = [ "style", "sid", "eid", "attributeOrder" -], bx = ["style", "caller", "category", "contents"], kx = ["tag", "marker", "contents"], Tx = [ +], Rx = ["style", "caller", "category", "contents"], $x = ["tag", "marker", "contents"], Lx = [ "chapter", "immutable-chapter", "verse", @@ -15346,89 +15347,89 @@ const px = ["style"], hx = ["style", "code"], Hs = ["style", "cid"], gx = [ "note", "unknown", "unmatched" -], rs = ` +], os = ` `; -function xx(e, t) { - const r = se(e); - if (!Pt(r)) +function Ix(e, t) { + const r = oe(e); + if (!Nt(r)) return; - const n = hh(r, "apply"); + const n = Mh(r, "apply"); return n === void 0 ? void 0 : [{ retain: n }, ...t, { delete: 1 }]; } -function hh(e, t = "delta-doc") { +function Mh(e, t = "delta-doc") { if (!e) return; - const r = Af(); + const r = Ff(); let n = 0; const i = [], s = [], o = e.getKey(); let a; for (const c of r) { const l = c.node; - for (let d = i.length - 1; d >= 0; d--) - if (ii(i[d], c)) { - const f = i[d]; - if (i.splice(d, 1), n += 1, a && f.getKey() === a.getKey()) + for (let u = i.length - 1; u >= 0; u--) + if (oi(i[u], c)) { + const p = i[u]; + if (i.splice(u, 1), n += 1, a && p.getKey() === a.getKey()) return n - 1; } - for (let d = s.length - 1; d >= 0; d--) - ii(s[d].node, c) && s.splice(d, 1); - const u = s[s.length - 1]; - if (u) { + for (let u = s.length - 1; u >= 0; u--) + oi(s[u].node, c) && s.splice(u, 1); + const d = s[s.length - 1]; + if (d) { if (l.getKey() === o) - return u.position; + return d.position; continue; } if (l.getKey() === o) { - if (_r(l) || Pt(l)) + if (Er(l) || Nt(l)) return n; - Et(l) && (a = l); + At(l) && (a = l); } - if (Et(l) && (i.includes(l) || i.push(l)), gh(l, t)) { + if (At(l) && (i.includes(l) || i.push(l)), Eh(l, t)) { if (l.getKey() === o) return n; s.push({ node: l, position: n }), n += 1; continue; } - n += Qc(l, t); + n += il(l, t); } if (a) return n; } -function ju(e, t, r = "delta-doc") { - if (e.length < 2 || !Sx(e[0]) || !Cx(e[1])) +function Xu(e, t, r = "delta-doc") { + if (e.length < 2 || !Fx(e[0]) || !Ux(e[1])) return; const n = e[0].retain; - return t.read(() => _x(n, r)?.getKey()); + return t.read(() => Dx(n, r)?.getKey()); } -function _x(e, t = "delta-doc") { - const r = Af(); +function Dx(e, t = "delta-doc") { + const r = Ff(); let n = 0; const i = [], s = []; for (const o of r) { const a = o.node; - for (let u = i.length - 1; u >= 0; u--) - if (ii(i[u], o)) { - const d = i[u]; - if (i.splice(u, 1), n === e) - return d; + for (let d = i.length - 1; d >= 0; d--) + if (oi(i[d], o)) { + const u = i[d]; + if (i.splice(d, 1), n === e) + return u; n += 1; } - for (let u = s.length - 1; u >= 0; u--) - ii(s[u].node, o) && s.splice(u, 1); + for (let d = s.length - 1; d >= 0; d--) + oi(s[d].node, o) && s.splice(d, 1); const c = s[s.length - 1]; if (c) { if (c.position === e) return c.node; continue; } - if (Et(a) && (i.includes(a) || i.push(a)), gh(a, t)) { + if (At(a) && (i.includes(a) || i.push(a)), Eh(a, t)) { if (n === e) return a; s.push({ node: a, position: n }), n += 1; continue; } - const l = Qc(a, t); - if (_r(a) && l > 0 && e >= n && e < n + l || Pt(a) && n === e) + const l = il(a, t); + if (Er(a) && l > 0 && e >= n && e < n + l || Nt(a) && n === e) return a; n += l; } @@ -15438,56 +15439,56 @@ function _x(e, t = "delta-doc") { n += 1; } } -function ii(e, t) { - return e ? t ? !Ks(t.node, e.getKey()) : !0 : !1; +function oi(e, t) { + return e ? t ? !Ws(t.node, e.getKey()) : !0 : !1; } -function _r(e) { - return v(e) && !Pt(e); +function Er(e) { + return S(e) && !Nt(e); } -function Pt(e) { - return Ge(e) || ge(e) || Be(e) || j(e) || De(e) || Qr(e); +function Nt(e) { + return Je(e) || me(e) || He(e) || K(e) || Ue(e) || Ir(e); } -function Fr(e, t) { +function jr(e, t) { return t?.insert != null && typeof t.insert == "object" && e in t.insert; } -function Cx(e) { +function Ux(e) { if (e.insert == null || typeof e.insert != "object") return !1; const t = Object.keys(e.insert)[0]; - return e.insert != null && typeof e.insert == "object" && t in e.insert && Tx.includes(t); + return e.insert != null && typeof e.insert == "object" && t in e.insert && Lx.includes(t); } -function Sx(e) { +function Fx(e) { return e.retain != null && typeof e.retain == "number"; } -function gh(e, t) { - return j(e) || De(e) ? !0 : t === "apply" && F(e) && Pt(e); +function Eh(e, t) { + return K(e) || Ue(e) ? !0 : t === "apply" && L(e) && Nt(e); } -function mh(e) { +function Ah(e) { const t = e.getParent(); - return Ft(e) && ae(t) && t.getFirstChild() === e; + return zt(e) && le(t) && t.getFirstChild() === e; } -function Fa(e) { +function Wa(e) { const t = e.getParent(); - return t !== null && nt(t, Le) !== null; + return t !== null && rt(t, De) !== null; } -function vx(e) { +function zx(e) { const t = e.getParent(); - return D(t) && e.getTextContent() === Dt && t.getChildrenSize() === 1; + return I(t) && e.getTextContent() === Ut && t.getChildrenSize() === 1; } -function Mx(e) { +function Kx(e) { const t = e.getParent(); - if (!j(t)) + if (!K(t)) return !1; const r = e.getPreviousSibling(); - return O(r) && r === t.getFirstChild() && e.getTextContent() === At(t.getCaller()); + return O(r) && r === t.getFirstChild() && e.getTextContent() === Pt(t.getCaller()); } -function Ex(e) { - return !Hp(e) && Qc(e, "delta-doc") === e.getTextContentSize(); +function jx(e) { + return !sh(e) && il(e, "delta-doc") === e.getTextContentSize(); } -function Qc(e, t) { - if (Pt(e)) +function il(e, t) { + if (Nt(e)) return 1; - if (v(e)) { + if (S(e)) { const r = e.getTextContent(); return t === "delta-doc" && // A bare cursor host (EmptyVerseCaretGuardPlugin) is a transient, collab-invisible node: // its insertion is never emitted, so it contributes nothing to DOC-DELTA positions or the @@ -15497,25 +15498,25 @@ function Qc(e, t) { // and adds raw `getTextContentSize()`), so excluding it here left a replace-embed retain // one short whenever a host rested before the target — a footnote-popover save then // deleted the unit BEFORE the note instead of the note itself. - (qc(e) || mh(e) || ne(e, oe) === "marker-trailing-space" || // An attribute value keyed by its own state, not by ancestry: a CHAR span's run is a direct + (Uc(e) || Ah(e) || ie(e, ae) === "marker-trailing-space" || // An attribute value keyed by its own state, not by ancestry: a CHAR span's run is a direct // TextNode child of the span, never wrapped (`displayRunRegistry.ts`'s char descriptor // writes "owner-children"), so `$hasAttributeRunAncestor` cannot see it. That shape is at // rest on every `\w …|strong="…"\w*`, and the ops stream already omits those bytes // (`isNodeAttributeText` in editor-delta.adaptor.ts), so counting them here would put this // side out of step with the op stream on ordinary Scripture. - ne(e, oe) === "attribute" || Fa(e) || // The remaining ops-stream exclusions, so this side and $handleTextNodes count the same + ie(e, ae) === "attribute" || Wa(e) || // The remaining ops-stream exclusions, so this side and $handleTextNodes count the same // bytes (docs/standard-view-invariants.md §II — extend the shared list, never fork it): // the legacy NBSP-`|` byte-prefixed attribute text the ops stream still honors for // pre-state-tag peers and persisted deltas, the empty-char placeholder, and the // editable-mode note caller in caller position. - r.startsWith(Sc) || vx(e) || Mx(e)) ? 0 : e.getTextContentSize(); + r.startsWith(Nc) || zx(e) || Kx(e)) ? 0 : e.getTextContentSize(); } return 0; } -function za(e, t) { - const r = { insert: e.__text }, n = ne(e, Vr); +function Ha(e, t) { + const r = { insert: e.__text }, n = ie(e, Yr); if (n && (r.attributes = { segment: n }), t && t.length > 0) { - const i = yh(t); + const i = Ph(t); i && (r.attributes = { ...r.attributes, char: i @@ -15523,178 +15524,178 @@ function za(e, t) { } return r; } -function Bu(e) { - const t = new qi(); +function Qu(e) { + const t = new Ii(); return e.isEmpty() || e.read(() => { - const r = Fe(); + const r = Ke(); if (!r || r.isEmpty()) return; const n = r.getChildren(); - if (n.length === 1 && dr(n[0]) && (!n[0].getChildren() || n[0].getChildrenSize() === 0)) + if (n.length === 1 && fr(n[0]) && (!n[0].getChildren() || n[0].getChildrenSize() === 0)) return; - const i = Ax(); + const i = Bx(); for (const s of i) t.push(s); }), t; } -function Zc(e, t) { - const r = [], n = ui(e, t), i = [], s = [], o = [], a = /* @__PURE__ */ new Set(); +function sl(e, t) { + const r = [], n = pi(e, t), i = [], s = [], o = [], a = /* @__PURE__ */ new Set(); for (let c = 0; c < n.length; c++) { const l = n[c].node; - r.push(...Vu(l, c, n, i, s, o, a)); + r.push(...Zu(l, c, n, i, s, o, a)); } for (const c of i) - r.push(...Vu(c, n.length, n, i, s, o, a)); + r.push(...Zu(c, n.length, n, i, s, o, a)); return r; } -function Ax() { - return Zc(); +function Bx() { + return sl(); } -function Vu(e, t, r, n, i, s, o) { +function Zu(e, t, r, n, i, s, o) { if (!e) return []; const a = [], c = r[t + 1]; - return Px(e, a, n), Nx(e, a, i, s, o), Ox(e, t, r, i, o, s, a), Ge(e) && a.push($x(e)), ge(e) && a.push(Lx(e)), Be(e) && a.push(Dx(e)), Qr(e) && a.push(Ux(e)), qx(e, a, s), wx(e, a, s), jx(c, s), a; + return Vx(e, a, n), Wx(e, a, i, s, o), Hx(e, t, r, i, o, s, a), Je(e) && a.push(Xx(e)), me(e) && a.push(Zx(e)), He(e) && a.push(e_(e)), Ir(e) && a.push(t_(e)), Jx(e, a, s), Gx(e, a, s), s_(c, s), a; } -function Px(e, t, r) { +function Vx(e, t, r) { if (!e.isInline()) { const n = r.pop(); - pt(n) ? t.push(Rx(n)) : ae(n) ? t.push(Ix(n)) : dr(n) && t.push({ insert: rs }); + ht(n) ? t.push(Yx(n)) : le(n) ? t.push(Qx(n)) : fr(n) && t.push({ insert: os }); } - Et(e) && (r.includes(e) || r.push(e)); + At(e) && (r.includes(e) || r.push(e)); } -function Nx(e, t, r, n, i) { - if (!v(e) || Oe(e) || Qr(e)) +function Wx(e, t, r, n, i) { + if (!S(e) || Ae(e) || Ir(e)) return; const s = e.getParent(); - if (j(s) && s.getFirstChild() === e) + if (K(s) && s.getFirstChild() === e) return; const o = Xt(e) !== void 0; - if (O(e) && (o || mh(e) || Fa(e) || Hp(e)) || ne(e, oe) === "marker-trailing-space") + if (O(e) && (o || Ah(e) || Wa(e) || sh(e)) || ie(e, ae) === "marker-trailing-space") return; let a = e.getTextContent(); - if (ps(a)) + if (bs(a)) return; const c = e.getPreviousSibling(); - if (j(s) && O(c) && c === s.getFirstChild() && a === At(s.getCaller())) + if (K(s) && O(c) && c === s.getFirstChild() && a === Pt(s.getCaller())) return; - const l = D(s) ? s : void 0, u = l?.getFirstChild(); - o && l && O(u) && c === u && a.startsWith(I) && (a = a.slice(1)); - const d = a.startsWith(Sc) || ne(e, oe) === "attribute" || Fa(e), f = !!l && a === Dt && l.getChildrenSize() === 1, p = vo(e, n), m = p ? r.filter((k) => p.children.includes(k)) : r, g = za(e, m); - if (g.insert = a, p) { - if (!a || a === I || d) + const l = I(s) ? s : void 0, d = l?.getFirstChild(); + o && l && O(d) && c === d && a.startsWith(R) && (a = a.slice(1)); + const u = a.startsWith(Nc) || ie(e, ae) === "attribute" || Wa(e), p = !!l && a === Ut && l.getChildrenSize() === 1, f = Po(e, n), g = f ? r.filter((k) => f.children.includes(k)) : r, h = Ha(e, g); + if (h.insert = a, f) { + if (!a || a === R || u) return; - p.contentsOps?.push(g); + f.contentsOps?.push(h); } else - f || d || t.push(g); - const y = a !== "" && !f && !(d && l); + p || u || t.push(h); + const y = a !== "" && !p && !(u && l); if (r.length > 0 && y) for (const k of r) i.add(k); } -function Ox(e, t, r, n, i, s, o) { - D(e) && !n.includes(e) && n.push(e); +function Hx(e, t, r, n, i, s, o) { + I(e) && !n.includes(e) && n.push(e); const a = r[t + 1]; for (const c of n.toReversed()) - if (ii(c, a)) { + if (oi(c, a)) { if (n.pop(), !i.has(c)) { - const l = zx(c), u = vo(c, s); - u ? u.contentsOps?.push(l) : o.push(l); + const l = n_(c), d = Po(c, s); + d ? d.contentsOps?.push(l) : o.push(l); } i.delete(c); } } -function wx(e, t, r) { - if (!j(e)) +function Gx(e, t, r) { + if (!K(e)) return; - const n = Fx(e), i = vo(e, r), s = { + const n = r_(e), i = Po(e, r), s = { node: e, - children: ui(e).map((o) => o.node), + children: pi(e).map((o) => o.node), contentsOps: n.insert.note?.contents?.ops }; r.push(s), i?.contentsOps ? i.contentsOps.push(n) : t.push(n); } -function qx(e, t, r) { - if (!De(e)) +function Jx(e, t, r) { + if (!Ue(e)) return; - const n = Kx(e), i = vo(e, r), s = { + const n = i_(e), i = Po(e, r), s = { node: e, - children: ui(e).map((o) => o.node), + children: pi(e).map((o) => o.node), contentsOps: n.insert.unknown?.contents?.ops }; r.push(s), i?.contentsOps ? i.contentsOps.push(n) : t.push(n); } -function Zr(e, t) { +function nn(e, t) { const r = t.getUnknownAttributes(); r && Object.assign(e, r); } -function Rx(e) { - const t = { style: Gi, code: e.__code }; - return Zr(t, e), { insert: rs, attributes: { book: t } }; +function Yx(e) { + const t = { style: Qi, code: e.__code }; + return nn(t, e), { insert: os, attributes: { book: t } }; } -function $x(e) { - const t = { style: Fs, number: e.__number }; - return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), Zr(t, e), { insert: { chapter: t } }; +function Xx(e) { + const t = { style: Bs, number: e.__number }; + return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), nn(t, e), { insert: { chapter: t } }; } -function Ix(e) { +function Qx(e) { const t = { style: e.__marker }; - return Zr(t, e), { insert: rs, attributes: { para: t } }; + return nn(t, e), { insert: os, attributes: { para: t } }; } -function Lx(e) { - const t = { style: zs, number: e.__number }; - return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), Zr(t, e), { insert: { verse: t } }; +function Zx(e) { + const t = { style: Vs, number: e.__number }; + return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), nn(t, e), { insert: { verse: t } }; } -function Dx(e) { +function e_(e) { const t = { style: e.__marker }; - return e.__sid && (t.sid = e.__sid), e.__eid && (t.eid = e.__eid), e.__attributeOrder && (t.attributeOrder = e.__attributeOrder), Zr(t, e), { insert: { milestone: t } }; + return e.__sid && (t.sid = e.__sid), e.__eid && (t.eid = e.__eid), e.__attributeOrder && (t.attributeOrder = e.__attributeOrder), nn(t, e), { insert: { milestone: t } }; } -function Ux(e) { +function t_(e) { return { insert: { unmatched: { marker: e.__marker } } }; } -function Fx(e) { +function r_(e) { const t = { style: e.__marker, caller: e.__caller }; - e.__category && (t.category = e.__category), Zr(t, e), e.getChildrenSize() > 1 && (t.contents = { ops: [] }); - const r = { insert: { note: t } }, n = ne(e, Vr); + e.__category && (t.category = e.__category), nn(t, e), e.getChildrenSize() > 1 && (t.contents = { ops: [] }); + const r = { insert: { note: t } }, n = ie(e, Yr); return n && (r.attributes = { segment: n }), r; } -function zx(e) { - const t = { insert: "" }, r = yh([e]); +function n_(e) { + const t = { insert: "" }, r = Ph([e]); return r && (t.attributes = { char: r }), t; } -function Kx(e) { +function i_(e) { const t = { tag: e.getTag() }, r = e.getMarker(); - return r && (t.marker = r), Zr(t, e), e.getChildrenSize() > 0 && (t.contents = { ops: [] }), { insert: { unknown: t } }; + return r && (t.marker = r), nn(t, e), e.getChildrenSize() > 0 && (t.contents = { ops: [] }), { insert: { unknown: t } }; } -function vo(e, t) { +function Po(e, t) { for (let r = t.length - 1; r >= 0; r--) { const n = t[r]; if (n.children.includes(e)) return n; } } -function jx(e, t) { +function s_(e, t) { for (let r = t.length - 1; r >= 0; r--) - ii(t[r].node, e) && t.splice(r, 1); + oi(t[r].node, e) && t.splice(r, 1); } -function yh(e) { +function Ph(e) { if (e.length === 0) return; - const t = e.map(Bx); + const t = e.map(o_); return t.length === 1 ? t[0] : t; } -function Bx(e) { - const t = { style: e.__marker }, r = ne(e, kn); - return r && (t.cid = r), Zr(t, e), t; +function o_(e) { + const t = { style: e.__marker }, r = ie(e, _n); + return r && (t.cid = r), nn(t, e), t; } -const bh = 1; -class Gt extends ls { +const Nh = 1; +class Gt extends hs { __caller; __previewText; __onClick; - constructor(t = Bi, r = "", n, i) { + constructor(t = Gi, r = "", n, i) { super(i), this.__caller = t, this.__previewText = r, this.__onClick = n ?? (() => { }); } @@ -15707,14 +15708,14 @@ class Gt extends ls { } static importDOM() { return { - span: (t) => Wx(t) ? { - conversion: Vx, + span: (t) => c_(t) ? { + conversion: a_, priority: 1 } : null }; } static importJSON(t) { - return el().updateFromJSON(t); + return ol().updateFromJSON(t); } updateFromJSON(t) { return super.updateFromJSON(t).setCaller(t.caller).setPreviewText(t.previewText).setOnClick(t.onClick); @@ -15755,17 +15756,17 @@ class Gt extends ls { } exportDOM(t) { const { element: r } = super.exportDOM(t); - return r && An(r) && (r.classList.add(this.getType()), r.setAttribute("data-caller", this.getCaller()), r.setAttribute("data-preview-text", this.getPreviewText())), { element: r }; + return r && On(r) && (r.classList.add(this.getType()), r.setAttribute("data-caller", this.getCaller()), r.setAttribute("data-preview-text", this.getPreviewText())), { element: r }; } decorate(t) { const r = this.getParent(); if (!r) return null; - const n = r.getKey(), i = r.getIsCollapsed(), s = this.__key, o = (c) => this.__onClick?.(c, n, i, () => Hx(t, n), (l) => Gx(t, n, s, l), () => Jx(t, n), () => Yx(t, n)), a = `${this.__caller}_${this.__previewText}}`.replace(/\s+/g, "").substring(0, 25); - return C("button", { onClick: o, title: this.__previewText, "data-caller-id": a, children: this.__caller === Bi && i ? ( + const n = r.getKey(), i = r.getIsCollapsed(), s = this.__key, o = (c) => this.__onClick?.(c, n, i, () => l_(t, n), (l) => u_(t, n, s, l), () => d_(t, n), () => f_(t, n)), a = `${this.__caller}_${this.__previewText}}`.replace(/\s+/g, "").substring(0, 25); + return v("button", { onClick: o, title: this.__previewText, "data-caller-id": a, children: this.__caller === Gi && i ? ( // Caller is generated by CSS (footnote or cross-reference sequence, per note marker) "" - ) : this.__caller === Lf && i ? ( + ) : this.__caller === Jf && i ? ( // PT9: the hidden caller displays as `*` when collapsed "*" ) : this.__caller }); @@ -15776,7 +15777,7 @@ class Gt extends ls { caller: this.getCaller(), previewText: this.getPreviewText(), onClick: this.getOnClick(), - version: bh + version: Nh }; } // Mutation @@ -15784,59 +15785,59 @@ class Gt extends ls { return !1; } } -function Vx(e) { +function a_(e) { const t = e.getAttribute("data-caller") ?? "", r = e.getAttribute("data-preview-text") ?? ""; - return { node: el(t, r) }; + return { node: ol(t, r) }; } -function el(e, t, r) { - return je(new Gt(e, t, r)); +function ol(e, t, r) { + return Ve(new Gt(e, t, r)); } -function Wx(e) { +function c_(e) { return e ? e.classList.contains(Gt.getType()) : !1; } function St(e) { return e instanceof Gt; } -function Hx(e, t) { +function l_(e, t) { return e.getEditorState().read(() => { - const r = se(t); - if (!j(r)) + const r = oe(t); + if (!K(r)) throw new Error(`getNoteCaller: Note node not found: ${t}`); return r.getCaller(); }); } -function Gx(e, t, r, n) { +function u_(e, t, r, n) { e.update(() => { - const i = se(t); - if (!j(i)) + const i = oe(t); + if (!K(i)) throw new Error(`setNoteCaller: Note node not found: ${t}`); i.setCaller(n); - const s = se(r); + const s = oe(r); if (!St(s)) throw new Error(`setNoteCaller: Caller node not found: ${r}`); s.setCaller(n); }); } -function Jx(e, t) { +function d_(e, t) { return e.getEditorState().read(() => { - const r = se(t); - if (!j(r)) + const r = oe(t); + if (!K(r)) throw new Error(`getNoteOps: Note node not found: ${t}`); - return Zc(r); + return sl(r); }); } -function Yx(e, t) { +function f_(e, t) { return e.getEditorState().read(() => { let r = 0; - for (const { node: n } of ui()) - if (j(n)) { + for (const { node: n } of pi()) + if (K(n)) { if (n.getKey() === t) return r; r += 1; } }); } -const Xx = [ +const p_ = [ "a", "b", "c", @@ -15863,151 +15864,151 @@ const Xx = [ "x", "y", "z" -], Qx = ["†"]; -function Mo(e) { - if (kh()) +], h_ = ["†"]; +function No(e) { + if (Oh()) return; const { start: t } = e; let { end: r } = e; r ??= t; - let [n, i] = Wu(t), [s, o] = Wu(r); + let [n, i] = ed(t), [s, o] = ed(r); if (!n || !s || i === void 0 || o === void 0) return; - [n, i] = Hu(n, i), [s, o] = Hu(s, o); - const a = bc(); - return a.anchor = su(n.getKey(), i, Gu(n)), a.focus = su(s.getKey(), o, Gu(s)), a; + [n, i] = td(n, i), [s, o] = td(s, o); + const a = vc(); + return a.anchor = pu(n.getKey(), i, rd(n)), a.focus = pu(s.getKey(), o, rd(s)), a; } -function tl() { - if (kh()) +function al() { + if (Oh()) return; const e = w(); - if (!e || !N(e)) + if (!e || !P(e)) return; - const t = e.isBackward() ? e.focus.getNode() : e.anchor.getNode(), r = e.isBackward() ? e.focus.offset : e.anchor.offset, n = Gs(t, r); + const t = e.isBackward() ? e.focus.getNode() : e.anchor.getNode(), r = e.isBackward() ? e.focus.offset : e.anchor.offset, n = Qs(t, r); if (e.isCollapsed()) return { start: n }; - const i = e.isBackward() ? e.anchor.getNode() : e.focus.getNode(), s = e.isBackward() ? e.anchor.offset : e.focus.offset, o = Gs(i, s); + const i = e.isBackward() ? e.anchor.getNode() : e.focus.getNode(), s = e.isBackward() ? e.anchor.offset : e.focus.offset, o = Qs(i, s); return { start: n, end: o }; } -function Wu(e) { - if (oy(e)) { - const t = yf(e.jsonPath); - let r = Fe(); +function ed(e) { + if (ky(e)) { + const t = Pf(e.jsonPath); + let r = Ke(); for (let n = 0; n < t.length; n++) { - if (!r || !F(r)) + if (!r || !L(r)) return [void 0, void 0]; - const i = di(r)[t[n]]; + const i = hi(r)[t[n]]; if (!i) return [void 0, void 0]; if (i.type === "text") - return n !== t.length - 1 ? [void 0, void 0] : Ok(i, e.offset) ?? [void 0, void 0]; + return n !== t.length - 1 ? [void 0, void 0] : Hk(i, e.offset) ?? [void 0, void 0]; r = i.node; } - return r && F(r) ? [r, wk(r, e.offset)] : [void 0, void 0]; + return r && L(r) ? [r, Gk(r, e.offset)] : [void 0, void 0]; } - if (ay(e) || cy(e)) { - const t = Mi(e.jsonPath); + if (Ty(e) || xy(e)) { + const t = Ni(e.jsonPath); if (!t) return [void 0, void 0]; - if (F(t)) { + if (L(t)) { const n = t.getLastChild(); - if (n && v(n)) + if (n && S(n)) return [n, n.getTextContent().length]; } const r = t.getNextSibling(); - return r && F(r) ? [r, 0] : [void 0, void 0]; + return r && L(r) ? [r, 0] : [void 0, void 0]; } - if (ly(e)) { - const t = Mi(e.jsonPath); + if (_y(e)) { + const t = Ni(e.jsonPath); if (!t) return [void 0, void 0]; - if (F(t)) { + if (L(t)) { const n = t.getLastChild(); - if (n && v(n)) + if (n && S(n)) return [n, n.getTextContent().length]; } const r = t.getNextSibling(); - return r && F(r) ? [r, 0] : [void 0, void 0]; + return r && L(r) ? [r, 0] : [void 0, void 0]; } - if (uy(e)) { - const t = Mi(e.jsonPath); - if (!t || !F(t)) + if (Cy(e)) { + const t = Ni(e.jsonPath); + if (!t || !L(t)) return [void 0, void 0]; - const r = na(t, "opening"); + const r = ca(t, "opening"); if (r) return [r, 0]; const n = t.getFirstChild(); - return n && v(n) ? [n, 0] : [void 0, void 0]; + return n && S(n) ? [n, 0] : [void 0, void 0]; } - if (dy(e)) { - const t = Mi(e.jsonPath); - if (!t || !F(t)) + if (vy(e)) { + const t = Ni(e.jsonPath); + if (!t || !L(t)) return [void 0, void 0]; - const r = na(t, "closing"); + const r = ca(t, "closing"); if (r) { const i = r.getTextContent(), s = Math.min(e.closingMarkerOffset, i.length); return [r, s]; } const n = t.getLastChild(); - return n && v(n) ? [n, n.getTextContent().length] : [void 0, void 0]; + return n && S(n) ? [n, n.getTextContent().length] : [void 0, void 0]; } - if (fy(e)) { - const t = e.jsonPath.match(/\.(\w+)$|^\$\.(\w+)$|\['([^']+)'\]$/), r = t?.[1] ?? t?.[2] ?? t?.[3], n = Mi(e.jsonPath); - if (!n || !F(n)) + if (Sy(e)) { + const t = e.jsonPath.match(/\.(\w+)$|^\$\.(\w+)$|\['([^']+)'\]$/), r = t?.[1] ?? t?.[2] ?? t?.[3], n = Ni(e.jsonPath); + if (!n || !L(n)) return [void 0, void 0]; if (r === "marker") { - const s = na(n, "opening"); + const s = ca(n, "opening"); if (s) { const o = e.propertyOffset + 1, a = s.getTextContent(); return [s, Math.min(o, a.length)]; } } const i = n.getFirstChild(); - return i && v(i) ? [i, 0] : [void 0, void 0]; + return i && S(i) ? [i, 0] : [void 0, void 0]; } - throw new Error(`Unsupported UsjDocumentLocation type: ${py(e)}. All UsjDocumentLocation subtypes should be supported: UsjMarkerLocation, UsjClosingMarkerLocation, UsjTextContentLocation, UsjPropertyValueLocation, UsjAttributeKeyLocation, UsjAttributeMarkerLocation, andUsjClosingAttributeMarkerLocation. Received: ${JSON.stringify(e)}`); + throw new Error(`Unsupported UsjDocumentLocation type: ${My(e)}. All UsjDocumentLocation subtypes should be supported: UsjMarkerLocation, UsjClosingMarkerLocation, UsjTextContentLocation, UsjPropertyValueLocation, UsjAttributeKeyLocation, UsjAttributeMarkerLocation, andUsjClosingAttributeMarkerLocation. Received: ${JSON.stringify(e)}`); } -function Hu(e, t) { - if (!Pr(e)) +function td(e, t) { + if (!Rr(e)) return [e, t]; const r = e.getTextContent().length; if (t < 0 || t >= r) return [e, t]; const n = e.getParent(); - if (!n || !F(n)) + if (!n || !L(n)) return [e, t]; const i = e.getIndexWithinParent(); return i < 0 ? [e, t] : [n, i]; } -function Gu(e) { - return F(e) ? "element" : "text"; +function rd(e) { + return L(e) ? "element" : "text"; } -function na(e, t) { +function ca(e, t) { const r = e.getChildren(); for (const n of r) { if (O(n) && n.getMarkerSyntax() === t || t === "closing" && O(n) && n.getMarkerSyntax() === "selfClosing") return n; - if (Pr(n)) { + if (Rr(n)) { const s = n.getTextContent().endsWith("*"); if (t === "opening" && !s || t === "closing" && s) return n; } } } -function Mi(e) { - const t = new RegExp(/^(\$(?:\.content\[\d+\])*)(?:\.|$|\[)/).exec(e), r = t ? t[1] : e, n = yf(r); - let i = Fe(); +function Ni(e) { + const t = new RegExp(/^(\$(?:\.content\[\d+\])*)(?:\.|$|\[)/).exec(e), r = t ? t[1] : e, n = Pf(r); + let i = Ke(); for (const s of n) { - if (!i || !F(i)) + if (!i || !L(i)) return; - const o = di(i)[s]; + const o = hi(i)[s]; i = o?.type === "element" ? o.node : void 0; } return i; } -function Gs(e, t) { +function Qs(e, t) { if (O(e)) { - const r = e.getMarkerSyntax(), n = Zx(e), i = n ? on(un(n)) : on(un(e)); + const r = e.getMarkerSyntax(), n = g_(e), i = n ? ln(pn(n)) : ln(pn(e)); if (r === "closing" || r === "selfClosing") return { jsonPath: i, @@ -16025,100 +16026,100 @@ function Gs(e, t) { } if (Ce(e)) { const r = e.getChildrenSize(), n = e.getChildAtIndex(Math.min(t, r - 1)); - if (v(n)) { + if (S(n)) { const s = t >= r ? n.getTextContentSize() : 0; - return Gs(n, s); + return Qs(n, s); } - const i = yo(e); + const i = xo(e); if (i?.is(e.getParent())) { const s = e.getIndexWithinParent(), o = t >= r ? s + 1 : s; - return Gs(i, o); + return Qs(i, o); } } - if (F(e)) { + if (L(e)) { const r = e.getChildAtIndex(t); - if (Pr(r)) { - const i = r.getTextContent().endsWith("*"), s = on(un(e)); + if (Rr(r)) { + const i = r.getTextContent().endsWith("*"), s = ln(pn(e)); return i ? { jsonPath: s, closingMarkerOffset: 0 } : { jsonPath: s }; } - const n = Np(e, t); + const n = Kp(e, t); return n.type === "text" ? { - jsonPath: on([...un(e), n.index]), + jsonPath: ln([...pn(e), n.index]), offset: n.offset } : { - jsonPath: on(un(e)), + jsonPath: ln(pn(e)), offset: n.index }; } - if (v(e)) { - const r = Nk(e, t); + if (S(e)) { + const r = Wk(e, t); if (r) return { - jsonPath: on([ - ...un(r.parent), + jsonPath: ln([ + ...pn(r.parent), r.index ]), offset: r.offset }; } - return { jsonPath: on(un(e)), offset: t }; + return { jsonPath: ln(pn(e)), offset: t }; } -function Zx(e) { +function g_(e) { const t = e.getParent(); - if (!t || !F(t)) + if (!t || !L(t)) return; - const r = e_(e); - return r && !Et(r) && !v(r) && !Ce(r) ? r : t; + const r = m_(e); + return r && !At(r) && !S(r) && !Ce(r) ? r : t; } -function e_(e) { +function m_(e) { let t = e.getPreviousSibling(); for (; t; ) { - if (!Lc(t)) + if (!jc(t)) return t; t = t.getPreviousSibling(); } } -function un(e) { +function pn(e) { const t = []; let r = e; for (; r; ) { - const n = yo(r); + const n = xo(r); if (!n) break; - const i = Pk(n, r); + const i = Vk(n, r); i >= 0 && t.unshift(i), r = n; } return t; } -function kh() { - for (let e = Fe().getFirstChild(); e; e = e.getNextSibling()) - if (ts(e)) +function Oh() { + for (let e = Ke().getFirstChild(); e; e = e.getNextSibling()) + if (ss(e)) return !0; return !1; } -function Th(e, t, r, n, i, s, o) { - if (!Ee.isValidMarker(e)) +function wh(e, t, r, n, i, s, o) { + if (!Pe.isValidMarker(e)) throw new Error(`$insertNote: Invalid note marker '${e}'`); - const a = r ? Mo(r) : w(); - if (!N(a)) + const a = r ? No(r) : w(); + if (!P(a)) return; - const c = n_(a, e, n, i, s, o); + const c = k_(a, e, n, i, s, o); if (c === void 0) return; - const l = t ?? (Ri(e) === "crossref" ? s.defaultCrossRefCaller ?? "-" : s.defaultFootnoteCaller ?? "+"), u = xh(e, l, c, i, s, void 0, void 0); - return r_(u, a, i), u; + const l = t ?? (Di(e) === "crossref" ? s.defaultCrossRefCaller ?? "-" : s.defaultFootnoteCaller ?? "+"), d = qh(e, l, c, i, s, void 0, void 0); + return b_(d, a, i), d; } -function rl(e) { +function cl(e) { return e !== "expanded"; } -function t_(e) { +function y_(e) { if (!e.isCollapsed()) return; const { anchor: t } = e; if (t.type !== "text") return; const r = t.getNode(); - if (!v(r) || !D(r.getParent())) + if (!S(r) || !I(r.getParent())) return; if (O(r)) return t.offset === 0 && r.getMarkerSyntax() === "closing" ? r : void 0; @@ -16127,40 +16128,40 @@ function t_(e) { const n = r.getNextSibling(); return O(n) && n.getMarkerSyntax() === "closing" ? n : void 0; } -function r_(e, t, r) { - const n = rl(r?.noteMode); - e.setIsCollapsed(n), t.isCollapsed() || xk(t), ah(t); - const i = t_(t); - i ? (i.insertBefore(e), e.selectNext(0, 0)) : t.insertNodes([e]), n || e.getChildren().reverse().find(D)?.selectEnd(); +function b_(e, t, r) { + const n = cl(r?.noteMode); + e.setIsCollapsed(n), t.isCollapsed() || Ik(t), kh(t); + const i = y_(t); + i ? (i.insertBefore(e), e.selectNext(0, 0)) : t.insertNodes([e]), n || e.getChildren().reverse().find(I)?.selectEnd(); } -function jn(e, t, r) { - const n = xr(e); +function Wn(e, t, r) { + const n = Mr(e); n.setUnknownAttributes({ closed: "false" }); const i = r?.markerMode === "editable"; - i ? n.append(ot(e)) : r?.markerMode === "visible" && n.append(Tr("marker", we(e))); - const s = t === "" ? Dt : i ? I + t : t; - return n.append(pe(s)), n; + i ? n.append(lt(e)) : r?.markerMode === "visible" && n.append(Sr("marker", Re(e))); + const s = t === "" ? Ut : i ? R + t : t; + return n.append(he(s)), n; } -function n_(e, t, r, n, i, s) { - const o = [], { chapterNum: a, verseNum: c, verse: l } = r ?? {}, u = i.chapterVerseSeparator ?? ":", d = i.verseRangeSeparator ?? "-", f = a !== void 0 && c !== void 0 ? `${a}${u}${(l ?? `${c}`).replace(/-/g, () => d)} ` : void 0; +function k_(e, t, r, n, i, s) { + const o = [], { chapterNum: a, verseNum: c, verse: l } = r ?? {}, d = i.chapterVerseSeparator ?? ":", u = i.verseRangeSeparator ?? "-", p = a !== void 0 && c !== void 0 ? `${a}${d}${(l ?? `${c}`).replace(/-/g, () => u)} ` : void 0; switch (t) { case "f": case "fe": case "ef": case "efe": - if (f !== void 0 && o.push(jn("fr", f, n)), !e.isCollapsed()) { - const p = Yu(e); - p.length > 0 && o.push(jn("fq", p, n)); + if (p !== void 0 && o.push(Wn("fr", p, n)), !e.isCollapsed()) { + const f = id(e); + f.length > 0 && o.push(Wn("fq", f, n)); } - o.push(jn("ft", "", n)); + o.push(Wn("ft", "", n)); break; case "x": case "ex": - if (f !== void 0 && o.push(jn("xo", f, n)), !e.isCollapsed()) { - const p = Yu(e); - p.length > 0 && o.push(jn("xq", p, n)); + if (p !== void 0 && o.push(Wn("xo", p, n)), !e.isCollapsed()) { + const f = id(e); + f.length > 0 && o.push(Wn("xq", f, n)); } - o.push(jn("xt", "", n)); + o.push(Wn("xt", "", n)); break; default: s?.warn(`$createNoteChildren: Unsupported note marker '${t}'`); @@ -16168,45 +16169,45 @@ function n_(e, t, r, n, i, s) { } return o; } -function xh(e, t, r, n, i, s, o) { - const a = o === "false", c = a ? !1 : rl(n?.noteMode), l = Ec(e, t, c); - s && bt(l, Vr, () => s); - const u = n?.isNoteShellEditable === !1; - let d, f; - n?.markerMode === "editable" ? (d = ot(e), u && d.setMode("token"), a || (f = ot(e, "closing"))) : n?.markerMode === "visible" && (d = Tr("marker", we(e) + " "), a || (f = Tr("marker", rt(e)))); - let p; - if (d && l.append(d), n?.markerMode === "editable" && !c) - t === "" ? l.append(...r) : (p = pe(At(l.__caller)), u && p.setMode("token"), l.append(p, ...r)); +function qh(e, t, r, n, i, s, o) { + const a = o === "false", c = a ? !1 : cl(n?.noteMode), l = qc(e, t, c); + s && kt(l, Yr, () => s); + const d = n?.isNoteShellEditable === !1; + let u, p; + n?.markerMode === "editable" ? (u = lt(e), d && u.setMode("token"), a || (p = lt(e, "closing"))) : n?.markerMode === "visible" && (u = Sr("marker", Re(e) + " "), a || (p = Sr("marker", ot(e)))); + let f; + if (u && l.append(u), n?.markerMode === "editable" && !c) + t === "" ? l.append(...r) : (f = he(Pt(l.__caller)), d && f.setMode("token"), l.append(f, ...r)); else { - const m = () => mo(), g = r.flatMap(s_(m)); + const g = () => To(), h = r.flatMap(x_(g)); if (t === "") - l.append(...g); + l.append(...h); else { - const y = Rc(r); + const y = Fc(r); let k = () => { }; - i?.noteCallerOnClick && (k = i.noteCallerOnClick), p = el(l.__caller, y, k), l.append(p, m(), ...g); + i?.noteCallerOnClick && (k = i.noteCallerOnClick), f = ol(l.__caller, y, k), l.append(f, g(), ...h); } } - return f && l.append(f), l; + return p && l.append(p), l; } -function Ju(e) { +function nd(e) { if (typeof e == "string") { - const i = se(e); - return j(i) ? i : void 0; + const i = oe(e); + return K(i) ? i : void 0; } - const t = ui(); + const t = pi(); if (t.length <= 0) return; - const n = t.filter((i) => j(i.node))[e]?.node; - if (j(n)) + const n = t.filter((i) => K(i.node))[e]?.node; + if (K(n)) return n; } -function i_(e, t) { +function T_(e, t) { const r = t?.noteMode === "collapsed"; if (e.setIsCollapsed(r), r) { const n = e.getPreviousSibling(); - if (wn(n) || !n) { + if ($n(n) || !n) { const i = e.getParent(); if (i) { const s = e.getIndexWithinParent(); @@ -16215,46 +16216,46 @@ function i_(e, t) { } else n.selectEnd(); } else - e.getChildren().reverse().find(D)?.selectEnd(); + e.getChildren().reverse().find(I)?.selectEnd(); } -function s_(e) { +function x_(e) { return (t) => Yt(t) ? [t] : [t, e()]; } -function o_(e) { +function __(e) { const t = e.getParent(); - return t !== null && nt(t, j) !== null; + return t !== null && rt(t, K) !== null; } -function Yu(e) { - if (!N(e)) +function id(e) { + if (!P(e)) return ""; const t = e.getNodes(); if (t.length === 0) return ""; - const r = t[0], n = t[t.length - 1], i = e.anchor.isBefore(e.focus), [s, o] = kc(e); + const r = t[0], n = t[t.length - 1], i = e.anchor.isBefore(e.focus), [s, o] = Sc(e); let a = ""; for (const c of t) - if (!(j(c) || St(c) || o_(c)) && !O(c) && !Qr(c) && ne(c, oe) !== "attribute") { - if (ge(c)) { + if (!(K(c) || St(c) || __(c)) && !O(c) && !Ir(c) && ie(c, ae) !== "attribute") { + if (me(c)) { a += `\\+fv ${c.getNumber()}\\+fv*`; continue; } - if (v(c)) { + if (S(c)) { let l = c.getTextContent(); c === r && c === n ? l = s < o ? l.slice(s, o) : l.slice(o, s) : c === r ? l = i ? l.slice(s) : l.slice(o) : c === n && (l = i ? l.slice(0, o) : l.slice(0, s)), a += l; } } return a.replace(/[ \t\r\n\f\v]+/g, " ").trim(); } -const nl = [ +const ll = [ Gt, - Ct, - ...HT -], a_ = [ - hi, - ...nl -], c_ = En((e, t) => { + vt, + ...lx +], C_ = [ + yi, + ...ll +], v_ = Nn((e, t) => { const { coords: r, children: n, style: i, ...s } = e, o = r !== void 0; - return C("div", { ref: t, className: "floating-box", "aria-hidden": !o, style: { + return v("div", { ref: t, className: "floating-box", "aria-hidden": !o, style: { ...i, position: "absolute", zIndex: 1e3, @@ -16264,28 +16265,28 @@ const nl = [ opacity: o ? 1 : 0 }, ...s, children: n }); }); -function l_() { - const [e, t] = de(void 0), [r, n] = de(), i = Z(null), s = he((a, c) => { +function S_() { + const [e, t] = fe(void 0), [r, n] = fe(), i = Q(null), s = ge((a, c) => { i.current && i.current(); const l = a.commonAncestorContainer.nodeType === a.commonAncestorContainer.TEXT_NODE ? a : a.commonAncestorContainer; - i.current = Wy(l, c, () => { - Hy(l, c, { + i.current = cb(l, c, () => { + lb(l, c, { placement: "bottom-start", - middleware: [Gy(), Jy()] - }).then((u) => { - n(u.placement), t((d) => d?.x === u.x && d?.y === u.y ? d : { x: u.x, y: u.y }); + middleware: [ub(), db()] + }).then((d) => { + n(d.placement), t((u) => u?.x === d.x && u?.y === d.y ? u : { x: d.x, y: d.y }); }).catch(() => { t(void 0); }); }); - }, []), o = he(() => { + }, []), o = ge(() => { i.current && (t(void 0), i.current(), i.current = null); }, []); - return z(() => o, [o]), { coords: e, placement: r, updatePosition: s, cleanup: o }; + return F(() => o, [o]), { coords: e, placement: r, updatePosition: s, cleanup: o }; } -function u_({ isOpen: e, floatingBoxRef: t }) { - const { coords: r, updatePosition: n, cleanup: i, placement: s } = l_(); - return z(() => { +function M_({ isOpen: e, floatingBoxRef: t }) { + const { coords: r, updatePosition: n, cleanup: i, placement: s } = S_(); + return F(() => { if (!e || !t.current) { i(); return; @@ -16298,103 +16299,103 @@ function u_({ isOpen: e, floatingBoxRef: t }) { return n(o, t.current), i; }, [i, e, t, n]), { coords: r, placement: s }; } -const d_ = ey(c_); -function _h({ isOpen: e = !1, children: t }) { - const r = Z(null), { coords: n, placement: i } = u_({ isOpen: e, floatingBoxRef: r }), s = ze(() => n ? typeof t == "function" ? t : () => t : () => null, [t, n]); - return hn( - C(d_, { ref: r, coords: n, style: n ? void 0 : { display: "none" }, children: s({ isOpen: e, placement: i }) }), +const E_ = py(v_); +function Rh({ isOpen: e = !1, children: t }) { + const r = Q(null), { coords: n, placement: i } = M_({ isOpen: e, floatingBoxRef: r }), s = je(() => n ? typeof t == "function" ? t : () => t : () => null, [t, n]); + return yn( + v(E_, { ref: r, coords: n, style: n ? void 0 : { display: "none" }, children: s({ isOpen: e, placement: i }) }), // Read at render rather than at module scope: this module sits in the import graph of the // package's utility entry points, so touching `document` on load throws for any consumer that // imports one of them outside a DOM environment (a Node-environment unit test, SSR). document.body ); } -const Ch = gf(void 0); -function il() { - const e = mf(Ch); +const $h = Ef(void 0); +function ul() { + const e = Af($h); if (!e) throw new Error("useMenuContext must be used within a MenuProvider"); return e; } -function f_(e, t) { - const [r, n] = de(0), [i, s] = de(-1), o = ze(() => e ?? [], [e]), a = { +function A_(e, t) { + const [r, n] = fe(0), [i, s] = fe(-1), o = je(() => e ?? [], [e]), a = { menuItems: o, activeIndex: r, selectedIndex: i, onSelectOption: t ?? (() => { }) - }, c = he(() => { - n((d) => { - const f = o.length; - return f ? (d - 1 + f) % f : 0; + }, c = ge(() => { + n((u) => { + const p = o.length; + return p ? (u - 1 + p) % p : 0; }); - }, [o.length]), l = he(() => { - n((d) => { - const f = o.length; - return f ? (d + 1) % f : 0; + }, [o.length]), l = ge(() => { + n((u) => { + const p = o.length; + return p ? (u + 1) % p : 0; }); - }, [o.length]), u = he(() => { - const d = o.length; - if (r >= 0 && r < d) { - const f = o[r]; - t?.(f), s(r); + }, [o.length]), d = ge(() => { + const u = o.length; + if (r >= 0 && r < u) { + const p = o[r]; + t?.(p), s(r); } }, [r, o, t]); return { state: a, moveUp: c, moveDown: l, - select: u, + select: d, setActiveIndex: n, setSelectedIndex: s }; } -function p_({ children: e, menuItems: t, onSelectOption: r, ...n }) { - const i = f_(t, r); - return C(Ch.Provider, { value: i, children: C("div", { ...n, children: e }) }); +function P_({ children: e, menuItems: t, onSelectOption: r, ...n }) { + const i = A_(t, r); + return v($h.Provider, { value: i, children: v("div", { ...n, children: e }) }); } -const Sh = En(({ index: e, children: t, onMouseEnter: r, onClick: n, ...i }, s) => { - const { state: { activeIndex: o }, setActiveIndex: a, setSelectedIndex: c, select: l } = il(), u = he((f) => { - l(), c(-1), n?.(f); - }, [n, l, c]), d = he((f) => { - a(e), r?.(f); +const Lh = Nn(({ index: e, children: t, onMouseEnter: r, onClick: n, ...i }, s) => { + const { state: { activeIndex: o }, setActiveIndex: a, setSelectedIndex: c, select: l } = ul(), d = ge((p) => { + l(), c(-1), n?.(p); + }, [n, l, c]), u = ge((p) => { + a(e), r?.(p); }, [e, a, r]); - return C("button", { ref: s, role: "menuitem", ...i, onClick: u, onMouseEnter: d, "aria-selected": e !== void 0 && o === e ? "true" : void 0, tabIndex: -1, children: t }); + return v("button", { ref: s, role: "menuitem", ...i, onClick: d, onMouseEnter: u, "aria-selected": e !== void 0 && o === e ? "true" : void 0, tabIndex: -1, children: t }); }); -function h_({ children: e, autoIndex: t = !0, ...r }) { - const n = Z(null), { state: { activeIndex: i, menuItems: s } } = il(), o = ze(() => s ? typeof e == "function" ? e : () => e : () => null, [e, s]), a = ze(() => { +function N_({ children: e, autoIndex: t = !0, ...r }) { + const n = Q(null), { state: { activeIndex: i, menuItems: s } } = ul(), o = je(() => s ? typeof e == "function" ? e : () => e : () => null, [e, s]), a = je(() => { const c = o(s); - return t ? ty.map(c, (l, u) => ry(l) && l.type === Sh && l.props.index === void 0 ? ny(l, { index: u }) : l) : c; + return t ? hy.map(c, (l, d) => gy(l) && l.type === Lh && l.props.index === void 0 ? my(l, { index: d }) : l) : c; }, [o, t, s]); - return z(() => { + return F(() => { if (n.current) { const c = n.current, l = c.children[i]; if (l) { - const u = c.getBoundingClientRect(), d = l.getBoundingClientRect(); - d.bottom > u.bottom ? c.scrollTop += d.bottom - u.bottom : d.top < u.top && (c.scrollTop -= u.top - d.top); + const d = c.getBoundingClientRect(), u = l.getBoundingClientRect(); + u.bottom > d.bottom ? c.scrollTop += u.bottom - d.bottom : u.top < d.top && (c.scrollTop -= d.top - u.top); } } - }, [i]), C("div", { ref: n, role: "menu", ...r, children: a }); + }, [i]), v("div", { ref: n, role: "menu", ...r, children: a }); } -const g_ = (e, t, r) => Ns(e, r).toLowerCase().includes(t.toLowerCase()), Xu = (e) => Object.keys(e).find((t) => typeof e[t] == "string") || "", Ns = (e, t) => { +const O_ = (e, t, r) => $s(e, r).toLowerCase().includes(t.toLowerCase()), sd = (e) => Object.keys(e).find((t) => typeof e[t] == "string") || "", $s = (e, t) => { const r = e[t]; return typeof r == "string" ? r : String(r); }; -function m_(e) { +function w_(e) { const { query: t, items: r, filterBy: n, filter: i, sortBy: s, sortingOptions: o } = e, { caseSensitive: a = !1, priorityOrder: c = ["exact", "startsWith", "contains"] } = o || {}, l = a ? t : t.toLowerCase(); - let u, d; - i ? (d = i, u = r.length > 0 ? Xu(r[0]) : "") : (u = n || (r.length > 0 ? Xu(r[0]) : ""), d = (m, g) => g_(m, g, u)); - const f = s || u, p = /* @__PURE__ */ new Map(); - return r.filter((m) => { + let d, u; + i ? (u = i, d = r.length > 0 ? sd(r[0]) : "") : (d = n || (r.length > 0 ? sd(r[0]) : ""), u = (g, h) => O_(g, h, d)); + const p = s || d, f = /* @__PURE__ */ new Map(); + return r.filter((g) => { try { - return d(m, t); - } catch (g) { - return console.warn("Error filtering item:", m, g), !1; - } - }).sort((m, g) => { - const y = (S) => (p.has(S) || p.set(S, Ns(S, f).toLowerCase()), p.get(S) ?? ""), k = a ? Ns(m, f) : y(m), _ = a ? Ns(g, f) : y(g); - for (const S of c) - switch (S) { + return u(g, t); + } catch (h) { + return console.warn("Error filtering item:", g, h), !1; + } + }).sort((g, h) => { + const y = (C) => (f.has(C) || f.set(C, $s(C, p).toLowerCase()), f.get(C) ?? ""), k = a ? $s(g, p) : y(g), _ = a ? $s(h, p) : y(h); + for (const C of c) + switch (C) { case "exact": if (k === l && _ !== l) return -1; @@ -16408,27 +16409,27 @@ function m_(e) { return 1; break; case "contains": { - const P = k.indexOf(l), A = _.indexOf(l); - if (P !== -1 && A === -1) + const M = k.indexOf(l), E = _.indexOf(l); + if (M !== -1 && E === -1) return -1; - if (A !== -1 && P === -1) + if (E !== -1 && M === -1) return 1; - if (P !== -1 && A !== -1) - return P - A; + if (M !== -1 && E !== -1) + return M - E; break; } } return k.localeCompare(_); }); } -const ia = { - Root: p_, - Options: h_, - Option: Sh +const la = { + Root: P_, + Options: N_, + Option: Lh }; -function y_(e) { +function q_(e) { const { query: t, items: r, filterBy: n, filter: i, sortBy: s, sortingOptions: o } = e; - return ze(() => m_({ + return je(() => w_({ query: t, items: r, filterBy: n, @@ -16437,17 +16438,17 @@ function y_(e) { sortingOptions: o }), [t, r, n, i, s, o]); } -function b_() { - const { moveUp: e, moveDown: t, select: r } = il(); - return ze(() => ({ +function R_() { + const { moveUp: e, moveDown: t, select: r } = ul(); + return je(() => ({ moveUp: e, moveDown: t, select: r }), [e, t, r]); } -const k_ = () => { - const e = b_(), [t] = ce(); - z(() => { +const $_ = () => { + const e = R_(), [t] = ce(); + F(() => { const r = (n) => { const s = { ArrowDown: () => e?.moveDown(), @@ -16457,62 +16458,62 @@ const k_ = () => { }[n.key]; return s ? (s(), n.preventDefault(), n.stopPropagation(), !0) : !1; }; - return t.registerCommand(vr, r, Ne); + return t.registerCommand(Nr, r, qe); }, [t, e]); }; -function T_() { - return k_(), null; +function L_() { + return $_(), null; } -const x_ = ["Shift", "Control", "Alt", "Meta"]; -function vh(e) { - const { options: t, onSelectOption: r, onClose: n, inverse: i, query: s, menuOpenKey: o, onFilterChange: a, passthroughKeys: c } = e, [l] = ce(), u = s !== void 0, [d, f] = de(""), p = u ? s ?? "" : d, m = y_({ query: p, items: t, filterBy: "name" }), g = (y) => { +const I_ = ["Shift", "Control", "Alt", "Meta"]; +function Ih(e) { + const { options: t, onSelectOption: r, onClose: n, inverse: i, query: s, menuOpenKey: o, onFilterChange: a, passthroughKeys: c } = e, [l] = ce(), d = s !== void 0, [u, p] = fe(""), f = d ? s ?? "" : u, g = q_({ query: f, items: t, filterBy: "name" }), h = (y) => { n?.(), r ? r(y) : y.action(l); }; - return z(() => { - a?.(p, m); - }, [a, p, m]), z(() => l.registerCommand(vr, (y) => { - if (u || c?.includes(y.key) || x_.includes(y.key)) + return F(() => { + a?.(f, g); + }, [a, f, g]), F(() => l.registerCommand(Nr, (y) => { + if (d || c?.includes(y.key) || I_.includes(y.key)) return !1; if ((y.ctrlKey || y.metaKey || y.altKey) && !y.getModifierState("AltGraph")) return n?.(), !1; const _ = { Escape: () => n?.(), Backspace: () => { - p.length === 0 ? n?.() : f((S) => S.slice(0, -1)); + f.length === 0 ? n?.() : p((C) => C.slice(0, -1)); } }[y.key]; - return _ ? (y.stopPropagation(), y.preventDefault(), _(), !0) : y.key.length === 1 ? (y.stopPropagation(), y.preventDefault(), y.key !== o && f((S) => S + y.key), !0) : !1; - }, Ne), [l, u, p, o, n, c]), Te(ia.Root, { className: `autocomplete-menu-container ${i ? "inverse" : ""}`, menuItems: m, onSelectOption: (y) => g(y), children: [!u && C("input", { value: p, type: "text", disabled: !0 }), C(T_, {}), C(ia.Options, { className: "autocomplete-menu-options", autoIndex: !1, children: (y) => y.map((_, S) => Te(ia.Option, { index: S, children: [C("span", { className: "label", children: _.label ?? _.name }), C("span", { className: "description", children: _.description })] }, _.name)) })] }); + return _ ? (y.stopPropagation(), y.preventDefault(), _(), !0) : y.key.length === 1 ? (y.stopPropagation(), y.preventDefault(), y.key !== o && p((C) => C + y.key), !0) : !1; + }, qe), [l, d, f, o, n, c]), xe(la.Root, { className: `autocomplete-menu-container ${i ? "inverse" : ""}`, menuItems: g, onSelectOption: (y) => h(y), children: [!d && v("input", { value: f, type: "text", disabled: !0 }), v(L_, {}), v(la.Options, { className: "autocomplete-menu-options", autoIndex: !1, children: (y) => y.map((_, C) => xe(la.Option, { index: C, children: [v("span", { className: "label", children: _.label ?? _.name }), v("span", { className: "description", children: _.description })] }, _.name)) })] }); } -function __({ trigger: e, items: t }) { - const [r] = ce(), [n, i] = de(!1), s = he((o) => { +function D_({ trigger: e, items: t }) { + const [r] = ce(), [n, i] = fe(!1), s = ge((o) => { o.key === "Escape" && n ? (i(!1), r.focus()) : o.key === e && !n && (o.preventDefault(), i(!0)); }, [r, e, n]); - return z(() => r.registerRootListener((o) => { + return F(() => r.registerRootListener((o) => { if (o) return o.addEventListener("keydown", s), () => { o.removeEventListener("keydown", s); }; - }), [r, s]), z(() => r.registerUpdateListener(({ prevEditorState: o, editorState: a }) => { + }), [r, s]), F(() => r.registerUpdateListener(({ prevEditorState: o, editorState: a }) => { const c = o.read(() => { const l = w(); - if (N(l)) + if (P(l)) return l; }); a.read(() => { const l = w(); - !N(l) || c?.is(l) || i(!1); + !P(l) || c?.is(l) || i(!1); }); - }), [r]), t && C(_h, { isOpen: n, children: ({ placement: o }) => C(vh, { options: t, onClose: () => i(!1), inverse: o === "top-start", menuOpenKey: e }) }); + }), [r]), t && v(Rh, { isOpen: n, children: ({ placement: o }) => v(Ih, { options: t, onClose: () => i(!1), inverse: o === "top-start", menuOpenKey: e }) }); } -function C_({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) { - return { markersMenuItems: ze(() => { +function U_({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) { + return { markersMenuItems: je(() => { if (!t || !e) return; - const i = lr(t); + const i = ur(t); if (i?.children) return Object.values(i.children).flatMap((s) => s.map((o) => { - const a = lr(o), { action: c } = r(o, a); + const a = ur(o), { action: c } = r(o, a); return { name: o, label: o, @@ -16524,132 +16525,132 @@ function C_({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) { })); }, [t, r, e]) }; } -function Fi(e, t) { +function Bi(e, t) { return `${e}:${t}`; } -function S_(e, t) { - z(() => { - if (!e.hasNodes([Qe])) +function F_(e, t) { + F(() => { + if (!e.hasNodes([et])) throw new Error("AnnotationPlugin: TypedMarkNode not registered on editor!"); const r = /* @__PURE__ */ new Map(); - return Ve(Pf(e, Qe, (n) => Hi(n.getTypedIDs(), n.getTypedOnClicks(), n.getTypedOnRemoves(), n.getTypedOnMouseEnters(), n.getTypedOnMouseLeaves()), (n, i) => { + return Fe(zf(e, et, (n) => Xi(n.getTypedIDs(), n.getTypedOnClicks(), n.getTypedOnRemoves(), n.getTypedOnMouseEnters(), n.getTypedOnMouseLeaves()), (n, i) => { const s = n.getTypedOnClicks(), o = n.getTypedOnRemoves(), a = n.getTypedOnMouseEnters(), c = n.getTypedOnMouseLeaves(); - for (const [l, u] of Object.entries(n.getTypedIDs())) - u.forEach((d) => { - const f = s[l]?.[d], p = o[l]?.[d], m = a[l]?.[d], g = c[l]?.[d]; - i.addID(l, d, f, p, m, g); + for (const [l, d] of Object.entries(n.getTypedIDs())) + d.forEach((u) => { + const p = s[l]?.[u], f = o[l]?.[u], g = a[l]?.[u], h = c[l]?.[u]; + i.addID(l, u, p, f, g, h); }); n.getWritable().__suppressOnRemoveCallbacks = !0; - }), e.registerMutationListener(Qe, (n) => { + }), e.registerMutationListener(et, (n) => { e.getEditorState().read(() => { for (const [i, s] of n) { - const o = se(i); + const o = oe(i); let a = {}; s === "destroyed" ? a = r.get(i) ?? {} : Ce(o) && (a = o.getTypedIDs()); for (const [c, l] of Object.entries(a)) - if (!Qe.isReservedType(c)) - for (const u of l) { - let d = t.get(Fi(c, u)); - a[c] = l, r.set(i, a), s === "destroyed" ? d !== void 0 && (d.delete(i), d.size === 0 && t.delete(Fi(c, u))) : (d === void 0 && (d = /* @__PURE__ */ new Set(), t.set(Fi(c, u), d)), d.has(i) || d.add(i)); + if (!et.isReservedType(c)) + for (const d of l) { + let u = t.get(Bi(c, d)); + a[c] = l, r.set(i, a), s === "destroyed" ? u !== void 0 && (u.delete(i), u.size === 0 && t.delete(Bi(c, d))) : (u === void 0 && (u = /* @__PURE__ */ new Set(), t.set(Bi(c, d), u)), u.has(i) || u.add(i)); } } }); }, { skipInitialization: !0 })); }, [e, t]); } -const v_ = En(function({ logger: t }, r) { - const [n] = ce(), i = ze(() => /* @__PURE__ */ new Map(), []); - S_(n, i); +const z_ = Nn(function({ logger: t }, r) { + const [n] = ce(), i = je(() => /* @__PURE__ */ new Map(), []); + F_(n, i); const s = (o, a, c) => { - const l = Array.from(c ?? i.get(Fi(o, a)) ?? []); + const l = Array.from(c ?? i.get(Bi(o, a)) ?? []); if (l.length !== 0) - for (const u of l) { - const d = se(u); - Ce(d) && (d.deleteID(o, a), d.hasNoIDsForEveryType() && Us(d)); + for (const d of l) { + const u = oe(d); + Ce(u) && (u.deleteID(o, a), u.hasNoIDsForEveryType() && js(u)); } }; - return gc(r, () => ({ - setAnnotation(o, a, c, l, u, d, f) { - if (Qe.isReservedType(a)) + return _c(r, () => ({ + setAnnotation(o, a, c, l, d, u, p) { + if (et.isReservedType(a)) throw new Error(`setAnnotation: Can't directly set this reserved annotation type '${a}'. Use the appropriate plugin instead.`); n.update(() => { - const p = Mo(o); - if (p === void 0) { + const f = No(o); + if (f === void 0) { t?.error("Failed to find start or end node of the annotation."); return; } - s(a, c), Zf(p, a, c, l, u, d, f); - }, { tag: Sa }); + s(a, c), dp(f, a, c, l, d, u, p); + }, { tag: Na }); }, removeAnnotation(o, a) { - if (Qe.isReservedType(o)) + if (et.isReservedType(o)) throw new Error(`removeAnnotation: Can't directly remove this reserved annotation type '${o}'. Use the appropriate plugin instead.`); - const c = i.get(Fi(o, a)); + const c = i.get(Bi(o, a)); c === void 0 || c.size === 0 || n.update(() => { s(o, a, c); - }, { tag: Sa }); + }, { tag: Na }); } })), null; -}), M_ = []; -function E_({ ignoreHistoryMergeTagChange: e = !0, ignoreSelectionChange: t = !1, ignoreTags: r = M_, onChange: n }) { +}), K_ = []; +function j_({ ignoreHistoryMergeTagChange: e = !0, ignoreSelectionChange: t = !1, ignoreTags: r = K_, onChange: n }) { const [i] = ce(); - return cs(() => { + return ps(() => { if (n) return i.registerUpdateListener((s) => { - const { editorState: o, dirtyElements: a, dirtyLeaves: c, prevEditorState: l, tags: u } = s; + const { editorState: o, dirtyElements: a, dirtyLeaves: c, prevEditorState: l, tags: d } = s; if (t && a.size === 0 && c.size === 0 || // A `MARKER_SETTLE_TAG` commit carries the merge tag only to stay out of the undo // stack — its bytes really did change, so it must reach `onChange` like any edit. // Without this exemption the cached USJ and the emitted delta both keep showing the // pre-settle bytes, and the host saves a document the editor is no longer displaying. - e && u.has(kf) && !u.has(Ff) || r.some((f) => u.has(f)) || l.isEmpty()) + e && d.has(Of) && !d.has(Qf) || r.some((p) => d.has(p)) || l.isEmpty()) return; - const d = A_(i, s); - d.length !== 0 && n(o, i, u, d); + const u = B_(i, s); + u.length !== 0 && n(o, i, d, u); }); }, [i, e, t, r, n]), null; } -function A_(e, { dirtyLeaves: t, prevEditorState: r }) { - let n = new qi(); +function B_(e, { dirtyLeaves: t, prevEditorState: r }) { + let n = new Ii(); return e.getEditorState().read(() => { - const i = t.values().next().value ?? "", s = se(i), o = s !== null && Xt(s) !== void 0; - if (t.size === 1 && v(s) && !o && Ex(s)) { - const a = hh(s); + const i = t.values().next().value ?? "", s = oe(i), o = s !== null && Xt(s) !== void 0; + if (t.size === 1 && S(s) && !o && jx(s)) { + const a = Mh(s); if (a !== void 0) { const c = r.read(() => { - const d = se(i); - return new qi([v(d) ? za(d) : { insert: "" }]); - }), l = new qi([za(s)]), u = new qi(a > 0 ? [{ retain: a }] : []); - n = n.concat(u).concat(c.diff(l)); + const u = oe(i); + return new Ii([S(u) ? Ha(u) : { insert: "" }]); + }), l = new Ii([Ha(s)]), d = new Ii(a > 0 ? [{ retain: a }] : []); + n = n.concat(d).concat(c.diff(l)); } } else { - const a = Bu(r), c = Bu(e.getEditorState()); + const a = Qu(r), c = Qu(e.getEditorState()); n = a.diff(c); } }), n.ops; } -const sl = "formatted", Mh = "unformatted", Eh = "paragraph-structure", ol = "standard", Ah = "block-verse", P_ = { - [sl]: "Formatted", - [Mh]: "Unformatted", - [Eh]: "Paragraph Structure", - [ol]: "Standard", - [Ah]: "Block Verse" +const dl = "formatted", Dh = "unformatted", Uh = "paragraph-structure", fl = "standard", Fh = "block-verse", V_ = { + [dl]: "Formatted", + [Dh]: "Unformatted", + [Uh]: "Paragraph Structure", + [fl]: "Standard", + [Fh]: "Block Verse" }; -function gi(e) { +function bi(e) { return e?.showParaMarkerPrefixes !== !1; } -let al, cl; -function N_(e) { - const t = ll(e); +let pl, hl; +function W_(e) { + const t = gl(e); if (!t) throw new Error(`Invalid view mode: ${e}`); - al = e, cl = t; + pl = e, hl = t; } -N_(sl); -const MP = () => al, Eo = () => cl; -function ll(e) { +W_(dl); +const YP = () => pl, Oo = () => hl; +function gl(e) { let t; - switch (e ?? al) { - case sl: + switch (e ?? pl) { + case dl: t = { markerMode: "hidden", noteMode: "collapsed", @@ -16657,7 +16658,7 @@ function ll(e) { isFormattedFont: !0 }; break; - case Mh: + case Dh: t = { markerMode: "editable", noteMode: "expanded", @@ -16665,7 +16666,7 @@ function ll(e) { isFormattedFont: !1 }; break; - case Eh: + case Uh: t = { markerMode: "hidden", noteMode: "collapsed", @@ -16675,7 +16676,7 @@ function ll(e) { hasActiveTextFocusBox: !0 }; break; - case ol: + case fl: t = { markerMode: "editable", noteMode: "collapsed", @@ -16683,7 +16684,7 @@ function ll(e) { isFormattedFont: !0 }; break; - case Ah: + case Fh: t = { markerMode: "hidden", noteMode: "collapsed", @@ -16695,173 +16696,173 @@ function ll(e) { } return t; } -function EP(e) { +function XP(e) { if (!e) return; - const t = Qu(e); - return Object.keys(P_).find((r) => wt(Qu(ll(r)), t)); + const t = od(e); + return Object.keys(V_).find((r) => qt(od(gl(r)), t)); } -const O_ = { +const H_ = { showCharMarkerTitles: !0, hasGutterParaMarkers: !1, hasActiveTextFocusBox: !1, verseLayout: "inline" }; -function Qu(e) { +function od(e) { if (!e) return e; const t = Object.fromEntries(Object.entries(e).filter(([, r]) => r !== void 0)); - return { ...O_, ...t }; + return { ...H_, ...t }; } -function Ao(e) { +function wo(e) { if (!e) return !1; const { markerMode: t, hasSpacing: r, isFormattedFont: n, hasGutterParaMarkers: i, hasActiveTextFocusBox: s } = e; return t === "editable" && r && n && !i && !s; } -function w_(e) { +function G_(e) { if (e) - return ns(e) ? Ct : e.markerMode === "editable" ? dt : Ct; + return as(e) ? vt : e.markerMode === "editable" ? ft : vt; } -function ns(e) { +function as(e) { return e?.verseLayout === "block"; } -function q_(e) { - const t = [], r = e ?? cl; - return r && (t.push(`${lb}${r.markerMode}`), r.hasSpacing && t.push(ab), r.isFormattedFont && t.push(cb)), t; +function J_(e) { + const t = [], r = e ?? hl; + return r && (t.push(`${Sb}${r.markerMode}`), r.hasSpacing && t.push(Cb), r.isFormattedFont && t.push(vb)), t; } -function R_(e, t, r, n) { +function Y_(e, t, r, n) { let i = 0; e.forEach((s) => { if ("retain" in s) - i += $_(s, i, t, n); + i += X_(s, i, t, n); else if ("delete" in s) { if (typeof s.delete != "number" || s.delete <= 0) { n?.error(`Invalid delete operation: ${JSON.stringify(s)}`); return; } - n?.debug(`Delete: ${s.delete}`), L_(i, s.delete, n); - } else "insert" in s ? typeof s.insert == "string" ? (n?.debug(`Insert: '${s.insert}'`), i += D_(i, s.insert, s.attributes, t, n)) : typeof s.insert == "object" && s.insert !== null ? (n?.debug(`Insert embed: ${JSON.stringify(s.insert)}`), F_(i, s, t, r, n) ? i += 1 : n?.error(`Failed to process insert embed operation: ${JSON.stringify(s.insert)} at index ${i}. Document may be inconsistent.`)) : n?.error(`Insert of unknown type: ${JSON.stringify(s.insert)}`) : n?.error(`Unknown operation: ${JSON.stringify(s)}`); + n?.debug(`Delete: ${s.delete}`), Z_(i, s.delete, n); + } else "insert" in s ? typeof s.insert == "string" ? (n?.debug(`Insert: '${s.insert}'`), i += eC(i, s.insert, s.attributes, t, n)) : typeof s.insert == "object" && s.insert !== null ? (n?.debug(`Insert embed: ${JSON.stringify(s.insert)}`), rC(i, s, t, r, n) ? i += 1 : n?.error(`Failed to process insert embed operation: ${JSON.stringify(s.insert)} at index ${i}. Document may be inconsistent.`)) : n?.error(`Insert of unknown type: ${JSON.stringify(s.insert)}`) : n?.error(`Unknown operation: ${JSON.stringify(s)}`); }); } -function $_(e, t, r, n) { - return typeof e.retain != "number" || e.retain < 0 ? (n?.error(`Invalid retain operation: ${JSON.stringify(e)}`), 0) : (n?.debug(`Retain: ${e.retain}`), e.attributes && (n?.debug(`Retain attributes: ${JSON.stringify(e.attributes)}`), I_(t, e.retain, e.attributes, r, n)), e.retain); +function X_(e, t, r, n) { + return typeof e.retain != "number" || e.retain < 0 ? (n?.error(`Invalid retain operation: ${JSON.stringify(e)}`), 0) : (n?.debug(`Retain: ${e.retain}`), e.attributes && (n?.debug(`Retain attributes: ${JSON.stringify(e.attributes)}`), Q_(t, e.retain, e.attributes, r, n)), e.retain); } -function I_(e, t, r, n, i) { +function Q_(e, t, r, n, i) { i?.debug(`Applying attributes for range [${e}, ${e + t - 1}] with attributes: ${JSON.stringify(r)}`); let s = t, o = 0, a = -1; - const c = Fe(); - function l(u) { + const c = Ke(); + function l(d) { if (s <= 0) return !0; - if (_r(u)) { - const d = u.getTextContentSize(); - if (e < o + d && o < e + t) { - const f = Math.max(0, e - o), p = d - f, m = Math.min(s, p); - if (m > 0) { - let g = u; - const y = f > 0, k = m < d - f; + if (Er(d)) { + const u = d.getTextContentSize(); + if (e < o + u && o < e + t) { + const p = Math.max(0, e - o), f = u - p, g = Math.min(s, f); + if (g > 0) { + let h = d; + const y = p > 0, k = g < u - p; if (y && k) { - const [, _] = u.splitText(f); - [g] = _.splitText(m); - } else y ? [, g] = u.splitText(f) : k && ([g] = u.splitText(m)); - if (Hr(r)) { - const _ = g.getParent(); - if (D(_)) { - const S = r.char; - let P; - Array.isArray(S) ? a >= 0 && a <= S.length - 1 && (P = S[a]) : a === 0 && (P = S); - const A = P ? Tn(P, _) : !1; - if (A && Array.isArray(S) && S.length > 1) { - const B = pe(""); - g.replace(B); - const M = typeof r.segment == "string" ? r.segment : void 0, q = mi(S.slice(1), n, g, M); - let $ = B; - for (const J of q) - $.insertAfter(J), $ = J; - B.remove(), qt(r, g); - } else if (A) - qt(r, g); + const [, _] = d.splitText(p); + [h] = _.splitText(g); + } else y ? [, h] = d.splitText(p) : k && ([h] = d.splitText(g)); + if (Qr(r)) { + const _ = h.getParent(); + if (I(_)) { + const C = r.char; + let M; + Array.isArray(C) ? a >= 0 && a <= C.length - 1 && (M = C[a]) : a === 0 && (M = C); + const E = M ? Cn(M, _) : !1; + if (E && Array.isArray(C) && C.length > 1) { + const z = he(""); + h.replace(z); + const W = typeof r.segment == "string" ? r.segment : void 0, j = ki(C.slice(1), n, h, W); + let U = z; + for (const N of j) + U.insertAfter(N), U = N; + z.remove(), Rt(r, h); + } else if (E) + Rt(r, h); else { - g.remove(); - const B = Zu(g, r, n, i); - if (B && B.length > 0) { - let M = _; - for (const q of B) - M.insertAfter(q), M = q; + h.remove(); + const z = ad(h, r, n, i); + if (z && z.length > 0) { + let W = _; + for (const j of z) + W.insertAfter(j), W = j; } } } else { - const S = pe(""); - g.replace(S); - const P = Zu(g, r, n, i); - if (P && P.length > 0) { - let A = S; - for (const B of P) - A.insertAfter(B), A = B; - S.remove(); + const C = he(""); + h.replace(C); + const M = ad(h, r, n, i); + if (M && M.length > 0) { + let E = C; + for (const z of M) + E.insertAfter(z), E = z; + C.remove(); } else - S.replace(g); + C.replace(h); } } else - qt(r, g); - s -= m; + Rt(r, h); + s -= g; } } - o += d; - } else if (Pt(u)) - e <= o && o < e + t && s > 0 && (ed(u, r), s -= 1), o += 1; - else if (D(u)) { + o += u; + } else if (Nt(d)) + e <= o && o < e + t && s > 0 && (cd(d, r), s -= 1), o += 1; + else if (I(d)) { a += 1; - let d = !1; + let u = !1; if (e <= o && o < e + t && s > 0) - if (Hr(r)) { - const f = r.char; - let p; - if (Array.isArray(f) ? a >= 0 && a <= f.length - 1 && (p = f[a]) : a === 0 && (p = f), p) { - Ka(u, p.style), typeof p.cid == "string" && bt(u, kn, () => p.cid); - const m = Ue(p, Hs); - m && Object.keys(m).length > 0 ? u.setUnknownAttributes({ - ...u.getUnknownAttributes() ?? {}, - ...m - }) : u.setUnknownAttributes(void 0); + if (Qr(r)) { + const p = r.char; + let f; + if (Array.isArray(p) ? a >= 0 && a <= p.length - 1 && (f = p[a]) : a === 0 && (f = p), f) { + Ga(d, f.style), typeof f.cid == "string" && kt(d, _n, () => f.cid); + const g = ze(f, Xs); + g && Object.keys(g).length > 0 ? d.setUnknownAttributes({ + ...d.getUnknownAttributes() ?? {}, + ...g + }) : d.setUnknownAttributes(void 0); } - } else (r.char === !1 || r.char === null || J_(r.char)) && (d = !0); + } else (r.char === !1 || r.char === null || dC(r.char)) && (u = !0); if (s > 0) { - const f = u.getChildren(); - for (const p of f) { + const p = d.getChildren(); + for (const f of p) { if (s <= 0) break; - if (l(p) && s <= 0) - return d && _a(u), !0; + if (l(f) && s <= 0) + return u && Aa(d), !0; } } - d && _a(u), a -= 1; - } else if (Et(u)) { - const d = u.getChildren(); - for (const p of d) { + u && Aa(d), a -= 1; + } else if (At(d)) { + const u = d.getChildren(); + for (const f of u) { if (s <= 0) break; - if (l(p) && s <= 0) + if (l(f) && s <= 0) return !0; } - const f = 1; + const p = 1; if (e <= o && o < e + s && s > 0) { - if (!dr(u)) - ed(u, r); - else if (ul(r)) { - const p = Oh(r.para, n); - p && u.replace(p, !0); + if (!fr(d)) + cd(d, r); + else if (ml(r)) { + const f = jh(r.para, n); + f && d.replace(f, !0); } - s -= f; + s -= p; } - o += f; - } else if (F(u)) { - const d = u.getChildren(); - for (const f of d) { + o += p; + } else if (L(d)) { + const u = d.getChildren(); + for (const p of u) { if (s <= 0) break; - if (l(f) && s <= 0) + if (l(p) && s <= 0) return !0; } } @@ -16869,124 +16870,124 @@ function I_(e, t, r, n, i) { } l(c), s > 0 && i?.warn(`$applyAttributes: Not all characters in the retain operation (length ${t}) could be processed. Remaining: ${s}. targetIndex: ${e}, final currentIndex: ${o}`); } -function Zu(e, t, r, n) { - const i = typeof t.segment == "string" ? t.segment : void 0, s = mi(t.char, r, e, i), o = s.find(D); +function ad(e, t, r, n) { + const i = typeof t.segment == "string" ? t.segment : void 0, s = ki(t.char, r, e, i), o = s.find(I); if (!o) { - n?.error(`Failed to create CharNode for text transformation. Style: ${Array.isArray(t.char) ? t.char[0].style : t.char?.style}. Falling back to standard text attributes.`), qt(t, e); + n?.error(`Failed to create CharNode for text transformation. Style: ${Array.isArray(t.char) ? t.char[0].style : t.char?.style}. Falling back to standard text attributes.`), Rt(t, e); return; } const a = {}; - $h.forEach((u) => { - e.hasFormat(u) && (a[u] = "true"); + Hh.forEach((d) => { + e.hasFormat(d) && (a[d] = "true"); }); const c = {}; - Object.entries(t).forEach(([u, d]) => { - u === "segment" || u === "char" || (typeof d == "string" ? c[u] = d : d === !0 ? c[u] = "true" : d === !1 && (c[u] = "false")); + Object.entries(t).forEach(([d, u]) => { + d === "segment" || d === "char" || (typeof u == "string" ? c[d] = u : u === !0 ? c[d] = "true" : u === !1 && (c[d] = "false")); }); const l = { ...o.getUnknownAttributes() ?? {}, ...a, ...c }; - return Object.keys(l).length > 0 && o.setUnknownAttributes(l), qt(t, e), s; + return Object.keys(l).length > 0 && o.setUnknownAttributes(l), Rt(t, e), s; } -function Ph(e, t) { +function zh(e, t) { e.setMarker(t); const r = e.getFirstChild(); - O(r) ? (r.setMarker(t), r.setTextContent(we(t))) : Yt(r) && r.getTextType() === "marker" && r.setTextContent(we(t) + I); + O(r) ? (r.setMarker(t), r.setTextContent(Re(t))) : Yt(r) && r.getTextType() === "marker" && r.setTextContent(Re(t) + R); } -function Ka(e, t) { +function Ga(e, t) { const r = e.getMarker(); if (e.setMarker(t), t === r) return; e.getChildren().forEach((o) => { O(o) && o.getMarker() === r && o.setMarker(t); }); - const n = D(e.getParent()), i = e.getFirstChild(); - Yt(i) && i.getTextType() === "marker" && i.getTextContent() === we(r, n) && i.setTextContent(we(t, n)); + const n = I(e.getParent()), i = e.getFirstChild(); + Yt(i) && i.getTextType() === "marker" && i.getTextContent() === Re(r, n) && i.setTextContent(Re(t, n)); const s = e.getLastChild(); - Yt(s) && s.getTextType() === "marker" && s.getTextContent() === rt(r, n) && s.setTextContent(rt(t, n)); + Yt(s) && s.getTextType() === "marker" && s.getTextContent() === ot(r, n) && s.setTextContent(ot(t, n)); } -function ed(e, t) { +function cd(e, t) { for (const r of Object.keys(t)) { const n = t[r]; - if (r === "char" && D(e) && Hr(t)) { - const i = ja(n); - if (Ka(e, i.style), typeof i.cid == "string") { + if (r === "char" && I(e) && Qr(t)) { + const i = Ja(n); + if (Ga(e, i.style), typeof i.cid == "string") { const o = i.cid; - bt(e, kn, () => o); + kt(e, _n, () => o); } - const s = Ue(i, Hs); + const s = ze(i, Xs); s && Object.keys(s).length > 0 && e.setUnknownAttributes({ ...e.getUnknownAttributes() ?? {}, ...s }); continue; } - typeof n == "string" && (Ge(e) || ge(e) || Be(e) || j(e) || De(e) ? e.setUnknownAttributes({ + typeof n == "string" && (Je(e) || me(e) || He(e) || K(e) || Ue(e) ? e.setUnknownAttributes({ ...e.getUnknownAttributes() ?? {}, [r]: n - }) : (pt(e) || ae(e) || D(e)) && (r === "style" && ae(e) ? Ph(e, n) : r === "style" && D(e) ? Ka(e, n) : r === "code" && pt(e) ? e.setCode(n) : e.setUnknownAttributes({ + }) : (ht(e) || le(e) || I(e)) && (r === "style" && le(e) ? zh(e, n) : r === "style" && I(e) ? Ga(e, n) : r === "code" && ht(e) ? e.setCode(n) : e.setUnknownAttributes({ ...e.getUnknownAttributes() ?? {}, [r]: n - })), r === "segment" && bt(e, Vr, () => n)); + })), r === "segment" && kt(e, Yr, () => n)); } } -function L_(e, t, r) { +function Z_(e, t, r) { if (t <= 0) return; - const n = Fe(); + const n = Ke(); let i = 0, s = t; function o(a) { if (s <= 0) return !0; - if (_r(a)) { + if (Er(a)) { let c = a.getTextContentSize(); if (e < i + c && i < e + s) { - const l = Math.max(0, e - i), u = c - l, d = Math.min(s, u); - d > 0 && (a.spliceText(l, d, ""), a.getTextContentSize() === 0 && a.remove(), r?.debug(`Deleted ${d} length from TextNode (key: ${a.getKey()}) at nodeOffset ${l}. Original targetIndex: ${e}, current currentIndex: ${i}.`), s -= d, c -= d); + const l = Math.max(0, e - i), d = c - l, u = Math.min(s, d); + u > 0 && (a.spliceText(l, u, ""), a.getTextContentSize() === 0 && a.remove(), r?.debug(`Deleted ${u} length from TextNode (key: ${a.getKey()}) at nodeOffset ${l}. Original targetIndex: ${e}, current currentIndex: ${i}.`), s -= u, c -= u); } i += c; - } else if (Pt(a)) + } else if (Nt(a)) e <= i && i < e + s ? (a.remove(), r?.debug(`Deleted embed node (key: ${a.getKey()}) at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`), s -= 1) : i += 1; - else if (Et(a)) { + else if (At(a)) { const c = a.getChildren().slice(), l = a.getChildren(); - for (const u of l) { + for (const d of l) { if (s <= 0) break; - if (o(u) && s <= 0) + if (o(d) && s <= 0) return !0; } - if (e <= i && i < e + s && Et(a)) { + if (e <= i && i < e + s && At(a)) { s -= 1; - const u = a.getChildren().length; - if (c.length > 0 && u === 0) + const d = a.getChildren().length; + if (c.length > 0 && d === 0) (a.getParent()?.getChildren() ?? []).length > 1 ? (a.remove(), r?.debug(`Removed entire ParaNode that had all its content deleted at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`)) : (a.replace(Ht(), !0), r?.debug(`Replaced last ParaNode with ImpliedParaNode at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`)); else if (s > 0) { - const p = a.getNextSibling(); - if (p && Se(p)) { - let m = i + 1; - const g = p.getChildren(); - for (const k of g) { + const f = a.getNextSibling(); + if (f && ve(f)) { + let g = i + 1; + const h = f.getChildren(); + for (const k of h) { if (s <= 0) break; const _ = i; - if (i = m, o(k)) { + if (i = g, o(k)) { i = _; break; } - _r(k) ? m += k.getTextContentSize() : Pt(k) && (m += 1), i = _; + Er(k) ? g += k.getTextContentSize() : Nt(k) && (g += 1), i = _; } - const y = p.getChildren(); + const y = f.getChildren(); for (const k of y) k.remove(), a.append(k); - p.remove(), r?.debug(`Merged next paragraph into current one after deleting symbolic close at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`); + f.remove(), r?.debug(`Merged next paragraph into current one after deleting symbolic close at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`); } else a.replace(Ht(), !0); - } else ae(a) ? a.replace(Ht(), !0) : a.remove(); + } else le(a) ? a.replace(Ht(), !0) : a.remove(); } i += 1; - } else if (F(a)) { + } else if (L(a)) { const c = a.getChildren(); for (const l of c) { if (s <= 0) @@ -16999,146 +17000,146 @@ function L_(e, t, r) { } o(n), s > 0 && r?.warn(`Delete operation could not remove all requested characters. Remaining to delete: ${s}. Original targetIndex: ${e}, OT length: ${t}. Final currentIndex: ${i}`); } -function D_(e, t, r, n, i) { - if (t === rs) - return td(e, r, n, i); - if (t.endsWith(rs) && !ul(r)) { +function eC(e, t, r, n, i) { + if (t === os) + return ld(e, r, n, i); + if (t.endsWith(os) && !ml(r)) { const s = t.slice(0, -1); let o = 0; if (s.length > 0) { - if (Hr(r)) + if (Qr(r)) throw new Error("Text + LF should not have char attributes"); - o += Js(e, s, r, i); + o += Zs(e, s, r, i); } - return o += td(e + o, r, n, i), o; - } else return Hr(r) ? U_(e, t, r, n, i) : Js(e, t, r, i); + return o += ld(e + o, r, n, i), o; + } else return Qr(r) ? tC(e, t, r, n, i) : Zs(e, t, r, i); } -function U_(e, t, r, n, i) { +function tC(e, t, r, n, i) { i?.debug(`Attempting to insert CharNode with text "${t}" and attributes ${JSON.stringify(r.char)} at index ${e}`); - const s = pe(t === "" ? Dt : t); - qt(r, s); + const s = he(t === "" ? Ut : t); + Rt(r, s); let o; { let y = function(k) { - if (_r(k)) { + if (Er(k)) { const _ = k.getTextContentSize(); - if (e >= g && e < g + _) { - const S = k.getParent(); - return D(S) && (o = S), !0; + if (e >= h && e < h + _) { + const C = k.getParent(); + return I(C) && (o = C), !0; } - g += _; - } else if (Pt(k)) - g += 1; - else if (D(k)) { + h += _; + } else if (Nt(k)) + h += 1; + else if (I(k)) { const _ = k.getChildren(); - for (const S of _) - if (y(S)) + for (const C of _) + if (y(C)) return !0; - } else if (F(k)) { + } else if (L(k)) { const _ = k.getChildren(); - for (const S of _) - if (y(S)) + for (const C of _) + if (y(C)) return !0; - Et(k) && (g += 1); + At(k) && (h += 1); } return !1; }; - const m = Fe(); - let g = 0; - y(m); + const g = Ke(); + let h = 0; + y(g); } let a = r.char; if (Array.isArray(a)) { if (o) { - const m = a[0]; - m && Tn(m, o) ? (a = a.slice(1), a.length === 1 && (a = a[0])) : o = void 0; + const g = a[0]; + g && Cn(g, o) ? (a = a.slice(1), a.length === 1 && (a = a[0])) : o = void 0; } - } else o && (Tn(a, o) || (o = void 0)); - const c = typeof r.segment == "string" ? r.segment : void 0, u = mi(a, n, s, c, o ? [o] : void 0); - if (u.length === 0) + } else o && (Cn(a, o) || (o = void 0)); + const c = typeof r.segment == "string" ? r.segment : void 0, d = ki(a, n, s, c, o ? [o] : void 0); + if (d.length === 0) return t.length; - const d = u.find(D); - if (!d) - return i?.error(`CharNode style is missing for text "${t}". Attributes: ${JSON.stringify(r.char)}. Falling back to rich text insertion.`), Js(e, t, void 0, i); - const f = {}; - for (const [m, g] of Object.entries(r)) - m !== "char" && m !== "segment" && typeof g == "string" && (f[m] = g); - Object.keys(f).length > 0 && d.setUnknownAttributes(f); - let p = !0; - for (const m of u) - if (!Nh(e, m, i)) { - p = !1; + const u = d.find(I); + if (!u) + return i?.error(`CharNode style is missing for text "${t}". Attributes: ${JSON.stringify(r.char)}. Falling back to rich text insertion.`), Zs(e, t, void 0, i); + const p = {}; + for (const [g, h] of Object.entries(r)) + g !== "char" && g !== "segment" && typeof h == "string" && (p[g] = h); + Object.keys(p).length > 0 && u.setUnknownAttributes(p); + let f = !0; + for (const g of d) + if (!Kh(e, g, i)) { + f = !1; break; } - return p ? t.length : (i?.error(`Failed to insert CharNode with text "${t}" at index ${e}. Falling back to rich text.`), Js(e, t, void 0, i)); + return f ? t.length : (i?.error(`Failed to insert CharNode with text "${t}" at index ${e}. Falling back to rich text.`), Zs(e, t, void 0, i)); } -function Js(e, t, r, n) { +function Zs(e, t, r, n) { if (t.length <= 0) return n?.debug("Attempted to insert empty string. No action taken."), 0; - const i = Fe(); + const i = Ke(); let s = 0, o = !1; function a(c) { if (o) return !0; - if (_r(c)) { + if (Er(c)) { const l = c.getTextContentSize(); if (e >= s && e <= s + l) { - const u = e - s, d = pe(t); - if (qt(r, d), u === 0) - c.insertBefore(d); - else if (u === l) { - const f = c.getParent(); - D(f) && !Hr(r) ? f.insertAfter(d) : c.insertAfter(d); + const d = e - s, u = he(t); + if (Rt(r, u), d === 0) + c.insertBefore(u); + else if (d === l) { + const p = c.getParent(); + I(p) && !Qr(r) ? p.insertAfter(u) : c.insertAfter(u); } else { - const [, f] = c.splitText(u); - f.insertBefore(d); + const [, p] = c.splitText(d); + p.insertBefore(u); } - return n?.debug(`Inserted text "${t}" in/around TextNode (key: ${c.getKey()}) at nodeOffset ${u}. Original targetIndex: ${e}, currentIndex at node start: ${s}.`), o = !0, !0; + return n?.debug(`Inserted text "${t}" in/around TextNode (key: ${c.getKey()}) at nodeOffset ${d}. Original targetIndex: ${e}, currentIndex at node start: ${s}.`), o = !0, !0; } s += l; - } else if (Pt(c)) + } else if (Nt(c)) s += 1; - else if (D(c)) { + else if (I(c)) { if (!o && e === s) { - const d = pe(t); - qt(r, d); - const f = c.getFirstChild(); - return f ? f.insertBefore(d) : c.append(d), n?.debug(`Inserted text "${t}" at beginning of CharNode ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; + const u = he(t); + Rt(r, u); + const p = c.getFirstChild(); + return p ? p.insertBefore(u) : c.append(u), n?.debug(`Inserted text "${t}" at beginning of CharNode ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; } - const u = c.getChildren(); - for (const d of u) { - if (a(d)) + const d = c.getChildren(); + for (const u of d) { + if (a(u)) return !0; if (o) break; } if (!o && e === s) { - const d = pe(t); - return qt(r, d), c.append(d), n?.debug(`Appended text "${t}" to end of CharNode ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; + const u = he(t); + return Rt(r, u), c.append(u), n?.debug(`Appended text "${t}" to end of CharNode ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; } - } else if (Et(c)) { + } else if (At(c)) { if (!o && e === s) { - const d = pe(t); - qt(r, d); - const f = c.getFirstChild(); - return f ? f.insertBefore(d) : c.append(d), n?.debug(`Inserted text "${t}" at beginning of container ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; + const u = he(t); + Rt(r, u); + const p = c.getFirstChild(); + return p ? p.insertBefore(u) : c.append(u), n?.debug(`Inserted text "${t}" at beginning of container ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; } - const u = c.getChildren(); - for (const d of u) { - if (a(d)) + const d = c.getChildren(); + for (const u of d) { + if (a(u)) return !0; if (o) break; } if (!o && e === s) { - const d = pe(t); - return qt(r, d), c.append(d), n?.debug(`Appended text "${t}" to end of container ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; + const u = he(t); + return Rt(r, u), c.append(u), n?.debug(`Appended text "${t}" to end of container ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0; } s += 1; - } else if (F(c)) { + } else if (L(c)) { const l = c.getChildren(); - for (const u of l) { - if (a(u)) + for (const d of l) { + if (a(d)) return !0; if (o) break; @@ -17148,125 +17149,125 @@ function Js(e, t, r, n) { } if (a(i), !o && e === s) { n?.debug(`Insertion point matches end of document (targetIndex: ${e}, final currentIndex: ${s}). Appending text to new ParaNode.`); - const c = pe(t); - qt(r, c); + const c = he(t); + Rt(r, c); const l = Ht().append(c); i.append(l), o = !0; } return o ? t.length : (n?.warn(`$insertRichText: Could not find insertion point for text "${t}" at targetIndex ${e}. Final currentIndex: ${s}. Text not inserted.`), 0); } -function Nh(e, t, r) { - const n = Fe(); +function Kh(e, t, r) { + const n = Ke(); let i = 0, s = !1; function o(a) { if (s) return !0; if (a === n && e === 0 && !n.getFirstChild()) return t.isInline() ? (r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into empty root, wrapped in ImpliedParaNode. targetIndex: ${e}`), n.append(Ht().append(t))) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} directly into empty root. targetIndex: ${e}`), n.append(t)), s = !0, !0; - if (!F(a)) + if (!L(a)) return !1; const c = a.getChildren(); for (const l of c) { if (e === i && !s) { if (a === n && t.isInline()) - if (Se(l)) { + if (ve(l)) { r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into existing ${l.getType()} at beginning. targetIndex: ${e}`); - const u = l.getFirstChild(); - u ? u.insertBefore(t) : l.append(t); + const d = l.getFirstChild(); + d ? d.insertBefore(t) : l.append(t); } else r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into root before ${l.getType()}, wrapping in ImpliedParaNode. targetIndex: ${e}`), l.insertBefore(Ht().append(t)); else l.insertBefore(t), r?.debug(`$insertNodeAtCharacterOffset: Inserted node ${t.getType()} (key: ${t.getKey()}) before child ${l.getType()} (key: ${l.getKey()}) in ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, currentIndex: ${i}`); return s = !0, !0; } - if (_r(l)) { - const u = l.getTextContentSize(); - if (!s && e > i && e < i + u) { - const d = e - i, [f] = l.splitText(d); - return f.insertAfter(t), r?.debug(`$insertNodeAtCharacterOffset: Inserted node ${t.getType()} (key: ${t.getKey()}) by splitting TextNode (key: ${l.getKey()}) at offset ${d}. targetIndex: ${e}, currentIndex at node start: ${i}`), s = !0, !0; + if (Er(l)) { + const d = l.getTextContentSize(); + if (!s && e > i && e < i + d) { + const u = e - i, [p] = l.splitText(u); + return p.insertAfter(t), r?.debug(`$insertNodeAtCharacterOffset: Inserted node ${t.getType()} (key: ${t.getKey()}) by splitting TextNode (key: ${l.getKey()}) at offset ${u}. targetIndex: ${e}, currentIndex at node start: ${i}`), s = !0, !0; } - i += u; - } else if (Pt(l)) + i += d; + } else if (Nt(l)) i += 1; - else if (D(l)) { + else if (I(l)) { if (o(l)) return !0; - } else if (Et(l)) { - const u = l; - if (o(u)) + } else if (At(l)) { + const d = l; + if (o(d)) return !0; - const d = i; - if (dr(u) && Et(t) && // Target is at the ImpliedPara's implicit newline - e === d && !s) - return r?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode (key: ${u.getKey()}) with block node '${t.getType()}' (key: ${t.getKey()}) at OT index ${e}.`), l.replace(t, !0), i = d + 1, s = !0, !0; + const u = i; + if (fr(d) && At(t) && // Target is at the ImpliedPara's implicit newline + e === u && !s) + return r?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode (key: ${d.getKey()}) with block node '${t.getType()}' (key: ${t.getKey()}) at OT index ${e}.`), l.replace(t, !0), i = u + 1, s = !0, !0; i += 1; - } else if (F(l) && o(l)) + } else if (L(l) && o(l)) return !0; if (s) return !0; } - return F(a) && !s && (e === i || a === n && e > i) ? a === n ? (t.isInline() ? (r?.debug(`$insertNodeAtCharacterOffset: Appending inline node ${t.getType()} to root. Wrapping in new ImpliedParaNode. targetIndex: ${e}, current document OT length: ${i}.`), n.append(Ht().append(t))) : (r?.debug(`$insertNodeAtCharacterOffset: Appending block node ${t.getType()} to root. targetIndex: ${e}, current document OT length: ${i}.`), n.append(t)), s = !0, !0) : ( + return L(a) && !s && (e === i || a === n && e > i) ? a === n ? (t.isInline() ? (r?.debug(`$insertNodeAtCharacterOffset: Appending inline node ${t.getType()} to root. Wrapping in new ImpliedParaNode. targetIndex: ${e}, current document OT length: ${i}.`), n.append(Ht().append(t))) : (r?.debug(`$insertNodeAtCharacterOffset: Appending block node ${t.getType()} to root. targetIndex: ${e}, current document OT length: ${i}.`), n.append(t)), s = !0, !0) : ( // Appending to an existing container (ParaNode, ImpliedParaNode) // currentNode here is the container itself. currentIndex is at the point of currentNode's // closing marker. targetIndex === currentIndex means we are inserting at the conceptual end // of this container. - Se(a) ? dr(a) && ae(t) && e === i ? (r?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode container (key: ${a.getKey()}) with ParaNode ${t.getType()} (key: ${t.getKey()}) via append logic. targetIndex: ${e}`), a.replace(t, !0), s = !0, !0) : t.isInline() || !Se(t) ? (r?.debug(`$insertNodeAtCharacterOffset: Appending node ${t.getType()} to existing container ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, container end OT index: ${i}.`), a.append(t), s = !0, !0) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} after container ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, container end OT index: ${i}.`), a.insertAfter(t), s = !0, !0) : (D(a) ? (r?.debug(`$insertNodeAtCharacterOffset: Inserting node ${t.getType()} after CharNode (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.insertAfter(t)) : t.isInline() || !Se(t) ? (r?.debug(`$insertNodeAtCharacterOffset: Appending node ${t.getType()} to generic element ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.append(t)) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} after generic element ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.insertAfter(t)), s = !0, !0) + ve(a) ? fr(a) && le(t) && e === i ? (r?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode container (key: ${a.getKey()}) with ParaNode ${t.getType()} (key: ${t.getKey()}) via append logic. targetIndex: ${e}`), a.replace(t, !0), s = !0, !0) : t.isInline() || !ve(t) ? (r?.debug(`$insertNodeAtCharacterOffset: Appending node ${t.getType()} to existing container ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, container end OT index: ${i}.`), a.append(t), s = !0, !0) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} after container ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, container end OT index: ${i}.`), a.insertAfter(t), s = !0, !0) : (I(a) ? (r?.debug(`$insertNodeAtCharacterOffset: Inserting node ${t.getType()} after CharNode (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.insertAfter(t)) : t.isInline() || !ve(t) ? (r?.debug(`$insertNodeAtCharacterOffset: Appending node ${t.getType()} to generic element ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.append(t)) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} after generic element ${a.getType()} (key: ${a.getKey()}). targetIndex: ${e}, element end OT index: ${i}.`), a.insertAfter(t)), s = !0, !0) ) : s; } return o(n), s || r?.warn(`$insertNodeAtCharacterOffset: Could not find insertion point for node ${t.getType()} (key: ${t.getKey()}) at targetIndex ${e}. Final currentIndex: ${i}. Node not inserted.`), s; } -function F_(e, t, r, n, i) { +function rC(e, t, r, n, i) { let s; - return Fr("chapter", t) ? s = K_(t.insert.chapter, r) : Fr("verse", t) ? s = j_(t.insert.verse, r) : Fr("ms", t) ? s = B_(t.insert.ms) : Fr("note", t) ? s = wh(t, r, n, i) : Fr("unknown", t) ? s = qh(t, r, n, i) : Fr("unmatched", t) && (s = W_(t.insert.unmatched, r)), s ? Nh(e, s, i) : (i?.error(`$insertEmbedAtCurrentIndex: Cannot create LexicalNode for embed object: ${JSON.stringify(t.insert)}`), !1); + return jr("chapter", t) ? s = iC(t.insert.chapter, r) : jr("verse", t) ? s = sC(t.insert.verse, r) : jr("ms", t) ? s = oC(t.insert.ms) : jr("note", t) ? s = Bh(t, r, n, i) : jr("unknown", t) ? s = Vh(t, r, n, i) : jr("unmatched", t) && (s = cC(t.insert.unmatched, r)), s ? Kh(e, s, i) : (i?.error(`$insertEmbedAtCurrentIndex: Cannot create LexicalNode for embed object: ${JSON.stringify(t.insert)}`), !1); } -function td(e, t, r, n) { +function ld(e, t, r, n) { let i; - ul(t) ? i = Oh(t.para, r) : G_(t) && (i = z_(t.book)), i ??= Ht(); - const s = i, o = ae(s), a = dr(s); + ml(t) ? i = jh(t.para, r) : uC(t) && (i = nC(t.book)), i ??= Ht(); + const s = i, o = le(s), a = fr(s); let c = 0, l = !1; - function u(d) { + function d(u) { if (l) return !0; - if (_r(d)) { - const f = d.getTextContentSize(); - if (e >= c && e <= c + f) { - const p = d.getParent(); - if (ae(p) && (o || a)) { - n?.debug(`Splitting ParaNode (marker: ${p.getMarker()}) with LF attributes at targetIndex ${e}`); - const m = e - c, [g] = m > 0 ? d.splitText(m) : [void 0]; - let y, k = g?.getPreviousSibling(); + if (Er(u)) { + const p = u.getTextContentSize(); + if (e >= c && e <= c + p) { + const f = u.getParent(); + if (le(f) && (o || a)) { + n?.debug(`Splitting ParaNode (marker: ${f.getMarker()}) with LF attributes at targetIndex ${e}`); + const g = e - c, [h] = g > 0 ? u.splitText(g) : [void 0]; + let y, k = h?.getPreviousSibling(); for (; k; ) { const _ = k; k = k.getPreviousSibling(), y ? y.insertBefore(_) : s.append(_), y = _; } - return g && s.append(g), p.insertBefore(s), l = !0, !0; + return h && s.append(h), f.insertBefore(s), l = !0, !0; } } - c += f; - } else if (Pt(d)) + c += p; + } else if (Nt(u)) c += 1; - else if (Et(d)) { - const f = d.getChildren(); - for (const p of f) { - if (u(p)) + else if (At(u)) { + const p = u.getChildren(); + for (const f of p) { + if (d(f)) return !0; if (l) break; } if (e === c) { - if (dr(d) && s) - return n?.debug(`Replacing ImpliedParaNode (key: ${d.getKey()}) with ParaNode at targetIndex ${e}`), d.replace(s, !0), l = !0, !0; - if (ae(d) && s) { - const p = d; - return n?.debug(`Creating new block node with LF attributes after existing ParaNode (marker: ${p.getMarker()}) at targetIndex ${e}`), p.insertAfter(s), l = !0, !0; + if (fr(u) && s) + return n?.debug(`Replacing ImpliedParaNode (key: ${u.getKey()}) with ParaNode at targetIndex ${e}`), u.replace(s, !0), l = !0, !0; + if (le(u) && s) { + const f = u; + return n?.debug(`Creating new block node with LF attributes after existing ParaNode (marker: ${f.getMarker()}) at targetIndex ${e}`), f.insertAfter(s), l = !0, !0; } } - if (c += 1, e === c && ae(d) && s) - return n?.debug(`Creating new block node after existing ParaNode (marker: ${d.getMarker()}) at targetIndex ${e}`), d.insertAfter(s), l = !0, !0; - } else if (F(d)) { - const f = d.getChildren(); - for (const p of f) { - if (u(p)) + if (c += 1, e === c && le(u) && s) + return n?.debug(`Creating new block node after existing ParaNode (marker: ${u.getMarker()}) at targetIndex ${e}`), u.insertAfter(s), l = !0, !0; + } else if (L(u)) { + const p = u.getChildren(); + for (const f of p) { + if (d(f)) return !0; if (l) break; @@ -17274,75 +17275,75 @@ function td(e, t, r, n) { } return l; } - return u(Fe()), l || n?.warn(`Could not find location to handle newline with para attributes at targetIndex ${e}. Final currentIndex: ${c}.`), 1; + return d(Ke()), l || n?.warn(`Could not find location to handle newline with para attributes at targetIndex ${e}. Final currentIndex: ${c}.`), 1; } -function z_(e) { +function nC(e) { const { style: t, code: r } = e; - if (!t || t !== Gi || !r || !Ut.isValidBookCode(r)) + if (!t || t !== Qi || !r || !Ft.isValidBookCode(r)) return; - const n = Ue(e, hx); - return ip(r, n); + const n = ze(e, Nx); + return mp(r, n); } -function Oh(e, t) { +function jh(e, t) { const { style: r } = e; if (!r) return; - const n = Ue(e, px), i = Ji(r, n); - if (!gi(t)) + const n = ze(e, Px), i = Zi(r, n); + if (!bi(t)) return i; if (t.markerMode === "editable") - i.append(ot(r), mo()); + i.append(lt(r), To()); else if (t.markerMode === "visible" || t.hasGutterParaMarkers) { - const s = we(r) + I; - i.append(t.hasGutterParaMarkers ? Ib(s) : Tr("marker", s)); + const s = Re(r) + R; + i.append(t.hasGutterParaMarkers ? Qb(s) : Sr("marker", s)); } return i; } -function K_(e, t) { +function iC(e, t) { if (!e) return; const { number: r, sid: n, altnumber: i, pubnumber: s } = e; if (!r) return; - const o = Ue(e, gx); + const o = ze(e, Ox); let a; if (t.markerMode === "editable") - a = ap(r, n, i, s, o); + a = kp(r, n, i, s, o); else { const c = t.markerMode === "visible"; - a = Oc(r, c, n, i, s, o); + a = Ic(r, c, n, i, s, o); } return a; } -function j_(e, t) { +function sC(e, t) { if (!e) return; const { style: r, number: n, sid: i, altnumber: s, pubnumber: o } = e; if (!n) return; - const a = Ue(e, mx); + const a = ze(e, wx); let c; if (t.markerMode === "editable") { if (!r) return; - const l = Lt(r, n); - c = yp(n, l, i, s, o, a); + const l = Dt(r, n); + c = Pp(n, l, i, s, o, a); } else { const l = t.markerMode === "visible"; - c = Gc(n, l, i, s, o, a); + c = el(n, l, i, s, o, a); } return c; } -function B_(e) { +function oC(e) { if (!e) return; const { style: t, sid: r, eid: n, attributeOrder: i } = e; if (!t) return; - const s = Ue(e, yx); - return jf(t, r, n, s, i); + const s = ze(e, qx); + return tp(t, r, n, s, i); } -function wh(e, t, r, n) { +function Bh(e, t, r, n) { const i = e.insert; if (!i.note) return; @@ -17350,50 +17351,50 @@ function wh(e, t, r, n) { if (!s || o == null) return; o === "" && n?.warn("Note has empty caller. Only use for note editing."); - const l = Ue(i.note, bx), u = typeof l?.closed == "string" ? l.closed : void 0, d = e.attributes?.segment; - let f; - d && typeof d == "string" && (f = d); - const p = []; - for (const g of c?.ops ?? []) - if (typeof g.insert == "string") - if (Hr(g.attributes)) { - const y = mi(g.attributes.char, t, pe(g.insert), void 0, Rh(g.attributes.char, p), !1, t.markerMode === "editable"); - p.push(...y); + const l = ze(i.note, Rx), d = typeof l?.closed == "string" ? l.closed : void 0, u = e.attributes?.segment; + let p; + u && typeof u == "string" && (p = u); + const f = []; + for (const h of c?.ops ?? []) + if (typeof h.insert == "string") + if (Qr(h.attributes)) { + const y = ki(h.attributes.char, t, he(h.insert), void 0, Wh(h.attributes.char, f), !1, t.markerMode === "editable"); + f.push(...y); } else - p.push(pe(g.insert)); - return xh(s, o, p, t, r, f, u).setCategory(a).setUnknownAttributes(l); + f.push(he(h.insert)); + return qh(s, o, f, t, r, p, d).setCategory(a).setUnknownAttributes(l); } -function qh(e, t, r, n) { +function Vh(e, t, r, n) { const i = e.insert.unknown; if (!i) return; const { tag: s, marker: o, contents: a } = i; if (!s) return; - const c = Ue(i, kx), l = Nc(s, o, c), u = a?.ops ?? []; - u.length > 0 && V_(u, t, r, n).forEach((p) => l.append(p)); - const d = e.attributes?.segment; - return typeof d == "string" && bt(l, Vr, () => d), l; + const c = ze(i, $x), l = Lc(s, o, c), d = a?.ops ?? []; + d.length > 0 && aC(d, t, r, n).forEach((f) => l.append(f)); + const u = e.attributes?.segment; + return typeof u == "string" && kt(l, Yr, () => u), l; } -function V_(e, t, r, n) { +function aC(e, t, r, n) { const i = []; for (const s of e) { if (typeof s.insert == "string") { - if (Hr(s.attributes)) { - const o = pe(s.insert), a = mi(s.attributes.char, t, o, void 0, Rh(s.attributes.char, i)); + if (Qr(s.attributes)) { + const o = he(s.insert), a = ki(s.attributes.char, t, o, void 0, Wh(s.attributes.char, i)); i.push(...a); } else - i.push(pe(s.insert)); + i.push(he(s.insert)); continue; } if (!(!s.insert || typeof s.insert != "object")) { - if (Fr("unknown", s)) { - const o = qh(s, t, r, n); + if (jr("unknown", s)) { + const o = Vh(s, t, r, n); o && i.push(o); continue; } - if (Fr("note", s)) { - const o = wh(s, t, r, n); + if (jr("note", s)) { + const o = Bh(s, t, r, n); o && i.push(o); continue; } @@ -17402,62 +17403,62 @@ function V_(e, t, r, n) { } return i; } -function W_(e, t) { +function cC(e, t) { if (!e) return; const { marker: r } = e; if (!r) return; - const n = jc(r); + const n = Jc(r); return t.markerMode === "editable" && n.setMode("normal"), n; } -function Rh(e, t) { +function Wh(e, t) { if (!(!Array.isArray(e) && e.style === "fp" && !e.cid)) return t; } -function ja(e) { +function Ja(e) { return e.style.startsWith("+") ? { ...e, style: e.style.slice(1) } : e; } -function mi(e, t, r, n, i, s = !1, o = !1) { - v(r) && r.getTextContentSize() === 0 && r.setTextContent(Dt); +function ki(e, t, r, n, i, s = !1, o = !1) { + S(r) && r.getTextContentSize() === 0 && r.setTextContent(Ut); const a = () => { - o && v(r) && r.getTextContent() !== Dt && r.setTextContent(I + r.getTextContent()); + o && S(r) && r.getTextContent() !== Ut && r.setTextContent(R + r.getTextContent()); }; if (Array.isArray(e)) { if (e.length === 0) throw new Error("Empty charAttr array"); - const c = e.map(ja), l = c[0], u = i?.[i.length - 1]; - if (D(u) && Tn(l, u)) - return c.length > 1 ? mi(c.slice(1), t, r, void 0, void 0, !0, o).forEach((p) => u.append(p)) : r && u.append(r), []; + const c = e.map(Ja), l = c[0], d = i?.[i.length - 1]; + if (I(d) && Cn(l, d)) + return c.length > 1 ? ki(c.slice(1), t, r, void 0, void 0, !0, o).forEach((f) => d.append(f)) : r && d.append(r), []; a(); - const d = c.reduceRight((f, p, m) => { - const g = xr(p.style, Ue(p, Hs)); - if (typeof p.cid == "string" && bt(g, kn, () => p.cid), n && m === c.length - 1 && bt(g, Vr, () => n), f) - if (D(f)) { - const y = f.getMarker(), k = []; - oa(y, k, t, !0), k.forEach((S) => g.append(S)), g.append(f); + const u = c.reduceRight((p, f, g) => { + const h = Mr(f.style, ze(f, Xs)); + if (typeof f.cid == "string" && kt(h, _n, () => f.cid), n && g === c.length - 1 && kt(h, Yr, () => n), p) + if (I(p)) { + const y = p.getMarker(), k = []; + da(y, k, t, !0), k.forEach((C) => h.append(C)), h.append(p); const _ = []; - sa(f, _, t, !0), _.forEach((S) => g.append(S)); + ua(p, _, t, !0), _.forEach((C) => h.append(C)); } else - g.append(f); - return g; + h.append(p); + return h; }, r); - return oa(l.style, d, t, s), sa(d, d, t, s), [d]; + return da(l.style, u, t, s), ua(u, u, t, s), [u]; } else { - const c = ja(e), l = i?.[i.length - 1]; - if (D(l) && Tn(c, l)) + const c = Ja(e), l = i?.[i.length - 1]; + if (I(l) && Cn(c, l)) return r && l.append(r), []; a(); - const u = xr(c.style, Ue(c, Hs)); - return typeof c.cid == "string" && bt(u, kn, () => c.cid), n && bt(u, Vr, () => n), r && u.append(r), oa(c.style, u, t, s), sa(u, u, t, s), [u]; + const d = Mr(c.style, ze(c, Xs)); + return typeof c.cid == "string" && kt(d, _n, () => c.cid), n && kt(d, Yr, () => n), r && d.append(r), da(c.style, d, t, s), ua(d, d, t, s), [d]; } } -function sa(e, t, r, n = !1) { - e.getUnknownAttributes()?.closed !== "false" && H_(e.getMarker(), t, r, !1, n); +function ua(e, t, r, n = !1) { + e.getUnknownAttributes()?.closed !== "false" && lC(e.getMarker(), t, r, !1, n); } -function oa(e, t, r, n = !1) { +function da(e, t, r, n = !1) { let i; - if (r?.markerMode === "editable" ? i = ot(e, "opening", n) : r?.markerMode === "visible" && (i = Tr("marker", we(e, n))), i) + if (r?.markerMode === "editable" ? i = lt(e, "opening", n) : r?.markerMode === "visible" && (i = Sr("marker", Re(e, n))), i) if (Array.isArray(t)) t.push(i); else { @@ -17465,37 +17466,37 @@ function oa(e, t, r, n = !1) { s ? s.insertBefore(i) : t.append(i); } } -function H_(e, t, r, n = !1, i = !1) { +function lC(e, t, r, n = !1, i = !1) { let s; - r?.markerMode === "editable" ? n ? s = ot("", "selfClosing") : s = ot(e, "closing", i) : r?.markerMode === "visible" && (s = Tr("marker", n ? rt("") : rt(e, i))), s && (Array.isArray(t) ? t.push(s) : t.append(s)); + r?.markerMode === "editable" ? n ? s = lt("", "selfClosing") : s = lt(e, "closing", i) : r?.markerMode === "visible" && (s = Sr("marker", n ? ot("") : ot(e, i))), s && (Array.isArray(t) ? t.push(s) : t.append(s)); } -function G_(e) { +function uC(e) { return !!e && !!e.book && typeof e.book == "object" && e.book !== null && "style" in e.book && typeof e.book.style == "string" && "code" in e.book && typeof e.book.code == "string"; } -function ul(e) { +function ml(e) { return !!e && !!e.para && typeof e.para == "object" && e.para !== null && "style" in e.para && typeof e.para.style == "string"; } -function Hr(e) { +function Qr(e) { return !!e && !!e.char && typeof e.char == "object" && e.char !== null && (!Array.isArray(e.char) && "style" in e.char && typeof e.char.style == "string" || Array.isArray(e.char) && e.char.length > 0 && "style" in e.char[0] && typeof e.char[0].style == "string"); } -function J_(e) { +function dC(e) { return typeof e == "object" && e !== null && !Array.isArray(e) && Object.keys(e).length === 0; } -function qt(e, t) { +function Rt(e, t) { if (e) for (const r of Object.keys(e)) { if (r === "segment" && typeof e[r] == "string") { const n = e[r]; - bt(t, Vr, () => n); + kt(t, Yr, () => n); continue; } - if (Y_(r)) { + if (fC(r)) { const n = !!e[r], i = r, s = t.hasFormat(i); (n && !s || !n && s) && t.toggleFormat(i); } } } -const $h = [ +const Hh = [ "bold", "underline", "strikethrough", @@ -17508,100 +17509,103 @@ const $h = [ "uppercase", "capitalize" ]; -function Y_(e) { - return $h.includes(e); +function fC(e) { + return Hh.includes(e); } -function X_() { +function pC() { const [e] = ce(); - return z(() => e.registerCommand(lo, (t) => (Q_(t), !1), mn), [e]), null; + return F(() => e.registerCommand(mo, (t) => (hC(t), !1), kn), [e]), null; } -function Q_(e) { - if (Z_(e.target)) +function hC(e) { + if (gC(e.target)) return; const t = w(); - N(t) && eC(t); + P(t) && mC(t); } -function yi(e) { +function Ti(e) { let t = e.getFirstChild(), r = 0; for (; t !== null; ) - if (Ft(t)) - r++, t = t.getNextSibling(), v(t) && t.getTextContent() === I && (r++, t = t.getNextSibling()); - else if (ge(t)) + if (zt(t)) + r++, t = t.getNextSibling(), S(t) && t.getTextContent() === R && (r++, t = t.getNextSibling()); + else if (me(t)) r++, t = t.getNextSibling(); else break; return r === 0 ? !1 : (Qt(e, r), !0); } -function Z_(e) { - if (!Tf(e)) +function gC(e) { + if (!wf(e)) return !1; - const t = li(e); - if (!Lb(t)) + const t = fi(e); + if (!Zb(t)) return !1; const r = t.getParent(); - return r ? Se(r) ? yi(r) : (Qt(r, t.getIndexWithinParent() + 1), !0) : !1; + return r ? ve(r) ? Ti(r) : (Qt(r, t.getIndexWithinParent() + 1), !0) : !1; } -function eC(e) { +function mC(e) { if (!e.isCollapsed()) return !1; const { anchor: t } = e; if (t.type !== "element" || t.offset !== 0) return !1; - const r = se(t.key); - if (!Se(r)) + const r = oe(t.key); + if (!ve(r)) return !1; const n = r.getFirstChild(); - return !Pr(n) && !wn(n) ? !1 : yi(r); + return !Rr(n) && !$n(n) ? !1 : Ti(r); } -function tC() { +function yC() { const [e] = ce(); - return z(() => { - const t = (r) => r instanceof KeyboardEvent && !rC(r) || !Po() ? !1 : (r instanceof Event && r.preventDefault(), !0); - return Ve( - e.registerCommand(vr, t, Ne), - e.registerCommand(Tc, t, Ne), + return F(() => { + const t = (r) => r instanceof KeyboardEvent && !bC(r) || !qo() ? !1 : (r instanceof Event && r.preventDefault(), !0); + return Fe( + e.registerCommand(Nr, t, qe), + e.registerCommand(Mc, t, qe), // CUT and PASTE run at CRITICAL because their standard-view handlers — the ones that // actually copy and then remove — are themselves registered at HIGH, where the winner is // decided by registration order rather than by intent. A refusal has to outrank the actor it // refuses, not tie with it. The engine's own CRITICAL cut arm, which records what a cut would // cover, TIES with this refusal, so it consults `$selectionReachesIntoOpaqueBlock` itself // rather than relying on order: an arm this refusal leaves behind would outlive the gesture. - e.registerCommand(yr, t, Wt), - e.registerCommand(yn, t, Wt), + e.registerCommand(_r, t, tt), + e.registerCommand(Wt, t, tt), // DROP is judged by the drop TARGET, not the live selection: Lexical dispatches // DROP_COMMAND straight from the DOM handler with no selection update, so at drop time // `$getSelection()` still holds whatever was selected when the drag STARTED. Testing that // inverted both promises above — dragging a caption OUT of a figure was refused (source // inside), while dragging outside text INTO a caption was allowed (source outside). - e.registerCommand(xc, (r) => { + e.registerCommand(Ec, (r) => { if (!(r instanceof Event) || !(r.target instanceof Node)) return !1; - const n = li(r.target); - return !n || !Gr(n) ? !1 : (r.preventDefault(), !0); - }, Ne), - e.registerCommand(my, t, Ne), - e.registerCommand(yy, t, Ne), - e.registerCommand(by, t, Ne) + const n = fi(r.target); + return !n || !Zr(n) ? !1 : (r.preventDefault(), !0); + }, qe), + e.registerCommand(Py, t, qe), + e.registerCommand(Ny, t, qe), + e.registerCommand(Oy, t, qe) ); }, [e]), null; } -function rC(e) { +function bC(e) { return e.isComposing || e.keyCode === 229 ? !0 : !(typeof e.getModifierState == "function" && e.getModifierState("AltGraph")) && (e.ctrlKey || e.metaKey || e.altKey) ? !1 : e.key.length === 1 || e.key === "Backspace" || e.key === "Delete" || e.key === "Enter"; } -function Gr(e) { - return nt(e, (t) => De(t) || Qp(t)) ?? void 0; +function Zr(e) { + return rt(e, Gh) ?? void 0; } -function Po() { +function Gh(e) { + return Ue(e) || uh(e); +} +function qo() { const e = w(); - return N(e) ? Gr(e.anchor.getNode()) !== void 0 || Gr(e.focus.getNode()) !== void 0 : !1; + return P(e) ? Zr(e.anchor.getNode()) !== void 0 || Zr(e.focus.getNode()) !== void 0 : !1; } -function nC(e, t, r) { +function kC(e, t, r) { if (e.height === 0) return !1; const n = e.height / 4; return t.some((i) => r === "down" ? i.top >= e.bottom - n : i.bottom <= e.top + n); } -function iC(e, t) { +function TC(e, t) { if (typeof window > "u") return !1; const r = window.getSelection(); @@ -17618,107 +17622,107 @@ function iC(e, t) { o.collapse(!0); const a = Array.from(n.querySelectorAll('[data-marker="v"]')); let c, l; - for (const d of a) { - const f = document.createRange(); - if (f.selectNode(d), o.compareBoundaryPoints(Range.START_TO_START, f) > 0) - c = d; + for (const u of a) { + const p = document.createRange(); + if (p.selectNode(u), o.compareBoundaryPoints(Range.START_TO_START, p) > 0) + c = u; else { - l = d; + l = u; break; } } if (!c) return !1; - const u = document.createRange(); - return u.setStartAfter(c), l ? u.setEndBefore(l) : u.setEnd(n, n.childNodes.length), nC(s, Array.from(u.getClientRects()), t); + const d = document.createRange(); + return d.setStartAfter(c), l ? d.setEndBefore(l) : d.setEnd(n, n.childNodes.length), kC(s, Array.from(d.getClientRects()), t); } catch { return !1; } } -function sC(e, t, r, n) { - if (!kC(t) || iC(e, r)) +function xC(e, t, r, n) { + if (!$C(t) || TC(e, r)) return !1; - const i = r === "up" ? fx(t) : dx(t); + const i = r === "up" ? Ax(t) : Ex(t); return i && n.preventDefault(), i; } -function oC({ viewOptions: e }) { +function _C({ viewOptions: e }) { const [t] = ce(); - return aC(t, e), null; + return CC(t, e), null; } -function aC(e, t) { - z(() => { - if (!e.hasNodes([pr, Ct, Ee])) +function CC(e, t) { + F(() => { + if (!e.hasNodes([hr, vt, Pe])) throw new Error("ArrowNavigationPlugin: ImmutableChapterNode, ImmutableVerseNode or NoteNode not registered on editor!"); const r = (n) => { const i = w(); - if (!N(i)) + if (!P(i)) return !1; const s = t?.markerMode === "editable", o = e.getRootElement(); if (s && o && (n.key === "ArrowLeft" || n.key === "ArrowRight") && n.shiftKey && !n.altKey && !n.ctrlKey && !n.metaKey) { - const u = rd(o), d = hC(i, nd(u, n.key) ? "next" : "previous"); - return d && n.preventDefault(), d; + const d = ud(o), u = NC(i, dd(d, n.key) ? "next" : "previous"); + return u && n.preventDefault(), u; } if (!i.isCollapsed()) return !1; if (n.key === "ArrowUp" || n.key === "ArrowDown") { if (n.shiftKey || n.altKey || n.ctrlKey || n.metaKey) return !1; - const u = n.key === "ArrowUp" ? "up" : "down"; - return sC(e, i, u, n); + const d = n.key === "ArrowUp" ? "up" : "down"; + return xC(e, i, d, n); } if (n.key !== "ArrowLeft" && n.key !== "ArrowRight" || !o) return !1; - const a = rd(o), c = n.shiftKey || n.altKey || n.ctrlKey || n.metaKey; + const a = ud(o), c = n.shiftKey || n.altKey || n.ctrlKey || n.metaKey; let l = !1; - return nd(a, n.key) ? l = !c && od(i, "next") || !c && lC(i) || yC(i) || !c && s && sd(i, "next") : cC(a, n.key) && (l = !c && od(i, "previous") || !c && uC(i) || bC(i, t) || !c && s && sd(i, "previous")), l && n.preventDefault(), l; + return dd(a, n.key) ? l = !c && hd(i, "next") || !c && SC(i) || qC(i) || !c && s && pd(i, "next") : vC(a, n.key) && (l = !c && hd(i, "previous") || !c && MC(i) || RC(i, t) || !c && s && pd(i, "previous")), l && n.preventDefault(), l; }; - return e.registerCommand(vr, r, Ne); + return e.registerCommand(Nr, r, qe); }, [e, t]); } -function rd(e) { +function ud(e) { return e.dir || "ltr"; } -function nd(e, t) { +function dd(e, t) { return e === "ltr" && t === "ArrowRight" || e === "rtl" && t === "ArrowLeft"; } -function cC(e, t) { +function vC(e, t) { return e === "ltr" && t === "ArrowLeft" || e === "rtl" && t === "ArrowRight"; } -function Ba(e) { - if (!D(e) || e.getMarker() !== "fp") +function Ya(e) { + if (!I(e) || e.getMarker() !== "fp") return; const t = Xt(e); if (!(!t || t.getIsCollapsed())) return e; } -function lC(e) { - const t = Ba(_p(e)); +function SC(e) { + const t = Ya(Rp(e)); if (!t) return !1; const r = e.anchor; return r.type === "text" && r.offset !== r.getNode().getTextContentSize() ? !1 : (Qt(t, 0), !0); } -function uC(e) { +function MC(e) { const t = e.anchor, r = t.getNode(); if (t.type === "text") { - const n = Ba(r.getParent()); - return !n || !r.is(n.getFirstChild()) ? !1 : t.offset === 1 ? (r.select(0, 0), !0) : t.offset !== 0 ? !1 : id(n); + const n = Ya(r.getParent()); + return !n || !r.is(n.getFirstChild()) ? !1 : t.offset === 1 ? (r.select(0, 0), !0) : t.offset !== 0 ? !1 : fd(n); } if (t.offset === 0) { - const n = Ba(r); - return n ? id(n) : !1; + const n = Ya(r); + return n ? fd(n) : !1; } return !1; } -function id(e) { +function fd(e) { const t = e.getPreviousSibling(); if (!t) return !1; - if (v(t)) + if (S(t)) return t.select(), !0; - if (F(t)) { + if (L(t)) { const i = t.getLastDescendant(); - return v(i) ? i.select() : t.selectEnd(), !0; + return S(i) ? i.select() : t.selectEnd(), !0; } const r = e.getParent(); if (!r) @@ -17726,39 +17730,39 @@ function id(e) { const n = e.getIndexWithinParent(); return r.select(n, n), !0; } -const Ys = typeof Intl.Segmenter > "u" ? void 0 : new Intl.Segmenter(void 0, { granularity: "grapheme" }); -function dC(e) { - if (Ys) - for (const { segment: r } of Ys.segment(e)) +const eo = typeof Intl.Segmenter > "u" ? void 0 : new Intl.Segmenter(void 0, { granularity: "grapheme" }); +function EC(e) { + if (eo) + for (const { segment: r } of eo.segment(e)) return r.length; const t = e.codePointAt(0); return t === void 0 ? 0 : String.fromCodePoint(t).length; } -function fC(e) { - if (Ys) { +function AC(e) { + if (eo) { let n = 0; - for (const { index: i } of Ys.segment(e)) + for (const { index: i } of eo.segment(e)) n = i; return n; } const t = e.codePointAt(Math.max(0, e.length - 2)), r = t !== void 0 && t > 65535; return Math.max(0, e.length - (r ? 2 : 1)); } -function Ih(e) { +function Jh(e) { for (let t = e; t; t = t.getParent()) - if (F(t) && !t.isInline()) + if (L(t) && !t.isInline()) return t; } -function Lh(e) { - return !!e && O(e) && Gr(e) !== void 0; +function Yh(e) { + return !!e && O(e) && Zr(e) !== void 0; } -function si(e) { - return v(e) && !e.isToken() && !Lh(e) && e.getTextContentSize() > 0; +function ai(e) { + return S(e) && !e.isToken() && !Yh(e) && e.getTextContentSize() > 0; } -function Dh(e) { - return us(e) ? !0 : j(e) ? e.getIsCollapsed() === !0 : v(e) ? (e.isToken() || Lh(e)) && e.getTextContentSize() > 0 : uo(e) ? !Be(e) : !1; +function Xh(e) { + return di(e) ? !0 : K(e) ? e.getIsCollapsed() === !0 : S(e) ? (e.isToken() || Yh(e)) && e.getTextContentSize() > 0 : ms(e) ? !He(e) : !1; } -function oi(e, t, r) { +function ci(e, t, r) { for (let n = e; n && !n.is(r); ) { const i = t === "next" ? n.getNextSibling() : n.getPreviousSibling(); if (i) @@ -17766,99 +17770,99 @@ function oi(e, t, r) { n = n.getParent(); } } -function No(e, t, r) { +function Ro(e, t, r) { for (let n = e; n; ) { - if (Dh(n)) + if (Xh(n)) return n; - if (F(n)) { - n = (t === "next" ? n.getFirstChild() : n.getLastChild()) ?? oi(n, t, r); + if (L(n)) { + n = (t === "next" ? n.getFirstChild() : n.getLastChild()) ?? ci(n, t, r); continue; } - if (si(n)) + if (ai(n)) return n; - n = oi(n, t, r); + n = ci(n, t, r); } } -function dl(e, t, r, n, i) { - return r === "element" && F(e) ? e.getChildAtIndex(n === "next" ? t : t - 1) ?? oi(e, n, i) : r === "text" && Dh(e) && (n === "next" ? t < e.getTextContentSize() : t > 0) ? e : oi(e, n, i); +function yl(e, t, r, n, i) { + return r === "element" && L(e) ? e.getChildAtIndex(n === "next" ? t : t - 1) ?? ci(e, n, i) : r === "text" && Xh(e) && (n === "next" ? t < e.getTextContentSize() : t > 0) ? e : ci(e, n, i); } -function aa(e, t) { +function fa(e, t) { if (e.kind === "text" && e.offset > 0) return e; - const r = dl(e.node, e.offset, e.kind, "previous", t), n = No(r, "previous", t); + const r = yl(e.node, e.offset, e.kind, "previous", t), n = Ro(r, "previous", t); if (!n) return e; - if (si(n)) + if (ai(n)) return { kind: "text", node: n, offset: n.getTextContentSize() }; const i = n.getParent(); return i ? { kind: "element", node: i, offset: n.getIndexWithinParent() + 1 } : e; } -function pC(e, t) { - const r = e.getNode(), n = Ih(r); +function PC(e, t) { + const r = e.getNode(), n = Jh(r); if (!n) return; - if (e.type === "text" && si(r)) { + if (e.type === "text" && ai(r)) { if (t === "next" && e.offset < r.getTextContentSize() || t === "previous" && e.offset > 1) return; if (t === "previous" && e.offset === 1) - return aa({ kind: "text", node: r, offset: 0 }, n); + return fa({ kind: "text", node: r, offset: 0 }, n); } - const i = dl(r, e.offset, e.type, t, n), s = No(i, t, n); + const i = yl(r, e.offset, e.type, t, n), s = Ro(i, t, n); if (!s) return; - if (si(s)) { - const c = s.getTextContent(), l = t === "next" ? dC(c) : fC(c); - return aa({ kind: "text", node: s, offset: l }, n); + if (ai(s)) { + const c = s.getTextContent(), l = t === "next" ? EC(c) : AC(c); + return fa({ kind: "text", node: s, offset: l }, n); } const o = s.getParent(); if (!o) return; const a = s.getIndexWithinParent(); - return aa({ kind: "element", node: o, offset: t === "next" ? a + 1 : a }, n); + return fa({ kind: "element", node: o, offset: t === "next" ? a + 1 : a }, n); } -function Uh(e, t, r) { - const n = r === "collapse" ? e.anchor : e.focus, i = pC(n, t); +function Qh(e, t, r) { + const n = r === "collapse" ? e.anchor : e.focus, i = PC(n, t); return !i || i.node.is(n.getNode()) && i.offset === n.offset && i.kind === n.type ? !1 : r === "collapse" ? (i.node.select(i.offset, i.offset), !0) : (e.focus.set(i.node.getKey(), i.offset, i.kind), !0); } -function sd(e, t) { - return Uh(e, t, "collapse"); +function pd(e, t) { + return Qh(e, t, "collapse"); } -function hC(e, t) { - return Uh(e, t, "extend"); +function NC(e, t) { + return Qh(e, t, "extend"); } -function gC(e, t, r) { +function OC(e, t, r) { const n = e.getNode(); - if (e.type === "text" && si(n) && (t === "next" ? e.offset < n.getTextContentSize() : e.offset > 0)) + if (e.type === "text" && ai(n) && (t === "next" ? e.offset < n.getTextContentSize() : e.offset > 0)) return !1; - const i = dl(n, e.offset, e.type, t, r); - return No(i, t, r) === void 0; + const i = yl(n, e.offset, e.type, t, r); + return Ro(i, t, r) === void 0; } -function mC(e, t) { - const r = Fe(); +function wC(e, t) { + const r = Ke(); for (let n = e; n; ) { - const i = oi(n, t, r), s = i && No(i, t, r); + const i = ci(n, t, r), s = i && Ro(i, t, r); if (!s) return; - if (n = Gr(s), !n) + if (n = Zr(s), !n) return s; } } -function od(e, t) { +function hd(e, t) { const r = e.anchor, n = r.getNode(); - if (Gr(n)) + if (Zr(n)) return !1; - const i = Ih(n); - if (!i || !gC(r, t, i)) + const i = Jh(n); + if (!i || !OC(r, t, i)) return !1; - const s = oi(i, t, Fe()), o = s && Gr(s); + const s = ci(i, t, Ke()), o = s && Zr(s); if (!o) return !1; - const a = mC(o, t); + const a = wC(o, t); if (!a) return !0; - if (si(a)) { - const u = t === "next" ? 0 : a.getTextContentSize(); - return a.select(u, u), !0; + if (ai(a)) { + const d = t === "next" ? 0 : a.getTextContentSize(); + return a.select(d, d), !0; } const c = a.getParent(); if (!c) @@ -17866,17 +17870,17 @@ function od(e, t) { const l = a.getIndexWithinParent() + (t === "next" ? 0 : 1); return c.select(l, l), !0; } -function ad(e) { +function gd(e) { const t = e.getParent(); if (!t) return; const r = e.getIndexWithinParent() + 1; t.select(r, r); } -function yC(e) { - const t = e.anchor.getNode(), r = _p(e); - if (j(r) && !O(r.getFirstChild())) { - if (Se(t)) { +function qC(e) { + const t = e.anchor.getNode(), r = Rp(e); + if (K(r) && !O(r.getFirstChild())) { + if (ve(t)) { if (e.anchor.offset === t.getChildrenSize()) return !1; } else if (!(e.anchor.offset === t.getTextContentSize())) @@ -17884,33 +17888,33 @@ function yC(e) { if (r.getIsCollapsed()) { if (r.is(r.getParent()?.getLastChild())) { const i = r.getParent()?.getNextSibling(); - return i && !(Se(i) && yi(i)) && i.selectStart(), !0; + return i && !(ve(i) && Ti(i)) && i.selectStart(), !0; } } else return Yt(r.getFirstChild()) ? r.select(2, 2) : r.select(1, 1), !0; } - if (Se(t) && j(r) && r.getIsCollapsed()) { + if (ve(t) && K(r) && r.getIsCollapsed()) { const i = r.getNextSibling(); - return i ? i.selectStart() : ad(r), !0; + return i ? i.selectStart() : gd(r), !0; } const n = r?.getParent(); - if (Yt(r) && j(n) && r.is(n?.getLastChild())) { + if (Yt(r) && K(n) && r.is(n?.getLastChild())) { const i = n.getNextSibling(); - return i ? i.selectStart() : n.getIsCollapsed() ? ad(n) : n.selectEnd(), !0; + return i ? i.selectStart() : n.getIsCollapsed() ? gd(n) : n.selectEnd(), !0; } return !1; } -function bC(e, t) { - const r = bk(e); - if (fs(r) && !r.getPreviousSibling()) +function RC(e, t) { + const r = Rk(e); + if (ys(r) && !r.getPreviousSibling()) return !0; if (!(e.anchor.offset === 0)) return !1; const i = e.anchor.getNode(); - if (pt(i.getParent())) + if (ht(i.getParent())) return !0; - if (j(r) && r.getIsCollapsed()) { + if (K(r) && r.getIsCollapsed()) { const o = r.getPreviousSibling(); - if (!wn(o)) + if (!$n(o)) return !1; const a = r.getParent(); if (!a) @@ -17918,12 +17922,12 @@ function bC(e, t) { const c = r.getIndexWithinParent(); return a.select(c, c), !0; } - if (Se(r) && t?.noteMode === "collapsed") { + if (ve(r) && t?.noteMode === "collapsed") { const o = r.getLastChild(); if (!o) return !1; - const a = nt(o, (c) => j(c)); - if (j(a) && a.getIsCollapsed()) { + const a = rt(o, (c) => K(c)); + if (K(a) && a.getIsCollapsed()) { const c = a.getParent(); if (!c) return !1; @@ -17943,103 +17947,103 @@ function bC(e, t) { } return !1; } -function kC(e) { +function $C(e) { if (e.anchor.type === "element") return !0; if (e.anchor.offset !== 0) return !1; const t = e.anchor.getNode().getPreviousSibling(); - return ge(t) && uo(t); + return me(t) && ms(t); } -function TC() { +function LC() { const [e] = ce(); - return xC(e), null; + return IC(e), null; } -function xC(e) { - z(() => { - if (!e.hasNodes([me])) +function IC(e) { + F(() => { + if (!e.hasNodes([be])) throw new Error("CharNodePlugin: CharNode not registered on editor!"); - return Ve( - e.registerNodeTransform(me, SC), + return Fe( + e.registerNodeTransform(be, FC), // Self-healing nested glyphs: whenever a char span is dirtied (created, moved, merged, // unwrapped), re-derive its glyphs' `+` from tree position — see nestedGlyphs.utils.ts // (`shared`) for the full representation rules this enforces. - e.registerNodeTransform(me, Uk), + e.registerNodeTransform(be, tT), // Self-healing display separators: every opening char glyph is followed by its NBSP // separator (text prefix or standalone spacer) — see markerSeparators.utils.ts (`shared`). - e.registerNodeTransform(me, zp), + e.registerNodeTransform(be, Zp), // Self-healing attribute display run: re-derive the `|…` run from unknownAttributes // whenever a span is dirtied — heals remote collab updates (delta-apply only calls // setUnknownAttributes) and structure surgery. $syncDisplayRun (displayRunSync.utils.ts, // `shared`), driven here with the char descriptor from displayRunRegistry.ts (`shared`). - e.registerNodeTransform(me, (t) => es(xn("char"), t)), - e.registerNodeTransform(Ke, vC) + e.registerNodeTransform(be, (t) => is(vn("char"), t)), + e.registerNodeTransform(Be, zC) ); }, [e]); } -function ca(e) { +function pa(e) { return e.getChildren().some(O); } -function _C(e, t) { +function DC(e, t) { const r = t.getFirstChild(); if (!O(r) || r.getMarkerSyntax() !== "opening") return !1; const n = r.getNextSibling(); - if (To(n)) { + if (vo(n)) { const i = n.getTextContent(); - i.startsWith(I) && (i === I ? n.remove() : n.setTextContent(i.slice(I.length))); + i.startsWith(R) && (i === R ? n.remove() : n.setTextContent(i.slice(R.length))); } return t.splice(1, 0, e.getChildren()), e.remove(), !0; } -function CC(e, t) { +function UC(e, t) { const r = t.getLastChild(), n = e.getChildren(); O(r) && r.getMarkerSyntax() === "closing" ? n.forEach((i) => r.insertBefore(i)) : t.append(...n), e.remove(); } -function SC(e) { - if (!D(e)) +function FC(e) { + if (!I(e)) return; if (e.isEmpty()) { e.remove(); return; } - if (ca(e)) + if (pa(e)) return; const t = e.getMarker(); if (t === "fp") return; - const r = ne(e, kn), n = e.getUnknownAttributes(), i = e.getNextSibling(); - if (D(i) && Tn({ style: t, cid: r }, i) && wt(n, i.getUnknownAttributes())) - if (ca(i)) { - if (_C(e, i)) + const r = ie(e, _n), n = e.getUnknownAttributes(), i = e.getNextSibling(); + if (I(i) && Cn({ style: t, cid: r }, i) && qt(n, i.getUnknownAttributes())) + if (pa(i)) { + if (DC(e, i)) return; } else e.append(...i.getChildren()), i.remove(); const s = e.getPreviousSibling(); - D(s) && Tn({ style: t, cid: r }, s) && wt(n, s.getUnknownAttributes()) && (ca(s) ? CC(e, s) : (s.append(...e.getChildren()), e.remove())); + I(s) && Cn({ style: t, cid: r }, s) && qt(n, s.getUnknownAttributes()) && (pa(s) ? UC(e, s) : (s.append(...e.getChildren()), e.remove())); } -function vC(e) { +function zC(e) { const t = e.getParent(); - if (!D(t) || t.getChildrenSize() !== 1) + if (!I(t) || t.getChildrenSize() !== 1) return; const r = e.getTextContent(); - r.length > 1 && r.startsWith(Dt) && (e.setTextContent(r.slice(1)), e.selectEnd()); + r.length > 1 && r.startsWith(Ut) && (e.setTextContent(r.slice(1)), e.selectEnd()); } -function Fh(e) { +function Zh(e) { return e.replaceAll(" ", " "); } -function zh() { +function eg() { const e = w(); return !!e && !e.isCollapsed(); } -function Kh(e) { - const t = () => !zh(); - return Ve(e.registerCommand(fo, t, Tt), e.registerCommand(yn, t, Tt)); -} -const fl = (e) => { - e.dispatchCommand(fo, null); -}, pl = (e) => { - e.dispatchCommand(yn, null); -}, hl = (e) => { +function tg(e) { + const t = () => !eg(); + return Fe(e.registerCommand(Hr, t, xt), e.registerCommand(Wt, t, xt)); +} +const bl = (e) => { + e.dispatchCommand(Hr, null); +}, kl = (e) => { + e.dispatchCommand(Wt, null); +}, Tl = (e) => { navigator.clipboard.read().then(async (t) => { if ((await navigator.permissions.query({ // @ts-expect-error These types are incorrect. @@ -18051,14 +18055,14 @@ const fl = (e) => { const n = new DataTransfer(), i = t[0]; for (const o of i.types) { const a = await (await i.getType(o)).text(); - n.setData(o, Fh(a)); + n.setData(o, Zh(a)); } const s = new ClipboardEvent("paste", { clipboardData: n }); - e.dispatchCommand(yr, s); + e.dispatchCommand(_r, s); }); -}, gl = (e) => { +}, xl = (e) => { navigator.clipboard.read().then(async () => { if ((await navigator.permissions.query({ // @ts-expect-error These types are incorrect. @@ -18068,156 +18072,156 @@ const fl = (e) => { return; } const r = new DataTransfer(), n = await navigator.clipboard.readText(); - r.setData("text/plain", Fh(n)); + r.setData("text/plain", Zh(n)); const i = new ClipboardEvent("paste", { clipboardData: r }); - e.dispatchCommand(yr, i); + e.dispatchCommand(_r, i); }); }; -function MC() { +function KC() { const [e] = ce(); - return z(() => { + return F(() => { const t = (r) => { const { key: n, shiftKey: i, metaKey: s, ctrlKey: o, altKey: a } = r; - !($s ? s : o) || a || (!i && n.toLowerCase() === "c" ? (r.preventDefault(), fl(e)) : !i && n.toLowerCase() === "x" ? (r.preventDefault(), pl(e)) : n.toLowerCase() === "v" && (r.preventDefault(), i ? gl(e) : hl(e))); + !(ri ? s : o) || a || (!i && n.toLowerCase() === "c" ? (r.preventDefault(), bl(e)) : !i && n.toLowerCase() === "x" ? (r.preventDefault(), kl(e)) : n.toLowerCase() === "v" && (r.preventDefault(), i ? xl(e) : Tl(e))); }; - return Ve( + return Fe( // Every copy/cut this plugin's shortcuts synthesize — and every one the context menu or an // editor ref synthesizes against the same editor — passes through this guard. - Kh(e), + tg(e), e.registerRootListener((r, n) => { n !== null && n.removeEventListener("keydown", t), r !== null && r.addEventListener("keydown", t); }) ); }, [e]), null; } -function EC({ logger: e }) { +function jC({ logger: e }) { const [t] = ce(); - return z(() => Ve( + return F(() => Fe( // When the backslash or forward slash key is typed. - t.registerCommand(vr, (r) => r.key !== "\\" && r.key !== "/" ? !1 : (r.preventDefault(), !0), Jn), + t.registerCommand(Nr, (r) => r.key !== "\\" && r.key !== "/" ? !1 : (r.preventDefault(), !0), Wr), // When the backslash or forward slash character is pasted into the editor. - t.registerCommand(yr, (r) => { + t.registerCommand(_r, (r) => { const n = r.clipboardData?.getData("text/plain"); return !n || !n.includes("\\") && !n.includes("/") ? !1 : (e?.info("CommandMenuPlugin: paste containing backslash or forward slash ignored."), r.preventDefault(), !0); - }, Jn), + }, Wr), // When the backslash or forward slash character is dragged into the editor. - t.registerCommand(xc, (r) => { + t.registerCommand(Ec, (r) => { const n = r.dataTransfer?.getData("text/plain"); return !n || !n.includes("\\") && !n.includes("/") ? !1 : (e?.info("CommandMenuPlugin: drag containing backslash or forward slash ignored."), r.preventDefault(), !0); - }, Jn) + }, Wr) ), [t, e]), null; } -function AC({ index: e, isSelected: t, onClick: r, onMouseEnter: n, option: i }) { +function BC({ index: e, isSelected: t, onClick: r, onMouseEnter: n, option: i }) { let s = "item"; - return t && (s += " selected"), i.isDisabled && (s += " disabled"), C("li", { tabIndex: -1, className: s, role: "option", "aria-selected": t, "aria-disabled": i.isDisabled, id: "typeahead-item-" + e, onMouseEnter: n, onClick: i.isDisabled ? void 0 : r, children: C("span", { className: "text", children: i.title }) }); + return t && (s += " selected"), i.isDisabled && (s += " disabled"), v("li", { tabIndex: -1, className: s, role: "option", "aria-selected": t, "aria-disabled": i.isDisabled, id: "typeahead-item-" + e, onMouseEnter: n, onClick: i.isDisabled ? void 0 : r, children: v("span", { className: "text", children: i.title }) }); } -function PC({ options: e, selectedItemIndex: t, onOptionClick: r, onOptionMouseEnter: n }) { - return C("div", { className: "typeahead-popover", children: C("ul", { children: e.map((i, s) => C(AC, { index: s, isSelected: t === s, onClick: () => r(i, s), onMouseEnter: () => n(s), option: i }, i.key)) }) }); +function VC({ options: e, selectedItemIndex: t, onOptionClick: r, onOptionMouseEnter: n }) { + return v("div", { className: "typeahead-popover", children: v("ul", { children: e.map((i, s) => v(BC, { index: s, isSelected: t === s, onClick: () => r(i, s), onMouseEnter: () => n(s), option: i }, i.key)) }) }); } -let NC = 0; -class Ei { +let WC = 0; +class Oi { key; title; onSelect; isDisabled; constructor(t, r) { - this.key = `context-menu-option-${NC++}`, this.title = t, this.onSelect = r.onSelect.bind(this), this.isDisabled = r.isDisabled || !1; + this.key = `context-menu-option-${WC++}`, this.title = t, this.onSelect = r.onSelect.bind(this), this.isDisabled = r.isDisabled || !1; } } -function OC({ options: e } = {}) { - const [t] = ce(), [r, n] = de(() => !t.isEditable()), [i, s] = de({ +function HC({ options: e } = {}) { + const [t] = ce(), [r, n] = fe(() => !t.isEditable()), [i, s] = fe({ isOpen: !1, x: 0, y: 0 - }), [o, a] = de(void 0), c = ze(() => { - const d = [ + }), [o, a] = fe(void 0), c = je(() => { + const u = [ // Cut/Copy with nothing selected leave the clipboard alone rather than writing a placeholder // over it — `registerEmptyCopyGuard` (mounted below) claims the command, so no selection // check is needed here. They are not disabled in that case, because this option list is // built once per editor rather than per menu opening, so its `isDisabled` flags cannot track // the live selection. - new Ei("Cut", { + new Oi("Cut", { onSelect: () => { - pl(t); + kl(t); }, isDisabled: r }), - new Ei("Copy", { + new Oi("Copy", { onSelect: () => { - fl(t); + bl(t); } }), - new Ei("Paste", { + new Oi("Paste", { onSelect: () => { - hl(t); + Tl(t); }, isDisabled: r }), - new Ei("Paste as Plain Text", { + new Oi("Paste as Plain Text", { onSelect: () => { - gl(t); + xl(t); }, isDisabled: r }) - ], f = (e ?? []).map((p) => new Ei(p.title, { onSelect: p.onSelect, isDisabled: p.isDisabled })); - return [...d, ...f]; - }, [t, r, e]), l = he(() => { - s((d) => ({ ...d, isOpen: !1 })), a(void 0); + ], p = (e ?? []).map((f) => new Oi(f.title, { onSelect: f.onSelect, isDisabled: f.isDisabled })); + return [...u, ...p]; + }, [t, r, e]), l = ge(() => { + s((u) => ({ ...u, isOpen: !1 })), a(void 0); }, []); - z(() => Kh(t), [t]), z(() => { - const d = (f) => { - const p = f.target; - t.getRootElement() === p || pp(p) || (f.preventDefault(), s({ isOpen: !0, x: f.clientX, y: f.clientY }), a(void 0)); + F(() => tg(t), [t]), F(() => { + const u = (p) => { + const f = p.target; + t.getRootElement() === f || Sp(f) || (p.preventDefault(), s({ isOpen: !0, x: p.clientX, y: p.clientY }), a(void 0)); }; - return t.registerRootListener((f, p) => { - p?.removeEventListener("contextmenu", d), f && f.addEventListener("contextmenu", d); + return t.registerRootListener((p, f) => { + f?.removeEventListener("contextmenu", u), p && p.addEventListener("contextmenu", u); }); - }, [t]), z(() => { + }, [t]), F(() => { if (!i.isOpen) return; - const d = () => { + const u = () => { l(); }; - return globalThis.addEventListener("scroll", d, !0), () => globalThis.removeEventListener("scroll", d, !0); - }, [i.isOpen, l]), z(() => { + return globalThis.addEventListener("scroll", u, !0), () => globalThis.removeEventListener("scroll", u, !0); + }, [i.isOpen, l]), F(() => { if (!i.isOpen) return; - const d = () => { + const u = () => { l(); }; - return document.addEventListener("pointerdown", d), () => document.removeEventListener("pointerdown", d); - }, [i.isOpen, l]), z(() => { + return document.addEventListener("pointerdown", u), () => document.removeEventListener("pointerdown", u); + }, [i.isOpen, l]), F(() => { if (!i.isOpen) return; - const d = (f) => { - if (f.key === "Escape") + const u = (p) => { + if (p.key === "Escape") l(); - else if (f.key === "ArrowDown") - f.preventDefault(), f.stopPropagation(), a((p) => p === void 0 ? 0 : (p + 1) % c.length); - else if (f.key === "ArrowUp") - f.preventDefault(), f.stopPropagation(), a((p) => p === void 0 ? c.length - 1 : (p - 1 + c.length) % c.length); - else if (f.key === "Enter" && o !== void 0) { - f.preventDefault(), f.stopPropagation(); - const p = c[o]; - p && !p.isDisabled && (t.update(() => { - p.onSelect(); + else if (p.key === "ArrowDown") + p.preventDefault(), p.stopPropagation(), a((f) => f === void 0 ? 0 : (f + 1) % c.length); + else if (p.key === "ArrowUp") + p.preventDefault(), p.stopPropagation(), a((f) => f === void 0 ? c.length - 1 : (f - 1 + c.length) % c.length); + else if (p.key === "Enter" && o !== void 0) { + p.preventDefault(), p.stopPropagation(); + const f = c[o]; + f && !f.isDisabled && (t.update(() => { + f.onSelect(); }), l()); } }; - return document.addEventListener("keydown", d, !0), () => document.removeEventListener("keydown", d, !0); - }, [i.isOpen, l, c, o, t]), z(() => t.registerEditableListener((d) => { - n(!d); + return document.addEventListener("keydown", u, !0), () => document.removeEventListener("keydown", u, !0); + }, [i.isOpen, l, c, o, t]), F(() => t.registerEditableListener((u) => { + n(!u); }), [t]); - const u = Z(null); - return cs(() => { - const d = u.current; - if (!d) + const d = Q(null); + return ps(() => { + const u = d.current; + if (!u) return; - const { width: f, height: p } = d.getBoundingClientRect(), m = Math.max(0, Math.min(i.x, globalThis.innerWidth - f)), g = Math.max(0, Math.min(i.y, globalThis.innerHeight - p)); - d.style.left = `${m}px`, d.style.top = `${g}px`, d.style.visibility = "visible"; - }, [i.isOpen, i.x, i.y]), i.isOpen ? zy.createPortal(C("div", { ref: u, className: "typeahead-popover auto-embed-menu", style: { + const { width: p, height: f } = u.getBoundingClientRect(), g = Math.max(0, Math.min(i.x, globalThis.innerWidth - p)), h = Math.max(0, Math.min(i.y, globalThis.innerHeight - f)); + u.style.left = `${g}px`, u.style.top = `${h}px`, u.style.visibility = "visible"; + }, [i.isOpen, i.x, i.y]), i.isOpen ? nb.createPortal(v("div", { ref: d, className: "typeahead-popover auto-embed-menu", style: { left: i.x, position: "fixed", top: i.y, @@ -18225,29 +18229,29 @@ function OC({ options: e } = {}) { visibility: "hidden", width: 200, zIndex: 9999 - }, onPointerDown: (d) => d.stopPropagation(), children: C(PC, { options: c, selectedItemIndex: o, onOptionClick: (d) => { - d.isDisabled || (t.update(() => { - d.onSelect(); + }, onPointerDown: (u) => u.stopPropagation(), children: v(VC, { options: c, selectedItemIndex: o, onOptionClick: (u) => { + u.isDisabled || (t.update(() => { + u.onSelect(); }), l()); - }, onOptionMouseEnter: (d) => { - a(d); + }, onOptionMouseEnter: (u) => { + a(u); } }) }), document.body) : null; } -function wC(e, t) { +function GC(e, t) { return e.startContainer === t.node && e.startOffset === t.offset; } -function qC(e) { - if (!_y(e.node)) +function JC(e) { + if (!$y(e.node)) return "before"; const t = e.node.nodeValue?.length ?? 0; return e.offset * 2 < t ? "before" : "after"; } -function RC(e) { +function YC(e) { return St(e); } -function la(e, t, r) { - const n = li(t.node); - if (!uo(n) || RC(n)) +function ha(e, t, r) { + const n = fi(t.node); + if (!ms(n) || YC(n)) return; const i = e.getElementByKey(n.getKey()); if (!i || i === t.node || !i.contains(t.node)) @@ -18259,42 +18263,42 @@ function la(e, t, r) { if (!(o < 0)) return { node: s, offset: r === "before" ? o : o + 1 }; } -function $C(e, t) { +function XC(e, t) { if (w()) return !1; - const r = e.getRootElement(), n = ky(r?.ownerDocument.defaultView ?? null); + const r = e.getRootElement(), n = wy(r?.ownerDocument.defaultView ?? null); if (!n || n.rangeCount === 0) return !1; const { anchorNode: i, anchorOffset: s, focusNode: o, focusOffset: a } = n; - if (!i || !o || !Ty(e, i, o)) + if (!i || !o || !qy(e, i, o)) return !1; const c = { node: i, offset: s }, l = { node: o, offset: a }; - let u, d; + let d, u; if (n.isCollapsed) - u = la(e, c, qC(c)), d = u; + d = ha(e, c, JC(c)), u = d; else { - const y = wC(n.getRangeAt(0), c); - u = la(e, c, y ? "before" : "after"), d = la(e, l, y ? "after" : "before"); + const y = GC(n.getRangeAt(0), c); + d = ha(e, c, y ? "before" : "after"), u = ha(e, l, y ? "after" : "before"); } - if (!u && !d) + if (!d && !u) return !1; - const f = u ?? c, p = d ?? l, m = { - anchorNode: f.node, - anchorOffset: f.offset, - focusNode: p.node, - focusOffset: p.offset - }, g = xy(m, e); - return g ? (ei(g), g.dirty = !t, t) : !1; -} -function IC() { - const [e] = ce(), t = Z(!1), r = Z(!1); - return z(() => { + const p = d ?? c, f = u ?? l, g = { + anchorNode: p.node, + anchorOffset: p.offset, + focusNode: f.node, + focusOffset: f.offset + }, h = Ry(g, e); + return h ? (Tn(h), h.dirty = !t, t) : !1; +} +function QC() { + const [e] = ce(), t = Q(!1), r = Q(!1); + return F(() => { const n = (s) => { "button" in s && s.button !== 0 || (t.current = !0); }, i = () => { t.current = !1, r.current && (r.current = !1, e.update(() => { const s = w(); - N(s) && (s.dirty = !0); + P(s) && (s.dirty = !0); })); }; return e.registerRootListener((s, o) => { @@ -18303,104 +18307,104 @@ function IC() { const c = s?.ownerDocument; c?.addEventListener("pointerdown", n, !0), c?.addEventListener("pointerup", i, !0), c?.addEventListener("pointercancel", i, !0); }); - }, [e]), z(() => e.registerCommand(ur, () => ($C(e, t.current) && (r.current = !0), !1), Wt), [e]), null; + }, [e]), F(() => e.registerCommand(dr, () => (XC(e, t.current) && (r.current = !0), !1), tt), [e]), null; } -function LC() { +function ZC() { const [e] = ce(); - return z(() => e.registerCommand(vr, (t) => { + return F(() => e.registerCommand(Nr, (t) => { const { key: r, shiftKey: n, metaKey: i, ctrlKey: s, altKey: o } = t; - if (!($s ? i : s) || o) + if (!(ri ? i : s) || o) return !1; const a = r.toLowerCase(); return !(a === "z" && !n) && !(a === "y" || a === "z" && n) ? !1 : (t.preventDefault(), !0); - }, Wt), [e]), null; + }, tt), [e]), null; } -function DC({ isEditable: e }) { +function ev({ isEditable: e }) { const [t] = ce(); - return cs(() => { + return ps(() => { t.setEditable(e); }, [t, e]), null; } -function cd(e) { - return !!e && qc(se(e)); +function md(e) { + return !!e && Uc(oe(e)); } -function jh(e) { - const [t] = ce(), r = Z(void 0), n = he((i) => { - const s = w(), o = N(s) && s.isCollapsed() ? s.anchor.key : void 0, a = r.current, c = cd(a); +function rg(e) { + const [t] = ce(), r = Q(void 0), n = ge((i) => { + const s = w(), o = P(s) && s.isCollapsed() ? s.anchor.key : void 0, a = r.current, c = md(a); a && !c && (r.current = void 0); let l; if (i) { - const u = i.getParentOrThrow(), d = i.getIndexWithinParent() + 1, f = _o(u, d); - if (f) - r.current = f.getKey(), l = f.getKey(); + const d = i.getParentOrThrow(), u = i.getIndexWithinParent() + 1, p = Mo(d, u); + if (p) + r.current = p.getKey(), l = p.getKey(); else { - const p = hk(); - i.insertAfter(p), r.current = p.getKey(), l = p.getKey(); + const f = Nk(); + i.insertAfter(f), r.current = f.getKey(), l = f.getKey(); } - Qt(u, d); + Qt(d, u); } if (a && c && a !== o && a !== l) { - const u = se(a); - v(u) && u.remove(), r.current === a && (r.current = void 0); + const d = oe(a); + S(d) && d.remove(), r.current === a && (r.current = void 0); } }, []); - return z(() => { + return F(() => { const i = () => { - const a = e(), c = w(), l = N(c) && c.isCollapsed() ? c.anchor.key : void 0, u = r.current; - (a || u && u !== l) && (jr(Br), n(a)); + const a = e(), c = w(), l = P(c) && c.isCollapsed() ? c.anchor.key : void 0, d = r.current; + (a || d && d !== l) && (Gr(Jr), n(a)); }, s = (a) => { if (a.getKey() !== r.current) return; const c = a.getTextContent(); - if (ps(c) || !c.includes(ti)) + if (bs(c) || !c.includes(ni)) return; - const l = w(), u = N(l) && l.isCollapsed() && l.anchor.key === a.getKey() ? l.anchor.offset : void 0; - if (gk(a), r.current = void 0, u !== void 0) { - const d = c.slice(0, u).split(ti).length - 1, f = Math.max(0, u - d); - a.select(f, f); + const l = w(), d = P(l) && l.isCollapsed() && l.anchor.key === a.getKey() ? l.anchor.offset : void 0; + if (Ok(a), r.current = void 0, d !== void 0) { + const u = c.slice(0, d).split(ni).length - 1, p = Math.max(0, d - u); + a.select(p, p); } - }, o = Ve(t.registerCommand(ur, () => (i(), !1), mn), t.registerCommand(_c, () => { + }, o = Fe(t.registerCommand(dr, () => (i(), !1), kn), t.registerCommand(Ac, () => { const a = r.current; if (!a) return !1; let c = !1; return t.getEditorState().read(() => { - c = cd(a); + c = md(a); }), c && t.update(() => { - const l = se(a); - v(l) && l.remove(); - }, { tag: Br }), r.current = void 0, !1; - }, mn), t.registerNodeTransform(Ke, s)); + const l = oe(a); + S(l) && l.remove(); + }, { tag: Jr }), r.current = void 0, !1; + }, kn), t.registerNodeTransform(Be, s)); return () => { o(), r.current = void 0; }; }, [t, e, n]), n; } -function UC() { +function tv() { const e = w(); - if (!N(e) || !e.isCollapsed()) + if (!P(e) || !e.isCollapsed()) return; const { anchor: t } = e; if (t.type !== "element") return; const r = t.getNode(); - if (!F(r)) + if (!L(r)) return; const n = r.getChildren(), i = n[t.offset - 1]; - if (!ge(i) || _o(r, t.offset)) + if (!me(i) || Mo(r, t.offset)) return; const s = n[t.offset]; - if (s === void 0 || ge(s)) + if (s === void 0 || me(s)) return i; } -function FC() { - return jh(UC), null; +function rv() { + return rg(tv), null; } -function zC({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, viewOptions: i, logger: s }) { +function nv({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, viewOptions: i, logger: s }) { const [o] = ce(); - return z(() => { + return F(() => { n.initialize?.(r, s); - }, [n, s, r]), z(() => { + }, [n, s, r]), F(() => { const a = t?.current ?? e; n.reset?.(); const c = n.serializeEditorState(a, i); @@ -18411,48 +18415,48 @@ function zC({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, v try { const l = o.parseEditorState(c); queueMicrotask(() => { - const u = o.getRootElement(), d = u?.ownerDocument.activeElement, f = u != null && d != null && (u === d || u.contains(d)); + const d = o.getRootElement(), u = d?.ownerDocument.activeElement, p = d != null && u != null && (d === u || d.contains(u)); o.update(() => { - f || jr(Cy), o.setEditorState(l), o.dispatchCommand(Sy, void 0); - }, { tag: Df }); + p || Gr(Ly), o.setEditorState(l), o.dispatchCommand(Iy, void 0); + }, { tag: Yf }); }); } catch { s?.error("LoadStatePlugin: error parsing or setting editor state."); } }, [o, n, s, e, t, i]), null; } -function KC({ expandedNoteKeyRef: e, nodeOptions: t, viewOptions: r, logger: n }) { +function iv({ expandedNoteKeyRef: e, nodeOptions: t, viewOptions: r, logger: n }) { const [i] = ce(); - return jC(t, n), BC(i, e, r, n), null; + return sv(t, n), ov(i, e, r, n), null; } -function jC(e, t) { - const r = Z(void 0), n = Z(void 0), i = e.noteCallers, s = e.crossRefCallers; - z(() => { +function sv(e, t) { + const r = Q(void 0), n = Q(void 0), i = e.noteCallers, s = e.crossRefCallers; + F(() => { let o = i; - (!o || o.length <= 0) && (o = Xx), r.current !== o && (r.current = o, ld("note-callers", o, t)); - }, [t, i]), z(() => { + (!o || o.length <= 0) && (o = p_), r.current !== o && (r.current = o, yd("note-callers", o, t)); + }, [t, i]), F(() => { let o = s; - (!o || o.length <= 0) && (o = Qx), n.current !== o && (n.current = o, ld("cross-ref-callers", o, t)); + (!o || o.length <= 0) && (o = h_), n.current !== o && (n.current = o, yd("cross-ref-callers", o, t)); }, [t, s]); } -function BC(e, t, r, n) { - z(() => { - if (!e.hasNodes([me, Ee, Gt])) +function ov(e, t, r, n) { + F(() => { + if (!e.hasNodes([be, Pe, Gt])) throw new Error("NoteNodePlugin: CharNode, NoteNode or ImmutableNoteCallerNode not registered on editor!"); - const i = (s) => e.update(() => XC(s)); - return Ve( + const i = (s) => e.update(() => pv(s)); + return Fe( // Remove NoteNode if it doesn't contain a caller node and ensure typed text goes before it. - e.registerNodeTransform(Ee, (s) => VC(s, r)), + e.registerNodeTransform(Pe, (s) => av(s, r)), // Update NoteNodeCaller preview text when NoteNode children text is changed. - e.registerNodeTransform(me, WC), - e.registerNodeTransform(Ke, HC), + e.registerNodeTransform(be, cv), + e.registerNodeTransform(Be, lv), // Ensure NBSP after caller. - e.registerNodeTransform(Gt, GC), + e.registerNodeTransform(Gt, uv), // Re-generate all note callers when a note is removed. - e.registerMutationListener(Gt, (s, { prevEditorState: o }) => JC(s, o)), + e.registerMutationListener(Gt, (s, { prevEditorState: o }) => dv(s, o)), // Handle the cursor moving next to a NoteNode. NoteNode arrow key navigation when note is // after a verse node is handled in the ArrowNavigationPlugin. - e.registerCommand(ur, () => YC(e, t, r, n), Tt), + e.registerCommand(dr, () => fv(e, t, r, n), xt), // Handle double-click of a word immediately following a NoteNode (no space between). e.registerRootListener((s, o) => { o !== null && o.removeEventListener("dblclick", i), s !== null && s.addEventListener("dblclick", i); @@ -18460,102 +18464,102 @@ function BC(e, t, r, n) { ); }, [e, t, n, r]); } -function VC(e, t) { +function av(e, t) { const r = e.getChildren(); if (!r.some((i) => St(i)) && t?.markerMode !== "editable" && e.getCaller() !== "" && e.remove(), r.length > 0) { const i = r[0]; - v(i) && !O(i) && i.getTextContent() !== At(e.getCaller()) && e.insertBefore(i); + S(i) && !O(i) && i.getTextContent() !== Pt(e.getCaller()) && e.insertBefore(i); } } -function WC(e) { +function cv(e) { const t = e.getParentOrThrow(), r = t.getChildren(), n = r.find((o) => St(o)); - if (!D(e) || !j(t) || !n) + if (!I(e) || !K(t) || !n) return; - const i = Rc(r); + const i = Fc(r); n.getPreviewText() !== i && n.setPreviewText(i); const s = e.getNextSibling(); - v(s) ? s.getTextContent() !== I && s.setTextContent(I) : e.insertAfter(pe(I)); + S(s) ? s.getTextContent() !== R && s.setTextContent(R) : e.insertAfter(he(R)); } -function HC(e) { +function lv(e) { const t = Xt(e), r = t?.getChildren(), n = r?.find((o) => St(o)); - if (!v(e) || !j(t) || !n || !r) + if (!S(e) || !K(t) || !n || !r) return; const i = e.getParent(); - if (!O(e) && j(i) && e.getTextContent() !== I && (e.setTextContent(I), e.selectEnd()), D(i) && i.getChildrenSize() === 1) { + if (!O(e) && K(i) && e.getTextContent() !== R && (e.setTextContent(R), e.selectEnd()), I(i) && i.getChildrenSize() === 1) { const o = e.getTextContent(); - o.length > 1 && o.startsWith(Dt) && (e.setTextContent(o.slice(1)), e.selectEnd()); + o.length > 1 && o.startsWith(Ut) && (e.setTextContent(o.slice(1)), e.selectEnd()); } - const s = Rc(r); + const s = Fc(r); n.getPreviewText() !== s && n.setPreviewText(s); } -function GC(e) { +function uv(e) { if (!St(e)) return; const t = e.getNextSibling(); - !v(t) || O(t) ? e.insertAfter(pe(I)) : t.getTextContent() !== I && t.setTextContent(I); + !S(t) || O(t) ? e.insertAfter(he(R)) : t.getTextContent() !== R && t.setTextContent(R); } -function JC(e, t) { +function dv(e, t) { for (const [r, n] of e) { if (n !== "destroyed") continue; const i = t.read(() => { - const o = se(r), a = o?.getParent(); - return St(o) && j(a) && a.getCaller() === Bi; + const o = oe(r), a = o?.getParent(); + return St(o) && K(a) && a.getCaller() === Gi; }), s = document.querySelector(".editor-input"); !i || !s || (s.classList.add("reset-counters"), s.offsetHeight, s.classList.remove("reset-counters")); } } -function YC(e, t, r, n) { +function fv(e, t, r, n) { if (r?.noteMode !== "expandInline") return !1; const i = w(); - if (!N(i) || !i.isCollapsed()) + if (!P(i) || !i.isCollapsed()) return !1; const s = i.anchor, o = s.getNode(); if (t.current) { - const a = nt(o, (c) => j(c)); + const a = rt(o, (c) => K(c)); if (a) t.current !== a.getKey() && (t.current = a.getKey()); else { - const c = se(t.current); - c && !c.getIsCollapsed() && (n?.debug("Cursor moved away from NoteNode, collapsing it"), Ai(e, t.current, n)), t.current = void 0; + const c = oe(t.current); + c && !c.getIsCollapsed() && (n?.debug("Cursor moved away from NoteNode, collapsing it"), wi(e, t.current, n)), t.current = void 0; } } if (s.offset === 0) { const a = o.getPreviousSibling(); - if (j(a)) { + if (K(a)) { n?.debug("Cursor is just after a NoteNode"); const c = a.getKey(); - a.getIsCollapsed() ? t.current = c : t.current = void 0, Ai(e, c, n); + a.getIsCollapsed() ? t.current = c : t.current = void 0, wi(e, c, n); } } if (s.offset === o.getTextContentSize()) { const a = o.getNextSibling(); - if (j(a)) { + if (K(a)) { n?.debug("Cursor is just before a NoteNode"); const c = a.getKey(); - a.getIsCollapsed() ? t.current = c : t.current = void 0, Ai(e, c, n); + a.getIsCollapsed() ? t.current = c : t.current = void 0, wi(e, c, n); } else if (!a) { - const c = nt(o, (l) => j(l)); - if (c && c.getIsCollapsed() && Se(c.getParent()) && c.is(c.getParent()?.getLastChild())) { + const c = rt(o, (l) => K(l)); + if (c && c.getIsCollapsed() && ve(c.getParent()) && c.is(c.getParent()?.getLastChild())) { n?.debug("Cursor is at end of note at end of para"); const l = c.getKey(); - t.current = l, Ai(e, l, n); + t.current = l, wi(e, l, n); } } } - if (Se(o)) { + if (ve(o)) { const a = o.getChildAtIndex(s.offset), c = a?.getPreviousSibling(); - if (wn(c) && j(a)) { + if ($n(c) && K(a)) { n?.debug("Cursor is between verse and NoteNode"); const l = a.getKey(); - a.getIsCollapsed() ? t.current = l : t.current = void 0, Ai(e, l, n); + a.getIsCollapsed() ? t.current = l : t.current = void 0, wi(e, l, n); } } return !1; } -function Ai(e, t, r) { - const n = se(t); +function wi(e, t, r) { + const n = oe(t); try { n?.toggleIsCollapsed(); } catch (i) { @@ -18569,23 +18573,23 @@ function Ai(e, t, r) { throw i; } } -function XC(e) { +function pv(e) { const t = w(); - if (!N(t)) + if (!P(t)) return; const r = t.anchor, n = t.focus, i = r.getNode(), s = n.getNode(); - if (j(i) && v(s)) { + if (K(i) && S(s)) { e.preventDefault(); - const o = bc(); - o.anchor.set(s.getKey(), 0, "text"), o.focus.set(s.getKey(), n.offset, "text"), ei(o); + const o = vc(); + o.anchor.set(s.getKey(), 0, "text"), o.focus.set(s.getKey(), n.offset, "text"), Tn(o); } } -function ld(e, t, r) { +function yd(e, t, r) { for (const n of document.styleSheets) try { const i = n.cssRules || n.rules; for (const s of i) - if (QC(s, e)) { + if (hv(s, e)) { const o = t.map((a) => `"${a}"`).join(" "); s.symbols = o; return; @@ -18595,13 +18599,13 @@ function ld(e, t, r) { } r?.warn(`Editor: counter style "${e}" not found.`); } -function QC(e, t) { +function hv(e, t) { return ( // This check could be simpler but as is also works for test mocks. typeof e == "object" && e !== null && "name" in e && e.name === t && "symbols" in e && typeof e.symbols == "string" ); } -function Oo(e) { +function $o(e) { if (e.getIsCollapsed() !== !1) return []; const t = []; @@ -18610,80 +18614,80 @@ function Oo(e) { break; t.push(n); } - const r = Xi(e); - return r && t.push(r), t.length > 0 && t.every((n) => v(n) && n.getMode() === "token") ? t : []; + const r = ts(e); + return r && t.push(r), t.length > 0 && t.every((n) => S(n) && n.getMode() === "token") ? t : []; } -function ZC(e) { +function gv(e) { const t = e.getParent(); - if (j(t)) - return Oo(t).some((r) => r.is(e)) ? t : void 0; + if (K(t)) + return $o(t).some((r) => r.is(e)) ? t : void 0; } -function Xs(e) { - const t = Oo(e), r = t[t.length - 1]; +function to(e) { + const t = $o(e), r = t[t.length - 1]; return r ? r.getIndexWithinParent() + 1 : 0; } -function eS(e, t) { +function mv(e, t) { for (let r = t; r; r = r.getParent()) if (e.is(r.getParent())) return r; } -function tS(e) { - const t = vy(); - if (!N(t)) +function yv(e) { + const t = Dy(); + if (!P(t)) return !1; const { anchor: r } = t, n = r.getNode(); if (e.is(n)) - return r.offset >= Xs(e); - const i = eS(e, n); - return i !== void 0 && i.getIndexWithinParent() >= Xs(e); + return r.offset >= to(e); + const i = mv(e, n); + return i !== void 0 && i.getIndexWithinParent() >= to(e); } -function Va(e) { +function Xa(e) { if (e.type !== "text") return; - const t = e.getNode(), r = ZC(t); + const t = e.getNode(), r = gv(t); if (r) - return rS(r, t, e.offset) ? void 0 : r; + return bv(r, t, e.offset) ? void 0 : r; } -function rS(e, t, r) { - const n = Oo(e), i = n[n.length - 1]; +function bv(e, t, r) { + const n = $o(e), i = n[n.length - 1]; return i !== void 0 && i.is(t) && r === i.getTextContentSize(); } -function nS(e) { - const t = Oo(e), r = t[t.length - 1]; - v(r) ? r.select(r.getTextContentSize(), r.getTextContentSize()) : Qt(e, Xs(e)); +function kv(e) { + const t = $o(e), r = t[t.length - 1]; + S(r) ? r.select(r.getTextContentSize(), r.getTextContentSize()) : Qt(e, to(e)); } -function iS(e = !1) { +function Tv(e = !1) { const t = w(); - if (!N(t)) + if (!P(t)) return !1; if (!t.isCollapsed()) - return sS(t.anchor, t.focus); - const r = Va(t.anchor); + return xv(t.anchor, t.focus); + const r = Xa(t.anchor); if (!r) return !1; - if (!e && tS(r)) { + if (!e && yv(r)) { const n = r.getParent(); if (!n) return !1; Qt(n, r.getIndexWithinParent()); } else - nS(r); + kv(r); return !0; } -function sS(e, t) { - const r = Va(e), n = Va(t); +function xv(e, t) { + const r = Xa(e), n = Xa(t); if (!r && !n) return !1; const i = e.isBefore(t); - return r && ud(e, r, i), n && ud(t, n, !i), !0; + return r && bd(e, r, i), n && bd(t, n, !i), !0; } -function ud(e, t, r) { +function bd(e, t, r) { const n = t.getParent(); - r && n ? e.set(n.getKey(), t.getIndexWithinParent(), "element") : e.set(t.getKey(), Xs(t), "element"); + r && n ? e.set(n.getKey(), t.getIndexWithinParent(), "element") : e.set(t.getKey(), to(t), "element"); } -function oS() { - const [e] = ce(), t = Z(!1); - return z(() => { +function _v() { + const [e] = ce(), t = Q(!1); + return F(() => { const r = () => { t.current = !0; }, n = () => { @@ -18695,73 +18699,73 @@ function oS() { const a = i?.ownerDocument; a?.addEventListener("pointerdown", r, !0), a?.addEventListener("pointerup", n, !0), a?.addEventListener("pointercancel", n, !0); }); - }, [e]), z(() => e.registerCommand(ur, () => (iS(t.current) && jr(Br), !1), mn), [e]), null; + }, [e]), F(() => e.registerCommand(dr, () => (Tv(t.current) && Gr(Jr), !1), kn), [e]), null; } -function aS({ onChange: e }) { +function Cv({ onChange: e }) { const [t] = ce(); - return z(() => t.registerCommand(ur, () => { - const r = tl(); + return F(() => t.registerCommand(dr, () => { + const r = al(); return e?.(r), !1; - }, Tt), [t, e]), null; + }, xt), [t, e]), null; } -function cS() { +function vv() { const [e] = ce(); - return lS(e), null; + return Sv(e), null; } -function lS(e) { - z(() => { - if (!e.hasNodes([Ze])) +function Sv(e) { + F(() => { + if (!e.hasNodes([nt])) throw new Error("ParaNodePlugin: ParaNode not registered on editor!"); - return e.registerNodeTransform(Ze, (t) => uS(t, e)); + return e.registerNodeTransform(nt, (t) => Mv(t, e)); }, [e]); } -function uS(e, t) { - Ua(t, e.getKey()) && ph(e.getFirstChild()), !(!ae(e) || e.getMarker() !== "b" || e.isEmpty() || !t.getEditorState().read(() => { - const i = se(e.getKey()); - return ae(i) && (i?.isEmpty() ?? !1); +function Mv(e, t) { + Va(t, e.getKey()) && Sh(e.getFirstChild()), !(!le(e) || e.getMarker() !== "b" || e.isEmpty() || !t.getEditorState().read(() => { + const i = oe(e.getKey()); + return le(i) && (i?.isEmpty() ?? !1); })) && e.clear(); } -function Bh({ onStateChange: e }) { - const [t] = ce(), [r, n] = de(t), i = Z(!1), s = Z(!1), o = Z(void 0), a = Z(void 0), c = he(() => { +function ng({ onStateChange: e }) { + const [t] = ce(), [r, n] = fe(t), i = Q(!1), s = Q(!1), o = Q(void 0), a = Q(void 0), c = ge(() => { const l = w(); - let u; - if (N(l)) { - const d = l.anchor.getNode(), f = l.focus.getNode(); - let p = d.getKey() === "root" ? d : nt(d, (k) => { + let d; + if (P(l)) { + const u = l.anchor.getNode(), p = l.focus.getNode(); + let f = u.getKey() === "root" ? u : rt(u, (k) => { const _ = k.getParent(); - return _ !== null && My(_); + return _ !== null && Uy(_); }); - p === null && (p = d.getTopLevelElementOrThrow()), ts(p) && (p = nt(d, ae) ?? p); - const m = p.getKey(), g = r.getElementByKey(m), y = Tk(d, f); - if (y && ux(y) && (u = y.getMarker()), g !== null && (ae(p) || pt(p) || fs(p))) { - o.current = p.getMarker(), a.current = u, e?.({ + f === null && (f = u.getTopLevelElementOrThrow()), ss(f) && (f = rt(u, le) ?? f); + const g = f.getKey(), h = r.getElementByKey(g), y = Lk(u, p); + if (y && Mx(y) && (d = y.getMarker()), h !== null && (le(f) || ht(f) || ys(f))) { + o.current = f.getMarker(), a.current = d, e?.({ canUndo: i.current, canRedo: s.current, blockMarker: o.current, - contextMarker: u + contextMarker: d }); return; } } - a.current = u; + a.current = d; }, [r, e]); - return z(() => t.registerCommand(ur, (l, u) => (c(), n(u), !1), Wt), [t, c]), z(() => Ve(r.registerUpdateListener(({ editorState: l }) => { + return F(() => t.registerCommand(dr, (l, d) => (c(), n(d), !1), tt), [t, c]), F(() => Fe(r.registerUpdateListener(({ editorState: l }) => { l.read(() => { c(); }); - }), r.registerCommand(Ey, (l) => (i.current = l, e?.({ + }), r.registerCommand(Fy, (l) => (i.current = l, e?.({ canUndo: i.current, canRedo: s.current, blockMarker: o.current, contextMarker: a.current - }), !1), Wt), r.registerCommand(Ay, (l) => (s.current = l, e?.({ + }), !1), tt), r.registerCommand(zy, (l) => (s.current = l, e?.({ canUndo: i.current, canRedo: s.current, blockMarker: o.current, contextMarker: a.current - }), !1), Wt)), [c, r, e]), null; + }), !1), tt)), [c, r, e]), null; } -function Vh(e) { +function ig(e) { if (e.key === "Enter" && !e.shiftKey) return "insertParagraph"; if (e.key === "Backspace") @@ -18771,26 +18775,26 @@ function Vh(e) { if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) return "insertText"; } -function Jr(e) { - return e ? Se(e) ? e : nt(e, (r) => Se(r)) ?? void 0 : void 0; +function en(e) { + return e ? ve(e) ? e : rt(e, (r) => ve(r)) ?? void 0 : void 0; } -function Wh(e) { - if (!N(e)) +function sg(e) { + if (!P(e)) return !1; const t = /* @__PURE__ */ new Set(); for (const r of e.getNodes()) { - const n = Jr(r); + const n = en(r); n && t.add(n.getKey()); } return t.size > 1; } -function ml(e) { - return N(e) && e.isCollapsed() && e.anchor.type === "element" || !N(e) && !mc(e) ? !1 : e.getNodes().some((t) => ge(t)); +function _l(e) { + return P(e) && e.isCollapsed() && e.anchor.type === "element" || !P(e) && !go(e) ? !1 : e.getNodes().some((t) => me(t)); } -function Hh(e) { - if (!N(e) || !e.isCollapsed()) +function og(e) { + if (!P(e) || !e.isCollapsed()) return !1; - const { anchor: t } = e, r = t.getNode(), n = Jr(r); + const { anchor: t } = e, r = t.getNode(), n = en(r); if (!n || t.offset !== 0) return !1; let i = r; @@ -18801,13 +18805,13 @@ function Hh(e) { } return !0; } -function Gh(e) { - if (!N(e) || !e.isCollapsed()) +function ag(e) { + if (!P(e) || !e.isCollapsed()) return !1; - const { anchor: t } = e, r = t.getNode(), n = Jr(r); + const { anchor: t } = e, r = t.getNode(), n = en(r); if (!n) return !1; - if (F(r)) { + if (L(r)) { if (t.offset !== r.getChildrenSize()) return !1; } else if (t.offset !== r.getTextContentSize()) @@ -18820,138 +18824,138 @@ function Gh(e) { } return !0; } -function dd(e, t) { - return !!Wa(e, t); +function kd(e, t) { + return !!Qa(e, t); } -function Wa(e, t) { - if (!N(e) || !e.isCollapsed()) +function Qa(e, t) { + if (!P(e) || !e.isCollapsed()) return; const { anchor: r } = e, n = r.getNode(); - if (r.type === "element" && F(n)) { + if (r.type === "element" && L(n)) { const s = n.getChildren(), o = t === "backward" ? r.offset - 1 : r.offset; if (o < 0) return; const a = s[o]; - return ge(a) ? a : void 0; + return me(a) ? a : void 0; } if (t === "backward") { if (r.offset !== 0) return; const s = n.getPreviousSibling(); - return ge(s) ? s : void 0; + return me(s) ? s : void 0; } if (r.offset !== n.getTextContentSize()) return; const i = n.getNextSibling(); - return ge(i) ? i : void 0; + return me(i) ? i : void 0; } -function Qs(e, t) { - if (!N(e)) +function ro(e, t) { + if (!P(e)) return !1; - const r = Jr(e.anchor.getNode()); + const r = en(e.anchor.getNode()); return r ? !!(t === "backward" ? r.getPreviousSibling() : r.getNextSibling()) : !1; } -function Qn(e) { - return ml(e) || Wh(e); +function ei(e) { + return _l(e) || sg(e); } -function Jh(e, t) { - if (ml(e) || Wh(e)) +function cg(e, t) { + if (_l(e) || sg(e)) return !0; - if (!N(e) || !e.isCollapsed()) + if (!P(e) || !e.isCollapsed()) return !1; switch (t) { case "insertParagraph": return !0; case "deleteBackward": - return Hh(e) && Qs(e, "backward") || dd(e, "backward"); + return og(e) && ro(e, "backward") || kd(e, "backward"); case "deleteForward": - return Gh(e) && Qs(e, "forward") || dd(e, "forward"); + return ag(e) && ro(e, "forward") || kd(e, "forward"); case "insertText": return !1; } } -function dS(e, t) { - if (!(!N(e) || !e.isCollapsed())) { +function Ev(e, t) { + if (!(!P(e) || !e.isCollapsed())) { if (t === "deleteBackward") { - const r = Wa(e, "backward"); + const r = Qa(e, "backward"); if (r) return { kind: "verse", node: r }; - if (Hh(e) && Qs(e, "backward")) { - const n = Jr(e.anchor.getNode()); - if (Se(n)) + if (og(e) && ro(e, "backward")) { + const n = en(e.anchor.getNode()); + if (ve(n)) return { kind: "para", node: n }; } return; } if (t === "deleteForward") { - const r = Wa(e, "forward"); + const r = Qa(e, "forward"); if (r) return { kind: "verse", node: r }; - if (Gh(e) && Qs(e, "forward")) { - const i = Jr(e.anchor.getNode())?.getNextSibling(); - if (Se(i)) + if (ag(e) && ro(e, "forward")) { + const i = en(e.anchor.getNode())?.getNextSibling(); + if (ve(i)) return { kind: "para", node: i }; } return; } } } -function fd(e, t) { +function Td(e, t) { if (!e) return !1; if (t.kind === "verse") - return mc(e) && e.has(t.key); + return go(e) && e.has(t.key); if (t.kind === "selection") { - if (!N(e) || e.isCollapsed() || !t.anchor || !t.focus) + if (!P(e) || e.isCollapsed() || !t.anchor || !t.focus) return !1; const { anchor: i, focus: s } = e; return i.key === t.anchor.key && i.offset === t.anchor.offset && i.type === t.anchor.type && s.key === t.focus.key && s.offset === t.focus.offset && s.type === t.focus.type; } - if (!N(e) || e.isCollapsed()) + if (!P(e) || e.isCollapsed()) return !1; - const r = Jr(e.anchor.getNode()), n = Jr(e.focus.getNode()); + const r = en(e.anchor.getNode()), n = en(e.focus.getNode()); return !!r && r.getKey() === t.key && !!n && n.getKey() === t.key; } -function Yh(e) { - if (v(e)) { +function lg(e) { + if (S(e)) { const t = e.getTextContentSize(); e.select(t, t); - } else F(e) ? e.selectEnd() : e.selectNext(0, 0); + } else L(e) ? e.selectEnd() : e.selectNext(0, 0); } -function fS(e) { +function Av(e) { const t = e.getPreviousSibling(); - if (!Se(t)) + if (!ve(t)) return; const r = t.getLastChild(), n = e.getChildren(); - t.append(...n), e.remove(), r ? Yh(r) : yi(t) || t.selectStart(); + t.append(...n), e.remove(), r ? lg(r) : Ti(t) || t.selectStart(); } -function Xh(e) { - return ge(e) || Ge(e) ? [] : Se(e) ? e.getChildren().flatMap(Xh) : [e]; +function ug(e) { + return me(e) || Je(e) ? [] : ve(e) ? e.getChildren().flatMap(ug) : [e]; } -function pS(e) { +function Pv(e) { const t = []; for (const r of e) { - const n = Xh(r); - n.length !== 0 && (Se(r) && t.length > 0 && t.push(pe(" ")), t.push(...n)); + const n = ug(r); + n.length !== 0 && (ve(r) && t.length > 0 && t.push(he(" ")), t.push(...n)); } return t; } -function pd(e, t) { +function xd(e, t) { (t == null || t > e.length) && (t = e.length); for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } -function hS(e) { +function Nv(e) { if (Array.isArray(e)) return e; } -function gS(e, t) { +function Ov(e, t) { var r = e == null ? null : typeof Symbol < "u" && e[Symbol.iterator] || e["@@iterator"]; if (r != null) { var n, i, s, o, a = [], c = !0, l = !1; try { if (s = (r = r.call(e)).next, t !== 0) for (; !(c = (n = s.call(r)).done) && (a.push(n.value), a.length !== t); c = !0) ; - } catch (u) { - l = !0, i = u; + } catch (d) { + l = !0, i = d; } finally { try { if (!c && r.return != null && (o = r.return(), Object(o) !== o)) return; @@ -18962,142 +18966,142 @@ function gS(e, t) { return a; } } -function mS() { +function wv() { throw new TypeError(`Invalid attempt to destructure non-iterable instance. In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`); } -function yS(e, t) { - return hS(e) || gS(e, t) || bS(e, t) || mS(); +function qv(e, t) { + return Nv(e) || Ov(e, t) || Rv(e, t) || wv(); } -function bS(e, t) { +function Rv(e, t) { if (e) { - if (typeof e == "string") return pd(e, t); + if (typeof e == "string") return xd(e, t); var r = {}.toString.call(e).slice(8, -1); - return r === "Object" && e.constructor && (r = e.constructor.name), r === "Map" || r === "Set" ? Array.from(e) : r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? pd(e, t) : void 0; + return r === "Object" && e.constructor && (r = e.constructor.name), r === "Map" || r === "Set" ? Array.from(e) : r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? xd(e, t) : void 0; } } -const Qh = Object.entries, hd = Object.setPrototypeOf, kS = Object.isFrozen, TS = Object.getPrototypeOf, xS = Object.getOwnPropertyDescriptor; -let et = Object.freeze, it = Object.seal, Hn = Object.create, Zh = typeof Reflect < "u" && Reflect, Ha = Zh.apply, Ga = Zh.construct; -et || (et = function(t) { +const dg = Object.entries, _d = Object.setPrototypeOf, $v = Object.isFrozen, Lv = Object.getPrototypeOf, Iv = Object.getOwnPropertyDescriptor; +let it = Object.freeze, at = Object.seal, Yn = Object.create, fg = typeof Reflect < "u" && Reflect, Za = fg.apply, ec = fg.construct; +it || (it = function(t) { return t; }); -it || (it = function(t) { +at || (at = function(t) { return t; }); -Ha || (Ha = function(t, r) { +Za || (Za = function(t, r) { for (var n = arguments.length, i = new Array(n > 2 ? n - 2 : 0), s = 2; s < n; s++) i[s - 2] = arguments[s]; return t.apply(r, i); }); -Ga || (Ga = function(t) { +ec || (ec = function(t) { for (var r = arguments.length, n = new Array(r > 1 ? r - 1 : 0), i = 1; i < r; i++) n[i - 1] = arguments[i]; return new t(...n); }); -const Bn = Je(Array.prototype.forEach), _S = Je(Array.prototype.lastIndexOf), gd = Je(Array.prototype.pop), Vn = Je(Array.prototype.push), CS = Je(Array.prototype.splice), zr = Array.isArray, $i = Je(String.prototype.toLowerCase), ua = Je(String.prototype.toString), md = Je(String.prototype.match), Pi = Je(String.prototype.replace), yd = Je(String.prototype.indexOf), SS = Je(String.prototype.trim), vS = Je(Number.prototype.toString), MS = Je(Boolean.prototype.toString), bd = typeof BigInt > "u" ? null : Je(BigInt.prototype.toString), kd = typeof Symbol > "u" ? null : Je(Symbol.prototype.toString), Xe = Je(Object.prototype.hasOwnProperty), Ni = Je(Object.prototype.toString), Ye = Je(RegExp.prototype.test), dn = ES(TypeError); -function Je(e) { +const Hn = Ye(Array.prototype.forEach), Dv = Ye(Array.prototype.lastIndexOf), Cd = Ye(Array.prototype.pop), Gn = Ye(Array.prototype.push), Uv = Ye(Array.prototype.splice), Br = Array.isArray, Ui = Ye(String.prototype.toLowerCase), ga = Ye(String.prototype.toString), vd = Ye(String.prototype.match), qi = Ye(String.prototype.replace), Sd = Ye(String.prototype.indexOf), Fv = Ye(String.prototype.trim), zv = Ye(Number.prototype.toString), Kv = Ye(Boolean.prototype.toString), Md = typeof BigInt > "u" ? null : Ye(BigInt.prototype.toString), Ed = typeof Symbol > "u" ? null : Ye(Symbol.prototype.toString), Ze = Ye(Object.prototype.hasOwnProperty), Ri = Ye(Object.prototype.toString), Qe = Ye(RegExp.prototype.test), hn = jv(TypeError); +function Ye(e) { return function(t) { t instanceof RegExp && (t.lastIndex = 0); for (var r = arguments.length, n = new Array(r > 1 ? r - 1 : 0), i = 1; i < r; i++) n[i - 1] = arguments[i]; - return Ha(e, t, n); + return Za(e, t, n); }; } -function ES(e) { +function jv(e) { return function() { for (var t = arguments.length, r = new Array(t), n = 0; n < t; n++) r[n] = arguments[n]; - return Ga(e, r); + return ec(e, r); }; } -function fe(e, t) { - let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : $i; - if (hd && hd(e, null), !zr(t)) +function pe(e, t) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : Ui; + if (_d && _d(e, null), !Br(t)) return e; let n = t.length; for (; n--; ) { let i = t[n]; if (typeof i == "string") { const s = r(i); - s !== i && (kS(t) || (t[n] = s), i = s); + s !== i && ($v(t) || (t[n] = s), i = s); } e[i] = !0; } return e; } -function AS(e) { +function Bv(e) { for (let t = 0; t < e.length; t++) - Xe(e, t) || (e[t] = null); + Ze(e, t) || (e[t] = null); return e; } -function st(e) { - const t = Hn(null); - for (const n of Qh(e)) { - var r = yS(n, 2); +function ct(e) { + const t = Yn(null); + for (const n of dg(e)) { + var r = qv(n, 2); const i = r[0], s = r[1]; - Xe(e, i) && (zr(s) ? t[i] = AS(s) : s && typeof s == "object" && s.constructor === Object ? t[i] = st(s) : t[i] = s); + Ze(e, i) && (Br(s) ? t[i] = Bv(s) : s && typeof s == "object" && s.constructor === Object ? t[i] = ct(s) : t[i] = s); } return t; } -function PS(e) { +function Vv(e) { switch (typeof e) { case "string": return e; case "number": - return vS(e); + return zv(e); case "boolean": - return MS(e); + return Kv(e); case "bigint": - return bd ? bd(e) : "0"; + return Md ? Md(e) : "0"; case "symbol": - return kd ? kd(e) : "Symbol()"; + return Ed ? Ed(e) : "Symbol()"; case "undefined": - return Ni(e); + return Ri(e); case "function": case "object": { if (e === null) - return Ni(e); + return Ri(e); const t = e, r = jt(t, "toString"); if (typeof r == "function") { const n = r(t); - return typeof n == "string" ? n : Ni(n); + return typeof n == "string" ? n : Ri(n); } - return Ni(e); + return Ri(e); } default: - return Ni(e); + return Ri(e); } } function jt(e, t) { for (; e !== null; ) { - const n = xS(e, t); + const n = Iv(e, t); if (n) { if (n.get) - return Je(n.get); + return Ye(n.get); if (typeof n.value == "function") - return Je(n.value); + return Ye(n.value); } - e = TS(e); + e = Lv(e); } function r() { return null; } return r; } -function NS(e) { +function Wv(e) { try { - return Ye(e, ""), !0; + return Qe(e, ""), !0; } catch { return !1; } } -const Td = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), da = et(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), fa = et(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), OS = et(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), pa = et(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), wS = et(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), xd = et(["#text"]), _d = et(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "command", "commandfor", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns"]), ha = et(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dominant-baseline", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-orientation", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), Cd = et(["accent", "accentunder", "align", "bevelled", "close", "columnalign", "columnlines", "columnspacing", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lquote", "lspace", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), vs = et(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), qS = it(/{{[\w\W]*|^[\w\W]*}}/g), RS = it(/<%[\w\W]*|^[\w\W]*%>/g), $S = it(/\${[\w\W]*/g), IS = it(/^data-[\-\w.\u00B7-\uFFFF]+$/), LS = it(/^aria-[\-\w]+$/), Sd = it( +const Ad = it(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), ma = it(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), ya = it(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), Hv = it(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), ba = it(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), Gv = it(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), Pd = it(["#text"]), Nd = it(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "command", "commandfor", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns"]), ka = it(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dominant-baseline", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-orientation", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), Od = it(["accent", "accentunder", "align", "bevelled", "close", "columnalign", "columnlines", "columnspacing", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lquote", "lspace", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), Ns = it(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), Jv = at(/{{[\w\W]*|^[\w\W]*}}/g), Yv = at(/<%[\w\W]*|^[\w\W]*%>/g), Xv = at(/\${[\w\W]*/g), Qv = at(/^data-[\-\w.\u00B7-\uFFFF]+$/), Zv = at(/^aria-[\-\w]+$/), wd = at( /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape -), DS = it(/^(?:\w+script|data):/i), US = it( +), eS = at(/^(?:\w+script|data):/i), tS = at( /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex -), FS = it(/^html$/i), zS = it(/^[a-z][.\w]*(-[.\w]+)+$/i), vd = it(/<[/\w!]/g), Md = it(/<[/\w]/g), KS = it(/<\/no(script|embed|frames)/i), jS = it(/\/>/i), Mt = { +), rS = at(/^html$/i), nS = at(/^[a-z][.\w]*(-[.\w]+)+$/i), qd = at(/<[/\w!]/g), Rd = at(/<[/\w]/g), iS = at(/<\/no(script|embed|frames)/i), sS = at(/\/>/i), Et = { element: 1, attribute: 2, text: 3, @@ -19113,9 +19117,9 @@ const Td = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a documentFragment: 11, notation: 12 // Deprecated -}, BS = function() { +}, oS = function() { return typeof window > "u" ? null : window; -}, VS = function(t, r) { +}, aS = function(t, r) { if (typeof t != "object" || typeof t.createPolicy != "function") return null; let n = null; @@ -19134,7 +19138,7 @@ const Td = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a } catch { return console.warn("TrustedTypes policy " + s + " could not be created."), null; } -}, Ed = function() { +}, $d = function() { return { afterSanitizeAttributes: [], afterSanitizeElements: [], @@ -19146,53 +19150,53 @@ const Td = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a uponSanitizeElement: [], uponSanitizeShadowNode: [] }; -}, Dr = function(t, r, n, i) { - return Xe(t, r) && zr(t[r]) ? fe(i.base ? st(i.base) : {}, t[r], i.transform) : n; +}, zr = function(t, r, n, i) { + return Ze(t, r) && Br(t[r]) ? pe(i.base ? ct(i.base) : {}, t[r], i.transform) : n; }; -function eg() { - let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : BS(); - const t = (U) => eg(U); - if (t.version = "3.4.13", t.removed = [], !e || !e.document || e.document.nodeType !== Mt.document || !e.Element) +function pg() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : oS(); + const t = (D) => pg(D); + if (t.version = "3.4.13", t.removed = [], !e || !e.document || e.document.nodeType !== Et.document || !e.Element) return t.isSupported = !1, t; let r = e.document; const n = r, i = n.currentScript; e.DocumentFragment; const s = e.HTMLTemplateElement, o = e.Node, a = e.Element, c = e.NodeFilter, l = e.NamedNodeMap; l === void 0 && (e.NamedNodeMap || e.MozNamedAttrMap), e.HTMLFormElement; - const u = e.DOMParser, d = e.trustedTypes, f = a.prototype, p = jt(f, "cloneNode"), m = jt(f, "remove"), g = jt(f, "nextSibling"), y = jt(f, "childNodes"), k = jt(f, "parentNode"), _ = jt(f, "shadowRoot"), S = jt(f, "attributes"), P = o && o.prototype ? jt(o.prototype, "nodeType") : null, A = o && o.prototype ? jt(o.prototype, "nodeName") : null, B = o && o.prototype ? jt(o.prototype, "ownerDocument") : null; + const d = e.DOMParser, u = e.trustedTypes, p = a.prototype, f = jt(p, "cloneNode"), g = jt(p, "remove"), h = jt(p, "nextSibling"), y = jt(p, "childNodes"), k = jt(p, "parentNode"), _ = jt(p, "shadowRoot"), C = jt(p, "attributes"), M = o && o.prototype ? jt(o.prototype, "nodeType") : null, E = o && o.prototype ? jt(o.prototype, "nodeName") : null, z = o && o.prototype ? jt(o.prototype, "ownerDocument") : null; if (typeof s == "function") { - const U = r.createElement("template"); - U.content && U.content.ownerDocument && (r = U.content.ownerDocument); - } - let M, q = "", $, J = !1, Q = 0; - const Me = function() { - if (Q > 0) - throw dn('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.'); - }, re = function(h) { - Me(), Q++; + const D = r.createElement("template"); + D.content && D.content.ownerDocument && (r = D.content.ownerDocument); + } + let W, j = "", U, N = !1, B = 0; + const re = function() { + if (B > 0) + throw hn('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.'); + }, X = function(m) { + re(), B++; try { - return M.createHTML(h); + return W.createHTML(m); } finally { - Q--; + B--; } - }, qe = function(h) { - Me(), Q++; + }, ye = function(m) { + re(), B++; try { - return M.createScriptURL(h); + return W.createScriptURL(m); } finally { - Q--; - } - }, be = function() { - return J || ($ = VS(d, i), J = !0), $; - }, tr = r, Re = tr.implementation, en = tr.createNodeIterator, gr = tr.createDocumentFragment, vt = tr.getElementsByTagName, te = n.importNode; - let E = Ed(); - t.isSupported = typeof Qh == "function" && typeof k == "function" && Re && Re.createHTMLDocument !== void 0; - const Y = qS, le = RS, W = $S, xe = IS, ht = LS, zt = DS, gt = US, We = zS; - let ct = Sd, ue = null; - const Rn = fe({}, [...Td, ...da, ...fa, ...pa, ...xd]); - let ye = null; - const Ti = fe({}, [..._d, ...ha, ...Cd, ...vs]); - let ve = Object.seal(Hn(null, { + B--; + } + }, ke = function() { + return N || (U = aS(u, i), N = !0), U; + }, tr = r, $e = tr.implementation, sn = tr.createNodeIterator, mr = tr.createDocumentFragment, Mt = tr.getElementsByTagName, ne = n.importNode; + let A = $d(); + t.isSupported = typeof dg == "function" && typeof k == "function" && $e && $e.createHTMLDocument !== void 0; + const G = Jv, de = Yv, Ee = Xv, ee = Qv, Se = Zv, yr = eS, wt = tS, on = nS; + let We = wd, ue = null; + const gt = pe({}, [...Ad, ...ma, ...ya, ...ba, ...Pd]); + let _e = null; + const br = pe({}, [...Nd, ...ka, ...Od, ...Ns]); + let Oe = Object.seal(Yn(null, { tagNameCheck: { writable: !0, configurable: !1, @@ -19211,8 +19215,8 @@ function eg() { enumerable: !0, value: !1 } - })), wr = null, qr = null; - const rr = Object.seal(Hn(null, { + })), kr = null, Ci = null; + const mt = Object.seal(Yn(null, { tagCheck: { writable: !0, configurable: !1, @@ -19226,10 +19230,10 @@ function eg() { value: null } })); - let Rr = !0, tn = !0, xi = !1, ms = !0, Kt = !1, R = !0, K = !1, G = !1, X = null, _e = null, lt = !1, Ot = !1, rn = !1, nn = !1, zl = !0, Kl = !1; - const jl = "user-content-"; - let Io = !0, ys = !1, $n = {}, nr = null; - const Lo = fe({}, [ + let xs = !0, Tr = !0, In = !1, vi = !0, rr = !1, Kt = !0, q = !1, V = !1, J = null, Z = null, Me = !1, Xe = !1, nr = !1, an = !1, Si = !0, Yl = !1; + const Xl = "user-content-"; + let zo = !0, _s = !1, Dn = {}, ir = null; + const Ko = pe({}, [ "annotation-xml", "audio", "colgroup", @@ -19266,244 +19270,244 @@ function eg() { "video", "xmp" ]); - let Bl = null; - const Vl = fe({}, ["audio", "video", "img", "source", "image", "track"]); - let Do = null; - const Wl = fe({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), bs = "http://www.w3.org/1998/Math/MathML", ks = "http://www.w3.org/2000/svg", ir = "http://www.w3.org/1999/xhtml"; - let In = ir, Uo = !1, Fo = null; - const Dm = fe({}, [bs, ks, ir], ua), Hl = et(["mi", "mo", "mn", "ms", "mtext"]); - let zo = fe({}, Hl); - const Gl = et(["annotation-xml"]); - let Ko = fe({}, Gl); - const Um = fe({}, ["title", "style", "font", "a", "script"]); - let _i = null; - const Fm = ["application/xhtml+xml", "text/html"], zm = "text/html"; - let $e = null, Ln = null; - const Km = r.createElement("form"), Jl = function(h) { - return h instanceof RegExp || h instanceof Function; - }, jo = function() { - let h = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; - if (Ln && Ln === h) + let Ql = null; + const Zl = pe({}, ["audio", "video", "img", "source", "image", "track"]); + let jo = null; + const eu = pe({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), Cs = "http://www.w3.org/1998/Math/MathML", vs = "http://www.w3.org/2000/svg", sr = "http://www.w3.org/1999/xhtml"; + let Un = sr, Bo = !1, Vo = null; + const Xm = pe({}, [Cs, vs, sr], ga), tu = it(["mi", "mo", "mn", "ms", "mtext"]); + let Wo = pe({}, tu); + const ru = it(["annotation-xml"]); + let Ho = pe({}, ru); + const Qm = pe({}, ["title", "style", "font", "a", "script"]); + let Mi = null; + const Zm = ["application/xhtml+xml", "text/html"], ey = "text/html"; + let Le = null, Fn = null; + const ty = r.createElement("form"), nu = function(m) { + return m instanceof RegExp || m instanceof Function; + }, Go = function() { + let m = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; + if (Fn && Fn === m) return; - (!h || typeof h != "object") && (h = {}), h = st(h), _i = // eslint-disable-next-line unicorn/prefer-includes - Fm.indexOf(h.PARSER_MEDIA_TYPE) === -1 ? zm : h.PARSER_MEDIA_TYPE, $e = _i === "application/xhtml+xml" ? ua : $i, ue = Dr(h, "ALLOWED_TAGS", Rn, { - transform: $e - }), ye = Dr(h, "ALLOWED_ATTR", Ti, { - transform: $e - }), Fo = Dr(h, "ALLOWED_NAMESPACES", Dm, { - transform: ua - }), Do = Dr(h, "ADD_URI_SAFE_ATTR", Wl, { - transform: $e, - base: Wl - }), Bl = Dr(h, "ADD_DATA_URI_TAGS", Vl, { - transform: $e, - base: Vl - }), nr = Dr(h, "FORBID_CONTENTS", Lo, { - transform: $e - }), wr = Dr(h, "FORBID_TAGS", st({}), { - transform: $e - }), qr = Dr(h, "FORBID_ATTR", st({}), { - transform: $e - }), $n = Xe(h, "USE_PROFILES") ? h.USE_PROFILES && typeof h.USE_PROFILES == "object" ? st(h.USE_PROFILES) : h.USE_PROFILES : !1, Rr = h.ALLOW_ARIA_ATTR !== !1, tn = h.ALLOW_DATA_ATTR !== !1, xi = h.ALLOW_UNKNOWN_PROTOCOLS || !1, ms = h.ALLOW_SELF_CLOSE_IN_ATTR !== !1, Kt = h.SAFE_FOR_TEMPLATES || !1, R = h.SAFE_FOR_XML !== !1, K = h.WHOLE_DOCUMENT || !1, Ot = h.RETURN_DOM || !1, rn = h.RETURN_DOM_FRAGMENT || !1, nn = h.RETURN_TRUSTED_TYPE || !1, lt = h.FORCE_BODY || !1, zl = h.SANITIZE_DOM !== !1, Kl = h.SANITIZE_NAMED_PROPS || !1, Io = h.KEEP_CONTENT !== !1, ys = h.IN_PLACE || !1, ct = NS(h.ALLOWED_URI_REGEXP) ? h.ALLOWED_URI_REGEXP : Sd, In = typeof h.NAMESPACE == "string" ? h.NAMESPACE : ir, zo = Xe(h, "MATHML_TEXT_INTEGRATION_POINTS") && h.MATHML_TEXT_INTEGRATION_POINTS && typeof h.MATHML_TEXT_INTEGRATION_POINTS == "object" ? st(h.MATHML_TEXT_INTEGRATION_POINTS) : fe({}, Hl), Ko = Xe(h, "HTML_INTEGRATION_POINTS") && h.HTML_INTEGRATION_POINTS && typeof h.HTML_INTEGRATION_POINTS == "object" ? st(h.HTML_INTEGRATION_POINTS) : fe({}, Gl); - const x = Xe(h, "CUSTOM_ELEMENT_HANDLING") && h.CUSTOM_ELEMENT_HANDLING && typeof h.CUSTOM_ELEMENT_HANDLING == "object" ? st(h.CUSTOM_ELEMENT_HANDLING) : Hn(null); - if (ve = Hn(null), Xe(x, "tagNameCheck") && Jl(x.tagNameCheck) && (ve.tagNameCheck = x.tagNameCheck), Xe(x, "attributeNameCheck") && Jl(x.attributeNameCheck) && (ve.attributeNameCheck = x.attributeNameCheck), Xe(x, "allowCustomizedBuiltInElements") && typeof x.allowCustomizedBuiltInElements == "boolean" && (ve.allowCustomizedBuiltInElements = x.allowCustomizedBuiltInElements), it(ve), Kt && (tn = !1), rn && (Ot = !0), $n && (ue = fe({}, xd), ye = Hn(null), $n.html === !0 && (fe(ue, Td), fe(ye, _d)), $n.svg === !0 && (fe(ue, da), fe(ye, ha), fe(ye, vs)), $n.svgFilters === !0 && (fe(ue, fa), fe(ye, ha), fe(ye, vs)), $n.mathMl === !0 && (fe(ue, pa), fe(ye, Cd), fe(ye, vs))), rr.tagCheck = null, rr.attributeCheck = null, Xe(h, "ADD_TAGS") && (typeof h.ADD_TAGS == "function" ? rr.tagCheck = h.ADD_TAGS : zr(h.ADD_TAGS) && (ue === Rn && (ue = st(ue)), fe(ue, h.ADD_TAGS, $e))), Xe(h, "ADD_ATTR") && (typeof h.ADD_ATTR == "function" ? rr.attributeCheck = h.ADD_ATTR : zr(h.ADD_ATTR) && (ye === Ti && (ye = st(ye)), fe(ye, h.ADD_ATTR, $e))), Xe(h, "ADD_URI_SAFE_ATTR") && zr(h.ADD_URI_SAFE_ATTR) && fe(Do, h.ADD_URI_SAFE_ATTR, $e), Xe(h, "FORBID_CONTENTS") && zr(h.FORBID_CONTENTS) && (nr === Lo && (nr = st(nr)), fe(nr, h.FORBID_CONTENTS, $e)), Xe(h, "ADD_FORBID_CONTENTS") && zr(h.ADD_FORBID_CONTENTS) && (nr === Lo && (nr = st(nr)), fe(nr, h.ADD_FORBID_CONTENTS, $e)), Io && (ue["#text"] = !0), K && fe(ue, ["html", "head", "body"]), ue.table && (fe(ue, ["tbody"]), delete wr.tbody), h.TRUSTED_TYPES_POLICY) { - if (typeof h.TRUSTED_TYPES_POLICY.createHTML != "function") - throw dn('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); - if (typeof h.TRUSTED_TYPES_POLICY.createScriptURL != "function") - throw dn('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); - const L = M; - M = h.TRUSTED_TYPES_POLICY; + (!m || typeof m != "object") && (m = {}), m = ct(m), Mi = // eslint-disable-next-line unicorn/prefer-includes + Zm.indexOf(m.PARSER_MEDIA_TYPE) === -1 ? ey : m.PARSER_MEDIA_TYPE, Le = Mi === "application/xhtml+xml" ? ga : Ui, ue = zr(m, "ALLOWED_TAGS", gt, { + transform: Le + }), _e = zr(m, "ALLOWED_ATTR", br, { + transform: Le + }), Vo = zr(m, "ALLOWED_NAMESPACES", Xm, { + transform: ga + }), jo = zr(m, "ADD_URI_SAFE_ATTR", eu, { + transform: Le, + base: eu + }), Ql = zr(m, "ADD_DATA_URI_TAGS", Zl, { + transform: Le, + base: Zl + }), ir = zr(m, "FORBID_CONTENTS", Ko, { + transform: Le + }), kr = zr(m, "FORBID_TAGS", ct({}), { + transform: Le + }), Ci = zr(m, "FORBID_ATTR", ct({}), { + transform: Le + }), Dn = Ze(m, "USE_PROFILES") ? m.USE_PROFILES && typeof m.USE_PROFILES == "object" ? ct(m.USE_PROFILES) : m.USE_PROFILES : !1, xs = m.ALLOW_ARIA_ATTR !== !1, Tr = m.ALLOW_DATA_ATTR !== !1, In = m.ALLOW_UNKNOWN_PROTOCOLS || !1, vi = m.ALLOW_SELF_CLOSE_IN_ATTR !== !1, rr = m.SAFE_FOR_TEMPLATES || !1, Kt = m.SAFE_FOR_XML !== !1, q = m.WHOLE_DOCUMENT || !1, Xe = m.RETURN_DOM || !1, nr = m.RETURN_DOM_FRAGMENT || !1, an = m.RETURN_TRUSTED_TYPE || !1, Me = m.FORCE_BODY || !1, Si = m.SANITIZE_DOM !== !1, Yl = m.SANITIZE_NAMED_PROPS || !1, zo = m.KEEP_CONTENT !== !1, _s = m.IN_PLACE || !1, We = Wv(m.ALLOWED_URI_REGEXP) ? m.ALLOWED_URI_REGEXP : wd, Un = typeof m.NAMESPACE == "string" ? m.NAMESPACE : sr, Wo = Ze(m, "MATHML_TEXT_INTEGRATION_POINTS") && m.MATHML_TEXT_INTEGRATION_POINTS && typeof m.MATHML_TEXT_INTEGRATION_POINTS == "object" ? ct(m.MATHML_TEXT_INTEGRATION_POINTS) : pe({}, tu), Ho = Ze(m, "HTML_INTEGRATION_POINTS") && m.HTML_INTEGRATION_POINTS && typeof m.HTML_INTEGRATION_POINTS == "object" ? ct(m.HTML_INTEGRATION_POINTS) : pe({}, ru); + const x = Ze(m, "CUSTOM_ELEMENT_HANDLING") && m.CUSTOM_ELEMENT_HANDLING && typeof m.CUSTOM_ELEMENT_HANDLING == "object" ? ct(m.CUSTOM_ELEMENT_HANDLING) : Yn(null); + if (Oe = Yn(null), Ze(x, "tagNameCheck") && nu(x.tagNameCheck) && (Oe.tagNameCheck = x.tagNameCheck), Ze(x, "attributeNameCheck") && nu(x.attributeNameCheck) && (Oe.attributeNameCheck = x.attributeNameCheck), Ze(x, "allowCustomizedBuiltInElements") && typeof x.allowCustomizedBuiltInElements == "boolean" && (Oe.allowCustomizedBuiltInElements = x.allowCustomizedBuiltInElements), at(Oe), rr && (Tr = !1), nr && (Xe = !0), Dn && (ue = pe({}, Pd), _e = Yn(null), Dn.html === !0 && (pe(ue, Ad), pe(_e, Nd)), Dn.svg === !0 && (pe(ue, ma), pe(_e, ka), pe(_e, Ns)), Dn.svgFilters === !0 && (pe(ue, ya), pe(_e, ka), pe(_e, Ns)), Dn.mathMl === !0 && (pe(ue, ba), pe(_e, Od), pe(_e, Ns))), mt.tagCheck = null, mt.attributeCheck = null, Ze(m, "ADD_TAGS") && (typeof m.ADD_TAGS == "function" ? mt.tagCheck = m.ADD_TAGS : Br(m.ADD_TAGS) && (ue === gt && (ue = ct(ue)), pe(ue, m.ADD_TAGS, Le))), Ze(m, "ADD_ATTR") && (typeof m.ADD_ATTR == "function" ? mt.attributeCheck = m.ADD_ATTR : Br(m.ADD_ATTR) && (_e === br && (_e = ct(_e)), pe(_e, m.ADD_ATTR, Le))), Ze(m, "ADD_URI_SAFE_ATTR") && Br(m.ADD_URI_SAFE_ATTR) && pe(jo, m.ADD_URI_SAFE_ATTR, Le), Ze(m, "FORBID_CONTENTS") && Br(m.FORBID_CONTENTS) && (ir === Ko && (ir = ct(ir)), pe(ir, m.FORBID_CONTENTS, Le)), Ze(m, "ADD_FORBID_CONTENTS") && Br(m.ADD_FORBID_CONTENTS) && (ir === Ko && (ir = ct(ir)), pe(ir, m.ADD_FORBID_CONTENTS, Le)), zo && (ue["#text"] = !0), q && pe(ue, ["html", "head", "body"]), ue.table && (pe(ue, ["tbody"]), delete kr.tbody), m.TRUSTED_TYPES_POLICY) { + if (typeof m.TRUSTED_TYPES_POLICY.createHTML != "function") + throw hn('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); + if (typeof m.TRUSTED_TYPES_POLICY.createScriptURL != "function") + throw hn('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); + const $ = W; + W = m.TRUSTED_TYPES_POLICY; try { - q = re(""); - } catch (V) { - throw M = L, V; + j = X(""); + } catch (H) { + throw W = $, H; } - } else h.TRUSTED_TYPES_POLICY === null ? (M = void 0, q = "") : (M === void 0 && (M = be()), M && typeof q == "string" && (q = re(""))); - et && et(h), Ln = h; - }, Yl = fe({}, [...da, ...fa, ...OS]), Xl = fe({}, [...pa, ...wS]), jm = function(h, x, L) { - return x.namespaceURI === ir ? h === "svg" : x.namespaceURI === bs ? h === "svg" && (L === "annotation-xml" || zo[L]) : !!Yl[h]; - }, Bm = function(h, x, L) { - return x.namespaceURI === ir ? h === "math" : x.namespaceURI === ks ? h === "math" && Ko[L] : !!Xl[h]; - }, Vm = function(h, x, L) { - return x.namespaceURI === ks && !Ko[L] || x.namespaceURI === bs && !zo[L] ? !1 : !Xl[h] && (Um[h] || !Yl[h]); - }, Wm = function(h) { - let x = k(h); + } else m.TRUSTED_TYPES_POLICY === null ? (W = void 0, j = "") : (W === void 0 && (W = ke()), W && typeof j == "string" && (j = X(""))); + it && it(m), Fn = m; + }, iu = pe({}, [...ma, ...ya, ...Hv]), su = pe({}, [...ba, ...Gv]), ry = function(m, x, $) { + return x.namespaceURI === sr ? m === "svg" : x.namespaceURI === Cs ? m === "svg" && ($ === "annotation-xml" || Wo[$]) : !!iu[m]; + }, ny = function(m, x, $) { + return x.namespaceURI === sr ? m === "math" : x.namespaceURI === vs ? m === "math" && Ho[$] : !!su[m]; + }, iy = function(m, x, $) { + return x.namespaceURI === vs && !Ho[$] || x.namespaceURI === Cs && !Wo[$] ? !1 : !su[m] && (Qm[m] || !iu[m]); + }, sy = function(m) { + let x = k(m); (!x || !x.tagName) && (x = { - namespaceURI: In, + namespaceURI: Un, tagName: "template" }); - const L = $i(h.tagName), V = $i(x.tagName); - return Fo[h.namespaceURI] ? h.namespaceURI === ks ? jm(L, x, V) : h.namespaceURI === bs ? Bm(L, x, V) : h.namespaceURI === ir ? Vm(L, x, V) : !!(_i === "application/xhtml+xml" && Fo[h.namespaceURI]) : !1; - }, $r = function(h) { - Vn(t.removed, { - element: h + const $ = Ui(m.tagName), H = Ui(x.tagName); + return Vo[m.namespaceURI] ? m.namespaceURI === vs ? ry($, x, H) : m.namespaceURI === Cs ? ny($, x, H) : m.namespaceURI === sr ? iy($, x, H) : !!(Mi === "application/xhtml+xml" && Vo[m.namespaceURI]) : !1; + }, Dr = function(m) { + Gn(t.removed, { + element: m }); try { - k(h).removeChild(h); + k(m).removeChild(m); } catch { - if (m(h), !k(h)) - throw dn("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place"); + if (g(m), !k(m)) + throw hn("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place"); } - }, Ts = function(h) { - Ci(h); - const x = y(h); + }, Ss = function(m) { + Ei(m); + const x = y(m); if (x) { - const V = []; - Bn(x, (H) => { - Vn(V, H); - }), Bn(V, (H) => { + const H = []; + Hn(x, (Y) => { + Gn(H, Y); + }), Hn(H, (Y) => { try { - m(H); + g(Y); } catch { } }); } - const L = S(h); - if (L) - for (let V = L.length - 1; V >= 0; --V) { - const H = L[V], ie = H && H.name; - if (typeof ie == "string") + const $ = C(m); + if ($) + for (let H = $.length - 1; H >= 0; --H) { + const Y = $[H], se = Y && Y.name; + if (typeof se == "string") try { - h.removeAttribute(ie); + m.removeAttribute(se); } catch { } } - }, sn = function(h, x) { + }, cn = function(m, x) { try { - Vn(t.removed, { - attribute: x.getAttributeNode(h), + Gn(t.removed, { + attribute: x.getAttributeNode(m), from: x }); } catch { - Vn(t.removed, { + Gn(t.removed, { attribute: null, from: x }); } - if (x.removeAttribute(h), h === "is") - if (Ot || rn) + if (x.removeAttribute(m), m === "is") + if (Xe || nr) try { - $r(x); + Dr(x); } catch { } else try { - x.setAttribute(h, ""); + x.setAttribute(m, ""); } catch { } - }, Hm = function(h) { - const x = S(h); + }, oy = function(m) { + const x = C(m); if (x) - for (let L = x.length - 1; L >= 0; --L) { - const V = x[L], H = V && V.name; - if (!(typeof H != "string" || ye[$e(H)])) + for (let $ = x.length - 1; $ >= 0; --$) { + const H = x[$], Y = H && H.name; + if (!(typeof Y != "string" || _e[Le(Y)])) try { - h.removeAttribute(H); + m.removeAttribute(Y); } catch { } } - }, Ci = function(h) { - const x = [h]; + }, Ei = function(m) { + const x = [m]; for (; x.length > 0; ) { - const L = x.pop(); - (P ? P(L) : L.nodeType) === Mt.element && Hm(L); - const H = y(L); - if (H) - for (let ie = H.length - 1; ie >= 0; --ie) - x.push(H[ie]); - } - }, Gm = function(h) { - if (!R) + const $ = x.pop(); + (M ? M($) : $.nodeType) === Et.element && oy($); + const Y = y($); + if (Y) + for (let se = Y.length - 1; se >= 0; --se) + x.push(Y[se]); + } + }, ay = function(m) { + if (!Kt) return; - const x = [h]; + const x = [m]; for (; x.length > 0; ) { - const L = x.pop(), V = P ? P(L) : L.nodeType; - if (V === Mt.processingInstruction || V === Mt.comment && Ye(Md, L.data)) { + const $ = x.pop(), H = M ? M($) : $.nodeType; + if (H === Et.processingInstruction || H === Et.comment && Qe(Rd, $.data)) { try { - m(L); + g($); } catch { } continue; } - if (V === Mt.element) { - const ie = L, ke = $e(A ? A(L) : L.nodeName); + if (H === Et.element) { + const se = $, Te = Le(E ? E($) : $.nodeName); try { - ie.hasAttribute && ie.hasAttribute("patchsrc") && ie.removeAttribute("patchsrc"), ie.hasAttribute && ie.hasAttribute("for") && ke !== "label" && ke !== "output" && ie.removeAttribute("for"); + se.hasAttribute && se.hasAttribute("patchsrc") && se.removeAttribute("patchsrc"), se.hasAttribute && se.hasAttribute("for") && Te !== "label" && Te !== "output" && se.removeAttribute("for"); } catch { } } - const H = y(L); - if (H) - for (let ie = H.length - 1; ie >= 0; --ie) - x.push(H[ie]); - } - }, Ql = function(h) { - let x = null, L = null; - if (lt) - h = "" + h; + const Y = y($); + if (Y) + for (let se = Y.length - 1; se >= 0; --se) + x.push(Y[se]); + } + }, ou = function(m) { + let x = null, $ = null; + if (Me) + m = "" + m; else { - const ie = md(h, /^[\r\n\t ]+/); - L = ie && ie[0]; + const se = vd(m, /^[\r\n\t ]+/); + $ = se && se[0]; } - _i === "application/xhtml+xml" && In === ir && (h = '' + h + ""); - const V = M ? re(h) : h; - if (In === ir) + Mi === "application/xhtml+xml" && Un === sr && (m = '' + m + ""); + const H = W ? X(m) : m; + if (Un === sr) try { - x = new u().parseFromString(V, _i); + x = new d().parseFromString(H, Mi); } catch { } if (!x || !x.documentElement) { - x = Re.createDocument(In, "template", null); + x = $e.createDocument(Un, "template", null); try { - x.documentElement.innerHTML = Uo ? q : V; + x.documentElement.innerHTML = Bo ? j : H; } catch { } } - const H = x.body || x.documentElement; - return h && L && H.insertBefore(r.createTextNode(L), H.childNodes[0] || null), In === ir ? vt.call(x, K ? "html" : "body")[0] : K ? x.documentElement : H; - }, Zl = function(h) { - const x = B ? B(h) : h.ownerDocument; - return en.call( - x || h, - h, + const Y = x.body || x.documentElement; + return m && $ && Y.insertBefore(r.createTextNode($), Y.childNodes[0] || null), Un === sr ? Mt.call(x, q ? "html" : "body")[0] : q ? x.documentElement : Y; + }, au = function(m) { + const x = z ? z(m) : m.ownerDocument; + return sn.call( + x || m, + m, // eslint-disable-next-line no-bitwise c.SHOW_ELEMENT | c.SHOW_COMMENT | c.SHOW_TEXT | c.SHOW_PROCESSING_INSTRUCTION | c.SHOW_CDATA_SECTION, null ); - }, xs = function(h) { - return h = Pi(h, Y, " "), h = Pi(h, le, " "), h = Pi(h, W, " "), h; - }, Bo = function(h) { + }, Ms = function(m) { + return m = qi(m, G, " "), m = qi(m, de, " "), m = qi(m, Ee, " "), m; + }, Jo = function(m) { var x; - h.normalize(); - const L = B ? B(h) : h.ownerDocument, V = en.call( - L || h, - h, + m.normalize(); + const $ = z ? z(m) : m.ownerDocument, H = sn.call( + $ || m, + m, // eslint-disable-next-line no-bitwise c.SHOW_TEXT | c.SHOW_COMMENT | c.SHOW_CDATA_SECTION | c.SHOW_PROCESSING_INSTRUCTION, null ); - let H = V.nextNode(); - for (; H; ) - H.data = xs(H.data), H = V.nextNode(); - const ie = (x = h.querySelectorAll) === null || x === void 0 ? void 0 : x.call(h, "template"); - ie && Bn(ie, (ke) => { - Dn(ke.content) && Bo(ke.content); + let Y = H.nextNode(); + for (; Y; ) + Y.data = Ms(Y.data), Y = H.nextNode(); + const se = (x = m.querySelectorAll) === null || x === void 0 ? void 0 : x.call(m, "template"); + se && Hn(se, (Te) => { + zn(Te.content) && Jo(Te.content); }); - }, _s = function(h) { - const x = A ? A(h) : null; - return typeof x != "string" || $e(x) !== "form" ? !1 : typeof h.nodeName != "string" || typeof h.textContent != "string" || typeof h.removeChild != "function" || // Realm-safe NamedNodeMap detection: equality against the cached + }, Es = function(m) { + const x = E ? E(m) : null; + return typeof x != "string" || Le(x) !== "form" ? !1 : typeof m.nodeName != "string" || typeof m.textContent != "string" || typeof m.removeChild != "function" || // Realm-safe NamedNodeMap detection: equality against the cached // prototype getter. Clobbered .attributes (e.g. ) // makes the direct read diverge from the cached read; a clean form // (same-realm OR foreign-realm) has both reads pointing at the same // canonical NamedNodeMap. - h.attributes !== S(h) || typeof h.removeAttribute != "function" || typeof h.setAttribute != "function" || typeof h.namespaceURI != "string" || typeof h.insertBefore != "function" || typeof h.hasChildNodes != "function" || // NodeType clobbering probe. Cached Node.prototype.nodeType getter + m.attributes !== C(m) || typeof m.removeAttribute != "function" || typeof m.setAttribute != "function" || typeof m.namespaceURI != "string" || typeof m.insertBefore != "function" || typeof m.hasChildNodes != "function" || // NodeType clobbering probe. Cached Node.prototype.nodeType getter // returns the integer 1 for any Element regardless of realm; direct // read on a clobbered form (e.g. ) returns // the named child element. Cheap addition — nodeType is read from // an internal slot, no serialization cost — and removes a residual // clobbering surface used by several mXSS / PI / comment branches // in _sanitizeElements that compare currentNode.nodeType directly. - h.nodeType !== P(h) || // HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named + m.nodeType !== M(m) || // HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named // "childNodes" shadows the prototype getter. Direct reads of // form.childNodes from a clobbered form return the named child // instead of the real NodeList, so any walk that reads it directly @@ -19514,85 +19518,85 @@ function eg() { // select, etc.) regardless of whether the named child happens to // carry a numeric .length, which a typeof-based probe would miss // (e.g. HTMLSelectElement.length is a defined unsigned-long). - h.childNodes !== y(h); - }, Dn = function(h) { - if (!P || typeof h != "object" || h === null) + m.childNodes !== y(m); + }, zn = function(m) { + if (!M || typeof m != "object" || m === null) return !1; try { - return P(h) === Mt.documentFragment; + return M(m) === Et.documentFragment; } catch { return !1; } - }, Si = function(h) { - if (!P || typeof h != "object" || h === null) + }, Ai = function(m) { + if (!M || typeof m != "object" || m === null) return !1; try { - return typeof P(h) == "number"; + return typeof M(m) == "number"; } catch { return !1; } }; - function sr(U, h, x) { - U.length !== 0 && Bn(U, (L) => { - L.call(t, h, x, Ln); + function or(D, m, x) { + D.length !== 0 && Hn(D, ($) => { + $.call(t, m, x, Fn); }); } - const Jm = function(h, x) { - return !!(R && h.hasChildNodes() && !Si(h.firstElementChild) && Ye(vd, h.textContent) && Ye(vd, h.innerHTML) || R && h.namespaceURI === ir && x === "style" && Si(h.firstElementChild) || h.nodeType === Mt.processingInstruction || R && h.nodeType === Mt.comment && Ye(Md, h.data)); - }, Ym = function(h, x, L) { - if (!wr[x] && nu(x) && (ve.tagNameCheck instanceof RegExp && Ye(ve.tagNameCheck, x) || ve.tagNameCheck instanceof Function && ve.tagNameCheck(x))) + const cy = function(m, x) { + return !!(Kt && m.hasChildNodes() && !Ai(m.firstElementChild) && Qe(qd, m.textContent) && Qe(qd, m.innerHTML) || Kt && m.namespaceURI === sr && x === "style" && Ai(m.firstElementChild) || m.nodeType === Et.processingInstruction || Kt && m.nodeType === Et.comment && Qe(Rd, m.data)); + }, ly = function(m, x, $) { + if (!kr[x] && du(x) && (Oe.tagNameCheck instanceof RegExp && Qe(Oe.tagNameCheck, x) || Oe.tagNameCheck instanceof Function && Oe.tagNameCheck(x))) return !1; - if (Io && !nr[x]) { - const V = k(h), H = y(h); - if (H && V) { - const ie = H.length; - for (let ke = ie - 1; ke >= 0; --ke) { - const Ie = h === L ? p(H[ke], !0) : H[ke]; - V.insertBefore(Ie, g(h)); + if (zo && !ir[x]) { + const H = k(m), Y = y(m); + if (Y && H) { + const se = Y.length; + for (let Te = se - 1; Te >= 0; --Te) { + const Ie = m === $ ? f(Y[Te], !0) : Y[Te]; + H.insertBefore(Ie, h(m)); } } } - return $r(h), !0; - }, eu = function(h, x, L, V) { - return h.length === 0 ? x : x === L || x === V ? st(x) : x; - }, tu = function(h, x) { - if (sr(E.beforeSanitizeElements, h, null), h !== x && k(h) === null) - return ys && Ci(h), !0; - if (_s(h)) - return $r(h), !0; - const L = $e(A ? A(h) : h.nodeName); - if (ue = eu(E.uponSanitizeElement, ue, Rn, X), sr(E.uponSanitizeElement, h, { - tagName: L, + return Dr(m), !0; + }, cu = function(m, x, $, H) { + return m.length === 0 ? x : x === $ || x === H ? ct(x) : x; + }, lu = function(m, x) { + if (or(A.beforeSanitizeElements, m, null), m !== x && k(m) === null) + return _s && Ei(m), !0; + if (Es(m)) + return Dr(m), !0; + const $ = Le(E ? E(m) : m.nodeName); + if (ue = cu(A.uponSanitizeElement, ue, gt, J), or(A.uponSanitizeElement, m, { + tagName: $, allowedTags: ue - }), h !== x && k(h) === null) - return ys && Ci(h), !0; - if (Jm(h, L)) - return $r(h), !0; - if (wr[L] || !(rr.tagCheck instanceof Function && rr.tagCheck(L)) && !ue[L]) { - const H = Ym(h, L, x); - return H === !1 && sr(E.afterSanitizeElements, h, null), H; - } - if ((P ? P(h) : h.nodeType) === Mt.element && !Wm(h) || (L === "noscript" || L === "noembed" || L === "noframes") && Ye(KS, h.innerHTML)) - return $r(h), !0; - if (Kt && h.nodeType === Mt.text) { - const H = xs(h.textContent); - h.textContent !== H && (Vn(t.removed, { - element: h.cloneNode() - }), h.textContent = H); - } - return sr(E.afterSanitizeElements, h, null), !1; - }, ru = function(h, x, L) { - if (qr[x] || R && x === "patchsrc" || R && x === "for" && h !== "label" && h !== "output" || zl && (x === "id" || x === "name") && (L in r || L in Km)) + }), m !== x && k(m) === null) + return _s && Ei(m), !0; + if (cy(m, $)) + return Dr(m), !0; + if (kr[$] || !(mt.tagCheck instanceof Function && mt.tagCheck($)) && !ue[$]) { + const Y = ly(m, $, x); + return Y === !1 && or(A.afterSanitizeElements, m, null), Y; + } + if ((M ? M(m) : m.nodeType) === Et.element && !sy(m) || ($ === "noscript" || $ === "noembed" || $ === "noframes") && Qe(iS, m.innerHTML)) + return Dr(m), !0; + if (rr && m.nodeType === Et.text) { + const Y = Ms(m.textContent); + m.textContent !== Y && (Gn(t.removed, { + element: m.cloneNode() + }), m.textContent = Y); + } + return or(A.afterSanitizeElements, m, null), !1; + }, uu = function(m, x, $) { + if (Ci[x] || Kt && x === "patchsrc" || Kt && x === "for" && m !== "label" && m !== "output" || Si && (x === "id" || x === "name") && ($ in r || $ in ty)) return !1; - const V = ye[x] || rr.attributeCheck instanceof Function && rr.attributeCheck(x, h); - if (!(tn && Ye(xe, x))) { - if (!(Rr && Ye(ht, x))) { - if (V) { - if (!Do[x]) { - if (!Ye(ct, Pi(L, gt, ""))) { - if (!((x === "src" || x === "xlink:href" || x === "href") && h !== "script" && yd(L, "data:") === 0 && Bl[h])) { - if (!(xi && !Ye(zt, Pi(L, gt, "")))) { - if (L) + const H = _e[x] || mt.attributeCheck instanceof Function && mt.attributeCheck(x, m); + if (!(Tr && Qe(ee, x))) { + if (!(xs && Qe(Se, x))) { + if (H) { + if (!jo[x]) { + if (!Qe(We, qi($, wt, ""))) { + if (!((x === "src" || x === "xlink:href" || x === "href") && m !== "script" && Sd($, "data:") === 0 && Ql[m])) { + if (!(In && !Qe(yr, qi($, wt, "")))) { + if ($) return !1; } } @@ -19602,113 +19606,113 @@ function eg() { // First condition does a very basic check if a) it's basically a valid custom element tagname AND // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck - !(nu(h) && (ve.tagNameCheck instanceof RegExp && Ye(ve.tagNameCheck, h) || ve.tagNameCheck instanceof Function && ve.tagNameCheck(h)) && (ve.attributeNameCheck instanceof RegExp && Ye(ve.attributeNameCheck, x) || ve.attributeNameCheck instanceof Function && ve.attributeNameCheck(x, h)) || // Alternative, second condition checks if it's an `is`-attribute, AND + !(du(m) && (Oe.tagNameCheck instanceof RegExp && Qe(Oe.tagNameCheck, m) || Oe.tagNameCheck instanceof Function && Oe.tagNameCheck(m)) && (Oe.attributeNameCheck instanceof RegExp && Qe(Oe.attributeNameCheck, x) || Oe.attributeNameCheck instanceof Function && Oe.attributeNameCheck(x, m)) || // Alternative, second condition checks if it's an `is`-attribute, AND // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck - x === "is" && ve.allowCustomizedBuiltInElements && (ve.tagNameCheck instanceof RegExp && Ye(ve.tagNameCheck, L) || ve.tagNameCheck instanceof Function && ve.tagNameCheck(L))) + x === "is" && Oe.allowCustomizedBuiltInElements && (Oe.tagNameCheck instanceof RegExp && Qe(Oe.tagNameCheck, $) || Oe.tagNameCheck instanceof Function && Oe.tagNameCheck($))) ) return !1; } } return !0; - }, Xm = fe({}, ["annotation-xml", "color-profile", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "missing-glyph"]), nu = function(h) { - return !Xm[$i(h)] && Ye(We, h); - }, Qm = function(h, x, L, V) { - if (M && typeof d == "object" && typeof d.getAttributeType == "function" && !L) - switch (d.getAttributeType(h, x)) { + }, uy = pe({}, ["annotation-xml", "color-profile", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "missing-glyph"]), du = function(m) { + return !uy[Ui(m)] && Qe(on, m); + }, dy = function(m, x, $, H) { + if (W && typeof u == "object" && typeof u.getAttributeType == "function" && !$) + switch (u.getAttributeType(m, x)) { case "TrustedHTML": - return re(V); + return X(H); case "TrustedScriptURL": - return qe(V); + return ye(H); } - return V; - }, Zm = function(h, x, L, V) { + return H; + }, fy = function(m, x, $, H) { try { - L ? h.setAttributeNS(L, x, V) : h.setAttribute(x, V), _s(h) ? $r(h) : gd(t.removed); + $ ? m.setAttributeNS($, x, H) : m.setAttribute(x, H), Es(m) ? Dr(m) : Cd(t.removed); } catch { - sn(x, h); + cn(x, m); } - }, iu = function(h) { - sr(E.beforeSanitizeAttributes, h, null); - const x = h.attributes; - if (!x || _s(h)) + }, fu = function(m) { + or(A.beforeSanitizeAttributes, m, null); + const x = m.attributes; + if (!x || Es(m)) return; - ye = eu(E.uponSanitizeAttribute, ye, Ti, _e); - const L = { + _e = cu(A.uponSanitizeAttribute, _e, br, Z); + const $ = { attrName: "", attrValue: "", keepAttr: !0, - allowedAttributes: ye, + allowedAttributes: _e, forceKeepAttr: void 0 }; - let V = x.length; - const H = $e(h.nodeName); - for (; V--; ) { - const ie = x[V], ke = ie.name, Ie = ie.namespaceURI, mt = ie.value, yt = $e(ke), Wo = mt; - let ut = ke === "value" ? Wo : SS(Wo); - if (L.attrName = yt, L.attrValue = ut, L.keepAttr = !0, L.forceKeepAttr = void 0, sr(E.uponSanitizeAttribute, h, L), ut = L.attrValue, Kl && (yt === "id" || yt === "name") && yd(ut, jl) !== 0 && (sn(ke, h), ut = jl + ut), R && Ye(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, ut)) { - sn(ke, h); + let H = x.length; + const Y = Le(m.nodeName); + for (; H--; ) { + const se = x[H], Te = se.name, Ie = se.namespaceURI, yt = se.value, bt = Le(Te), Xo = yt; + let dt = Te === "value" ? Xo : Fv(Xo); + if ($.attrName = bt, $.attrValue = dt, $.keepAttr = !0, $.forceKeepAttr = void 0, or(A.uponSanitizeAttribute, m, $), dt = $.attrValue, Yl && (bt === "id" || bt === "name") && Sd(dt, Xl) !== 0 && (cn(Te, m), dt = Xl + dt), Kt && Qe(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, dt)) { + cn(Te, m); continue; } - if (yt === "attributename" && md(ut, "href")) { - sn(ke, h); + if (bt === "attributename" && vd(dt, "href")) { + cn(Te, m); continue; } - if (!L.forceKeepAttr) { - if (!L.keepAttr) { - sn(ke, h); + if (!$.forceKeepAttr) { + if (!$.keepAttr) { + cn(Te, m); continue; } - if (!ms && Ye(jS, ut)) { - sn(ke, h); + if (!vi && Qe(sS, dt)) { + cn(Te, m); continue; } - if (Kt && (ut = xs(ut)), !ru(H, yt, ut)) { - sn(ke, h); + if (rr && (dt = Ms(dt)), !uu(Y, bt, dt)) { + cn(Te, m); continue; } - ut = Qm(H, yt, Ie, ut), ut !== Wo && Zm(h, ke, Ie, ut); + dt = dy(Y, bt, Ie, dt), dt !== Xo && fy(m, Te, Ie, dt); } } - sr(E.afterSanitizeAttributes, h, null); - }, Cs = function(h) { + or(A.afterSanitizeAttributes, m, null); + }, As = function(m) { let x = null; - const L = Zl(h); - for (sr(E.beforeSanitizeShadowDOM, h, null); x = L.nextNode(); ) - if (sr(E.uponSanitizeShadowNode, x, null), tu(x, h), iu(x), Dn(x.content) && Cs(x.content), (P ? P(x) : x.nodeType) === Mt.element) { - const H = _(x); - Dn(H) && (Vo(H), Cs(H)); + const $ = au(m); + for (or(A.beforeSanitizeShadowDOM, m, null); x = $.nextNode(); ) + if (or(A.uponSanitizeShadowNode, x, null), lu(x, m), fu(x), zn(x.content) && As(x.content), (M ? M(x) : x.nodeType) === Et.element) { + const Y = _(x); + zn(Y) && (Yo(Y), As(Y)); } - sr(E.afterSanitizeShadowDOM, h, null); - }, Vo = function(h) { + or(A.afterSanitizeShadowDOM, m, null); + }, Yo = function(m) { const x = [{ - node: h, + node: m, shadow: null }]; for (; x.length > 0; ) { - const L = x.pop(); - if (L.shadow) { - Cs(L.shadow); + const $ = x.pop(); + if ($.shadow) { + As($.shadow); continue; } - const V = L.node, ie = (P ? P(V) : V.nodeType) === Mt.element, ke = y(V); - if (ke) - for (let Ie = ke.length - 1; Ie >= 0; --Ie) + const H = $.node, se = (M ? M(H) : H.nodeType) === Et.element, Te = y(H); + if (Te) + for (let Ie = Te.length - 1; Ie >= 0; --Ie) x.push({ - node: ke[Ie], + node: Te[Ie], shadow: null }); - if (ie) { - const Ie = A ? A(V) : null; - if (typeof Ie == "string" && $e(Ie) === "template") { - const mt = V.content; - Dn(mt) && x.push({ - node: mt, + if (se) { + const Ie = E ? E(H) : null; + if (typeof Ie == "string" && Le(Ie) === "template") { + const yt = H.content; + zn(yt) && x.push({ + node: yt, shadow: null }); } } - if (ie) { - const Ie = _(V); - Dn(Ie) && x.push({ + if (se) { + const Ie = _(H); + zn(Ie) && x.push({ node: null, shadow: Ie }, { @@ -19718,169 +19722,169 @@ function eg() { } } }; - return t.sanitize = function(U) { - let h = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, x = null, L = null, V = null, H = null; - if (Uo = !U, Uo && (U = ""), typeof U != "string" && !Si(U) && (U = PS(U), typeof U != "string")) - throw dn("dirty is not a string, aborting"); + return t.sanitize = function(D) { + let m = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, x = null, $ = null, H = null, Y = null; + if (Bo = !D, Bo && (D = ""), typeof D != "string" && !Ai(D) && (D = Vv(D), typeof D != "string")) + throw hn("dirty is not a string, aborting"); if (!t.isSupported) - return U; - G ? (ue = X, ye = _e) : jo(h), (E.uponSanitizeElement.length > 0 || E.uponSanitizeAttribute.length > 0) && (ue = st(ue)), E.uponSanitizeAttribute.length > 0 && (ye = st(ye)), t.removed = []; - const ie = ys && typeof U != "string" && Si(U); - if (ie) { - Gm(U); - const mt = A ? A(U) : U.nodeName; - if (typeof mt == "string") { - const yt = $e(mt); - if (!ue[yt] || wr[yt]) - throw Ts(U), dn("root node is forbidden and cannot be sanitized in-place"); + return D; + V ? (ue = J, _e = Z) : Go(m), (A.uponSanitizeElement.length > 0 || A.uponSanitizeAttribute.length > 0) && (ue = ct(ue)), A.uponSanitizeAttribute.length > 0 && (_e = ct(_e)), t.removed = []; + const se = _s && typeof D != "string" && Ai(D); + if (se) { + ay(D); + const yt = E ? E(D) : D.nodeName; + if (typeof yt == "string") { + const bt = Le(yt); + if (!ue[bt] || kr[bt]) + throw Ss(D), hn("root node is forbidden and cannot be sanitized in-place"); } - if (_s(U)) - throw Ts(U), dn("root node is clobbered and cannot be sanitized in-place"); + if (Es(D)) + throw Ss(D), hn("root node is clobbered and cannot be sanitized in-place"); try { - Vo(U); - } catch (yt) { - throw Ts(U), yt; + Yo(D); + } catch (bt) { + throw Ss(D), bt; } - } else if (Si(U)) - x = Ql(""), L = x.ownerDocument.importNode(U, !0), L.nodeType === Mt.element && L.nodeName === "BODY" || L.nodeName === "HTML" ? x = L : x.appendChild(L), Vo(L); + } else if (Ai(D)) + x = ou(""), $ = x.ownerDocument.importNode(D, !0), $.nodeType === Et.element && $.nodeName === "BODY" || $.nodeName === "HTML" ? x = $ : x.appendChild($), Yo($); else { - if (!Ot && !Kt && !K && // eslint-disable-next-line unicorn/prefer-includes - U.indexOf("<") === -1) - return M && nn ? re(U) : U; - if (x = Ql(U), !x) - return Ot ? null : nn ? q : ""; - } - x && lt && $r(x.firstChild); - const ke = ie ? U : x; + if (!Xe && !rr && !q && // eslint-disable-next-line unicorn/prefer-includes + D.indexOf("<") === -1) + return W && an ? X(D) : D; + if (x = ou(D), !x) + return Xe ? null : an ? j : ""; + } + x && Me && Dr(x.firstChild); + const Te = se ? D : x; try { - const mt = Zl(ke); - for (; V = mt.nextNode(); ) - tu(V, ke), iu(V), Dn(V.content) && Cs(V.content); - } catch (mt) { - throw ie && (Ts(U), Bn(t.removed, (yt) => { - yt.element && Ci(yt.element); - })), mt; - } - if (ie) - return Bn(t.removed, (mt) => { - mt.element && Ci(mt.element); - }), Kt && Bo(U), U; - if (Ot) { - if (Kt && Bo(x), rn) - for (H = gr.call(x.ownerDocument); x.firstChild; ) - H.appendChild(x.firstChild); + const yt = au(Te); + for (; H = yt.nextNode(); ) + lu(H, Te), fu(H), zn(H.content) && As(H.content); + } catch (yt) { + throw se && (Ss(D), Hn(t.removed, (bt) => { + bt.element && Ei(bt.element); + })), yt; + } + if (se) + return Hn(t.removed, (yt) => { + yt.element && Ei(yt.element); + }), rr && Jo(D), D; + if (Xe) { + if (rr && Jo(x), nr) + for (Y = mr.call(x.ownerDocument); x.firstChild; ) + Y.appendChild(x.firstChild); else - H = x; - return (ye.shadowroot || ye.shadowrootmode) && (H = te.call(n, H, !0)), H; + Y = x; + return (_e.shadowroot || _e.shadowrootmode) && (Y = ne.call(n, Y, !0)), Y; } - let Ie = K ? x.outerHTML : x.innerHTML; - return K && ue["!doctype"] && x.ownerDocument && x.ownerDocument.doctype && x.ownerDocument.doctype.name && Ye(FS, x.ownerDocument.doctype.name) && (Ie = " -` + Ie), Kt && (Ie = xs(Ie)), M && nn ? re(Ie) : Ie; + let Ie = q ? x.outerHTML : x.innerHTML; + return q && ue["!doctype"] && x.ownerDocument && x.ownerDocument.doctype && x.ownerDocument.doctype.name && Qe(rS, x.ownerDocument.doctype.name) && (Ie = " +` + Ie), rr && (Ie = Ms(Ie)), W && an ? X(Ie) : Ie; }, t.setConfig = function() { - let U = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; - jo(U), G = !0, X = ue, _e = ye; + let D = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; + Go(D), V = !0, J = ue, Z = _e; }, t.clearConfig = function() { - Ln = null, G = !1, X = null, _e = null, M = $, q = ""; - }, t.isValidAttribute = function(U, h, x) { - Ln || jo({}); - const L = $e(U), V = $e(h); - return ru(L, V, x); - }, t.addHook = function(U, h) { - typeof h == "function" && Xe(E, U) && Vn(E[U], h); - }, t.removeHook = function(U, h) { - if (Xe(E, U)) { - if (h !== void 0) { - const x = _S(E[U], h); - return x === -1 ? void 0 : CS(E[U], x, 1)[0]; + Fn = null, V = !1, J = null, Z = null, W = U, j = ""; + }, t.isValidAttribute = function(D, m, x) { + Fn || Go({}); + const $ = Le(D), H = Le(m); + return uu($, H, x); + }, t.addHook = function(D, m) { + typeof m == "function" && Ze(A, D) && Gn(A[D], m); + }, t.removeHook = function(D, m) { + if (Ze(A, D)) { + if (m !== void 0) { + const x = Dv(A[D], m); + return x === -1 ? void 0 : Uv(A[D], x, 1)[0]; } - return gd(E[U]); + return Cd(A[D]); } - }, t.removeHooks = function(U) { - Xe(E, U) && (E[U] = []); + }, t.removeHooks = function(D) { + Ze(A, D) && (A[D] = []); }, t.removeAllHooks = function() { - E = Ed(); + A = $d(); }, t; } -var WS = eg(); -function HS({ structureProtectionMode: e = "off" }) { - const [t] = ce(), r = Z(void 0), [n, i] = de(void 0), s = he((o) => { +var cS = pg(); +function lS({ structureProtectionMode: e = "off" }) { + const [t] = ce(), r = Q(void 0), [n, i] = fe(void 0), s = ge((o) => { r.current = o, i(o); }, []); - return z(() => { + return F(() => { if (e === "off") return; - const o = (p) => { - const m = Vh(p); - if (!m) + const o = (f) => { + const g = ig(f); + if (!g) return !1; - const g = w(); - return e === "protected" ? g && Jh(g, m) ? (p.preventDefault(), !0) : !1 : m !== "deleteBackward" && m !== "deleteForward" ? !1 : a(m, p); - }, a = (p, m) => { - const g = w(), y = r.current; - if (y && g && fd(g, y)) { - if (s(void 0), m.preventDefault(), p !== y.intent) + const h = w(); + return e === "protected" ? h && cg(h, g) ? (f.preventDefault(), !0) : !1 : g !== "deleteBackward" && g !== "deleteForward" ? !1 : a(g, f); + }, a = (f, g) => { + const h = w(), y = r.current; + if (y && h && Td(h, y)) { + if (s(void 0), g.preventDefault(), f !== y.intent) return !0; - const _ = se(y.key) ?? void 0; + const _ = oe(y.key) ?? void 0; if (y.kind === "verse") { if (_) { - const S = _.getParent(), P = _.getPreviousSibling(), A = _.getNextSibling(); - _.remove(), P ? Yh(P) : A && v(A) ? A.select(0, 0) : S?.selectStart(); + const C = _.getParent(), M = _.getPreviousSibling(), E = _.getNextSibling(); + _.remove(), M ? lg(M) : E && S(E) ? E.select(0, 0) : C?.selectStart(); } - } else y.kind === "selection" ? N(g) && g.removeText() : Se(_) && fS(_); + } else y.kind === "selection" ? P(h) && h.removeText() : ve(_) && Av(_); return !0; } - if (!g) + if (!h) return !1; - const k = dS(g, p); + const k = Ev(h, f); if (k) { if (k.kind === "verse") { - const _ = xf(); - _.add(k.node.getKey()), ei(_); + const _ = qf(); + _.add(k.node.getKey()), Tn(_); } else { - const _ = bc(); - _.anchor.set(k.node.getKey(), 0, "element"), _.focus.set(k.node.getKey(), k.node.getChildrenSize(), "element"), ei(_); + const _ = vc(); + _.anchor.set(k.node.getKey(), 0, "element"), _.focus.set(k.node.getKey(), k.node.getChildrenSize(), "element"), Tn(_); } - return s({ key: k.node.getKey(), kind: k.kind, intent: p }), m.preventDefault(), !0; + return s({ key: k.node.getKey(), kind: k.kind, intent: f }), g.preventDefault(), !0; } - if (N(g) && !g.isCollapsed() && ml(g)) { - const _ = g.getNodes().filter(ge).map((A) => A.getKey()), { anchor: S, focus: P } = g; + if (P(h) && !h.isCollapsed() && _l(h)) { + const _ = h.getNodes().filter(me).map((E) => E.getKey()), { anchor: C, focus: M } = h; return s({ kind: "selection", - intent: p, + intent: f, key: _[0], - anchor: { key: S.key, offset: S.offset, type: S.type }, - focus: { key: P.key, offset: P.offset, type: P.type } - }), m.preventDefault(), !0; + anchor: { key: C.key, offset: C.offset, type: C.type }, + focus: { key: M.key, offset: M.offset, type: M.type } + }), g.preventDefault(), !0; } return !1; - }, c = (p) => { + }, c = (f) => { if (e !== "protected") return !1; - const m = w(); - return !m || !Qn(m) ? !1 : (p instanceof Event && p.preventDefault(), !0); - }, l = (p, m) => { - if (!p) + const g = w(); + return !g || !ei(g) ? !1 : (f instanceof Event && f.preventDefault(), !0); + }, l = (f, g) => { + if (!f) return !1; - const g = WS.sanitize(p), y = new DOMParser().parseFromString(g, "text/html"), k = pS(Yy(t, y)), _ = w(); - return N(_) && _.insertNodes(k), m.preventDefault(), !0; - }, u = (p) => { + const h = cS.sanitize(f), y = new DOMParser().parseFromString(h, "text/html"), k = Pv(fb(t, y)), _ = w(); + return P(_) && _.insertNodes(k), g.preventDefault(), !0; + }, d = (f) => { if (e !== "protected") return !1; - const m = w(); - return m && Qn(m) ? (p.preventDefault(), !0) : l(p.clipboardData?.getData("text/html"), p); - }, d = (p) => { + const g = w(); + return g && ei(g) ? (f.preventDefault(), !0) : l(f.clipboardData?.getData("text/html"), f); + }, u = (f) => { if (e !== "protected") return !1; - const m = w(); - return m && Qn(m) ? (p.preventDefault(), !0) : l(p.dataTransfer?.getData("text/html"), p); - }, f = () => { - const p = r.current; - p && t.getEditorState().read(() => { - fd(w(), p) || s(void 0); + const g = w(); + return g && ei(g) ? (f.preventDefault(), !0) : l(f.dataTransfer?.getData("text/html"), f); + }, p = () => { + const f = r.current; + f && t.getEditorState().read(() => { + Td(w(), f) || s(void 0); }); }; - return Ve( - t.registerCommand(vr, o, Ne), + return Fe( + t.registerCommand(Nr, o, qe), // CUT at CRITICAL, unlike KEY_DOWN above: a refusal has to outrank the actor it refuses, // not tie with it. The handlers that PERFORM a cut (the Standard-view and Markers-view // clipboard claims) register at HIGH, and at equal rank the winner is mount order — an @@ -19888,14 +19892,14 @@ function HS({ structureProtectionMode: e = "off" }) { // plugin were added between them. `OpaqueBlockGuardPlugin` states the same rule for the same // reason. KEY_DOWN stays at HIGH: it has no same-priority actor to outrank, and // `MarkerEditPlugin`'s KEY_DOWN handler must keep running ahead of it on every keystroke. - t.registerCommand(yn, c, Wt), - t.registerCommand(yr, u, Ne), - t.registerCommand(Py, c, Ne), - t.registerCommand(xc, d, Ne), - t.registerCommand(Tc, c, Ne), - t.registerUpdateListener(f) + t.registerCommand(Wt, c, tt), + t.registerCommand(_r, d, qe), + t.registerCommand(Ky, c, qe), + t.registerCommand(Ec, u, qe), + t.registerCommand(Mc, c, qe), + t.registerUpdateListener(p) ); - }, [t, e, s]), z(() => { + }, [t, e, s]), F(() => { const o = t.getRootElement(); if (!o) return; @@ -19905,21 +19909,21 @@ function HS({ structureProtectionMode: e = "off" }) { }; }, [t, n]), null; } -const AP = { +const QP = { ltr: "Left-to-right", rtl: "Right-to-left", auto: "Automatic" }; -function GS({ textDirection: e }) { +function uS({ textDirection: e }) { const [t] = ce(); - return JS(t, e), null; + return dS(t, e), null; } -function JS(e, t) { - z(() => (Ad(e, t), e.registerUpdateListener(({ dirtyElements: r }) => { - r.size > 0 && Ad(e, t); +function dS(e, t) { + F(() => (Ld(e, t), e.registerUpdateListener(({ dirtyElements: r }) => { + r.size > 0 && Ld(e, t); })), [e, t]); } -function Ad(e, t) { +function Ld(e, t) { if (t === "auto") return; const r = e.getRootElement(); @@ -19927,19 +19931,19 @@ function Ad(e, t) { const n = e._config.theme.placeholder, i = document.getElementsByClassName(n)[0]; i && (i.dir = t); } -function YS() { +function fS() { const [e] = ce(); - return XS(e), null; + return pS(e), null; } -function XS(e) { - z(() => { - if (!e.hasNodes([me, Ct, Ee, Ke, dt])) +function pS(e) { + F(() => { + if (!e.hasNodes([be, vt, Pe, Be, ft])) throw new Error("TextSpacingPlugin: CharNode, ImmutableVerseNode, NoteNode, TextNode or VerseNode not registered on editor!"); - return Ve( - e.registerNodeTransform(Ke, QS), - e.registerNodeTransform(Ke, (t) => ZS(t, e)), - e.registerNodeTransform(dt, Pd), - e.registerNodeTransform(Ct, Pd), + return Fe( + e.registerNodeTransform(Be, hS), + e.registerNodeTransform(Be, (t) => gS(t, e)), + e.registerNodeTransform(ft, Id), + e.registerNodeTransform(vt, Id), // Self-healing \va/\vp display runs: re-derive them from altnumber/pubnumber whenever // a verse is dirtied — heals remote collab updates (delta-apply only calls setAltnumber/ // setPubnumber) and structure surgery. Registered here (not a dedicated VerseNodePlugin, @@ -19948,122 +19952,122 @@ function XS(e) { // shape CharNodePlugin uses for chars. $syncDisplayRun (displayRunSync.utils.ts, `shared`), // driven `\va` first so `\vp`'s scan and insertion anchor find the healed `\va` wrapper // already in place. - e.registerNodeTransform(dt, (t) => { - es(xn("va"), t), es(xn("vp"), t); + e.registerNodeTransform(ft, (t) => { + is(vn("va"), t), is(vn("vp"), t); }) ); }, [e]); } -function QS(e) { +function hS(e) { if (!e.isAttached()) return; const t = e.getTextContent(), r = e.getNextSibling(), n = e.getParent(); - if (e.getMode() !== "normal" || t.endsWith(" ") && t.length > 1 || j(r) || D(n) || D(r) || Ce(n) || Ce(r) || De(n) || // An adjacent TextNode is the same logical text run (IME composition and annotation-wrap + if (e.getMode() !== "normal" || t.endsWith(" ") && t.length > 1 || K(r) || I(n) || I(r) || Ce(n) || Ce(r) || Ue(n) || // An adjacent TextNode is the same logical text run (IME composition and annotation-wrap // splits leave runs as multiple nodes, e.g. a segmented composition node that Lexical // won't merge). No structural space belongs inside a run — inserting one corrupts the // word itself (#513, complex scripts worst). This also protects a space-only node from // the placeholder cleanup below: between two text nodes it is real content. - v(r) || // An optbreak (`//`) — like a ref — is an inline UnknownNode carrying SIGNIFICANT surrounding + S(r) || // An optbreak (`//`) — like a ref — is an inline UnknownNode carrying SIGNIFICANT surrounding // whitespace (Paratext 9 preserves the spaces around `//` byte-for-byte). Forcing a trailing // space onto the text before one — or removing a lone space there — corrupts the authored form // and makes the space impossible to delete (the transform re-adds it every keystroke). Text // adjacent to an inline unknown is left exactly as authored, the same next-sibling exemption // already applied to notes, chars, and typed marks. Block-level unknowns (figures, sidebars) // keep the existing spacing behavior. - De(r) && r.isInlineTag() || // An attribute display run (char/milestone/verse — attributeDisplay.utils.ts) is engine-owned + Ue(r) && r.isInlineTag() || // An attribute display run (char/milestone/verse — attributeDisplay.utils.ts) is engine-owned // presentation, not paragraph prose: it must never gain a trailing space of its own, even // when it sits directly in a paragraph (a verse's \va/\vp value has no CharNode parent to // exempt it the way a char span's own run is already protected). - ne(e, oe) === "attribute" || // When a verse's/milestone's run rides inside an AttributeRunNode wrapper (AttributeRunNode.ts, + ie(e, ae) === "attribute" || // When a verse's/milestone's run rides inside an AttributeRunNode wrapper (AttributeRunNode.ts, // the shape the adaptor always builds now), its glyph children (MarkerNode, never textType // "attribute") need the same exemption the state-tagged value already gets above — a glyph is // a plain TextNode here, invisible to the state check, but is exactly as much engine-owned // presentation. The transform still exempts whichever shape — loose attribute text or a // wrapper's children — is actually in the tree, so a pre-flip loose editor state stays exempt // too. - Le(n)) + De(n)) return; - if (ge(e.getPreviousSibling()) && (t === "" || t === " ")) { + if (me(e.getPreviousSibling()) && (t === "" || t === " ")) { t !== "" && e.setTextContent(""); return; } - ge(r) && Jc(e); + me(r) && tl(e); } -function ZS(e, t) { +function gS(e, t) { const r = e.getParent(); - !De(r) || !e.isAttached() || Ua(t, e.getKey()) && !Ua(t, r.getKey()) && r.insertAfter(e); + !Ue(r) || !e.isAttached() || Va(t, e.getKey()) && !Va(t, r.getKey()) && r.insertAfter(e); } -function Pd(e) { +function Id(e) { if (!e.isAttached()) return; let t = e.getPreviousSibling(); for (; Ce(t); ) t = t.getLastChild(); - (D(t) || v(t) && Ce(t.getParent())) && e.insertBefore(pe(" ")); + (I(t) || S(t) && Ce(t.getParent())) && e.insertBefore(he(" ")); } -function yl(e) { - if (!j(e) || e.getIsCollapsed() !== !0) +function Cl(e) { + if (!K(e) || e.getIsCollapsed() !== !0) return; const t = e.getParent(); - return !t || t.isInline() || e.getNextSiblings().some((n) => !qc(n)) ? void 0 : e; + return !t || t.isInline() || e.getNextSiblings().some((n) => !Uc(n)) ? void 0 : e; } -function ev(e) { +function mS(e) { if (e.type !== "element") return; const t = e.getNode(); - if (F(t)) + if (L(t)) return t.getChildAtIndex(e.offset - 1) ?? void 0; } -function tv() { +function yS() { const e = w(); - if (!(!N(e) || !e.isCollapsed())) - return yl(ev(e.anchor)); + if (!(!P(e) || !e.isCollapsed())) + return Cl(mS(e.anchor)); } -function rv(e) { +function bS(e) { const t = w(); let r; - return N(t) ? t.isCollapsed() && (r = t.anchor.getNode()) : t || (r = tg(e.target)), r ? yl(nt(r, j)) : void 0; + return P(t) ? t.isCollapsed() && (r = t.anchor.getNode()) : t || (r = hg(e.target)), r ? Cl(rt(r, K)) : void 0; } -function tg(e) { - const t = Ny(e)?.anchorNode; - if (Tf(t)) - return li(t) ?? void 0; +function hg(e) { + const t = jy(e)?.anchorNode; + if (wf(t)) + return fi(t) ?? void 0; } -function nv(e) { +function kS(e) { if (w()) return; - const t = tg(e); - return t ? yl(nt(t, j)) : void 0; + const t = hg(e); + return t ? Cl(rt(t, K)) : void 0; } -function iv() { - const [e] = ce(), t = jh(tv); - return z(() => { +function TS() { + const [e] = ce(), t = rg(yS); + return F(() => { const r = (n) => { - jr(Br), t(n); + Gr(Jr), t(n); }; - return Ve(e.registerCommand(ur, () => { - const n = nv(e.getRootElement()); + return Fe(e.registerCommand(dr, () => { + const n = kS(e.getRootElement()); return n && r(n), !1; - }, mn), e.registerCommand(lo, (n) => { - const i = rv(n); + }, kn), e.registerCommand(mo, (n) => { + const i = bS(n); return i && r(i), !1; - }, mn)); + }, kn)); }, [e, t]), null; } -function sv({ trigger: e, scriptureReference: t, contextMarker: r, getMarkerAction: n }) { - const { markersMenuItems: i } = C_({ +function xS({ trigger: e, scriptureReference: t, contextMarker: r, getMarkerAction: n }) { + const { markersMenuItems: i } = U_({ scriptureReference: t, contextMarker: r, getMarkerAction: n }); - return C(__, { trigger: e, items: i }); + return v(D_, { trigger: e, items: i }); } -function ov({ trigger: e, scrRef: t, contextMarker: r, getMarkerAction: n, editableHarness: i }) { - const { book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l } = t, u = ze(() => ({ book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l }), [s, o, a, c, l]); - return i ? C(lv, { trigger: e, harness: i }) : C(sv, { trigger: e, scriptureReference: u, contextMarker: r, getMarkerAction: n }); +function _S({ trigger: e, scrRef: t, contextMarker: r, getMarkerAction: n, editableHarness: i }) { + const { book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l } = t, d = je(() => ({ book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l }), [s, o, a, c, l]); + return i ? v(SS, { trigger: e, harness: i }) : v(xS, { trigger: e, scriptureReference: d, contextMarker: r, getMarkerAction: n }); } -const av = [" ", "*"]; -function cv(e, t) { +const CS = [" ", "*"]; +function vS(e, t) { return { name: e.marker, label: e.marker, @@ -20076,28 +20080,28 @@ function cv(e, t) { applyOpts: t }; } -function lv({ trigger: e, harness: t }) { - const [r] = ce(), [n, i] = de(void 0), s = Z({ query: "", options: [] }), o = Z(0), a = he((f, p, m) => { - const g = p.find((y) => y.kind === "note" && y.marker === f); - if (g) { - t.apply(g, { trigger: "backslash", literalPrefixLanded: !1 }); +function SS({ trigger: e, harness: t }) { + const [r] = ce(), [n, i] = fe(void 0), s = Q({ query: "", options: [] }), o = Q(0), a = ge((p, f, g) => { + const h = f.find((y) => y.kind === "note" && y.marker === p); + if (h) { + t.apply(h, { trigger: "backslash", literalPrefixLanded: !1 }); return; } r.update(() => { const y = w(); - N(y) && y.insertText(`${e}${f}${m ? " " : ""}`); + P(y) && y.insertText(`${e}${p}${g ? " " : ""}`); }); }, [r, t, e]); - z(() => Ve(r.registerCommand(vr, (f) => { + F(() => Fe(r.registerCommand(Nr, (p) => { if (n) { - if ((f.key === "Enter" || f.key === "Tab") && s.current.options.length === 0) - return f.preventDefault(), f.stopPropagation(), !0; - if (f.key === "*" && n.trigger === "backslash") - return f.preventDefault(), f.stopPropagation(), i(void 0), t.commitTypedCloser(s.current.query), !0; - if (f.key === e && n.trigger === "backslash" && !n.hasTextSelection) { - f.preventDefault(), f.stopPropagation(); - const g = s.current.query; - return g ? (a(g, n.items, !1), Oy(() => { + if ((p.key === "Enter" || p.key === "Tab") && s.current.options.length === 0) + return p.preventDefault(), p.stopPropagation(), !0; + if (p.key === "*" && n.trigger === "backslash") + return p.preventDefault(), p.stopPropagation(), i(void 0), t.commitTypedCloser(s.current.query), !0; + if (p.key === e && n.trigger === "backslash" && !n.hasTextSelection) { + p.preventDefault(), p.stopPropagation(); + const h = s.current.query; + return h ? (a(h, n.items, !1), By(() => { const y = t.getContext(); s.current = { query: "", options: [] }, o.current += 1, i(y ? { trigger: "backslash", @@ -20107,93 +20111,93 @@ function lv({ trigger: e, harness: t }) { } : void 0); }), !0) : (i(void 0), r.update(() => { const y = w(); - N(y) && y.insertText(e); + P(y) && y.insertText(e); }), !0); } - if (f.key !== " " || n.trigger !== "backslash") + if (p.key !== " " || n.trigger !== "backslash") return !1; - f.preventDefault(), f.stopPropagation(), i(void 0); - const m = s.current.query; + p.preventDefault(), p.stopPropagation(), i(void 0); + const g = s.current.query; if (n.hasTextSelection) { - const g = n.items.find((y) => y.marker === m); - return g && t.apply(g, { trigger: "backslash", literalPrefixLanded: !1 }), !0; + const h = n.items.find((y) => y.marker === g); + return h && t.apply(h, { trigger: "backslash", literalPrefixLanded: !1 }), !0; } - return a(m, n.items, !0), !0; + return a(g, n.items, !0), !0; } - if (f.key !== e) + if (p.key !== e) return !1; - const p = t.getContext(); - return p ? (f.preventDefault(), s.current = { query: "", options: [] }, o.current += 1, i({ + const f = t.getContext(); + return f ? (p.preventDefault(), s.current = { query: "", options: [] }, o.current += 1, i({ trigger: "backslash", - hasTextSelection: p.hasTextSelection, - items: t.getItems(p), + hasTextSelection: f.hasTextSelection, + items: t.getItems(f), session: o.current }), !0) : !1; - }, Ne), r.registerCommand(_f, (f) => { - if (n || f === null || f.shiftKey) + }, qe), r.registerCommand(Rf, (p) => { + if (n || p === null || p.shiftKey) return !1; - const p = t.getContext(); - return !p || p.noteMarker || p.inMarkerText ? !1 : (f.preventDefault(), o.current += 1, i({ + const f = t.getContext(); + return !f || f.noteMarker || f.inMarkerText ? !1 : (p.preventDefault(), o.current += 1, i({ trigger: "enter", hasTextSelection: !1, - items: t.getEnterItems(p), + items: t.getEnterItems(f), session: o.current }), !0); - }, Jn)), [r, e, t, n, a]); - const c = he(() => i(void 0), []), l = he((f, p) => { - s.current = { query: f, options: p }; - }, []), u = he((f) => { - const { markerMenuItem: p, applyOpts: m } = f; - t.apply(p, m); - }, [t]), d = ze(() => n?.items.map((f) => ( + }, Wr)), [r, e, t, n, a]); + const c = ge(() => i(void 0), []), l = ge((p, f) => { + s.current = { query: p, options: f }; + }, []), d = ge((p) => { + const { markerMenuItem: f, applyOpts: g } = p; + t.apply(f, g); + }, [t]), u = je(() => n?.items.map((p) => ( // `literalPrefixLanded` is constant `false` under the active palette: the trigger never // lands, so an item commit never has a literal prefix to clean up. The field stays in // the apply contract because hosts whose own palettes DO land literals still pass true. - cv(f, { trigger: n.trigger, literalPrefixLanded: !1 }) + vS(p, { trigger: n.trigger, literalPrefixLanded: !1 }) )), [n]); - return n && C(_h, { isOpen: !0, children: ({ placement: f }) => C( - vh, - { options: d ?? [], onSelectOption: u, onClose: c, onFilterChange: l, inverse: f === "top-start", menuOpenKey: e, passthroughKeys: n.trigger === "backslash" ? av : void 0 }, + return n && v(Rh, { isOpen: !0, children: ({ placement: p }) => v( + Ih, + { options: u ?? [], onSelectOption: d, onClose: c, onFilterChange: l, inverse: p === "top-start", menuOpenKey: e, passthroughKeys: n.trigger === "backslash" ? CS : void 0 }, n.session ) }); } -function rg(e) { - return e.replaceAll(I, "~").replace(/ {2,}/g, (r) => I.repeat(r.length)); +function gg(e) { + return e.replaceAll(R, "~").replace(/ {2,}/g, (r) => R.repeat(r.length)); } -function uv(e) { - return e.replaceAll(I, " ").replaceAll("~", I); +function MS(e) { + return e.replaceAll(R, " ").replaceAll("~", R); } -function dv(e) { +function ES(e) { return e.replace(/ {2,}/g, " "); } -let Zs; -function fv(e) { - e && (Zs = e); +let no; +function AS(e) { + e && (no = e); } -function ng(e) { - return Ao(e); +function mg(e) { + return wo(e); } -function pv(e, t) { - return e.isEmpty() ? bf : ig(e.toJSON(), t); +function PS(e, t) { + return e.isEmpty() ? Nf : yg(e.toJSON(), t); } -function ig(e, t) { +function yg(e, t) { if (!e.root || !e.root.children) return; const r = e.root.children; - if (r.length === 1 && go(r[0]) && (!r[0].children || r[0].children.length === 0)) - return bf; - if (r.some(WT)) { - Zs?.error( + if (r.length === 1 && ko(r[0]) && (!r[0].children || r[0].children.length === 0)) + return Nf; + if (r.some(cx)) { + no?.error( "Block verse layout is not round-trippable to USJ. VerseBlockNode is read-only view state; use the source USJ instead." ); return; } - const n = sg(r), i = Bt(n, t); - return i ? { type: kr, version: br, content: i } : void 0; + const n = bg(r), i = Bt(n, t); + return i ? { type: vr, version: Cr, content: i } : void 0; } -function hv(e, t) { +function NS(e, t) { const { type: r, marker: n, unknownAttributes: i } = e; let s; - return e.code !== "" && (s = e.code), Ae({ + return e.code !== "" && (s = e.code), Ne({ type: r, marker: n, code: s, @@ -20201,10 +20205,10 @@ function hv(e, t) { content: t }); } -function gv(e) { +function OS(e) { const { marker: t, number: r, sid: n, altnumber: i, pubnumber: s, unknownAttributes: o } = e; - return Ae({ - type: Nt.getType(), + return Ne({ + type: Ot.getType(), marker: t, number: r, sid: n, @@ -20213,11 +20217,11 @@ function gv(e) { ...o }); } -function mv(e, t) { +function wS(e, t) { const { marker: r, sid: n, altnumber: i, pubnumber: s, unknownAttributes: o } = e, a = t && typeof t[0] == "string" ? t[0] : void 0; let { number: c } = e; - return c = Cp(r, a, c), Ae({ - type: Nt.getType(), + return c = $p(r, a, c), Ne({ + type: Ot.getType(), marker: r, number: c, sid: n, @@ -20226,11 +20230,11 @@ function mv(e, t) { ...o }); } -function yv(e) { +function qS(e) { const { marker: t, sid: r, altnumber: n, pubnumber: i, unknownAttributes: s } = e, { text: o } = e; let { number: a } = e; - return a = Cp(t, o, a), Ae({ - type: dt.getType(), + return a = $p(t, o, a), Ne({ + type: ft.getType(), marker: t, number: a, sid: r, @@ -20239,40 +20243,40 @@ function yv(e) { ...s }); } -function bv(e, t, r) { +function RS(e, t, r) { const { type: n, marker: i, unknownAttributes: s } = e, o = i === "" ? void 0 : i; - if (r?.markerMode === "editable" && !ng(r) && t) { + if (r?.markerMode === "editable" && !mg(r) && t) { const [a] = t; - typeof a == "string" && a.startsWith(I) && (t[0] = a.slice(1)); + typeof a == "string" && a.startsWith(R) && (t[0] = a.slice(1)); } - return Ae({ + return Ne({ type: n, marker: o, ...s, content: t }); } -function kv(e, t) { +function $S(e, t) { const { type: r, marker: n, unknownAttributes: i } = e; - return Ae({ + return Ne({ type: r, marker: n, ...i, content: t }); } -function Tv(e, t) { +function LS(e, t) { const { unknownAttributes: r } = e; - return Ae({ type: Yp, ...r, content: t }); + return Ne({ type: ch, ...r, content: t }); } -function xv(e, t) { +function IS(e, t) { const { marker: r, unknownAttributes: n } = e; - return Ae({ type: Zp, marker: r, ...n, content: t }); + return Ne({ type: dh, marker: r, ...n, content: t }); } -function _v(e, t) { +function DS(e, t) { const { marker: r, align: n, colspan: i, unknownAttributes: s } = e; - return Ae({ - type: th, + return Ne({ + type: ph, marker: r, align: n, colspan: i, @@ -20280,9 +20284,9 @@ function _v(e, t) { content: t }); } -function Cv(e, t) { +function US(e, t) { const { type: r, marker: n, caller: i, category: s, unknownAttributes: o } = e; - return Ae({ + return Ne({ type: r, marker: n, caller: i, @@ -20291,183 +20295,183 @@ function Cv(e, t) { content: t }); } -function Gn(e) { +function Xn(e) { const { type: t, marker: r, sid: n, eid: i, unknownAttributes: s, attributeOrder: o } = e; - return Ae({ + return Ne({ type: t, marker: r === "" ? void 0 : r, - ...Op({ sid: n, eid: i, ...s }, o) + ...jp({ sid: n, eid: i, ...s }, o) }); } -function Sv(e) { +function FS(e) { return e.text; } -function vv(e, t) { +function zS(e, t) { const { tag: r, marker: n, unknownAttributes: i } = e; - return Ae({ + return Ne({ type: r, marker: n, ...i, content: t }); } -function Mv(e) { +function KS(e) { const { marker: t } = e; return { - type: js, + type: Hs, marker: t === "" ? void 0 : t }; } -function Nd(e, t) { +function Dd(e, t) { const r = e[e.length - 1]; r && typeof r == "string" ? e[e.length - 1] = r + t : e.push(t); } -function Ev(e, t, r, n, i) { +function jS(e, t, r, n, i) { const s = Jt.getType(), o = t.filter((l) => !r.includes(l)); if (r.filter((l) => !t.includes(l)).forEach((l) => { - const u = Gn({ + const d = Xn({ type: s, - marker: Yn, + marker: Qn, eid: l }); - i.push(u); + i.push(d); }), o.forEach((l) => { - const u = Gn({ + const d = Xn({ type: s, - marker: bn, + marker: xn, sid: l }); - i.push(u); + i.push(d); }), t.length === 0) { - const l = Gn({ + const l = Xn({ type: s, - marker: bn + marker: xn }); i.push(l); } if (i.push(...e), t.length === 0) { - const l = Gn({ + const l = Xn({ type: s, - marker: Yn + marker: Qn }); i.push(l); } - (!n || !Qf(n)) && t.forEach((l) => { - const u = Gn({ + (!n || !up(n)) && t.forEach((l) => { + const d = Xn({ type: s, - marker: Yn, + marker: Qn, eid: l }); - i.push(u); + i.push(d); }); } function Bt(e, t, r, n = !1) { const i = []; let s, o = []; return e.forEach((a, c) => { - const l = a, u = a, d = a, f = a, p = a, m = a, g = a, y = a; + const l = a, d = a, u = a, p = a, f = a, g = a, h = a, y = a; switch (a.type) { - case Ut.getType(): + case Ft.getType(): i.push( - hv( + NS( l, Bt(l.children, t) ) ); break; - case pr.getType(): - i.push(gv(a)); + case hr.getType(): + i.push(OS(a)); break; - case Nt.getType(): + case Ot.getType(): i.push( - mv( - u, - Bt(u.children, t) + wS( + d, + Bt(d.children, t) ) ); break; - case Ct.getType(): - case dt.getType(): - i.push(yv(a)); + case vt.getType(): + case ft.getType(): + i.push(qS(a)); break; - case me.getType(): + case be.getType(): i.push( - bv( - d, - Bt(d.children, t, void 0, !0), + RS( + u, + Bt(u.children, t, void 0, !0), t ) ); break; - case Ze.getType(): + case nt.getType(): i.push( - kv( - f, - Bt(f.children, t) + $S( + p, + Bt(p.children, t) ) ); break; - case On.getType(): + case Rn.getType(): i.push( - Tv( + LS( a, Bt(a.children, t) ) ); break; - case fi.getType(): + case gi.getType(): i.push( - xv( + IS( a, Bt(a.children, t) ) ); break; - case pi.getType(): + case mi.getType(): i.push( - _v( + DS( a, Bt(a.children, t) ) ); break; - case Ee.getType(): + case Pe.getType(): i.push( - Cv( - p, - Bt(p.children, t, p.caller) + US( + f, + Bt(f.children, t, f.caller) ) ); break; - case Ar.getType(): - case Er.getType(): + case qr.getType(): + case wr.getType(): case Gt.getType(): - case Cf.getType(): - case hr.getType(): + case $f.getType(): + case gr.getType(): break; - case Qe.getType(): + case et.getType(): if (s = Bt( - g.children, + h.children, t, r, n ), s) { - const k = g.typedIDs[Kr]; + const k = h.typedIDs[Vr]; if (k) - Ev(s, k, o, e[c + 1], i), o = k; + jS(s, k, o, e[c + 1], i), o = k; else { const _ = s.shift(); - _ && (typeof _ == "string" ? Nd(i, _) : i.push(_)), s.length > 0 && i.push(...s); + _ && (typeof _ == "string" ? Dd(i, _) : i.push(_)), s.length > 0 && i.push(...s); } } break; case Jt.getType(): - i.push(Gn(a)); + i.push(Xn(a)); break; - case Ke.getType(): - if (m.text && // Drop a bare caret host (EmptyVerseCaretGuardPlugin). A legitimate ZWSP inside real text + case Be.getType(): + if (g.text && // Drop a bare caret host (EmptyVerseCaretGuardPlugin). A legitimate ZWSP inside real text // (Thai/Khmer line breaks) is not placeholder-only, so it still passes and is preserved. - !ps(m.text) && // A byte test, not (only) the separator state tag, and deliberately so: a lone-NBSP + !bs(g.text) && // A byte test, not (only) the separator state tag, and deliberately so: a lone-NBSP // text node stands in for THREE presentation shapes — the tagged separators the // forward adaptor builds, the empty-char placeholder, and an orphaned structural // prefix a split or deletion strands in its own (untagged) node. The known cost is @@ -20478,65 +20482,65 @@ function Bt(e, t, r, n = !1) { // exactly this shape, so in standard view only an authored lone-NBSP data string // (displayed as `~`, never as a bare NBSP node) is at stake — leaving the drop to // genuinely structural nodes. - m.text !== I && !m.text.startsWith(Sc) && // Char-span attribute display runs (bare `|…`, no NBSP prefix — see + g.text !== R && !g.text.startsWith(Nc) && // Char-span attribute display runs (bare `|…`, no NBSP prefix — see // usj-editor.adaptor's `addCharAttributes`) carry no NBSP prefix to strip against, so // the prefix check above can't catch them; the textType state tag is the only signal. - m[ds]?.textType !== "attribute" && (!r || m.text !== At(r))) { - let k = Sv(m); - ng(t) && (n && k.startsWith(I) && (k = k.slice(1)), k = dv(uv(k))), Nd(i, k); + g[gs]?.textType !== "attribute" && (!r || g.text !== Pt(r))) { + let k = FS(g); + mg(t) && (n && k.startsWith(R) && (k = k.slice(1)), k = ES(MS(k))), Dd(i, k); } break; - case Pn.getType(): + case wn.getType(): i.push( - vv( + zS( y, Bt(y.children, t) ) ); break; - case Or.getType(): - i.push(Mv(a)); + case Lr.getType(): + i.push(KS(a)); break; - case hi.getType(): - Zs?.error("Block verse layout is not round-trippable to USJ; skipping the block."); + case yi.getType(): + no?.error("Block verse layout is not round-trippable to USJ; skipping the block."); break; default: - Zs?.error(`Unexpected node type '${a.type}'!`); + no?.error(`Unexpected node type '${a.type}'!`); } }), i && i.length > 0 ? i : void 0; } -function sg(e) { - const t = e.findIndex((r) => go(r)); +function bg(e) { + const t = e.findIndex((r) => ko(r)); if (t >= 0) { - const r = e.slice(0, t), n = e[t].children, i = sg(e.slice(t + 1)); + const r = e.slice(0, t), n = e[t].children, i = bg(e.slice(t + 1)); e = [...r, ...n, ...i]; } return e; } -const Os = { - initialize: fv, - deserializeEditorState: pv -}, Av = /^sd\d*$/, Pv = /* @__PURE__ */ new Set([ - ...Object.entries(va).filter( - ([e, t]) => t.category === T.TitlesHeadings && t.type === b.Paragraph && !Av.test(e) +const Ls = { + initialize: AS, + deserializeEditorState: PS +}, BS = /^sd\d*$/, VS = /* @__PURE__ */ new Set([ + ...Object.entries(Oa).filter( + ([e, t]) => t.category === T.TitlesHeadings && t.type === b.Paragraph && !BS.test(e) ).map(([e]) => e), "qa" ]); -function Nv(e, t) { +function WS(e, t) { const r = []; let n; for (const i of e) { - if (sp(i) || kp(i)) { + if (yp(i) || Op(i)) { n = void 0, r.push(i); continue; } - if (!kk(i)) { - t && eo(i) && t.warn( + if (!$k(i)) { + t && io(i) && t.warn( `Verses inside a '${i.type}' are not grouped into blocks; the whole node stays with the surrounding verse.` ), n ? n.children.push(i) : r.push(i); continue; } - if (wc(i) && Pv.has(i.marker) && !eo(i)) { + if (Dc(i) && VS.has(i.marker) && !io(i)) { n = void 0, r.push(i); continue; } @@ -20544,78 +20548,78 @@ function Nv(e, t) { n ? n.children.push(i) : r.push(i); continue; } - og(i.children, t).forEach((s) => { - const o = Ov(i, s.nodes); + kg(i.children, t).forEach((s) => { + const o = HS(i, s.nodes); if (!s.verse) { if (!o) return; n ? n.children.push(o) : r.push(o); return; } - n = wv(s.verse), r.push(n), o && n.children.push(o); + n = GS(s.verse), r.push(n), o && n.children.push(o); }); } return r; } -function og(e, t) { +function kg(e, t) { const r = [{ nodes: [] }], n = (i) => r[r.length - 1].nodes.push(i); return e.forEach((i) => { - if (ag(i)) { + if (Tg(i)) { r.push({ verse: i, nodes: [i] }); return; } - if (Qf(i)) { - const s = og(i.children, t), [o, ...a] = s; - o.nodes.length > 0 && n(Od(i, o.nodes)), a.forEach((c) => { - r.push({ verse: c.verse, nodes: [Od(i, c.nodes)] }); + if (up(i)) { + const s = kg(i.children, t), [o, ...a] = s; + o.nodes.length > 0 && n(Ud(i, o.nodes)), a.forEach((c) => { + r.push({ verse: c.verse, nodes: [Ud(i, c.nodes)] }); }); return; } - t && eo(i) && t?.warn( + t && io(i) && t?.warn( `Verse marker nested inside a '${i.type}' node was not grouped into a block.` ), n(i); }), r; } -function Od(e, t) { +function Ud(e, t) { return { ...e, children: t }; } -function ag(e) { - return dh(e) && e.number !== ""; +function Tg(e) { + return Ch(e) && e.number !== ""; } -function eo(e) { +function io(e) { const t = e.children; - return Array.isArray(t) ? t.some((r) => ag(r) || eo(r)) : !1; + return Array.isArray(t) ? t.some((r) => Tg(r) || io(r)) : !1; } -function Ov(e, t) { +function HS(e, t) { if (t.length !== 0) return { ...e, children: t }; } -function wv(e) { +function GS(e) { return { - type: Bs, + type: Gs, number: e.number, children: [], direction: null, format: "", indent: 0, - version: ch + version: Th }; } -const wd = lg([]), qv = { - type: Cf.getType(), +const Fd = _g([]), JS = { + type: $f.getType(), version: 1 }; -let bl = [], ee, Sn, cg, _t; -function Rv(e, t) { - bl = [], Lv(e), Dv(t); +let vl = [], te, En, xg, Ct; +function YS(e, t) { + vl = [], ZS(e), eM(t); } -function $v(e = 0) { +function XS(e = 0) { } -function Iv(e, t) { - ee = t ?? Eo(); +function QS(e, t) { + te = t ?? Oo(); let r; - return e ? (e.type !== kr && _t?.warn(`This USJ type '${e.type}' didn't match the expected type '${kr}'.`), e.version !== br && _t?.warn( - `This USJ version '${e.version}' didn't match the expected version '${br}'.` - ), e.content.length > 0 ? (r = Qa(Ur(e.content)), ns(ee) && (r = Nv(r, _t))) : r = [wd]) : r = [wd], cg?.(bl), { + return e ? (e.type !== vr && Ct?.warn(`This USJ type '${e.type}' didn't match the expected type '${vr}'.`), e.version !== Cr && Ct?.warn( + `This USJ version '${e.version}' didn't match the expected version '${Cr}'.` + ), e.content.length > 0 ? (r = ic(Kr(e.content)), as(te) && (r = WS(r, Ct))) : r = [Fd]) : r = [Fd], xg?.(vl), { root: { children: r, direction: null, @@ -20626,32 +20630,32 @@ function Iv(e, t) { } }; } -function Lv(e) { - e && (Sn = e), e?.addMissingComments && (cg = e.addMissingComments); +function ZS(e) { + e && (En = e), e?.addMissingComments && (xg = e.addMissingComments); } -function Dv(e) { - e && (_t = e); +function eM(e) { + e && (Ct = e); } -function kl() { - return Ao(ee); +function Sl() { + return wo(te); } -function Uv(e) { +function tM(e) { return !e || e.length !== 1 || typeof e[0] != "string" ? "" : e[0]; } -function Fv(e) { +function rM(e) { let { marker: t } = e; - t !== Gi && _t?.warn(`Unexpected book marker '${t}'!`), t = t ?? Gi; + t !== Qi && Ct?.warn(`Unexpected book marker '${t}'!`), t = t ?? Qi; const { code: r } = e; - (!r || !Ut.isValidBookCode(r)) && _t?.warn(`Unexpected book code '${r}'!`); + (!r || !Ft.isValidBookCode(r)) && Ct?.warn(`Unexpected book code '${r}'!`); const n = []; - ee?.markerMode === "editable" || ee?.markerMode === "visible" ? n.push( - kt("marker", we(t) + " " + r + I) - ) : ee?.hasGutterParaMarkers && n.push(kt("marker", we(t) + I, !0)); - const i = Uv(e.content); - i && n.push(at(kl() ? rg(i) : i)); - const s = Ue(e, Qb); - return Ae({ - type: Ut.getType(), + te?.markerMode === "editable" || te?.markerMode === "visible" ? n.push( + Tt("marker", Re(t) + " " + r + R) + ) : te?.hasGutterParaMarkers && n.push(Tt("marker", Re(t) + R, !0)); + const i = tM(e.content); + i && n.push(ut(Sl() ? gg(i) : i)); + const s = ze(e, hk); + return Ne({ + type: Ft.getType(), marker: t, code: r ?? "", unknownAttributes: s, @@ -20659,20 +20663,20 @@ function Fv(e) { direction: null, format: "", indent: 0, - version: np + version: gp }); } -function zv(e) { +function nM(e) { let { marker: t } = e; - t !== Fs && _t?.warn(`Unexpected chapter marker '${t}'!`), t = t ?? Fs; - const { number: r, sid: n, altnumber: i, pubnumber: s } = e, o = Ue(e, Zb); + t !== Bs && Ct?.warn(`Unexpected chapter marker '${t}'!`), t = t ?? Bs; + const { number: r, sid: n, altnumber: i, pubnumber: s } = e, o = ze(e, gk); let a; - ee?.markerMode === "visible" && (a = !0); + te?.markerMode === "visible" && (a = !0); const c = [ - at(Lt(t, r) ?? "") + ut(Dt(t, r) ?? "") ]; - return ee?.markerMode === "editable" && iM(i, s, c), ee?.markerMode === "editable" ? Ae({ - type: Nt.getType(), + return te?.markerMode === "editable" && TM(i, s, c), te?.markerMode === "editable" ? Ne({ + type: Ot.getType(), marker: t, number: r ?? "", sid: n, @@ -20683,9 +20687,9 @@ function zv(e) { direction: null, format: "", indent: 0, - version: op - }) : Ae({ - type: pr.getType(), + version: bp + }) : Ne({ + type: hr.getType(), marker: t, number: r ?? "", showMarker: a, @@ -20693,17 +20697,17 @@ function zv(e) { altnumber: i, pubnumber: s, unknownAttributes: o, - version: dp + version: Cp }); } -function Kv(e) { +function iM(e) { let { marker: t } = e; - t !== zs && _t?.warn(`Unexpected verse marker '${t}'!`), t = t ?? zs; - const { number: r, sid: n, altnumber: i, pubnumber: s } = e, a = (w_(ee) ?? Ct).getType(), c = ee?.markerMode === "editable" ? mp : uh; - let l, u; - ee?.markerMode === "editable" ? l = Lt(t, r) : ee?.markerMode === "visible" && (u = !0); - const d = Ue(e, fk); - return Ae({ + t !== Vs && Ct?.warn(`Unexpected verse marker '${t}'!`), t = t ?? Vs; + const { number: r, sid: n, altnumber: i, pubnumber: s } = e, a = (G_(te) ?? vt).getType(), c = te?.markerMode === "editable" ? Ap : _h; + let l, d; + te?.markerMode === "editable" ? l = Dt(t, r) : te?.markerMode === "visible" && (d = !0); + const u = ze(e, Ak); + return Ne({ type: a, text: l, ...l === void 0 ? void 0 : { detail: 0, format: 0, mode: "normal", style: "" }, @@ -20712,66 +20716,66 @@ function Kv(e) { sid: n, altnumber: i, pubnumber: s, - showMarker: u, - unknownAttributes: d, + showMarker: d, + unknownAttributes: u, version: c }); } -function jv(e, t = [], r = !1) { +function sM(e, t = [], r = !1) { let { marker: n } = e; - me.isValidMarker(n, Sn?.extraValidMarkers) || _t?.warn(`Unexpected char marker '${n}'!`), n = n ?? ""; + be.isValidMarker(n, En?.extraValidMarkers) || Ct?.warn(`Unexpected char marker '${n}'!`), n = n ?? ""; const i = []; - if (ee?.markerMode === "editable") { + if (te?.markerMode === "editable") { const [a] = t; - ri(a) ? a.text = I + a.text : a && t.unshift(at(I)); + ii(a) ? a.text = R + a.text : a && t.unshift(ut(R)); } - t.length === 0 && t.push(at(Dt)), Ja(e.marker ?? "", i, r), i.push(...t); - const s = e.closed === "false", o = Ue(e, rk); - return s || eM(n, o, i), s || Ya(e.marker ?? "", i, !1, r), Ae({ - type: me.getType(), + t.length === 0 && t.push(ut(Ut)), tc(e.marker ?? "", i, r), i.push(...t); + const s = e.closed === "false", o = ze(e, bk); + return s || mM(n, o, i), s || rc(e.marker ?? "", i, !1, r), Ne({ + type: be.getType(), marker: n, unknownAttributes: o, children: i, direction: null, format: "", indent: 0, - version: up + version: _p }); } -function lg(e) { +function _g(e) { return { - type: Wr.getType(), + type: Xr.getType(), children: e, direction: null, format: "", indent: 0, textFormat: 0, textStyle: "", - version: hp + version: Mp }; } -function Bv(e, t = []) { +function oM(e, t = []) { let { marker: r } = e; - Ze.isValidMarker(r, Sn?.extraValidMarkers) || _t?.warn(`Unexpected para marker '${r}'!`), r = r ?? cr; + nt.isValidMarker(r, En?.extraValidMarkers) || Ct?.warn(`Unexpected para marker '${r}'!`), r = r ?? lr; const n = []; - if (gi(ee) && (ee?.markerMode === "editable" ? n.push( - ft(r), - at(I, fr, "token") - ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && n.push( - kt( + if (bi(te) && (te?.markerMode === "editable" ? n.push( + pt(r), + ut(R, pr, "token") + ) : (te?.markerMode === "visible" || te?.hasGutterParaMarkers) && n.push( + Tt( "marker", - we(r) + I, - ee?.hasGutterParaMarkers + Re(r) + R, + te?.hasGutterParaMarkers ) - )), n.push(...t), kl()) { + )), n.push(...t), Sl()) { const s = n.find( - (o) => !Dc(o) && !(ri(o) && o.text === I) + (o) => !Bc(o) && !(ii(o) && o.text === R) ); - ri(s) && !/^ +$/.test(s.text) && (s.text = s.text.replace(/^ +/, (o) => I.repeat(o.length))); + ii(s) && !/^ +$/.test(s.text) && (s.text = s.text.replace(/^ +/, (o) => R.repeat(o.length))); } - const i = Ue(e, uk); - return Ae({ - type: Ze.getType(), + const i = ze(e, Mk); + return Ne({ + type: nt.getType(), marker: r, unknownAttributes: i, children: n, @@ -20780,134 +20784,134 @@ function Bv(e, t = []) { indent: 0, textFormat: 0, textStyle: "", - version: gp + version: Ep }); } -function Tl() { +function Ml() { return { direction: null, format: "", indent: 0 }; } -function Vv(e, t = []) { - const r = Ue(e, hT); - return Ae({ - ...Tl(), - type: On.getType(), +function aM(e, t = []) { + const r = ze(e, NT); + return Ne({ + ...Ml(), + type: Rn.getType(), unknownAttributes: r, children: t, - version: Xp + version: lh }); } -function Wv(e, t = []) { - const r = Ue(e, yT), n = e.marker ?? Ra, i = []; - return ee?.markerMode === "editable" ? i.push( - ft(n), - at(I, fr, "token") - ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && i.push( - kt( +function cM(e, t = []) { + const r = ze(e, qT), n = e.marker ?? Fa, i = []; + return te?.markerMode === "editable" ? i.push( + pt(n), + ut(R, pr, "token") + ) : (te?.markerMode === "visible" || te?.hasGutterParaMarkers) && i.push( + Tt( "marker", - we(n) + I, - ee?.hasGutterParaMarkers + Re(n) + R, + te?.hasGutterParaMarkers ) - ), i.push(...t), Ae({ - ...Tl(), - type: fi.getType(), + ), i.push(...t), Ne({ + ...Ml(), + type: gi.getType(), marker: n, unknownAttributes: r, children: i, - version: eh + version: fh }); } -function Hv(e, t = []) { - const { marker: r, align: n, colspan: i } = e, s = [], o = r ?? $a, a = jp(o, i) ?? o; - ee?.markerMode === "editable" ? s.push( - ft(a), - at(I, fr, "token") - ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && s.push( - kt( +function lM(e, t = []) { + const { marker: r, align: n, colspan: i } = e, s = [], o = r ?? za, a = th(o, i) ?? o; + te?.markerMode === "editable" ? s.push( + pt(a), + ut(R, pr, "token") + ) : (te?.markerMode === "visible" || te?.hasGutterParaMarkers) && s.push( + Tt( "marker", - we(a) + I, - ee?.hasGutterParaMarkers + Re(a) + R, + te?.hasGutterParaMarkers ) ), s.push(...t); - const c = Ue( + const c = ze( e, - kT + $T ); - return Ae({ - ...Tl(), - type: pi.getType(), + return Ne({ + ...Ml(), + type: mi.getType(), marker: o, align: n, colspan: i, unknownAttributes: c, children: s, - version: rh + version: hh }); } -function Gv(e, t) { - const r = vk(t); +function uM(e, t) { + const r = zk(t); let n = () => { }; - return Sn?.noteCallerOnClick && (n = Sn.noteCallerOnClick), Ae({ + return En?.noteCallerOnClick && (n = En.noteCallerOnClick), Ne({ type: Gt.getType(), caller: e, previewText: r, onClick: n, - version: bh + version: Nh }); } -function Jv(e, t) { +function dM(e, t) { let { marker: r } = e; - Ee.isValidMarker(r, Sn?.extraValidMarkers) || _t?.warn(`Unexpected note marker '${r}'!`), r = r ?? Mc; - const { category: n } = e, i = e.caller ?? "*", s = e.closed === "false", o = s ? !1 : rl(ee?.noteMode), a = Ue(e, gb), c = ee?.isNoteShellEditable === !1 ? "token" : "normal"; - let l, u; - ee?.markerMode === "editable" ? (l = ft(r, "opening", !1, c), s || (u = ft(r, "closing"))) : ee?.markerMode === "visible" && (l = kt("marker", we(r) + " "), s || (u = kt("marker", rt(r)))); - const d = []; - let f; - if (l && d.push(l), ee?.markerMode === "editable" && !o) - f = at(At(i), void 0, c), d.push(f), nM(n, d), d.push(...t); + Pe.isValidMarker(r, En?.extraValidMarkers) || Ct?.warn(`Unexpected note marker '${r}'!`), r = r ?? wc; + const { category: n } = e, i = e.caller ?? "*", s = e.closed === "false", o = s ? !1 : cl(te?.noteMode), a = ze(e, Ob), c = te?.isNoteShellEditable === !1 ? "token" : "normal"; + let l, d; + te?.markerMode === "editable" ? (l = pt(r, "opening", !1, c), s || (d = pt(r, "closing"))) : te?.markerMode === "visible" && (l = Tt("marker", Re(r) + " "), s || (d = Tt("marker", ot(r)))); + const u = []; + let p; + if (l && u.push(l), te?.markerMode === "editable" && !o) + p = ut(Pt(i), void 0, c), u.push(p), kM(n, u), u.push(...t); else { - const p = at(I, fr, "token"); - f = Gv(i, t), d.push(f, p, ...t.flatMap(Yv(p))); + const f = ut(R, pr, "token"); + p = uM(i, t), u.push(p, f, ...t.flatMap(fM(f))); } - return u && d.push(u), Ae({ - type: Ee.getType(), + return d && u.push(d), Ne({ + type: Pe.getType(), marker: r, caller: i, isCollapsed: o, category: n, unknownAttributes: a, - children: d, + children: u, direction: null, format: "", indent: 0, - version: Bf + version: rp }); } -function Yv(e) { - return (t) => Yf(t) ? [t] : [t, e]; +function fM(e) { + return (t) => cp(t) ? [t] : [t, e]; } -function Xv(e) { +function pM(e) { let { marker: t } = e; - (!t || !Jt.isValidMarker(t, Sn?.extraValidMarkers)) && _t?.warn(`Unexpected milestone marker '${t}'!`), t = t ?? ""; - const { sid: r, eid: n } = e, i = Ue(e, vc), s = wp(e); - return Ae({ + (!t || !Jt.isValidMarker(t, En?.extraValidMarkers)) && Ct?.warn(`Unexpected milestone marker '${t}'!`), t = t ?? ""; + const { sid: r, eid: n } = e, i = ze(e, Oc), s = Bp(e); + return Ne({ type: Jt.getType(), marker: t, sid: r, eid: n, unknownAttributes: i, attributeOrder: s, - version: zf + version: Zf }); } -function qd(e, t = []) { +function zd(e, t = []) { return { - type: Qe.getType(), - typedIDs: { [Kr]: t }, + type: et.getType(), + typedIDs: { [Vr]: t }, children: e, direction: null, format: "", @@ -20915,21 +20919,21 @@ function qd(e, t = []) { version: 1 }; } -function Qv(e, t) { - const { marker: r } = e, n = e.type, i = Ue(e, Hb), s = []; - if (ee?.markerMode === "editable") { - const { opening: o, attributes: a, closingAttributes: c, closing: l } = Bp( +function hM(e, t) { + const { marker: r } = e, n = e.type, i = ze(e, lk), s = []; + if (te?.markerMode === "editable") { + const { opening: o, attributes: a, closingAttributes: c, closing: l } = rh( n, r, i ); - o && s.push(kt("marker", o)), a && s.push(kt("attribute", a)), s.push(...t), c && s.push(kt("attribute", c)), l && s.push(kt("marker", l)); + o && s.push(Tt("marker", o)), a && s.push(Tt("attribute", a)), s.push(...t), c && s.push(Tt("attribute", c)), l && s.push(Tt("marker", l)); } else s.push(...t); return s.forEach((o) => { - ri(o) && (o.mode = "token"); - }), Ae({ - type: Pn.getType(), + ii(o) && (o.mode = "token"); + }), Ne({ + type: wn.getType(), tag: n, marker: r, unknownAttributes: i, @@ -20937,27 +20941,27 @@ function Qv(e, t) { direction: null, format: "", indent: 0, - version: ep + version: fp }); } -function Zv(e) { +function gM(e) { return { - type: Or.getType(), + type: Lr.getType(), marker: e, - text: Ui(e), + text: ji(e), detail: 0, format: 0, // Editable marker mode edits the flagged bytes in place (the marker-edit engine pends and // settles them); every other mode has no engine to settle such an edit, so the node stays // atomic "token" text there — steppable and deletable whole, but not editable inside. - mode: ee?.markerMode === "editable" ? "normal" : "token", + mode: te?.markerMode === "editable" ? "normal" : "token", style: "", - version: Gp + version: oh }; } -function ft(e, t = "opening", r = !1, n = "normal") { +function pt(e, t = "opening", r = !1, n = "normal") { return { - type: hr.getType(), + type: gr.getType(), marker: e, markerSyntax: t, // Emit the flag only for nested glyphs; absence means non-nested (see MarkerNode.exportJSON). @@ -20970,9 +20974,9 @@ function ft(e, t = "opening", r = !1, n = "normal") { version: 1 }; } -function at(e, t = void 0, r = "normal") { +function ut(e, t = void 0, r = "normal") { const n = { - type: Ke.getType(), + type: Be.getType(), text: e, detail: 0, format: 0, @@ -20980,285 +20984,285 @@ function at(e, t = void 0, r = "normal") { style: "", version: 1 }; - return t !== void 0 && (n[ds] = { textType: t }), n; + return t !== void 0 && (n[gs] = { textType: t }), n; } -function kt(e, t, r = !1) { +function Tt(e, t, r = !1) { const n = { - type: Er.getType(), + type: wr.getType(), text: t, textType: e, - version: Jf + version: ap }; - return r && (n[ds] = { [Pc.key]: !0 }), n; + return r && (n[gs] = { [$c.key]: !0 }), n; } -function is(e, t) { +function cs(e, t) { return { - type: Ar.getType(), + type: qr.getType(), runKind: e, children: t, direction: null, format: "", indent: 0, - version: tp + version: pp }; } -function Ja(e, t, r = !1) { - ee?.markerMode === "editable" ? t.push(ft(e, "opening", r)) : ee?.markerMode === "visible" && t.push(kt("marker", we(e, r))); +function tc(e, t, r = !1) { + te?.markerMode === "editable" ? t.push(pt(e, "opening", r)) : te?.markerMode === "visible" && t.push(Tt("marker", Re(e, r))); } -function Ya(e, t, r = !1, n = !1) { - ee?.markerMode === "editable" ? r ? t.push(ft("", "selfClosing")) : t.push(ft(e, "closing", n)) : ee?.markerMode === "visible" && t.push( - kt( +function rc(e, t, r = !1, n = !1) { + te?.markerMode === "editable" ? r ? t.push(pt("", "selfClosing")) : t.push(pt(e, "closing", n)) : te?.markerMode === "visible" && t.push( + Tt( "marker", - r ? rt("") : rt(e, n) + r ? ot("") : ot(e, n) ) ); } -function eM(e, t, r) { - if (ee?.markerMode !== "editable" || !t) return; - const n = ar(t, po(e)); - n && r.push(at(n, "attribute")); +function mM(e, t, r) { + if (te?.markerMode !== "editable" || !t) return; + const n = cr(t, yo(e)); + n && r.push(ut(n, "attribute")); } -function Rd(e, t) { - if (e.type !== "ms" || ee?.markerMode !== "editable" && ee?.markerMode !== "visible") return; - const { marker: r, sid: n, eid: i } = e, s = Ue(e, vc), o = qp( +function Kd(e, t) { + if (e.type !== "ms" || te?.markerMode !== "editable" && te?.markerMode !== "visible") return; + const { marker: r, sid: n, eid: i } = e, s = ze(e, Oc), o = Vp( n, i, s, - wp(e) - ), a = ar(o, ho(r ?? "")); + Bp(e) + ), a = cr(o, bo(r ?? "")); if (!a) return; - const c = I + a; - ee?.markerMode === "editable" ? t.push(at(c, "attribute")) : t.push(kt("attribute", c)); + const c = R + a; + te?.markerMode === "editable" ? t.push(ut(c, "attribute")) : t.push(Tt("attribute", c)); } -function tM(e, t) { +function yM(e, t) { const r = e.marker ?? ""; - if (ee?.markerMode === "editable") { + if (te?.markerMode === "editable") { const n = []; - Ja(r, n), Rd(e, n), Ya(r, n, !0), t.push(is("milestone", n)); + tc(r, n), Kd(e, n), rc(r, n, !0), t.push(cs("milestone", n)); } else - Ja(r, t), Rd(e, t), Ya(r, t, !0); + tc(r, t), Kd(e, t), rc(r, t, !0); } -function $d(e, t, r) { +function jd(e, t, r) { t !== void 0 && r.push( - is(e, [ - ft(e, "opening"), - at(I + t, "attribute"), - ft(e, "closing") + cs(e, [ + pt(e, "opening"), + ut(R + t, "attribute"), + pt(e, "closing") ]) ); } -function rM(e, t) { - ee?.markerMode === "editable" && ($d("va", e.altnumber, t), $d("vp", e.pubnumber, t)); +function bM(e, t) { + te?.markerMode === "editable" && (jd("va", e.altnumber, t), jd("vp", e.pubnumber, t)); } -function nM(e, t) { +function kM(e, t) { e !== void 0 && t.push( - is("cat", [ - ft("cat", "opening"), - at(I + e, "attribute"), - ft("cat", "closing") + cs("cat", [ + pt("cat", "opening"), + ut(R + e, "attribute"), + pt("cat", "closing") ]) ); } -function iM(e, t, r) { +function TM(e, t, r) { e !== void 0 && r.push( - is("ca", [ - ft("ca", "opening"), - at(I + e, "attribute"), - ft("ca", "closing") + cs("ca", [ + pt("ca", "opening"), + ut(R + e, "attribute"), + pt("ca", "closing") ]) ), t !== void 0 && r.push( - is("cp", [ - ft("cp", "opening"), - at(I + t, "attribute") + cs("cp", [ + pt("cp", "opening"), + ut(R + t, "attribute") ]) ); } -function Id(e, t) { +function Bd(e, t) { return e.length <= 0 || t === 0 ? e : e.map((r) => r - t); } -function sM(e, t) { +function xM(e, t) { const r = e.indexOf(t, 0); r > -1 && e.splice(r, 1); } -function Ld(e, t) { - t.marker === bn && t.sid !== void 0 && e.push(t.sid), t.marker === Yn && t.eid !== void 0 && sM(e, t.eid); +function Vd(e, t) { + t.marker === xn && t.sid !== void 0 && e.push(t.sid), t.marker === Qn && t.eid !== void 0 && xM(e, t.eid); } -function Xa(e, t, r = !1, n = []) { +function nc(e, t, r = !1, n = []) { if (t.length <= 0 || t[0] >= e.length) return e; const i = t.shift(), s = t.length > 0 ? t.shift() : e.length - 1; if (i === void 0 || s === void 0 || s >= e.length || e.length <= 0) return e; - const o = e.slice(0, i), a = r ? [qd(o, [...n])] : o, c = e[i]; - Ld(n, c); - const l = Xa( + const o = e.slice(0, i), a = r ? [zd(o, [...n])] : o, c = e[i]; + Vd(n, c); + const l = nc( e.slice(i + 1, s), - Id(t, i + 1), - c.marker === bn, + Bd(t, i + 1), + c.marker === xn, n - ), u = qd(l, [...n]), d = e[s]; - Ld(n, d); - const f = Xa( + ), d = zd(l, [...n]), u = e[s]; + Vd(n, u); + const p = nc( e.slice(s + 1), - Id(t, s + 1), - d.marker === bn, + Bd(t, s + 1), + u.marker === xn, n ); - return [...a, u, ...f]; + return [...a, d, ...p]; } -function Ur(e, t = !1) { +function Kr(e, t = !1) { const r = [], n = []; return e?.forEach((i) => { if (typeof i == "string") - i && n.push(at(kl() ? rg(i) : i)); + i && n.push(ut(Sl() ? gg(i) : i)); else if (!i.type) - _t?.error("Marker type is missing!"); + Ct?.error("Marker type is missing!"); else switch (i.type) { - case Ut.getType(): - n.push(Fv(i)); + case Ft.getType(): + n.push(rM(i)); break; - case Nt.getType(): - n.push(zv(i)); + case Ot.getType(): + n.push(nM(i)); break; - case dt.getType(): - ee?.hasSpacing || n.push(qv), n.push(Kv(i)), rM(i, n); + case ft.getType(): + te?.hasSpacing || n.push(JS), n.push(iM(i)), bM(i, n); break; - case me.getType(): + case be.getType(): n.push( - jv(i, Ur(i.content, !0), t) + sM(i, Kr(i.content, !0), t) ); break; - case Ze.getType(): - n.push(Bv(i, Ur(i.content))); + case nt.getType(): + n.push(oM(i, Kr(i.content))); break; - case Ee.getType(): - n.push(Jv(i, Ur(i.content))); + case Pe.getType(): + n.push(dM(i, Kr(i.content))); break; case Jt.getType(): - Kf(i.marker ?? "") && (r.push(n.length), i.sid !== void 0 && bl?.push(i.sid)), n.push(Xv(i)), tM(i, n); + ep(i.marker ?? "") && (r.push(n.length), i.sid !== void 0 && vl?.push(i.sid)), n.push(pM(i)), yM(i, n); break; - case Or.getType(): - n.push(Zv(i.marker ?? "")); + case Lr.getType(): + n.push(gM(i.marker ?? "")); break; - case Yp: - n.push(Vv(i, Ur(i.content))); + case ch: + n.push(aM(i, Kr(i.content))); break; - case Zp: - n.push(Wv(i, Ur(i.content))); + case dh: + n.push(cM(i, Kr(i.content))); break; - case th: - n.push(Hv(i, Ur(i.content))); + case ph: + n.push(lM(i, Kr(i.content))); break; default: - _t?.warn(`Unknown type-marker '${i.type}-${i.marker}'!`), n.push(Qv(i, Ur(i.content))); + Ct?.warn(`Unknown type-marker '${i.type}-${i.marker}'!`), n.push(hM(i, Kr(i.content))); } - }), Xa(n, r); + }), nc(n, r); } -function Qa(e) { +function ic(e) { const t = e.findIndex( - (n) => sp(n) || kp(n) || wc(n) || // A table is a block root in its own right; without this it would be swept into an implied + (n) => yp(n) || Op(n) || Dc(n) || // A table is a block root in its own right; without this it would be swept into an implied // para alongside any sibling text/verse nodes. - mT(n) + wT(n) ); if (t >= 0) { - const n = Qa(e.slice(0, t)), i = e[t], s = Qa(e.slice(t + 1)); + const n = ic(e.slice(0, t)), i = e[t], s = ic(e.slice(t + 1)); return [...n, i, ...s]; - } else if (e.some((n) => "text" in n && "mode" in n || dh(n))) - return [lg(e)]; + } else if (e.some((n) => "text" in n && "mode" in n || Ch(n))) + return [_g(e)]; return e; } -const Cr = { - initialize: Rv, - reset: $v, - serializeEditorState: Iv +const Ar = { + initialize: YS, + reset: XS, + serializeEditorState: QS }; -function ug(e) { +function Cg(e) { if (e && !O(e)) { - if (v(e)) return e; - if (F(e)) + if (S(e)) return e; + if (L(e)) for (const t of e.getChildren()) { - const r = ug(t); + const r = Cg(t); if (r) return r; } } } -function oM() { +function _M() { const e = w(); - if (!N(e)) return !1; + if (!P(e)) return !1; if (e.isCollapsed()) { const t = e.anchor.getNode(), r = e.anchor.offset; - if ((v(t) && !O(t) ? Cn(t) : void 0) && v(t)) { - const i = pe(" "); + if ((S(t) && !O(t) ? Mn(t) : void 0) && S(t)) { + const i = he(" "); if (r <= 0) t.insertBefore(i); else if (r >= t.getTextContentSize()) t.insertAfter(i); else { const [o] = t.splitText(r); o.insertAfter(i); } - ni(i, { renderGlyphs: !0, closeImplicitSpans: !0 }); - const s = ug(i.getNextSibling()); + si(i, { renderGlyphs: !0, closeImplicitSpans: !0 }); + const s = Cg(i.getNextSibling()); if (s) { - const o = s.getTextContent(), a = o.startsWith(I) ? I : "", c = o.slice(a.length); + const o = s.getTextContent(), a = o.startsWith(R) ? R : "", c = o.slice(a.length); c.startsWith(" ") && s.setTextContent(a + c.slice(1)); } return i.select(1, 1), !0; } - return v(t) && t.getTextContent()[r] === " " ? (t.select(r + 1, r + 1), !0) : (e.insertText(" "), !0); + return S(t) && t.getTextContent()[r] === " " ? (t.select(r + 1, r + 1), !0) : (e.insertText(" "), !0); } - for (const t of dg(e)) { - if (!Cn(t)) continue; - ni(t, { renderGlyphs: !0, closeImplicitSpans: !0 }); + for (const t of vg(e)) { + if (!Mn(t)) continue; + si(t, { renderGlyphs: !0, closeImplicitSpans: !0 }); const r = t.getLatest(), n = r.getTextContent(); - n.startsWith(I) && r.setTextContent(n.slice(I.length)); + n.startsWith(R) && r.setTextContent(n.slice(R.length)); } return !0; } -function dg(e) { - const [t, r] = kc(e), [n, i] = e.isBackward() ? [r, t] : [t, r], s = e.getNodes(), o = []; +function vg(e) { + const [t, r] = Sc(e), [n, i] = e.isBackward() ? [r, t] : [t, r], s = e.getNodes(), o = []; return s.forEach((a, c) => { - if (!v(a) || O(a) || ne(a, oe) === "attribute") return; - const l = a.getTextContentSize(), u = c === 0 ? n : 0, d = c === s.length - 1 ? Math.min(i, l) : l; - if (u >= d) return; - const f = a.splitText(u, d), p = f.length === 3 ? f[1] : d === l ? f[f.length - 1] : f[0]; - p && o.push(p); + if (!S(a) || O(a) || ie(a, ae) === "attribute") return; + const l = a.getTextContentSize(), d = c === 0 ? n : 0, u = c === s.length - 1 ? Math.min(i, l) : l; + if (d >= u) return; + const p = a.splitText(d, u), f = p.length === 3 ? p[1] : u === l ? p[p.length - 1] : p[0]; + f && o.push(f); }), o; } -function aM() { +function CM() { const e = w(); - if (!N(e)) return !1; + if (!P(e)) return !1; const t = e.focus.getNode(); - return Cn(t) ? Se(Bc(t)) : !1; + return Mn(t) ? ve(Yc(t)) : !1; } -function fg() { +function Sg() { let e = w(); - if (!N(e) || !e.isCollapsed()) return !1; + if (!P(e) || !e.isCollapsed()) return !1; let t = e.anchor.getNode(); - if (O(t) && !Hc(t, e.anchor.offset)) { + if (O(t) && !Zc(t, e.anchor.offset)) { const c = t.getParent(); - if (D(c) && t.is(c.getLastChild())) { - if (c.selectNext(0, 0), e = w(), !N(e) || !e.isCollapsed()) return !1; + if (I(c) && t.is(c.getLastChild())) { + if (c.selectNext(0, 0), e = w(), !P(e) || !e.isCollapsed()) return !1; t = e.anchor.getNode(); } } - if (!v(t) || O(t) || !Cn(t)) return !1; - const r = Bc(t); - if (!Se(r)) return !1; - const n = pe(""), i = e.anchor.offset; + if (!S(t) || O(t) || !Mn(t)) return !1; + const r = Yc(t); + if (!ve(r)) return !1; + const n = he(""), i = e.anchor.offset; if (i <= 0) t.insertBefore(n); else if (i >= t.getTextContentSize()) t.insertAfter(n); else { const [, c] = t.splitText(i); c.insertBefore(n); } - ni(n, { renderGlyphs: !0 }); + si(n, { renderGlyphs: !0 }); const s = n.getNextSiblings(); n.remove(); const o = r.insertNewAfter(e, !1); o.append(...s); const [a] = s; - return D(a) ? Vc(a) : o.select(0, 0), !0; + return I(a) ? Xc(a) : o.select(0, 0), !0; } -const pg = { +const Mg = { c: { // Deliberately still trusts reference.chapterNum, unlike `v` below - the chapter-number // reinstatement work (a separate branch/PR) owns rewriting this action to scan the tree. @@ -21267,23 +21271,23 @@ const pg = { return { content: [{ type: "chapter", marker: "c", - number: `${Tp(Fe().getChildren(), t) !== void 0 ? t + 1 : t}` + number: `${wp(Ke().getChildren(), t) !== void 0 ? t + 1 : t}` }] }; } }, v: { action: () => { - const e = w(), t = $c(e), r = Xc(t); + const e = w(), t = zc(e), r = nl(t); let n, i = !1; if (!r) n = "1"; else { const o = r.getNumber(); - n = Ek(0, o); - const a = sx(r); + n = jk(0, o); + const a = xx(r); if (a) { const c = a.getNumber(); - i = n === c || Ep(c) && Ic(parseInt(n, 10), c); + i = n === c || Up(c) && Kc(parseInt(n, 10), c); } } return { content: [{ @@ -21294,32 +21298,32 @@ const pg = { } } }; -function Za(e, t) { - return Ee.isValidMarker(e, t) || !!pg[e] || Ze.isValidMarker(e, t) || me.isValidMarker(e, t); +function sc(e, t) { + return Pe.isValidMarker(e, t) || !!Mg[e] || nt.isValidMarker(e, t) || be.isValidMarker(e, t); } -function cM(e, t) { - return me.isNoteContentMarker(e) ? !1 : me.isValidMarker(e, t); +function vM(e, t) { + return be.isNoteContentMarker(e) ? !1 : be.isValidMarker(e, t); } -function hg(e, t, r, n, i, s) { - const o = Th( +function Eg(e, t, r, n, i, s) { + const o = wh( e, void 0, void 0, t, - n ?? Eo(), + n ?? Oo(), i ?? {}, s ); return o && !o.getIsCollapsed() && (r.current = o.getKey()), o?.getKey(); } -function ec(e, t, r, n, i, s, o) { - if (Ee.isValidMarker(e, n?.extraValidMarkers)) { +function oc(e, t, r, n, i, s, o) { + if (Pe.isValidMarker(e, n?.extraValidMarkers)) { let l; - return { action: (d) => { - d.editor.update(() => { - l = hg( + return { action: (u) => { + u.editor.update(() => { + l = Eg( e, - d.reference, + u.reference, t, r, n, @@ -21328,126 +21332,126 @@ function ec(e, t, r, n, i, s, o) { }, s); }, label: void 0, getInsertedNoteKey: () => l }; } - const a = hM(e, n?.extraValidMarkers); + const a = NM(e, n?.extraValidMarkers); return a ? { action: (l) => { l.editor.update(() => { - const u = w(); - N(u) && (ah(u), l.noteText = u.getTextContent()); - const { content: d, highlightInserted: f } = a.action(l), p = Uu(d, Cr, r), m = Go(p); - if (N(u)) { - const g = u.anchor.getNode(), y = g.getParent(), k = Cn(g), _ = u.anchor.key === u.focus.key; - if (D(m) && k && _ && !ga(m, o)) - dM( - u, - m, + const d = w(); + P(d) && (kh(d), l.noteText = d.getTextContent()); + const { content: u, highlightInserted: p } = a.action(l), f = Hu(u, Ar, r), g = Zo(f); + if (P(d)) { + const h = d.anchor.getNode(), y = h.getParent(), k = Mn(h), _ = d.anchor.key === d.focus.key; + if (I(g) && k && _ && !Ta(g, o)) + EM( + d, g, + h, r?.markerMode === "editable" ); - else if (D(m) && !_ && !ga(m, o) && fM(u)) - pM(u, m, r?.markerMode === "editable"); - else if (u.getTextContent().length > 0) - gM( - u, - () => Go(p) + else if (I(g) && !_ && !Ta(g, o) && AM(d)) + PM(d, g, r?.markerMode === "editable"); + else if (d.getTextContent().length > 0) + OM( + d, + () => Zo(f) ); - else if (F(m) && !m.isInline()) { - const S = u.insertParagraph(); - if (S) { - const P = S.getChildren(); - m.append(...P), S.replace(m), Se(m) && yi(m) || m.selectStart(); + else if (L(g) && !g.isInline()) { + const C = d.insertParagraph(); + if (C) { + const M = C.getChildren(); + g.append(...M), C.replace(g), ve(g) && Ti(g) || g.selectStart(); } - } else if (D(m) && v(g) && !O(g) && D(g.getParent()) && u.isCollapsed() && // NEST-able only. A non-NEST style at a caret inside ANY char span — nested or note-level + } else if (I(g) && S(h) && !O(h) && I(h.getParent()) && d.isCollapsed() && // NEST-able only. A non-NEST style at a caret inside ANY char span — nested or note-level // — is already claimed by the `$applyNonNestInsideChar` branch above, whose guard is this // one minus this test. Stating it here rather than branching on it inside keeps that // division visible at the guard instead of implying a second non-NEST path exists. - ga(m, o)) { - const S = g.getParent(); - if (D(S)) { - const P = u.anchor.offset; - if (P === 0) g.insertBefore(m); - else if (P >= g.getTextContentSize()) g.insertAfter(m); + Ta(g, o)) { + const C = h.getParent(); + if (I(C)) { + const M = d.anchor.offset; + if (M === 0) h.insertBefore(g); + else if (M >= h.getTextContentSize()) h.insertAfter(g); else { - const [B] = g.splitText(P); - B.insertAfter(m); + const [z] = h.splitText(M); + z.insertAfter(g); } - m.getChildren().forEach((B) => { - O(B) && B.setNested(!0); + g.getChildren().forEach((z) => { + O(z) && z.setNested(!0); }); - const A = m.getChildren().find((B) => v(B) && !O(B)); - A && v(A) ? A.select( - A.getTextContentSize(), - A.getTextContentSize() - ) : m.selectEnd(); + const E = g.getChildren().find((z) => S(z) && !O(z)); + E && S(E) ? E.select( + E.getTextContentSize(), + E.getTextContentSize() + ) : g.selectEnd(); } - } else if (v(g) && !O(g) && u.isCollapsed() && (j(y) || D(y) && j(y.getParent()))) { - const S = D(y) ? y : void 0, P = S ? lM(g, u.anchor.offset) : []; - let B = (S ?? g).insertAfter(m); - if (Pr(m)) { - const M = { - ...r || Eo(), + } else if (S(h) && !O(h) && d.isCollapsed() && (K(y) || I(y) && K(y.getParent()))) { + const C = I(y) ? y : void 0, M = C ? SM(h, d.anchor.offset) : []; + let z = (C ?? h).insertAfter(g); + if (Rr(g)) { + const W = { + ...r || Oo(), markerMode: "hidden" - }, q = Uu( - d, - Cr, - M - ), $ = Go(q); - B = B.insertAfter($); + }, j = Hu( + u, + Ar, + W + ), U = Zo(j); + z = z.insertAfter(U); } - if (P.length > 0 && S) { - const M = to(S).append(...P); - B.insertAfter(M), S.isEmpty() && S.remove(); - } else v(B.getNextSibling()) || B.insertAfter(pe(I)); - F(B) && B.selectEnd(); - } else if (u.insertNodes([m]), vM(m), f) { - const S = xf(); - S.add(m.getKey()), ei(S); - } else if (D(m)) { - const S = m.getChildren().find((P) => v(P) && !O(P)); - S && v(S) ? S.select( - S.getTextContentSize(), - S.getTextContentSize() - ) : m.selectEnd(); + if (M.length > 0 && C) { + const W = so(C).append(...M); + z.insertAfter(W), C.isEmpty() && C.remove(); + } else S(z.getNextSibling()) || z.insertAfter(he(R)); + L(z) && z.selectEnd(); + } else if (d.insertNodes([g]), zM(g), p) { + const C = qf(); + C.add(g.getKey()), Tn(C); + } else if (I(g)) { + const C = g.getChildren().find((M) => S(M) && !O(M)); + C && S(C) ? C.select( + C.getTextContentSize(), + C.getTextContentSize() + ) : g.selectEnd(); } else { - const S = m.getNextSibling(); - S ? S.selectStart() : m.selectStart(); + const C = g.getNextSibling(); + C ? C.selectStart() : g.selectStart(); } } else - u?.insertNodes([m]); + d?.insertNodes([g]); }, s); }, label: a?.label } : { action: () => { }, label: void 0 }; } -function lM(e, t) { +function SM(e, t) { const r = e.getTextContentSize(); let n; return t <= 0 ? n = e : t >= r ? n = e.getNextSibling() : n = e.splitText(t)[1] ?? e.getNextSibling(), n ? [n, ...n.getNextSiblings()] : []; } -function ga(e, t) { - return ((t ?? Vs).markers[e.getMarker()]?.occursUnder ?? []).includes("NEST") && e.getUnknownAttributes()?.closed !== "false"; +function Ta(e, t) { + return ((t ?? Js).markers[e.getMarker()]?.occursUnder ?? []).includes("NEST") && e.getUnknownAttributes()?.closed !== "false"; } -function uM(e, t) { +function MM(e, t) { t && e.getChildren().forEach((i) => { O(i) && i.setNested(!0); - }), e.getChildren().some((i) => O(i) && i.getMarkerSyntax() === "closing") || e.append(ot(e.getMarker(), "closing", t)); + }), e.getChildren().some((i) => O(i) && i.getMarkerSyntax() === "closing") || e.append(lt(e.getMarker(), "closing", t)); const n = e.getUnknownAttributes(); if (n?.closed === "false") { const i = { ...n }; delete i.closed, e.setUnknownAttributes(Object.keys(i).length > 0 ? i : void 0); } } -function dM(e, t, r, n) { +function EM(e, t, r, n) { let i = t; - if (e.anchor.type === "element" && D(r)) { + if (e.anchor.type === "element" && I(r)) { const o = r.getChildren(), a = o[e.anchor.offset - 1], c = o[e.anchor.offset]; a ? a.insertAfter(t) : c ? c.insertBefore(t) : r.append(t); - } else if (e.isCollapsed() || !v(r)) { + } else if (e.isCollapsed() || !S(r)) { const o = e.anchor.offset; - if (v(r) && o > 0 && o < r.getTextContentSize()) { + if (S(r) && o > 0 && o < r.getTextContentSize()) { const [a] = r.splitText(o); a.insertAfter(t); - } else v(r) && o >= r.getTextContentSize() ? r.insertAfter(t) : r.insertBefore(t); + } else S(r) && o >= r.getTextContentSize() ? r.insertAfter(t) : r.insertBefore(t); } else { - const [o, a] = ai(e); + const [o, a] = li(e); let c = r; if (o > 0) { const l = c.splitText(o); @@ -21455,39 +21459,39 @@ function dM(e, t, r, n) { } c.getTextContentSize() > a - o && (c = c.splitText(a - o)[0]), i = c; } - if (ni(i, { renderGlyphs: n }), i !== t) { - i.insertBefore(t), v(i) && !i.getTextContent().startsWith(I) && i.setTextContent(I + i.getTextContent()); - const o = t.getChildren().find((a) => v(a) && !O(a)); + if (si(i, { renderGlyphs: n }), i !== t) { + i.insertBefore(t), S(i) && !i.getTextContent().startsWith(R) && i.setTextContent(R + i.getTextContent()); + const o = t.getChildren().find((a) => S(a) && !O(a)); o ? o.replace(i) : t.append(i); } - const s = t.getChildren().find((o) => v(o) && !O(o)); - v(s) ? s.select(s.getTextContentSize(), s.getTextContentSize()) : t.selectEnd(); + const s = t.getChildren().find((o) => S(o) && !O(o)); + S(s) ? s.select(s.getTextContentSize(), s.getTextContentSize()) : t.selectEnd(); } -function fM(e) { +function AM(e) { let t, r = !1; for (const n of e.getNodes()) { - if (O(n) || D(n)) continue; - if (!v(n) || n.getType() !== Ke.getType() || ne(n, oe) === "attribute") return !1; - const i = Bc(n); + if (O(n) || I(n)) continue; + if (!S(n) || n.getType() !== Be.getType() || ie(n, ae) === "attribute") return !1; + const i = Yc(n); if (!i) return !1; if (t === void 0) t = i; else if (!t.is(i)) return !1; - Cn(n) && (r = !0); + Mn(n) && (r = !0); } return r; } -function pM(e, t, r) { - const n = dg(e); +function PM(e, t, r) { + const n = vg(e); if (n.length === 0) return; n.forEach((a) => { - if (!Cn(a)) return; - ni(a, { renderGlyphs: r }); + if (!Mn(a)) return; + si(a, { renderGlyphs: r }); const c = a.getLatest(), l = c.getTextContent(); - l.startsWith(I) && c.setTextContent(l.slice(I.length)); + l.startsWith(R) && c.setTextContent(l.slice(R.length)); }); const i = n[0].getLatest(); - i.insertBefore(t), i.getTextContent().startsWith(I) || i.setTextContent(I + i.getTextContent()); - const s = t.getChildren().find((a) => v(a) && !O(a)); + i.insertBefore(t), i.getTextContent().startsWith(R) || i.setTextContent(R + i.getTextContent()); + const s = t.getChildren().find((a) => S(a) && !O(a)); s ? s.replace(i) : t.append(i); let o = i.getLatest(); n.slice(1).forEach((a) => { @@ -21495,24 +21499,24 @@ function pM(e, t, r) { o.insertAfter(c), o = c; }), o.select(o.getTextContentSize(), o.getTextContentSize()); } -function hM(e, t) { - let r = pg[e]; - return r || (Ze.isValidMarker(e, t) ? r = { - action: () => ({ content: [{ type: Ze.getType(), marker: e, content: [] }] }) - } : me.isValidMarker(e, t) && (r = { +function NM(e, t) { + let r = Mg[e]; + return r || (nt.isValidMarker(e, t) ? r = { + action: () => ({ content: [{ type: nt.getType(), marker: e, content: [] }] }) + } : be.isValidMarker(e, t) && (r = { action: () => { - const n = { type: me.getType(), marker: e }; - return (me.isValidFootnoteMarker(e) || me.isValidCrossReferenceMarker(e)) && (n.closed = "false"), { content: [n] }; + const n = { type: be.getType(), marker: e }; + return (be.isValidFootnoteMarker(e) || be.isValidCrossReferenceMarker(e)) && (n.closed = "false"), { content: [n] }; } })), r; } -function gM(e, t) { - const r = e.getNodes(), [n, i] = ai(e); +function OM(e, t) { + const r = e.getNodes(), [n, i] = li(e); let s; r.forEach((o, a) => { - if (F(s) && s.isParentOf(o)) + if (L(s) && s.isParentOf(o)) return; - const c = gg( + const c = Ag( o, a === 0, a === r.length - 1, @@ -21524,35 +21528,35 @@ function gM(e, t) { return; } let l = !1; - s || (s = t(), c.insertBefore(s), l = !0, D(s) && s.getChildren().some((d) => O(d) && d.getMarkerSyntax() === "opening") && uM(s, D(s.getParent()))), yM(c, s, l); - }), (v(s) || F(s)) && s.selectEnd(); + s || (s = t(), c.insertBefore(s), l = !0, I(s) && s.getChildren().some((u) => O(u) && u.getMarkerSyntax() === "opening") && MM(s, I(s.getParent()))), qM(c, s, l); + }), (S(s) || L(s)) && s.selectEnd(); } -function ai(e) { +function li(e) { const t = e.anchor.offset, r = e.focus.offset; return e.isBackward() ? [r, t] : [t, r]; } -function xl(e) { - return Ce(e) || j(e) || j(e.getParent()); +function El(e) { + return Ce(e) || K(e) || K(e.getParent()); } -function gg(e, t, r, n, i) { - if (!xl(e)) { - if (v(e)) - return mM(e, t, r, n, i); - if (F(e) && e.isInline()) +function Ag(e, t, r, n, i) { + if (!El(e)) { + if (S(e)) + return wM(e, t, r, n, i); + if (L(e) && e.isInline()) return e; } } -function mM(e, t, r, n, i) { +function wM(e, t, r, n, i) { const s = e.getTextContentSize(), o = t ? n : 0, a = r ? i : s; if (o === 0 && a === 0) return; const c = e.splitText(o, a); return c.length === 1 ? c[0] : c.length === 3 || a === s ? c[1] : c[0]; } -function yM(e, t, r) { - if (v(t)) { - const n = tc(e, t); +function qM(e, t, r) { + if (S(t)) { + const n = ac(e, t); t.setTextContent(n), e.remove(); - } else if (F(t)) { + } else if (L(t)) { const n = t.getChildren(), i = n.find( (s) => O(s) && s.getMarkerSyntax() !== "opening" ); @@ -21564,190 +21568,190 @@ function yM(e, t, r) { for (let o = 0; o < s; o++) t.getFirstChild()?.remove(); } else t.append(e); - tc(e, t), r && D(t) && t.getChildren().some((s) => O(s)) && v(e) && !O(e) && !e.getTextContent().startsWith(I) && e.setTextContent(I + e.getTextContent()); + ac(e, t), r && I(t) && t.getChildren().some((s) => O(s)) && S(e) && !O(e) && !e.getTextContent().startsWith(R) && e.setTextContent(R + e.getTextContent()); } } -function tc(e, t) { +function ac(e, t) { let r = e.getTextContent(); - if (v(e) && t.isInline() && r.startsWith(" ") && r.trimStart() !== "") { + if (S(e) && t.isInline() && r.startsWith(" ") && r.trimStart() !== "") { r = r.trimStart(), e.setTextContent(r); const n = t.getPreviousSibling(); - Jc(n), v(n) || t.insertBefore(pe(" ")); + tl(n), S(n) || t.insertBefore(he(" ")); } return r; } -function mg(e, t, r) { +function Pg(e, t, r) { if (e.isCollapsed()) { - const u = e.anchor.getNode(), d = e.anchor.offset, f = vn(u, t); - if (!f) return !1; - const p = v(u) ? u.getTextContentSize() : 0; - if (Dd(f, r), v(u) && u.isAttached()) { - const m = u.getTextContentSize(), g = Math.max(p - m, 0), y = Math.max(0, Math.min(d - g, m)), k = w(); - N(k) && k.setTextNodeRange(u, y, u, y); + const d = e.anchor.getNode(), u = e.anchor.offset, p = An(d, t); + if (!p) return !1; + const f = S(d) ? d.getTextContentSize() : 0; + if (Wd(p, r), S(d) && d.isAttached()) { + const g = d.getTextContentSize(), h = Math.max(f - g, 0), y = Math.max(0, Math.min(u - h, g)), k = w(); + P(k) && k.setTextNodeRange(d, y, d, y); } return !0; } - const n = e.getNodes(), i = e.isBackward(), [s, o] = ai(e); - if (!Cl(n, t, s, o)) return !1; - const a = _l(n, s, o); + const n = e.getNodes(), i = e.isBackward(), [s, o] = li(e); + if (!Pl(n, t, s, o)) return !1; + const a = Al(n, s, o); if (a.length === 0) return !1; const c = /* @__PURE__ */ new Set(); let l = !1; - return a.forEach((u) => { - const d = vn(u, t); - if (!d || c.has(d.getKey())) return; - c.add(d.getKey()); - const f = Tg(d, a); - f && (Dd(f, r), l = !0); - }), xg(a, i), l; + return a.forEach((d) => { + const u = An(d, t); + if (!u || c.has(u.getKey())) return; + c.add(u.getKey()); + const p = qg(u, a); + p && (Wd(p, r), l = !0); + }), Rg(a, i), l; } -function Dd(e, t) { +function Wd(e, t) { e.getChildren().forEach((n) => { - Ft(n) && n.remove(); + zt(n) && n.remove(); }); const r = e.getChildren(); - if (r.length === 0 || e.getTextContent() === Dt) { + if (r.length === 0 || e.getTextContent() === Ut) { e.remove(); return; } t?.markerMode === "editable" && r.forEach((n) => { const i = n.getTextContent(); - v(n) && i.startsWith(I) && n.setTextContent(i.slice(I.length)); - }), _a(e); + S(n) && i.startsWith(R) && n.setTextContent(i.slice(R.length)); + }), Aa(e); } -function _l(e, t, r) { +function Al(e, t, r) { const n = []; return e.forEach((i, s) => { - const o = gg( + const o = Ag( i, s === 0, s === e.length - 1, t, r ); - v(o) && n.push(o); + S(o) && n.push(o); }), n; } -function vn(e, t) { +function An(e, t) { let r = e, n; - for (; r && !Se(r); ) { - if (j(r)) return; - !n && D(r) && (t === void 0 || r.getMarker() === t) && (n = r), r = r.getParent(); + for (; r && !ve(r); ) { + if (K(r)) return; + !n && I(r) && (t === void 0 || r.getMarker() === t) && (n = r), r = r.getParent(); } return n; } -function yg(e) { - const t = nt( +function Ng(e) { + const t = rt( e, - (r) => j(r) || Se(r) + (r) => K(r) || ve(r) ); - return j(t); + return K(t); } -function bg(e) { +function Og(e) { return e.filter( - (t) => !xl(t) && (v(t) || F(t) && t.isInline()) + (t) => !El(t) && (S(t) || L(t) && t.isInline()) ); } -function bM(e, t, r) { +function RM(e, t, r) { const n = /* @__PURE__ */ new Set(); return e.forEach((i, s) => { - if (!v(i) || xl(i)) return; + if (!S(i) || El(i)) return; const o = i.getTextContentSize(), a = s === 0 ? t : 0, c = s === e.length - 1 ? r : o; a === 0 && c === o && n.add(i.getKey()); }), n; } -function kM(e, t, r) { +function $M(e, t, r) { return e.getChildren().some( - (n) => F(n) && t.some((i) => n.isParentOf(i)) && !kg(n, r) + (n) => L(n) && t.some((i) => n.isParentOf(i)) && !wg(n, r) ); } -function Cl(e, t, r, n, i) { - const s = bg(e), o = bM(e, r, n), a = /* @__PURE__ */ new Set(); +function Pl(e, t, r, n, i) { + const s = Og(e), o = RM(e, r, n), a = /* @__PURE__ */ new Set(); return s.some((c) => { - const l = vn(c, t); - return !l || a.has(l.getKey()) || (a.add(l.getKey()), l.getMarker() === i) ? !1 : !kM(l, s, o); + const l = An(c, t); + return !l || a.has(l.getKey()) || (a.add(l.getKey()), l.getMarker() === i) ? !1 : !$M(l, s, o); }); } -function kg(e, t) { - return e.getAllTextNodes().every((r) => t.has(r.getKey()) || Ft(r)); +function wg(e, t) { + return e.getAllTextNodes().every((r) => t.has(r.getKey()) || zt(r)); } -function Tg(e, t) { +function qg(e, t) { const r = new Set(t.map((l) => l.getKey())), n = e.getChildren(), i = []; - for (const [l, u] of n.entries()) - if (r.has(u.getKey())) + for (const [l, d] of n.entries()) + if (r.has(d.getKey())) i.push(l); - else if (F(u) && t.some((d) => u.isParentOf(d))) { - if (!kg(u, r)) return; + else if (L(d) && t.some((u) => d.isParentOf(u))) { + if (!wg(d, r)) return; i.push(l); } if (i.length === 0) return; let s = i[0], o = i[i.length - 1]; - if (s > 0 && Ft(n[s - 1]) && (s -= 1), o < n.length - 1 && Ft(n[o + 1]) && (o += 1), s === 0 && o === n.length - 1) return e; + if (s > 0 && zt(n[s - 1]) && (s -= 1), o < n.length - 1 && zt(n[o + 1]) && (o += 1), s === 0 && o === n.length - 1) return e; const a = n.slice(o + 1); - a.length > 0 && e.insertAfter(to(e).append(...a)); + a.length > 0 && e.insertAfter(so(e).append(...a)); const c = n.slice(0, s); - return c.length > 0 && e.insertBefore(to(e).append(...c)), e; + return c.length > 0 && e.insertBefore(so(e).append(...c)), e; } -function to(e) { - return wy(e); +function so(e) { + return Vy(e); } -function xg(e, t) { +function Rg(e, t) { const r = w(), n = e[0], i = e[e.length - 1]; - if (!N(r) || !n.isAttached() || !i.isAttached()) + if (!P(r) || !n.isAttached() || !i.isAttached()) return; const s = i.getTextContentSize(); t ? r.setTextNodeRange(i, s, n, 0) : r.setTextNodeRange(n, 0, i, s); } -function TM(e, t, r) { +function LM(e, t, r) { if (e.isCollapsed()) { - const l = vn(e.anchor.getNode(), r); - return !l || l.getMarker() === t ? !1 : (Su(l, t), !0); + const l = An(e.anchor.getNode(), r); + return !l || l.getMarker() === t ? !1 : (wu(l, t), !0); } - const n = e.getNodes(), [i, s] = ai(e); - if (!Cl(n, r, i, s, t)) return !1; - const o = _l(n, i, s); + const n = e.getNodes(), [i, s] = li(e); + if (!Pl(n, r, i, s, t)) return !1; + const o = Al(n, i, s); if (o.length === 0) return !1; const a = /* @__PURE__ */ new Set(); let c = !1; return o.forEach((l) => { - const u = vn(l, r); - if (!u || a.has(u.getKey()) || (a.add(u.getKey()), u.getMarker() === t)) return; - const d = Tg(u, o); - d && (Su(d, t), c = !0); + const d = An(l, r); + if (!d || a.has(d.getKey()) || (a.add(d.getKey()), d.getMarker() === t)) return; + const u = qg(d, o); + u && (wu(u, t), c = !0); }), c; } -function xM(e, t, r, n) { +function IM(e, t, r, n) { if (e.isCollapsed()) return !1; const i = r?.filter( (y) => y !== t - ), s = e.getNodes(), [o, a] = ai(e); + ), s = e.getNodes(), [o, a] = li(e); if (!!!i?.some( - (y) => Cl(s, y, o, a) - ) && !_M(s, t)) return !1; + (y) => Pl(s, y, o, a) + ) && !DM(s, t)) return !1; let l = !1; i?.forEach((y) => { const k = w(); - N(k) && mg(k, y, n) && (l = !0); + P(k) && Pg(k, y, n) && (l = !0); }); - const u = w(); - if (!N(u)) return l; - const d = u.isBackward(), [f, p] = ai(u), m = _l( - u.getNodes(), - f, - p + const d = w(); + if (!P(d)) return l; + const u = d.isBackward(), [p, f] = li(d), g = Al( + d.getNodes(), + p, + f ); - if (m.length === 0) return l; - const g = m.filter( - (y) => !yg(y) && !vn(y, t) + if (g.length === 0) return l; + const h = g.filter( + (y) => !Ng(y) && !An(y, t) ); - return g.length > 0 && (CM(g).forEach((y) => SM(y, t)), l = !0), xg(m, d), l; + return h.length > 0 && (UM(h).forEach((y) => FM(y, t)), l = !0), Rg(g, u), l; } -function _M(e, t) { - return bg(e).some( - (r) => !yg(r) && !vn(r, t) +function DM(e, t) { + return Og(e).some( + (r) => !Ng(r) && !An(r, t) ); } -function CM(e) { +function UM(e) { const t = []; let r; return e.forEach((n) => { @@ -21755,16 +21759,16 @@ function CM(e) { r && i?.getNextSibling()?.is(n) ? r.push(n) : (r = [n], t.push(r)); }), t; } -function SM(e, t) { +function FM(e, t) { const r = e[0].getPreviousSibling(), n = e[e.length - 1].getNextSibling(), i = [r, n].find( - (a) => D(a) && a.getMarker() === t - ), s = i ? to(i) : xr(t); - e[0].insertBefore(s), s.append(...e), i === r || tc(e[0], s); + (a) => I(a) && a.getMarker() === t + ), s = i ? so(i) : Mr(t); + e[0].insertBefore(s), s.append(...e), i === r || ac(e[0], s); } -function vM(e) { - ge(e) && (Jc(e.getPreviousSibling()), ph(e.getNextSibling())); +function zM(e) { + me(e) && (tl(e.getPreviousSibling()), Sh(e.getNextSibling())); } -const _g = { +const $g = { chapter: "chapter", verse: "verse", char: "char", @@ -21779,13 +21783,13 @@ const _g = { strikethrough: "editor-text-strikethrough", underlineStrikethrough: "editor-text-underlineStrikethrough" } -}, Ud = "psc-active-text", Ms = "psc-empty-text"; -function MM({ viewOptions: e }) { - const [t] = ce(), r = Z(void 0), n = e?.hasActiveTextFocusBox ?? !1; - return z(() => { +}, Hd = "psc-active-text", Os = "psc-empty-text"; +function KM({ viewOptions: e }) { + const [t] = ce(), r = Q(void 0), n = e?.hasActiveTextFocusBox ?? !1; + return F(() => { if (!n) return; function i(o) { - r.current && t.getElementByKey(r.current)?.classList.remove(Ud), r.current = o, o && t.getElementByKey(o)?.classList.add(Ud); + r.current && t.getElementByKey(r.current)?.classList.remove(Hd), r.current = o, o && t.getElementByKey(o)?.classList.add(Hd); } const s = [ // Clicking the ellipsis placeholder (rendered as ::after on the empty verse span) hits the @@ -21795,59 +21799,59 @@ function MM({ viewOptions: e }) { // handlers already run inside an editor update, so we set selection directly here rather than // calling editor.update() from a DOM listener. t.registerCommand( - lo, + mo, (o) => { const a = o.target; if (!(a instanceof Element)) return !1; - const c = a.closest(`.${Ms}`); + const c = a.closest(`.${Os}`); if (!c) return !1; - const l = li(c); - if (!ge(l)) return !1; - const u = l.getParent(); - if (!F(u)) return !1; - const d = l.getIndexWithinParent() + 1; - return u.select(d, d), !1; + const l = fi(c); + if (!me(l)) return !1; + const d = l.getParent(); + if (!L(d)) return !1; + const u = l.getIndexWithinParent() + 1; + return d.select(u, u), !1; }, - Tt + xt ), t.registerUpdateListener(({ editorState: o }) => { - const { newActiveKey: a, activeVerseKey: c, emptyKeys: l, nonEmptyKeys: u } = o.read(() => { - const d = ma(), f = EM(), p = [], m = []; - return Fe().getChildren().forEach((g) => { - if (!F(g)) return; - const { emptyKeys: y, nonEmptyKeys: k } = PM(g); - p.push(...y), m.push(...k); - }), { newActiveKey: d, activeVerseKey: f, emptyKeys: p, nonEmptyKeys: m }; + const { newActiveKey: a, activeVerseKey: c, emptyKeys: l, nonEmptyKeys: d } = o.read(() => { + const u = xa(), p = jM(), f = [], g = []; + return Ke().getChildren().forEach((h) => { + if (!L(h)) return; + const { emptyKeys: y, nonEmptyKeys: k } = VM(h); + f.push(...y), g.push(...k); + }), { newActiveKey: u, activeVerseKey: p, emptyKeys: f, nonEmptyKeys: g }; }); - a !== r.current && i(a), l.forEach((d) => { - d === c ? t.getElementByKey(d)?.classList.remove(Ms) : t.getElementByKey(d)?.classList.add(Ms); - }), u.forEach((d) => t.getElementByKey(d)?.classList.remove(Ms)); + a !== r.current && i(a), l.forEach((u) => { + u === c ? t.getElementByKey(u)?.classList.remove(Os) : t.getElementByKey(u)?.classList.add(Os); + }), d.forEach((u) => t.getElementByKey(u)?.classList.remove(Os)); }), t.registerCommand( - _c, + Ac, () => (i(void 0), !1), - Tt + xt ), t.registerCommand( - qy, + Wy, () => { - const o = t.getEditorState().read(ma); + const o = t.getEditorState().read(xa); return o !== r.current && i(o), !1; }, - Tt + xt ) ]; - return i(t.getEditorState().read(ma)), Ve(...s); + return i(t.getEditorState().read(xa)), Fe(...s); }, [t, n]), null; } -function ma() { - return AM(w() ?? void 0)?.getKey(); +function xa() { + return BM(w() ?? void 0)?.getKey(); } -function EM() { +function jM() { const e = w(); - if (!N(e)) return; + if (!P(e)) return; const t = e.anchor, r = t.getNode(), n = r.getTopLevelElement(); - if (!F(n)) return; + if (!L(n)) return; let i; if (r.is(n)) i = t.offset; @@ -21861,23 +21865,23 @@ function EM() { const s = n.getChildren(); let o; for (let a = 0; a < i && a < s.length; a++) - ge(s[a]) && (o = s[a].getKey()); + me(s[a]) && (o = s[a].getKey()); return o; } -function AM(e) { - if (N(e)) +function BM(e) { + if (P(e)) return e.anchor.getNode().getTopLevelElement() ?? void 0; } -function PM(e) { +function VM(e) { const t = e.getChildren(), r = [], n = []; for (let i = 0; i < t.length; i++) { const s = t[i]; - if (!ge(s)) continue; + if (!me(s)) continue; let o = !1; for (let a = i + 1; a < t.length; a++) { const c = t[a]; - if (ge(c)) break; - if (!(Yt(c) || O(c)) && c.getTextContent().replaceAll(Is, "").trim() !== "") { + if (me(c)) break; + if (!(Yt(c) || O(c)) && c.getTextContent().replaceAll(Fs, "").trim() !== "") { o = !0; break; } @@ -21886,131 +21890,647 @@ function PM(e) { } return { emptyKeys: r, nonEmptyKeys: n }; } -const NM = /^\+/; -function Sl(e, t) { - const r = t.replace(NM, ""); - return Object.hasOwn(e.markers, r) ? e.markers[r] : void 0; -} -function Cg(e, t) { - if (e.length === 0) return { keep: 0, joins: !0 }; - if (t.occursUnder.length === 0) return { keep: e.length, joins: !1 }; - for (let r = e.length - 1; r >= 0; r--) - if (t.occursUnder.includes(e[r].marker) && (r === e.length - 1 || t.rank === 0 || e[r + 1].rank <= t.rank)) - return { keep: r + 1, joins: !0 }; -} -function Sg(e, t) { - return Cg(e, t) !== void 0; +function ls(e) { + if (e !== void 0) + return Number.isNaN(e) ? 0 : Math.max(0, Math.floor(e)); } -function rc(e, t) { - const r = Cg(e, t); - return r ? (r.joins && (e.length = r.keep, e.push(t)), !0) : !1; +function Lg(e, t) { + return e.length <= t ? e : e.slice(0, Nl(e, t)); } -function ro(e, t, r) { - const n = F(e) ? e.getChildren().filter(O) : []; - if (n.length === 0) { - r.set(e.getKey(), t); - return; +function Nl(e, t) { + if (t >= e.length) return e.length; + let r = 0; + for (const { index: n, segment: i } of Zy(e)) { + if (n + i.length > t) break; + r = n + i.length; } - for (const i of n) r.set(i.getKey(), t); + return r; } -function OM(e, t, r, n, i) { - const s = Sl(n, t); - if (!s) { - ro(e, "unknown", i); - return; +const WM = 16; +function Ol(e, t = (r) => r.getTextContent().length) { + const r = w(); + if (!P(r) || r.isCollapsed() || t(r) <= e) return !1; + const [n, i] = r.isBackward() ? [r.focus, r.anchor] : [r.anchor, r.focus], s = { key: n.key, offset: n.offset, type: n.type }, o = Gd(n), a = Gd(i), c = r.getNodes(), l = new Map(c.map((g) => [g.getKey(), JM(g)])), d = (g) => { + const h = g > 0 ? GM(c, l, o, a, g) : void 0; + return r.anchor.set(s.key, s.offset, s.type), h ? r.focus.set(h.node.getKey(), h.offset, "text") : r.focus.set(s.key, s.offset, s.type), !!h; + }; + let u = Math.min(e, r.getTextContent().length); + if (d(u) && t(r) <= e) return !0; + let p = 0, f = 0; + u -= 1; + for (let g = 0; g < WM && p <= u; g++) { + const h = Math.floor((p + u) / 2); + d(h) && t(r) <= e ? (f = h, p = h + 1) : u = h - 1; + } + return d(f), !0; +} +function HM(e, t) { + const r = ls(e); + if (r === void 0 || r <= 0 || !Ol(r, t)) return !1; + const n = w(); + return P(n) && n.isCollapsed(); +} +function Ig(e, t, r) { + return HM(t, r) ? (e?.preventDefault(), !0) : !1; +} +function cc(e) { + return e.isToken() || O(e) || Ae(e) || Ir(e); +} +function GM(e, t, r, n, i) { + const s = e.length - 1; + let o = 0, a = !0, c, l; + const d = (p) => YM(l ? l.endBefore : p, e, r), u = e.length > 0 ? t.get(e[0].getKey()) : void 0; + for (let p = 0; p <= s; p++) { + const f = e[p], g = t.get(f.getKey()), h = g && u?.is(g) ? void 0 : g; + if (l && !l.node.is(h) && (c && l.node.isParentOf(c.node) && (c = l.endBefore), l = void 0), h && !l && (l = { node: h, endBefore: c }), L(f) && !f.isInline()) { + if (!a) { + if (o + 1 > i) return d(c); + o += 1; + } + a = !f.isEmpty(); + continue; + } + if (a = !1, S(f)) { + const y = p === 0 ? r : 0, k = p === s ? n : f.getTextContentSize(), _ = k - y, C = cc(f); + if (C && o + _ > i) return d(c); + if (!C && o + _ >= i) { + const M = Nl(f.getTextContent(), y + (i - o)); + return d({ node: f, offset: Math.max(y, M) }); + } + o += _, c = { node: f, offset: k }; + } else if (ms(f) || di(f)) { + const y = f.getTextContentSize(); + if (o + y > i) return d(c); + o += y; + } } - const o = s.occursUnder ?? []; - o.length > 0 && !o.includes(r) && ro(e, "invalid", i); + return d(c); } -function zi(e, t, r, n, i) { - for (const s of e.getChildren()) - if (D(s)) { - const o = s.getMarker(); - i || OM(s, o, t, r, n), zi(s, t, r, n, i || o === "xq"); - } else if (ge(s)) { - if (i) continue; - const o = Sl(r, "v"); - o ? (o.occursUnder ?? []).length > 0 && !(o.occursUnder ?? []).includes(t) && n.set(s.getKey(), "invalid") : n.set(s.getKey(), "unknown"); - } else j(s) ? zi(s, s.getMarker(), r, n, i) : De(s) || F(s) && zi(s, t, r, n, i); +function JM(e) { + let t; + for (let r = e; r; r = r.getParent()) + (K(r) || Gh(r)) && (t = r); + return t; } -function wM(e, t) { - const r = /* @__PURE__ */ new Map(), n = [], i = (o, a) => { - const c = Sl(e, a); - if (!c) { - ro(o, "unknown", r), rc(n, { marker: a, rank: 0, occursUnder: [] }); - return; - } - const l = { - marker: a, - rank: c.rank ?? 0, - occursUnder: c.occursUnder ?? [] - }; - rc(n, l) || ro(o, "invalid", r); - }, s = (o) => !t || t.has(o.getKey()); - for (const o of Fe().getChildren()) - De(o) || (pt(o) || Ge(o) ? i(o, o.getMarker()) : ae(o) ? (i(o, o.getMarker()), s(o) && zi(o, o.getMarker(), e, r, !1)) : F(o) && s(o) && zi(o, "p", e, r, !1)); - return r; +function YM(e, t, r) { + if (!e) return e; + const n = rt(e.node, (y) => L(y) && !y.isInline()), i = L(n) ? n.getAllTextNodes() : [e.node], s = i.findIndex((y) => y.is(e.node)); + if (s < 0) return e; + const o = Math.max(0, s - 1), a = Math.min(i.length - 1, s + 1); + let c = ""; + const l = []; + for (let y = o; y <= a; y++) + l.push(c.length), c += i[y].getTextContent(); + const d = l[s - o], u = d + e.offset, p = Nl(c, u); + if (p === u) return e; + const f = (y) => t[0]?.is(y) ?? !1; + if (cc(e.node)) return { ...e, offset: f(e.node) ? r : 0 }; + if (p >= d) { + const y = p - d; + return y >= (f(e.node) ? r : 0) ? { ...e, offset: y } : void 0; + } + const g = s > o ? i[s - 1] : void 0; + if (!g || !t.some((y) => y.is(g))) return; + if (cc(g)) + return { node: g, offset: f(g) ? r : 0 }; + const h = p - l[0]; + return h >= (f(g) ? r : 0) ? { node: g, offset: h } : void 0; +} +function Gd(e) { + if (e.type === "text") return e.offset; + const t = e.getNode(); + return L(t) && e.offset === t.getChildrenSize() ? t.getTextContent().length : 0; } -function qM(e) { - return !!e?.includes("(basic)"); +const er = String.raw`\w-`, Dg = "a-z0-9", XM = `[a-z][${Dg}]*`, QM = new RegExp( + String.raw`^\\(\+?[${er}]+)[ \u00A0]$` +), Ug = new RegExp(String.raw`^\\(\+?[${er}]+)$`), ZM = new RegExp(String.raw`^\\\+?[${er}]*\*$`), eE = new RegExp( + String.raw`^\\(\+?[${er}]+)(?:[ \u00A0]|$)` +), tE = new RegExp( + String.raw`^\\(\+?)([${er}]+)` +), rE = new RegExp( + String.raw`\\\+?[${er}]+(?:\\?\*|[ \u00A0])` +), nE = new RegExp( + String.raw`\\\+?[${er}]*$` +), iE = new RegExp( + String.raw`^\\(${XM})( |$)` +), sE = new RegExp( + String.raw`\\[${Dg}+*]*$`, + "i" +), oo = "usfm:", Fg = "usfmopen", zg = "usfmclosed"; +function oE(e) { + return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -function RM(e) { - if (e !== void 0) - return e.replace(/\s*\(basic\)/g, "").trim(); +const aE = new RegExp( + [oo, Fg, zg].map(oE).join("|") +), cE = "\uFEFF", lE = /^usfm_(.+)$/; +function uE(e) { + return e.nodeType === Node.ELEMENT_NODE; } -function vg(e, t) { - return !e.startsWith("zpa") && e !== "c" && Za(e, t); +function dE(e) { + return e.replace( + /%([0-9a-fA-F]{4})/g, + (t, r) => String.fromCharCode(Number.parseInt(r, 16)) + ); } -function vl(e, t) { - const r = Object.hasOwn(e.markers, t) ? e.markers[t] : void 0; - if (r) - return { marker: t, rank: r.rank ?? 0, occursUnder: r.occursUnder ?? [] }; +function fE(e) { + return e.startsWith(oo) ? dE(e.slice(oo.length)).replace(/\r\n?|\n/g, " ") : ""; } -function Mg(e, t) { - const r = []; - for (const n of t) { - const i = vl(e, n); - i && rc(r, i); +function Kg(e) { + for (const t of e.classList) { + const r = lE.exec(t); + if (r) return r[1]; } - return r; -} -function ws(e, t) { - return { - marker: e.marker, - kind: t, - // `isBasic` reads the ORIGINAL description; the emitted one has the token removed. - description: RM(e.description), - isBasic: qM(e.description) - }; -} -function $M(e, t) { - const r = (s) => { - const o = /^(.*?)(\d+)$/.exec(s); - return o ? { prefix: o[1], digits: Number(o[2]) } : { prefix: s }; - }, n = r(e), i = r(t); - return n.prefix !== i.prefix ? n.prefix < i.prefix ? -1 : 1 : n.digits === void 0 ? i.digits === void 0 ? 0 : -1 : i.digits === void 0 ? 1 : n.digits - i.digits; } -function nc(e, t) { - return e.isBasic !== t.isBasic ? e.isBasic ? -1 : 1 : $M(e.marker, t.marker); +function pE(e) { + const t = Kg(e); + if (t !== void 0) + return e.classList.contains("nested") ? `+${t}` : t; } -function ic(e, t, r) { - if (t.noteMarker) return []; - const n = Mg(e, t.previousParaMarkers); - return Object.values(e.markers).filter( - (i) => i.styleType === "paragraph" && vg(i.marker, r) - ).filter((i) => { - const s = vl(e, i.marker); - return s !== void 0 && Sg(n, s); - }).map((i) => ws(i, "paragraph")).sort(nc); +function hE(e) { + const t = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_COMMENT); + for (let r = t.nextNode(); r; r = t.nextNode()) + if ((r.nodeValue ?? "").startsWith(oo)) return !0; + for (const r of e.querySelectorAll("*")) { + const { classList: n } = r; + if (!(!n.contains(Fg) && !n.contains(zg)) && Kg(r) !== void 0) + return !0; + } + return !1; } -function IM(e, t, r) { - const { noteMarker: n, paraMarker: i } = t, s = Object.values(e.markers).filter( - (c) => vg(c.marker, r) - ); +function jg(e, t, r) { + if (e.nodeType === Node.TEXT_NODE) { + t || r.push(e.nodeValue ?? ""); + return; + } + if (e.nodeType === Node.COMMENT_NODE) { + r.push(fE(e.nodeValue ?? "")); + return; + } + if (!uE(e)) return; + const { classList: n } = e, i = (d) => e.childNodes.forEach((u) => jg(u, d, r)); + if (t) { + i(!n.contains("include")); + return; + } + if (n.contains("exclude")) { + i(!0); + return; + } + const s = e.tagName.toLowerCase(); + if (s === "br") { + r.push(` +`); + return; + } + if (s === "span" && e.getAttribute("class") === "attribute") { + r.push(e.textContent ?? ""); + return; + } + const o = s === "div" || s === "tr", a = o || s === "span" || s === "th" || s === "td" ? pE(e) : void 0, c = a !== void 0 && n.contains("usfmopen"), l = a !== void 0 && n.contains("usfmclosed"); + o && r.push(` +`), (c || l) && r.push(`\\${a} `), i(!1), l && r.push(`\\${a}*`), o && r.push(` +`); +} +function gE(e) { + if (!aE.test(e)) return; + const { body: t } = new DOMParser().parseFromString(e, "text/html"); + if (t.querySelectorAll("script,style,template").forEach((n) => n.remove()), !hE(t)) return; + const r = []; + return t.childNodes.forEach((n) => jg(n, !1, r)), r.join("").replaceAll(cE, "").replaceAll(R, " ").replace(/\r\n?/g, ` +`).replace(/\n+/g, ` +`).replace(/^\n|\n$/g, ""); +} +function mE(e) { + const t = e.getTextContent(); + if (!t.includes(" ")) return; + const r = ie(e, ae); + if (r === "attribute" || r === pr) return; + for (let o = e.getParent(); o; o = o.getParent()) + if (ht(o) || we(o) || Ue(o)) return; + const n = t.startsWith(R) && I(e.getParent()), i = n ? t.slice(1) : t, s = (n ? R : "") + i.replace(/ (?=[ \u00A0])/g, R).replace(new RegExp("(?<=\\u00A0) ", "g"), R); + s !== t && e.setTextContent(s); +} +function yE(e) { + const { body: t } = new DOMParser().parseFromString(e, "text/html"); + return t.querySelectorAll("script,style,template").forEach((r) => r.remove()), t.querySelectorAll("br").forEach((r) => r.replaceWith(` +`)), t.querySelectorAll("p,div,li,td,th,tr,h1,h2,h3,h4,h5,h6,blockquote,pre").forEach((r) => r.after(` +`)), (t.textContent ?? "").replace(/\n+/g, ` +`).replace(/^\n|\n$/g, ""); +} +function bE(e, t) { + if (!e) return !1; + try { + const r = JSON.parse(e); + if (typeof r != "object" || r === null) return !1; + const { namespace: n, nodes: i } = r; + return n === t && Array.isArray(i); + } catch { + return !1; + } +} +function lc(e, t) { + const r = e && typeof e == "object" && "clipboardData" in e ? e.clipboardData : void 0; + if (!r) return; + const n = (a) => a.replace(/\r\n?/g, ` +`), i = n(r.getData("text/plain")), s = r.getData("text/html"), o = s ? gE(s) : void 0; + return { + text: o ? n(o) : i || (s ? n(yE(s)) : ""), + isInternal: bE( + r.getData("application/x-lexical-editor"), + t + ) + }; +} +const Jd = String.raw`\\(?:\+?[${er}]+\*?|\*)`, kE = new RegExp( + String.raw`(?<=${Jd})\u00A0|\u00A0(?=${Jd})`, + "g" +); +function wl(e) { + return e.replace(/^\u00A0+/gm, (t) => " ".repeat(t.length)).replace(kE, " ").replaceAll(R, "~"); +} +const Bg = new RegExp( + String.raw`\\c(?![${er}])[ \u00A0]*[^\s\\]*`, + "g" +), Vg = new RegExp(String.raw`\\id(?![${er}])[^\n\\]*`, "g"), TE = new RegExp( + String.raw`^(?:${Bg.source}|${Vg.source})` +); +function ql(e) { + return e.split(` +`).map((t) => { + const r = t.replace(Bg, "").replace(Vg, ""); + return TE.test(t) && r.trim() === "" && t.trim() !== "" ? void 0 : r; + }).filter((t) => t !== void 0).join(` +`); +} +function uc(e) { + if (S(e) && ie(e, ae) === "attribute") return !0; + for (let t = e; t; t = t.getParent()) + if (De(t)) return !0; + return !1; +} +function xE(e) { + return uc(e.anchor.getNode()) || uc(e.focus.getNode()); +} +function _E(e) { + const { anchor: t, focus: r } = e; + return t.key === r.key && uc(t.getNode()); +} +function CE(e, t) { + const n = _E(e) ? t : wl(ql(t)); + n && e.insertText(n.replace(/\n/g, " ")); +} +function vE(e, t = !1, r = () => { +}) { + const n = lc(e, ti()._config.namespace); + if (!n) return !1; + const i = w(), s = P(i) && xE(i); + if (!s && n.isInternal || t && P(i) && ei(i)) + return !1; + const { text: o } = n; + if (!o || !P(i)) return !1; + if (e?.preventDefault(), s) + return CE(i, o), !0; + const a = wl(ql(o)); + if (!a) return !0; + const c = a.split(` +`); + if (t) + return i.insertText(c.join(" ")), !0; + if (c.length < 2) + return i.insertText(a), !0; + r(), i.isCollapsed() || i.removeText(); + const l = ti(); + return c.forEach((d, u) => { + if (u > 0 && l.dispatchCommand(Us, void 0), d === "") return; + const p = w(); + P(p) && p.insertText(d); + }), !0; +} +function SE(e) { + if (e.getTextContent() !== R) return !1; + const t = e.getParent(); + return K(t) ? !St(e.getPreviousSibling()) : !1; +} +function ME(e, t) { + if (t || e.getTextContent() !== R) return ""; + const r = e.getParent(); + if (!K(r) || !St(e.getPreviousSibling())) return ""; + const n = r.getCategory(); + return n ? `\\cat ${n}\\cat*` : ""; +} +function EE(e) { + const t = e.getParent(); + return (K(t) ? t.getCaller() : void 0) || Gi; +} +function AE(e) { + const t = e.getParent(); + return !t || Zr(t) === void 0; +} +function Lo(e) { + const t = e.getNodes(); + if (t.length === 0) return ""; + const r = t[0], n = t[t.length - 1], { anchor: i, focus: s } = e, o = i.isBefore(s), [a, c] = Sc(e); + let l = "", d = !0; + for (const u of t) { + if (L(u) && !u.isInline()) { + !d && AE(u) && (l += ` +`), d = !u.isEmpty(); + continue; + } + if (d = !1, St(u)) + (u !== n || !e.isCollapsed()) && (l += (u === r ? "" : " ") + EE(u)); + else if (S(u)) { + let p = u.getTextContent(); + u === r ? u === n ? (i.type !== "element" || s.type !== "element" || s.offset === i.offset) && (p = a < c ? p.slice(a, c) : p.slice(c, a)) : p = o ? p.slice(a) : p.slice(c) : u === n && (p = o ? p.slice(0, c) : p.slice(0, a)), l += SE(u) ? "" : p.replaceAll(R, " ") + ME(u, u === n); + } else (ms(u) || di(u)) && (u !== n || !e.isCollapsed()) && (l += u.getTextContent().replaceAll(R, " ")); + } + return l; +} +function ao(e) { + return e.split(` +`).map((t) => t.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")).map( + (t) => t ? `

${t}

` : "

" + ).join(""); +} +function PE(e) { + return e.getNodes().some( + (t) => [t, ...t.getParents()].some( + (r) => ht(r) || we(r) + ) + ); +} +function Yd(e) { + const t = w(); + if (!P(t) || t.isCollapsed()) return; + const r = Lo(t), n = { + "text/plain": r, + "text/html": ao(r) + }; + if (qo() || PE(t)) return n; + const i = tb(e); + return i && (n["application/x-lexical-editor"] = i), n; +} +function Xd(e, t, r, n) { + const i = w(); + if (!P(i) || i.isCollapsed()) + return (!e || !("clipboardData" in e)) && !eg(); + const s = Yd(t); + return s ? Rl(e, t, i, s, r, { + copyLimit: n, + $payloadFor: () => Yd(t) ?? { "text/plain": "" }, + $measure: (o) => Lo(o).length + }) : !1; +} +function Rl(e, t, r, n, i, s = {}) { + const { copyLimit: o } = s, a = s.$payloadFor ?? ((p) => ({ "text/plain": p.getTextContent() })), c = s.$measure ?? ((p) => a(p)["text/plain"]?.length ?? 0); + let l = Qd(n, o); + const d = i && t.isEditable(), u = ls(o); + return l !== n && u !== void 0 && (Tn(r), Ol(u, c), l = Qd(a(r), o)), Wg( + e, + t, + l, + d ? () => r.removeText() : void 0 + ); +} +function Wg(e, t, r, n) { + const i = !r["text/plain"]; + if (!e || !("clipboardData" in e)) + return i || eb(t, null, r), n?.(), !0; + if (e.clipboardData == null) return !1; + if (e.preventDefault(), !i) + for (const [s, o] of Object.entries(r)) e.clipboardData.setData(s, o); + return n?.(), !0; +} +function Qd(e, t) { + const r = e["text/plain"], n = ls(t); + if (r === void 0 || n === void 0 || r.length <= n) return e; + const i = Lg(r, n); + return e["text/html"] === void 0 ? { "text/plain": i } : { "text/plain": i, "text/html": ao(i) }; +} +const Zd = /* @__PURE__ */ new WeakMap(); +function NE({ limit: e }) { + const [t] = ce(), r = Q(ls(e)), n = Q(void 0); + return F(() => { + r.current = ls(e), n.current?.(); + }, [e]), F(() => { + let i, s; + const o = (f) => { + const g = r.current; + if (g === void 0) return; + const h = Zd.get(f); + if (f.defaultPrevented && !h) return; + const y = t.getRootElement(); + if (!y) return; + const k = y.ownerDocument.getSelection(); + if (!k || k.rangeCount === 0 || k.isCollapsed || !k.containsNode(y, !0)) return; + f.preventDefault(); + const _ = Math.min(h?.limit ?? g, g), C = Lg(h?.text ?? k.toString(), _); + Zd.set(f, { limit: _, text: C }); + const M = f.clipboardData; + if (M) { + if (!C) { + h && M.clearData(); + return; + } + M.setData("text/plain", C); + } + }, a = (f) => { + r.current !== void 0 && Gy(f, "a", { ctrlKey: !ri, metaKey: ri }) && (wE(s?.activeElement, t.getRootElement()) || f.preventDefault()); + }, c = () => { + s?.removeEventListener("copy", o), s?.removeEventListener("keydown", a, !0), s = void 0; + }, l = () => { + const f = r.current === void 0 ? void 0 : i; + f !== s && (c(), s = f, s?.addEventListener("copy", o), s?.addEventListener("keydown", a, !0)); + }; + n.current = l; + const d = (f) => { + const g = r.current; + if (g === void 0) return !1; + if (g > 0) { + const h = w(), y = P(h) && h.isCollapsed(); + if (Ol(g), y || !P(h) || !h.isCollapsed()) return !1; + } + return f?.preventDefault(), !0; + }, u = (f, g) => { + const h = r.current; + if (h === void 0) return !1; + const y = f && typeof f == "object" && "clipboardData" in f ? f : null, k = w(); + if (go(k)) { + const _ = k.getTextContent(), C = _.length <= h, M = C ? _ : "", E = C && g && t.isEditable(); + return Wg( + y, + t, + { "text/plain": M }, + E ? () => { + for (const z of k.getNodes()) z.remove(); + } : void 0 + ); + } + return !P(k) || k.isCollapsed() ? !1 : Rl( + y, + t, + k, + { "text/plain": k.getTextContent() }, + g, + { copyLimit: h } + ); + }, p = Fe( + t.registerCommand(Hr, d, tt), + t.registerCommand(Wt, d, tt), + t.registerCommand( + Hr, + (f) => u(f, !1), + Wr + ), + t.registerCommand( + Wt, + (f) => u(f, !0), + Wr + ), + // A read-only editor never dispatches this — the document key-down listener covers it. + t.registerCommand( + Hy, + (f) => r.current === void 0 ? !1 : (f?.preventDefault(), !0), + tt + ), + t.registerRootListener((f) => { + i = f?.ownerDocument ?? void 0, l(); + }) + ); + return () => { + p(), n.current = void 0, c(); + }; + }, [t]), null; +} +const OE = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "number"]); +function wE(e, t) { + return e ? e instanceof HTMLTextAreaElement ? !0 : e instanceof HTMLInputElement ? OE.has(e.type) : !(e instanceof HTMLElement) || !e.isContentEditable ? !1 : !t?.contains(e) : !1; +} +const qE = /^\+/; +function $l(e, t) { + const r = t.replace(qE, ""); + return Object.hasOwn(e.markers, r) ? e.markers[r] : void 0; +} +function Hg(e, t) { + if (e.length === 0) return { keep: 0, joins: !0 }; + if (t.occursUnder.length === 0) return { keep: e.length, joins: !1 }; + for (let r = e.length - 1; r >= 0; r--) + if (t.occursUnder.includes(e[r].marker) && (r === e.length - 1 || t.rank === 0 || e[r + 1].rank <= t.rank)) + return { keep: r + 1, joins: !0 }; +} +function Gg(e, t) { + return Hg(e, t) !== void 0; +} +function dc(e, t) { + const r = Hg(e, t); + return r ? (r.joins && (e.length = r.keep, e.push(t)), !0) : !1; +} +function co(e, t, r) { + const n = L(e) ? e.getChildren().filter(O) : []; + if (n.length === 0) { + r.set(e.getKey(), t); + return; + } + for (const i of n) r.set(i.getKey(), t); +} +function RE(e, t, r, n, i) { + const s = $l(n, t); + if (!s) { + co(e, "unknown", i); + return; + } + const o = s.occursUnder ?? []; + o.length > 0 && !o.includes(r) && co(e, "invalid", i); +} +function Vi(e, t, r, n, i) { + for (const s of e.getChildren()) + if (I(s)) { + const o = s.getMarker(); + i || RE(s, o, t, r, n), Vi(s, t, r, n, i || o === "xq"); + } else if (me(s)) { + if (i) continue; + const o = $l(r, "v"); + o ? (o.occursUnder ?? []).length > 0 && !(o.occursUnder ?? []).includes(t) && n.set(s.getKey(), "invalid") : n.set(s.getKey(), "unknown"); + } else K(s) ? Vi(s, s.getMarker(), r, n, i) : Ue(s) || L(s) && Vi(s, t, r, n, i); +} +function $E(e, t) { + const r = /* @__PURE__ */ new Map(), n = [], i = (o, a) => { + const c = $l(e, a); + if (!c) { + co(o, "unknown", r), dc(n, { marker: a, rank: 0, occursUnder: [] }); + return; + } + const l = { + marker: a, + rank: c.rank ?? 0, + occursUnder: c.occursUnder ?? [] + }; + dc(n, l) || co(o, "invalid", r); + }, s = (o) => !t || t.has(o.getKey()); + for (const o of Ke().getChildren()) + Ue(o) || (ht(o) || Je(o) ? i(o, o.getMarker()) : le(o) ? (i(o, o.getMarker()), s(o) && Vi(o, o.getMarker(), e, r, !1)) : L(o) && s(o) && Vi(o, "p", e, r, !1)); + return r; +} +function LE(e) { + return !!e?.includes("(basic)"); +} +function IE(e) { + if (e !== void 0) + return e.replace(/\s*\(basic\)/g, "").trim(); +} +function Jg(e, t) { + return !e.startsWith("zpa") && e !== "c" && sc(e, t); +} +function Ll(e, t) { + const r = Object.hasOwn(e.markers, t) ? e.markers[t] : void 0; + if (r) + return { marker: t, rank: r.rank ?? 0, occursUnder: r.occursUnder ?? [] }; +} +function Yg(e, t) { + const r = []; + for (const n of t) { + const i = Ll(e, n); + i && dc(r, i); + } + return r; +} +function Is(e, t) { + return { + marker: e.marker, + kind: t, + // `isBasic` reads the ORIGINAL description; the emitted one has the token removed. + description: IE(e.description), + isBasic: LE(e.description) + }; +} +function DE(e, t) { + const r = (s) => { + const o = /^(.*?)(\d+)$/.exec(s); + return o ? { prefix: o[1], digits: Number(o[2]) } : { prefix: s }; + }, n = r(e), i = r(t); + return n.prefix !== i.prefix ? n.prefix < i.prefix ? -1 : 1 : n.digits === void 0 ? i.digits === void 0 ? 0 : -1 : i.digits === void 0 ? 1 : n.digits - i.digits; +} +function fc(e, t) { + return e.isBasic !== t.isBasic ? e.isBasic ? -1 : 1 : DE(e.marker, t.marker); +} +function pc(e, t, r) { + if (t.noteMarker) return []; + const n = Yg(e, t.previousParaMarkers); + return Object.values(e.markers).filter( + (i) => i.styleType === "paragraph" && Jg(i.marker, r) + ).filter((i) => { + const s = Ll(e, i.marker); + return s !== void 0 && Gg(n, s); + }).map((i) => Is(i, "paragraph")).sort(fc); +} +function UE(e, t, r) { + const { noteMarker: n, paraMarker: i } = t, s = Object.values(e.markers).filter( + (c) => Jg(c.marker, r) + ); if (n) return s.filter( (c) => c.styleType === "character" && (c.occursUnder ?? []).includes(n) - ).map((c) => ws(c, "character")).sort(nc); + ).map((c) => Is(c, "character")).sort(fc); if (!i) return []; const o = s.filter((c) => { if (c.styleType !== "character") return !1; @@ -22018,63 +22538,48 @@ function IM(e, t, r) { return l.length === 0 || l.includes(i); }), a = s.filter((c) => c.styleType === "note"); return [ - ...o.map((c) => ws(c, "character")), - ...a.map((c) => ws(c, "note")) - ].sort(nc); + ...o.map((c) => Is(c, "character")), + ...a.map((c) => Is(c, "note")) + ].sort(fc); } -function LM(e, t) { +function FE(e, t) { return t.map((r, n) => { const i = e.markers[r]?.endMarker ?? `${r}*`; return { marker: `${n === t.length - 1 ? "" : "+"}${i}`, kind: "closeTag", isBasic: !1 }; }); } -function DM(e, t) { +function zE(e, t) { return e.isBasic === t.isBasic ? 0 : e.isBasic ? -1 : 1; } -function UM(e, t, r) { +function KE(e, t, r) { return [ - ...LM(e, t.openCharMarkers), - ...IM(e, t, r) - ].sort(DM); + ...FE(e, t.openCharMarkers), + ...UE(e, t, r) + ].sort(zE); } -function FM(e, t, r) { - if (t.source === "paragraph") return ic(e, t, r); - const n = UM(e, t, r); - return n.length > 0 ? n : ic(e, t, r); +function jE(e, t, r) { + if (t.source === "paragraph") return pc(e, t, r); + const n = KE(e, t, r); + return n.length > 0 ? n : pc(e, t, r); } -function zM(e, t, r) { - const n = ic(e, t, r), i = Mg(e, t.previousParaMarkers), s = vl(e, "ip"), a = !t.previousParaMarkers.includes("c") && s && Sg(i, s) ? "ip" : "p", c = n.findIndex((u) => u.marker === a); +function BE(e, t, r) { + const n = pc(e, t, r), i = Yg(e, t.previousParaMarkers), s = Ll(e, "ip"), a = !t.previousParaMarkers.includes("c") && s && Gg(i, s) ? "ip" : "p", c = n.findIndex((d) => d.marker === a); if (c <= 0) return n; const [l] = n.splice(c, 1); return [l, ...n]; } -const er = String.raw`\w-`, Eg = "a-z0-9", KM = `[a-z][${Eg}]*`, jM = new RegExp( - String.raw`^\\(\+?[${er}]+)[ \u00A0]$` -), Ag = new RegExp(String.raw`^\\(\+?[${er}]+)$`), BM = new RegExp(String.raw`^\\\+?[${er}]*\*$`), VM = new RegExp( - String.raw`^\\(\+?[${er}]+)(?:[ \u00A0]|$)` -), WM = new RegExp( - String.raw`^\\(\+?)([${er}]+)` -), HM = new RegExp( - String.raw`\\\+?[${er}]+(?:\\?\*|[ \u00A0])` -), GM = new RegExp( - String.raw`\\\+?[${er}]*$` -), JM = new RegExp( - String.raw`^\\(${KM})( |$)` -), YM = new RegExp( - String.raw`\\[${Eg}+*]*$`, - "i" -), tt = ""; -function Pg(e) { - return e.length > 1 && e.startsWith(I) && e.charAt(1) !== tt ? e.slice(1) : e; +const st = ""; +function Xg(e) { + return e.length > 1 && e.startsWith(R) && e.charAt(1) !== st ? e.slice(1) : e; } -function Fd(e) { - return Dc(e) ? e.markerSyntax ?? "opening" : void 0; +function ef(e) { + return Bc(e) ? e.markerSyntax ?? "opening" : void 0; } -function Ng(e, t, r, n) { - const i = { ...n, noteMode: "expanded" }, s = Cr.serializeEditorState( +function Qg(e, t, r, n) { + const i = { ...n, noteMode: "expanded" }, s = Ar.serializeEditorState( { - type: kr, - version: br, + type: vr, + version: Cr, content: [ { ...e.getUnknownAttributes(), @@ -22090,16 +22595,16 @@ function Ng(e, t, r, n) { ).root.children, o = s.length === 1 ? s[0] : void 0, a = Array.isArray(o?.children) ? o.children : void 0; if (!a) return { failure: "shape" }; let c = 0; - for (; Fd(a[c]) === "opening"; ) c++; - if (a[c]?.text !== At(e.getCaller())) return { failure: "caller" }; + for (; ef(a[c]) === "opening"; ) c++; + if (a[c]?.text !== Pt(e.getCaller())) return { failure: "caller" }; c++; - let u = a.length; - for (; u > c && Fd(a[u - 1]) === "closing"; ) - u--; - const d = a.slice(c, u); - return d.length === 0 ? { failure: "empty" } : { children: d }; + let d = a.length; + for (; d > c && ef(a[d - 1]) === "closing"; ) + d--; + const u = a.slice(c, d); + return u.length === 0 ? { failure: "empty" } : { children: u }; } -function Es(e, t, r) { +function ws(e, t, r) { e.spans.push({ key: t.getKey(), start: e.text.length, @@ -22107,31 +22612,31 @@ function Es(e, t, r) { isSentinel: !1 }), e.text += r; } -function Oi(e, t) { - GM.test(e.text) && (e.text += " "), e.spans.push({ +function $i(e, t) { + nE.test(e.text) && (e.text += " "), e.spans.push({ key: t[0].getKey(), start: e.text.length, end: e.text.length + 1, isSentinel: !0 - }), e.sentinels.push(t), e.text += tt; + }), e.sentinels.push(t), e.text += st; } function It(e) { - return e.replaceAll(I, " "); + return e.replaceAll(R, " "); } -function XM(e, t, r = !1) { - if (Ao(t)) return It(e); - if (e === I) return " "; - const n = r && e.startsWith(I), i = n ? e.slice(1) : e; - return (n ? " " : "") + i.replaceAll(I, "~"); +function VE(e, t, r = !1) { + if (wo(t)) return It(e); + if (e === R) return " "; + const n = r && e.startsWith(R), i = n ? e.slice(1) : e; + return (n ? " " : "") + i.replaceAll(R, "~"); } -function Ki(e) { +function Wi(e) { const t = e.getTextContent(); - return Nn(e) && /^[\s\u00A0]*$/.test(t) ? " " : t; + return qn(e) && /^[\s\u00A0]*$/.test(t) ? " " : t; } -function Ml(e, t) { +function Il(e, t) { const r = e[t]; - if (!Be(r)) return []; - const { attribute: n, closing: i, wrapper: s } = ko(r); + if (!He(r)) return []; + const { attribute: n, closing: i, wrapper: s } = Co(r); if (s) return [s]; const o = r.getNextSibling(); if (!O(o) || o.getMarkerSyntax() !== "opening" || o.getMarker() !== r.getMarker()) @@ -22139,14 +22644,14 @@ function Ml(e, t) { const a = [o]; return n && a.push(n), i && a.push(i), a; } -function Og(e) { +function Zg(e) { return e.some((t) => t.getTextContent().length > 0); } -function El(e, t) { +function Dl(e, t) { const r = []; let n = e[t]; for (const i of ["va", "vp"]) { - const { opener: s, value: o, closer: a, wrapper: c } = Yi(n, i); + const { opener: s, value: o, closer: a, wrapper: c } = es(n, i); if (c) r.push(c), n = c; else if (s && o && a) @@ -22156,32 +22661,32 @@ function El(e, t) { } return r; } -function Al(e) { +function Ul(e) { return !!e.getUnknownAttributes(); } -function wo(e, t) { +function Io(e, t) { const r = t(e)?.type; - return r === b.Milestone || r === void 0 && Ac(e); + return r === b.Milestone || r === void 0 && Rc(e); } -function wg(e, t) { - return Be(e) ? !wo(e.getMarker(), t) : j(e) || De(e) ? !0 : Oe(e) ? Al(e) : D(e) ? qg(e, t) : !1; +function em(e, t) { + return He(e) ? !Io(e.getMarker(), t) : K(e) || Ue(e) ? !0 : Ae(e) ? Ul(e) : I(e) ? tm(e, t) : !1; } -function qg(e, t) { - if (Lk(e)) return !0; +function tm(e, t) { + if (Zk(e)) return !0; const r = e.getMarker(); - return !vb(r) && t(r) === void 0; + return !zb(r) && t(r) === void 0; } -const Rt = "", $t = ""; -function zd(e) { - return e.flatMap((t) => Le(t) ? t.getChildren() : [t]); +const $t = "", Lt = ""; +function tf(e) { + return e.flatMap((t) => De(t) ? t.getChildren() : [t]); } -function Ii(e, t, r, n = !1) { +function Fi(e, t, r, n = !1) { for (let i = 0; i < e.length; i++) { const s = e[i]; - if (Be(s)) { - const o = Ml(e, i); - wo(s.getMarker(), r) && Og(o) ? (t.push( - Rt, + if (He(s)) { + const o = Il(e, i); + Io(s.getMarker(), r) && Zg(o) ? (t.push( + $t, "ms", // `marker` is part of the state for the same reason the attributes below are, and it is // the one the SAVE leg reads: `createMilestoneMarker` (editor-usj.adaptor.ts) emits the @@ -22205,11 +22710,11 @@ function Ii(e, t, r, n = !1) { unknownAttributes: s.getUnknownAttributes() ?? null, attributeOrder: s.getAttributeOrder() ?? null }) - ), Ii(zd(o), t, r), t.push($t)) : t.push(tt), i += o.length; - } else if (Oe(s)) { - const o = El(e, i); - Al(s) ? t.push(tt) : (t.push( - Rt, + ), Fi(tf(o), t, r), t.push(Lt)) : t.push(st), i += o.length; + } else if (Ae(s)) { + const o = Dl(e, i); + Ul(s) ? t.push(st) : (t.push( + $t, "verse", It(s.getTextContent()), JSON.stringify({ @@ -22217,48 +22722,48 @@ function Ii(e, t, r, n = !1) { altnumber: s.getAltnumber() ?? null, pubnumber: s.getPubnumber() ?? null }) - ), Ii(zd(o), t, r), t.push($t)), i += o.length; - } else O(s) ? t.push(Rt, "marker", It(s.getTextContent()), $t) : Qr(s) ? t.push(Rt, "unmatched", It(s.getTextContent()), $t) : wg(s, r) ? t.push(tt) : us(s) ? t.push(" ") : v(s) ? t.push( + ), Fi(tf(o), t, r), t.push(Lt)), i += o.length; + } else O(s) ? t.push($t, "marker", It(s.getTextContent()), Lt) : Ir(s) ? t.push($t, "unmatched", It(s.getTextContent()), Lt) : em(s, r) ? t.push(st) : di(s) ? t.push(" ") : S(s) ? t.push( It( - n ? Pg(Ki(s)) : Ki(s) + n ? Xg(Wi(s)) : Wi(s) ) - ) : D(s) ? (t.push(Rt, "char", JSON.stringify(s.getUnknownAttributes() ?? null)), Ii(s.getChildren(), t, r, !0), t.push($t)) : F(s) ? (t.push(Rt, s.getType()), Ii(s.getChildren(), t, r), t.push($t)) : t.push(tt); + ) : I(s) ? (t.push($t, "char", JSON.stringify(s.getUnknownAttributes() ?? null)), Fi(s.getChildren(), t, r, !0), t.push(Lt)) : L(s) ? (t.push($t, s.getType()), Fi(s.getChildren(), t, r), t.push(Lt)) : t.push(st); } } -function bi(e, t) { +function xi(e, t) { const r = []; - return Ii(e, r, t), r.join(""); + return Fi(e, r, t), r.join(""); } -function Sr(e) { +function Pr(e) { const { children: t } = e; return Array.isArray(t) ? t : void 0; } -function ci(e) { +function ui(e) { const { text: t } = e; return typeof t == "string" ? t : void 0; } -function Pl(e) { +function Fl(e) { return e.type ?? ""; } -function Rg(e, t, r) { - return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : we(e, r); +function rm(e, t, r) { + return t === "closing" ? ot(e, r) : t === "selfClosing" ? ot("") : Re(e, r); } -function ya(e, t) { +function _a(e, t) { const r = e[t]; - if (!(!r || Pl(r) !== "attribute-run")) - return Sr(r) ?? []; + if (!(!r || Fl(r) !== "attribute-run")) + return Pr(r) ?? []; } -function ki(e, t) { +function _i(e, t) { const r = []; - return Li(e, r, t), r.join(""); + return zi(e, r, t), r.join(""); } -function Li(e, t, r, n = !1) { +function zi(e, t, r, n = !1) { for (let i = 0; i < e.length; i++) { - const s = e[i], o = Pl(s); + const s = e[i], o = Fl(s); if (o === "ms") { - const l = s, u = ya(e, i + 1); - u && wo(l.marker ?? "", r) ? (t.push( - Rt, + const l = s, d = _a(e, i + 1); + d && Io(l.marker ?? "", r) ? (t.push( + $t, "ms", // `marker` mirrored from `$appendSignature`'s fold: a glyph RENAME leaves the displayed // bytes identical on both sides, so only the milestone's own stale `marker` reveals the @@ -22272,17 +22777,17 @@ function Li(e, t, r, n = !1) { unknownAttributes: l.unknownAttributes ?? null, attributeOrder: l.attributeOrder ?? null }) - ), Li(u, t, r), t.push($t), i += 1) : t.push(tt); + ), zi(d, t, r), t.push(Lt), i += 1) : t.push(st); continue; } if (o === "verse") { const l = s; if (l.unknownAttributes) { - t.push(tt); + t.push(st); continue; } t.push( - Rt, + $t, "verse", It(l.text ?? ""), JSON.stringify({ @@ -22291,21 +22796,21 @@ function Li(e, t, r, n = !1) { pubnumber: l.pubnumber ?? null }) ); - let u = 0, d = ya(e, i + 1 + u); - for (; d; ) - Li(d, t, r), u++, d = ya(e, i + 1 + u); - t.push($t), i += u; + let d = 0, u = _a(e, i + 1 + d); + for (; u; ) + zi(u, t, r), d++, u = _a(e, i + 1 + d); + t.push(Lt), i += d; continue; } if (o === "marker") { const l = s; t.push( - Rt, + $t, "marker", It( - Rg(l.marker ?? "", l.markerSyntax, l.nested) + rm(l.marker ?? "", l.markerSyntax, l.nested) ), - $t + Lt ); continue; } @@ -22315,44 +22820,44 @@ function Li(e, t, r, n = !1) { } if (o === "char") { const l = s; - t.push(Rt, "char", JSON.stringify(l.unknownAttributes ?? null)), Li(Sr(s) ?? [], t, r, !0), t.push($t); + t.push($t, "char", JSON.stringify(l.unknownAttributes ?? null)), zi(Pr(s) ?? [], t, r, !0), t.push(Lt); continue; } if (o === "note" || o === "unknown") { - t.push(tt); + t.push(st); continue; } if (o === "unmatched") { - t.push(Rt, "unmatched", It(ci(s) ?? "")), t.push($t); + t.push($t, "unmatched", It(ui(s) ?? "")), t.push(Lt); continue; } - const a = ci(s); + const a = ui(s); if (a !== void 0) { - t.push(It(n ? Pg(a) : a)); + t.push(It(n ? Xg(a) : a)); continue; } - const c = Sr(s); - c ? (t.push(Rt, o), Li(c, t, r), t.push($t)) : t.push(tt); + const c = Pr(s); + c ? (t.push($t, o), zi(c, t, r), t.push(Lt)) : t.push(st); } } -function qo(e) { +function Do(e) { let t = 0; for (const r of e) { - const n = Sr(r); + const n = Pr(r); if (n) { - t += qo(n); + t += Do(n); continue; } - const i = ci(r); + const i = ui(r); if (i !== void 0) - for (const s of i) s === tt && t++; + for (const s of i) s === st && t++; } return t; } -function ss(e, t, r, n, i) { - Mn(e.getChildren(), t, r, n, i); +function us(e, t, r, n, i) { + Pn(e.getChildren(), t, r, n, i); } -function Mn(e, t, r, n, i) { +function Pn(e, t, r, n, i) { const s = () => { const o = i?.pending === !0; return i && (i.pending = !1), o; @@ -22360,211 +22865,211 @@ function Mn(e, t, r, n, i) { for (let o = 0; o < e.length; o++) { const a = e[o]; if (O(a)) - Es(t, a, It(a.getTextContent())); - else if (Be(a)) { + ws(t, a, It(a.getTextContent())); + else if (He(a)) { s(); - const c = Ml(e, o); - wo(a.getMarker(), r) && Og(c) ? Mn(c, t, r, n) : Oi(t, [a, ...c]), o += c.length; - } else if (j(a) || De(a)) - s(), Oi(t, [a]); - else if (Oe(a)) { + const c = Il(e, o); + Io(a.getMarker(), r) && Zg(c) ? Pn(c, t, r, n) : $i(t, [a, ...c]), o += c.length; + } else if (K(a) || Ue(a)) + s(), $i(t, [a]); + else if (Ae(a)) { s(); - const c = El(e, o); - Al(a) ? Oi(t, [a, ...c]) : (Es(t, a, It(Ki(a))), Mn(c, t, r, n)), o += c.length; - } else if (D(a)) - s(), qg(a, r) ? Oi(t, [a]) : ss(a, t, r, n, { pending: !0 }); - else if (us(a)) - s(), Es(t, a, " "); - else if (v(a)) { - const c = Nn(a) || ne(a, oe) === "attribute", l = s() && !c; - Es( + const c = Dl(e, o); + Ul(a) ? $i(t, [a, ...c]) : (ws(t, a, It(Wi(a))), Pn(c, t, r, n)), o += c.length; + } else if (I(a)) + s(), tm(a, r) ? $i(t, [a]) : us(a, t, r, n, { pending: !0 }); + else if (di(a)) + s(), ws(t, a, " "); + else if (S(a)) { + const c = qn(a) || ie(a, ae) === "attribute", l = s() && !c; + ws( t, a, - c ? It(Ki(a)) : XM(Ki(a), n, l) + c ? It(Wi(a)) : VE(Wi(a), n, l) ); - } else F(a) ? ss(a, t, r, n, i) : (s(), Oi(t, [a])); + } else L(a) ? us(a, t, r, n, i) : (s(), $i(t, [a])); } } -function Nl(e, t, r) { +function zl(e, t, r) { if (e.getUnknownAttributes()) return; const n = t(e.getMarker())?.type; if (n !== void 0 && n !== b.Unknown && n !== b.Paragraph) return; for (let s = e.getParent(); s !== null; s = s.getParent()) - if (De(s)) return; + if (Ue(s)) return; const i = { text: "", spans: [], sentinels: [] }; - return ss(e, i, t, r), i; + return us(e, i, t, r), i; } -function $g(e, t) { +function nm(e, t) { let r = 0; const n = (i) => { - if (v(i)) { + if (S(i)) { let s = i; for (; s; ) { - const a = s.getTextContent().indexOf(tt); + const a = s.getTextContent().indexOf(st); if (a < 0) break; let c = s, l; a > 0 && ([, c] = s.splitText(a)), c.getTextContent().length > 1 && ([c, l] = c.splitText(1)); - const u = t[r++]; - if (u && u.length > 0) { - let d = c; - for (const f of u) - d.insertAfter(f), d = f; + const d = t[r++]; + if (d && d.length > 0) { + let u = c; + for (const p of d) + u.insertAfter(p), u = p; } c.remove(), s = l; } - } else F(i) && [...i.getChildren()].forEach(n); + } else L(i) && [...i.getChildren()].forEach(n); }; e.forEach(n); } -function sc(e, t = []) { +function hc(e, t = []) { for (const r of e) - Oe(r) ? t.push(r) : F(r) && sc(r.getChildren(), t); + Ae(r) ? t.push(r) : L(r) && hc(r.getChildren(), t); return t; } -function Ig(e) { +function im(e) { let t = 0; const r = (n) => { - if (v(n)) - for (const i of n.getTextContent()) i === tt && t++; - else F(n) && n.getChildren().forEach(r); + if (S(n)) + for (const i of n.getTextContent()) i === st && t++; + else L(n) && n.getChildren().forEach(r); }; return e.forEach(r), t; } -function qn(e) { +function Ln(e) { let t = 0; for (const r of e) if (typeof r == "string") - for (const n of r) n === tt && t++; - else r.content && (t += qn(r.content)); + for (const n of r) n === st && t++; + else r.content && (t += Ln(r.content)); return t; } -function QM(e, t, r) { +function WE(e, t, r) { const n = { text: "", spans: [], sentinels: [] }; for (const i of e) - n.text.length > 0 && (n.text += " "), F(i) && ss(i, n, t, r); + n.text.length > 0 && (n.text += " "), L(i) && us(i, n, t, r); return { text: n.text, spans: n.spans }; } -const os = /\s/; -function Lg(e) { - return e.filter(Ro).length; +const ds = /\s/; +function sm(e) { + return e.filter(Uo).length; } -function Ro(e) { +function Uo(e) { if (e.isSentinel) return !1; - const t = se(e.key); - return v(t) && !O(t) && ne(t, oe) === "attribute"; + const t = oe(e.key); + return S(t) && !O(t) && ie(t, ae) === "attribute"; } -function ZM(e) { +function HE(e) { if (e.isSentinel) return !1; - const t = se(e.key); - return O(t) || Ro(e); + const t = oe(e.key); + return O(t) || Uo(e); } -function Kd(e, t, r, n) { +function rf(e, t, r, n) { let i = 0, s = 0; for (const o of e.spans) { const a = o.end - o.start, c = o.key === t; - if (!c && n && Ro(o)) continue; + if (!c && n && Uo(o)) continue; const l = c ? Math.min(o.isSentinel ? 1 : r, a) : a; - for (let u = 0; u < l; u++) - os.test(e.text[o.start + u]) ? s++ : (i++, s = 0); + for (let d = 0; d < l; d++) + ds.test(e.text[o.start + d]) ? s++ : (i++, s = 0); if (c) return { nonWsBefore: i, wsRun: s }; } } -function Ol(e, t, r) { - const n = Kd(e, t, r, !1); +function Kl(e, t, r) { + const n = rf(e, t, r, !1); if (!n) return; - const i = e.spans.find((o) => o.key === t), s = i && !ZM(i) ? Kd(e, t, r, !0) : void 0; - return { ...n, documentCoords: s, attributeRunSpans: Lg(e.spans) }; + const i = e.spans.find((o) => o.key === t), s = i && !HE(i) ? rf(e, t, r, !0) : void 0; + return { ...n, documentCoords: s, attributeRunSpans: sm(e.spans) }; } -function ba(e) { +function Ca(e) { if (e.isSentinel) return !1; - const t = se(e.key); + const t = oe(e.key); return O(t) && t.getMarkerSyntax() !== "opening"; } -function eE(e) { - const t = se(e.key); +function GE(e) { + const t = oe(e.key); if (!O(t)) return !1; const r = t.getParent(); - return D(r) ? (r.selectNext(0, 0), !0) : !1; + return I(r) ? (r.selectNext(0, 0), !0) : !1; } -function tE(e) { - const t = se(e.key), r = t?.getParent()?.getChildren(); +function JE(e) { + const t = oe(e.key), r = t?.getParent()?.getChildren(); if (!t || !r) return !1; const n = r.findIndex((s) => s.is(t)); if (n < 0) return !1; - const i = Oe(t) ? El(r, n) : Be(t) ? Ml(r, n) : []; + const i = Ae(t) ? Dl(r, n) : He(t) ? Il(r, n) : []; return (i[i.length - 1] ?? t).selectNext(0, 0), !0; } -function Dg(e, t, r) { - const { text: n, spans: i } = e, s = Lg(i) === t.attributeRunSpans ? t.documentCoords : void 0, o = s !== void 0; - let a, c = (s ?? t).nonWsBefore, l = (s ?? t).wsRun, u = !1; - e: for (const d of i) { - const f = d.end - d.start, p = !d.isSentinel && !ba(d); - if (!(o && Ro(d))) { - if (u) { - if (!p) continue; - a = { key: d.key, offset: 0 }; +function om(e, t, r) { + const { text: n, spans: i } = e, s = sm(i) === t.attributeRunSpans ? t.documentCoords : void 0, o = s !== void 0; + let a, c = (s ?? t).nonWsBefore, l = (s ?? t).wsRun, d = !1; + e: for (const u of i) { + const p = u.end - u.start, f = !u.isSentinel && !Ca(u); + if (!(o && Uo(u))) { + if (d) { + if (!f) continue; + a = { key: u.key, offset: 0 }; break; } - for (let m = 0; m < f; m++) { - const g = n[d.start + m]; - if (c === 0 && (l === 0 || !os.test(g))) { - if (p) { - a = { key: d.key, offset: m }; + for (let g = 0; g < p; g++) { + const h = n[u.start + g]; + if (c === 0 && (l === 0 || !ds.test(h))) { + if (f) { + a = { key: u.key, offset: g }; break e; } - u = !0; + d = !0; continue e; } - c > 0 ? os.test(g) || c-- : l--; + c > 0 ? ds.test(h) || c-- : l--; } if (c === 0 && l === 0) { - if (p) { - a = { key: d.key, offset: f }; + if (f) { + a = { key: u.key, offset: p }; break; } - u = !0; + d = !0; } } } if (!a) { - const d = i[i.length - 1]; - if (d && ba(d) && eE(d) || d?.isSentinel && tE(d)) return; - const f = [...i].reverse().find((p) => !p.isSentinel && !ba(p)); - f && (a = { key: f.key, offset: f.end - f.start }); + const u = i[i.length - 1]; + if (u && Ca(u) && GE(u) || u?.isSentinel && JE(u)) return; + const p = [...i].reverse().find((f) => !f.isSentinel && !Ca(f)); + p && (a = { key: p.key, offset: p.end - p.start }); } if (a) { - const d = se(a.key); - if (d && v(d)) { - d.select(a.offset, a.offset); + const u = oe(a.key); + if (u && S(u)) { + u.select(a.offset, a.offset); return; } } - r.find(F)?.selectStart(); + r.find(L)?.selectStart(); } -function Ug(e, t, r, n, i) { +function am(e, t, r, n, i) { if (r) { if (t === void 0) { - e.find(F)?.selectStart(); + e.find(L)?.selectStart(); return; } - Dg(QM(e, n, i), t, e); + om(WE(e, n, i), t, e); } } -function rE(e, t, r, n, i) { +function YE(e, t, r, n, i) { if (!r) return; if (t === void 0) { - e.find(F)?.selectStart(); + e.find(L)?.selectStart(); return; } const s = { text: "", spans: [], sentinels: [] }; - Mn(e, s, n, i), Dg({ text: s.text, spans: s.spans }, t, e); + Pn(e, s, n, i), om({ text: s.text, spans: s.spans }, t, e); } -function Fg(e, t) { +function cm(e, t) { if (e.length === 0) return !1; const { viewOptions: r, getMarker: n, logger: i } = t, s = { text: "", spans: [], sentinels: [] }; - for (const g of e) { - const y = Nl(g, n, r); + for (const h of e) { + const y = zl(h, n, r); if (!y) return i?.debug("[MarkerEdit] Tier 2 skipped: paragraph excluded by guard rails"), !1; s.text.length > 0 && (s.text += " "); @@ -22575,218 +23080,218 @@ function Fg(e, t) { } let o, a = !1; const c = w(); - if (N(c)) { - for (let g = c.anchor.getNode(); g; g = g.getParent()) - if (e.some((y) => y.is(g))) { + if (P(c)) { + for (let h = c.anchor.getNode(); h; h = h.getParent()) + if (e.some((y) => y.is(h))) { a = !0; break; } - c.isCollapsed() && (o = Ol(s, c.anchor.key, c.anchor.offset)); + c.isCollapsed() && (o = Kl(s, c.anchor.key, c.anchor.offset)); } - const l = Mr(s.text, { + const l = Or(s.text, { getMarker: n }); if (l.length === 0) return i?.debug("[MarkerEdit] Tier 2 skipped: tokenizer produced no content"), !1; - if (qn(l) !== s.sentinels.length) + if (Ln(l) !== s.sentinels.length) return i?.warn("[MarkerEdit] Tier 2 aborted: sentinel/preserved-node count mismatch"), !1; - const u = Cr.serializeEditorState( - { type: kr, version: br, content: l }, + const d = Ar.serializeEditorState( + { type: vr, version: Cr, content: l }, r ); - if (ki(u.root.children, n) === bi(e, n)) + if (_i(d.root.children, n) === xi(e, n)) return i?.debug("[MarkerEdit] Tier 2 skipped: rebuild is a no-op (fixed point)"), !1; - const d = u.root.children.map((g) => ao(g)); - if (Ig(d) !== s.sentinels.length) + const u = d.root.children.map((h) => po(h)); + if (im(u) !== s.sentinels.length) return i?.warn("[MarkerEdit] Tier 2 aborted: serialized sentinel/preserved-node count mismatch"), !1; - const f = sc(e).map((g) => ({ - number: g.getNumber(), - sid: g.getSid() - })), p = e[0]; - d.forEach((g) => p.insertBefore(g)), $g(d, s.sentinels), e.forEach((g) => g.remove()); - const m = sc(d); - for (let g = 0; g < f.length && g < m.length; g++) - m[g].getNumber() === f[g].number && m[g].setSid(f[g].sid); - return Ug(d, o, a, n, r), !0; -} -function zg(e, t, r) { - if (e.getIsCollapsed() !== !1 || !Ee.isValidMarker(e.getMarker())) return; + const p = hc(e).map((h) => ({ + number: h.getNumber(), + sid: h.getSid() + })), f = e[0]; + u.forEach((h) => f.insertBefore(h)), nm(u, s.sentinels), e.forEach((h) => h.remove()); + const g = hc(u); + for (let h = 0; h < p.length && h < g.length; h++) + g[h].getNumber() === p[h].number && g[h].setSid(p[h].sid); + return am(u, o, a, n, r), !0; +} +function lm(e, t, r) { + if (e.getIsCollapsed() !== !1 || !Pe.isValidMarker(e.getMarker())) return; const n = e.getChildren(); let i = 0; for (; i < n.length; ) { - const u = n[i]; - if (!O(u) || u.getMarkerSyntax() !== "opening") break; + const d = n[i]; + if (!O(d) || d.getMarkerSyntax() !== "opening") break; i++; } const s = n[i]; - if (!s || !(St(s) || v(s) && s.getTextContent() === At(e.getCaller()))) return; + if (!s || !(St(s) || S(s) && s.getTextContent() === Pt(e.getCaller()))) return; i++; let a = n.length; for (; a > i; ) { - const u = n[a - 1]; - if (!O(u) || u.getMarkerSyntax() !== "closing") break; + const d = n[a - 1]; + if (!O(d) || d.getMarkerSyntax() !== "closing") break; a--; } const c = n.slice(i, a), l = { text: "", spans: [], sentinels: [] }; - return Mn(c, l, t, r), { out: l, contentNodes: c }; + return Pn(c, l, t, r), { out: l, contentNodes: c }; } -function Kg(e) { +function um(e) { const t = e[0]; if (typeof t != "object" || t.type !== "char" || t.marker !== "cat" || !Object.keys(t).every((s) => s === "type" || s === "marker" || s === "content") || !t.content || t.content.length !== 1) return; const n = t.content[0]; - if (typeof n != "string" || n.includes(tt)) return; + if (typeof n != "string" || n.includes(st)) return; const i = n.trim(); if (i !== "") return e.shift(), i; } -function nE(e, t) { - const { viewOptions: r, getMarker: n, logger: i } = t, s = zg(e, n, r); +function XE(e, t) { + const { viewOptions: r, getMarker: n, logger: i } = t, s = lm(e, n, r); if (!s) return i?.debug("[MarkerEdit] Note Tier 2 skipped: note excluded by guard rails"), !1; const { out: o, contentNodes: a } = s; let c, l = !1; - const u = w(); - if (N(u)) { - for (let P = u.anchor.getNode(); P; P = P.getParent()) - if (e.is(P)) { + const d = w(); + if (P(d)) { + for (let M = d.anchor.getNode(); M; M = M.getParent()) + if (e.is(M)) { l = !0; break; } - u.isCollapsed() && (c = Ol(o, u.anchor.key, u.anchor.offset)); + d.isCollapsed() && (c = Kl(o, d.anchor.key, d.anchor.offset)); } - const d = Mr(o.text, { + const u = Or(o.text, { getMarker: n, isNoteContext: !0 }); - if (d.length === 0) + if (u.length === 0) return i?.debug("[MarkerEdit] Note Tier 2 skipped: tokenizer produced no content"), !1; - if (qn(d) !== o.sentinels.length) + if (Ln(u) !== o.sentinels.length) return i?.warn("[MarkerEdit] Note Tier 2 aborted: sentinel/preserved-node count mismatch"), !1; - const [f] = d; - if (d.length !== 1 || typeof f != "object" || f.type !== "para") + const [p] = u; + if (u.length !== 1 || typeof p != "object" || p.type !== "para") return i?.warn("[MarkerEdit] Note Tier 2 aborted: unexpected tokenized shape"), !1; - const p = f.content ?? [], m = Kg(p), g = Ng(e, p, m, r); - if (g.failure !== void 0) - return g.failure === "empty" ? i?.debug("[MarkerEdit] Note Tier 2 skipped: no content nodes after unwrap") : i?.warn( - g.failure === "caller" ? "[MarkerEdit] Note Tier 2 aborted: serialized note lacks the editable caller" : "[MarkerEdit] Note Tier 2 aborted: unexpected serialized shape" + const f = p.content ?? [], g = um(f), h = Qg(e, f, g, r); + if (h.failure !== void 0) + return h.failure === "empty" ? i?.debug("[MarkerEdit] Note Tier 2 skipped: no content nodes after unwrap") : i?.warn( + h.failure === "caller" ? "[MarkerEdit] Note Tier 2 aborted: serialized note lacks the editable caller" : "[MarkerEdit] Note Tier 2 aborted: unexpected serialized shape" ), !1; - if (qo(g.children) !== o.sentinels.length) + if (Do(h.children) !== o.sentinels.length) return i?.warn( "[MarkerEdit] Note Tier 2 aborted: serialized sentinel/preserved-node count mismatch" ), !1; - const y = e.getCategory() !== m; - if (y && e.setCategory(m), ki(g.children, n) === bi(a, n)) + const y = e.getCategory() !== g; + if (y && e.setCategory(g), _i(h.children, n) === xi(a, n)) return i?.debug("[MarkerEdit] Note Tier 2 skipped: rebuild is a no-op (fixed point)"), y; - const k = g.children.map((P) => ao(P)); - if (Ig(k) !== o.sentinels.length) + const k = h.children.map((M) => po(M)); + if (im(k) !== o.sentinels.length) return i?.warn("[MarkerEdit] Note Tier 2 aborted: parsed sentinel/preserved-node count mismatch"), y; const _ = a[0]; if (_) - k.forEach((P) => _.insertBefore(P)); + k.forEach((M) => _.insertBefore(M)); else { - const P = e.getChildren().find((A) => O(A) && A.getMarkerSyntax() === "closing"); - k.forEach((A) => P ? P.insertBefore(A) : e.append(A)); - } - $g(k, o.sentinels); - const S = new Set(o.sentinels.flat().map((P) => P.getKey())); - return a.forEach((P) => { - S.has(P.getKey()) || P.remove(); - }), rE(k, c, l, n, r), !0; -} -const jg = /* @__PURE__ */ new Set(["ca", "cp"]), wl = "cp"; -function Bg(e) { - if (!dr(e)) return !1; + const M = e.getChildren().find((E) => O(E) && E.getMarkerSyntax() === "closing"); + k.forEach((E) => M ? M.insertBefore(E) : e.append(E)); + } + nm(k, o.sentinels); + const C = new Set(o.sentinels.flat().map((M) => M.getKey())); + return a.forEach((M) => { + C.has(M.getKey()) || M.remove(); + }), YE(k, c, l, n, r), !0; +} +const dm = /* @__PURE__ */ new Set(["ca", "cp"]), jl = "cp"; +function fm(e) { + if (!fr(e)) return !1; const t = { text: "", spans: [], sentinels: [] }; - if (ss(e, t, lr, void 0), t.sentinels.length > 0) return !1; + if (us(e, t, ur, void 0), t.sentinels.length > 0) return !1; const r = t.text; if (r.trim() === "") return !1; - const n = Mr(r, { getMarker: lr }), [i] = n, s = n.length === 1 && typeof i == "object" && i.type === "para" && i.marker === "p" && !/^\s*\\p\s/.test(r) ? i.content ?? [] : n; + const n = Or(r, { getMarker: ur }), [i] = n, s = n.length === 1 && typeof i == "object" && i.type === "para" && i.marker === "p" && !/^\s*\\p\s/.test(r) ? i.content ?? [] : n; return s.length === 0 ? !1 : s.every( - (o) => typeof o == "string" ? o.trim() === "" : (o.type === "char" || o.type === "para") && (o.marker === "ca" || o.marker === wl) + (o) => typeof o == "string" ? o.trim() === "" : (o.type === "char" || o.type === "para") && (o.marker === "ca" || o.marker === jl) ); } -function $o(e) { +function Fo(e) { const t = []; for (let r = e.getNextSibling(); r; r = r.getNextSibling()) { - if (D(r) && jg.has(r.getMarker()) || Bg(r)) { + if (I(r) && dm.has(r.getMarker()) || fm(r)) { t.push(r); continue; } - ae(r) && r.getMarker() === wl && t.push(r); + le(r) && r.getMarker() === jl && t.push(r); break; } return t; } -function iE(e) { - const t = (n) => D(n) && jg.has(n.getMarker()) || Bg(n); - if (t(e) || ae(e) && e.getMarker() === wl) +function QE(e) { + const t = (n) => I(n) && dm.has(n.getMarker()) || fm(n); + if (t(e) || le(e) && e.getMarker() === jl) for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) { - if (Pe(n)) return n; + if (we(n)) return n; if (!t(n)) return; } } -function Vg(e, t, r) { +function pm(e, t, r) { if (Object.keys(e.getUnknownAttributes() ?? {}).length > 0) return; - const n = $o(e); - if (n.some((s) => ae(s) && s.getUnknownAttributes())) return; + const n = Fo(e); + if (n.some((s) => le(s) && s.getUnknownAttributes())) return; const i = { text: "", spans: [], sentinels: [] }; - if (Mn(e.getChildren(), i, t, r), Mn(n, i, t, r), !(i.sentinels.length > 0)) + if (Pn(e.getChildren(), i, t, r), Pn(n, i, t, r), !(i.sentinels.length > 0)) return i; } -function sE(e, t) { - const { viewOptions: r, getMarker: n, logger: i } = t, s = [e, ...$o(e)], o = Vg(e, n, r); +function ZE(e, t) { + const { viewOptions: r, getMarker: n, logger: i } = t, s = [e, ...Fo(e)], o = pm(e, n, r); if (!o) return i?.debug("[MarkerEdit] Chapter Tier 2 skipped: chapter excluded by guard rails"), !1; let a, c = !1; const l = w(); - if (N(l)) { - for (let m = l.anchor.getNode(); m; m = m.getParent()) - if (s.some((g) => g.is(m))) { + if (P(l)) { + for (let g = l.anchor.getNode(); g; g = g.getParent()) + if (s.some((h) => h.is(g))) { c = !0; break; } - l.isCollapsed() && (a = Ol(o, l.anchor.key, l.anchor.offset)); + l.isCollapsed() && (a = Kl(o, l.anchor.key, l.anchor.offset)); } - const u = Mr(o.text, { getMarker: n }), [d] = u; - if (u.length === 0 || typeof d != "object" || d.type !== "chapter") + const d = Or(o.text, { getMarker: n }), [u] = d; + if (d.length === 0 || typeof u != "object" || u.type !== "chapter") return i?.debug("[MarkerEdit] Chapter Tier 2 skipped: bytes no longer tokenize as a chapter"), !1; - if (qn(u) !== 0) + if (Ln(d) !== 0) return i?.warn("[MarkerEdit] Chapter Tier 2 aborted: unexpected preserved-node placeholder"), !1; - e.getSid() !== void 0 && (d.sid = e.getSid()); - const f = Cr.serializeEditorState( - { type: kr, version: br, content: u }, + e.getSid() !== void 0 && (u.sid = e.getSid()); + const p = Ar.serializeEditorState( + { type: vr, version: Cr, content: d }, r ); - if (ki(f.root.children, n) === bi(s, n)) { - let m = !1; - return e.getNumber() !== (d.number ?? "") && (e.setNumber(d.number ?? ""), m = !0), e.getAltnumber() !== d.altnumber && (e.setAltnumber(d.altnumber), m = !0), e.getPubnumber() !== d.pubnumber && (e.setPubnumber(d.pubnumber), m = !0), m || i?.debug("[MarkerEdit] Chapter Tier 2 skipped: rebuild is a no-op (fixed point)"), m; + if (_i(p.root.children, n) === xi(s, n)) { + let g = !1; + return e.getNumber() !== (u.number ?? "") && (e.setNumber(u.number ?? ""), g = !0), e.getAltnumber() !== u.altnumber && (e.setAltnumber(u.altnumber), g = !0), e.getPubnumber() !== u.pubnumber && (e.setPubnumber(u.pubnumber), g = !0), g || i?.debug("[MarkerEdit] Chapter Tier 2 skipped: rebuild is a no-op (fixed point)"), g; } - const p = f.root.children.map((m) => ao(m)); - return Pe(p[0]) ? (p.forEach((m) => e.insertBefore(m)), s.forEach((m) => m.remove()), Ug(p, a, c, n, r), !0) : (i?.warn("[MarkerEdit] Chapter Tier 2 aborted: serialized output is not a chapter"), !1); + const f = p.root.children.map((g) => po(g)); + return we(f[0]) ? (f.forEach((g) => e.insertBefore(g)), s.forEach((g) => g.remove()), am(f, a, c, n, r), !0) : (i?.warn("[MarkerEdit] Chapter Tier 2 aborted: serialized output is not a chapter"), !1); } -function as(e) { +function fs(e) { let t, r; for (let n = e; n; n = n.getParent()) { - if (De(n)) return; - !t && (j(n) || ae(n) || Pe(n)) && (t = n), Ry(n.getParent()) && (r = n); + if (Ue(n)) return; + !t && (K(n) || le(n) || we(n)) && (t = n), Jy(n.getParent()) && (r = n); } - return t && !t.is(r) ? t : (r ? iE(r) : void 0) ?? t; + return t && !t.is(r) ? t : (r ? QE(r) : void 0) ?? t; } function Vt(e, t) { - const r = as(e); - return r ? j(r) ? nE(r, t) : Pe(r) ? sE(r, t) : Fg([r], t) : !1; + const r = fs(e); + return r ? K(r) ? XE(r, t) : we(r) ? ZE(r, t) : cm([r], t) : !1; } -const oE = /* @__PURE__ */ new Set(["type", "marker", "content", "closed"]); -function jd(e, t) { +const eA = /* @__PURE__ */ new Set(["type", "marker", "content", "closed"]); +function nf(e, t) { const r = Object.entries(e).filter( - (n) => typeof n[1] == "string" && !oE.has(n[0]) + (n) => typeof n[1] == "string" && !eA.has(n[0]) ); if (r.length !== 0) { t.push("|"); for (const [n, i] of r) t.push(`${n}="${i}"`); } } -function qs(e, t) { +function Ds(e, t) { if (e) for (const r of e) { if (typeof r == "string") { @@ -22802,111 +23307,111 @@ function qs(e, t) { t.push(`\\${n} ${r.number ?? ""}`), r.altnumber !== void 0 && t.push(`\\ca ${r.altnumber}\\ca*`), r.pubnumber !== void 0 && t.push(`\\cp ${r.pubnumber}`); break; case "ms": - t.push(`\\${n}`), jd(r, t), t.push("\\*"); + t.push(`\\${n}`), nf(r, t), t.push("\\*"); break; case "unmatched": t.push(`\\${n}`); break; case "char": - t.push(`\\${n} `), qs(r.content, t), jd(r, t), i !== "false" && t.push(`\\${n}*`); + t.push(`\\${n} `), Ds(r.content, t), nf(r, t), i !== "false" && t.push(`\\${n}*`); break; case "note": { const s = r.caller, o = r.category; - t.push(`\\${n} ${s ?? ""}`), o !== void 0 && t.push(`\\cat ${o}\\cat*`), qs(r.content, t), i !== "false" && t.push(`\\${n}*`); + t.push(`\\${n} ${s ?? ""}`), o !== void 0 && t.push(`\\cat ${o}\\cat*`), Ds(r.content, t), i !== "false" && t.push(`\\${n}*`); break; } default: - t.push(`\\${n} `), qs(r.content, t); + t.push(`\\${n} `), Ds(r.content, t); } } } -function Bd(e, t, r) { - const n = as(e); - if (!ae(n)) return !1; +function sf(e, t, r) { + const n = fs(e); + if (!le(n)) return !1; const i = w(); - if (!N(i) || !i.isCollapsed()) return !1; + if (!P(i) || !i.isCollapsed()) return !1; let s = !1; - for (let u = i.anchor.getNode(); u; u = u.getParent()) - if (n.is(u)) { + for (let d = i.anchor.getNode(); d; d = d.getParent()) + if (n.is(d)) { s = !0; break; } if (!s) return !1; - const o = Nl(n, t, r); + const o = zl(n, t, r); if (!o) return !1; - const a = Mr(o.text, { getMarker: t }); + const a = Or(o.text, { getMarker: t }); if (a.length === 0) return !1; const c = /* @__PURE__ */ new Map(); - for (const u of o.text) - os.test(u) || c.set(u, (c.get(u) ?? 0) + 1); + for (const d of o.text) + ds.test(d) || c.set(d, (c.get(d) ?? 0) + 1); const l = []; - qs(a, l); - for (const u of l.join("").replaceAll(I, "~")) { - if (os.test(u)) continue; - const d = c.get(u); - d !== void 0 && d > 0 && c.set(u, d - 1); + Ds(a, l); + for (const d of l.join("").replaceAll(R, "~")) { + if (ds.test(d)) continue; + const u = c.get(d); + u !== void 0 && u > 0 && c.set(d, u - 1); } - for (const u of c.values()) if (u > 0) return !0; + for (const d of c.values()) if (d > 0) return !0; return !1; } -function ql(e, t) { - return Wg(e, t, b.Paragraph); +function Bl(e, t) { + return hm(e, t, b.Paragraph); } -function aE(e, t) { - return Wg(e, t, b.Character); +function tA(e, t) { + return hm(e, t, b.Character); } -function Wg(e, t, r) { +function hm(e, t, r) { const n = e.replace(/^\+/, ""); if (n === "v" || n === "c") return !1; const i = t(n)?.type; - return i !== void 0 && i !== b.Unknown ? i === r : !(Ee.isValidMarker(n) || Ac(n)); + return i !== void 0 && i !== b.Unknown ? i === r : !(Pe.isValidMarker(n) || Rc(n)); } -function cE(e) { - return [ot(e), mo()]; +function rA(e) { + return [lt(e), To()]; } -function Rl(e) { +function Vl(e) { Qt(e, 2); } -function lE(e) { +function nA(e) { const t = w(); - if (!N(t) || !t.isCollapsed()) return !1; + if (!P(t) || !t.isCollapsed()) return !1; const { anchor: r } = t; if (r.type === "element") return r.key === e.getKey() && r.offset === 0; const n = e.getFirstChild(); return n !== null && r.key === n.getKey() && r.offset === 0; } -function $l(e) { - const t = lE(e); - e.splice(0, 0, cE(e.getMarker())), t && Rl(e); +function Wl(e) { + const t = nA(e); + e.splice(0, 0, rA(e.getMarker())), t && Vl(e); } -function no(e, t) { - e.setMarker(t), $l(e), Rl(e); +function lo(e, t) { + e.setMarker(t), Wl(e), Vl(e); } -function uE(e, t) { +function iA(e, t) { const r = e.getFirstChild(); if (!r) return; const n = r.getNextSibling(); - if (!Nn(n)) { - if (v(n) && !O(n) && /^[ \u00A0]$/.test(n.getTextContent())) { + if (!qn(n)) { + if (S(n) && !O(n) && /^[ \u00A0]$/.test(n.getTextContent())) { if (t.collapsedDeleteCaretParas?.has(e.getKey())) { t.pendingKeys.add(e.getKey()); return; } - n.setTextContent(I), bt(n, oe, fr), n.setMode("token"); + n.setTextContent(R), kt(n, ae, pr), n.setMode("token"); return; } - if (Pp(e)) { + if (zp(e)) { t.pendingKeys.add(e.getKey()); return; } - r.insertAfter(mo()); + r.insertAfter(To()); } } -function Vd(e, t, r) { +function of(e, t, r) { const n = e.getNode(); if (n.is(t)) return r === "start" ? e.offset === 0 : e.offset === t.getChildrenSize(); - const i = e.type === "text" ? n.getTextContentSize() : F(n) ? n.getChildrenSize() : 0; + const i = e.type === "text" ? n.getTextContentSize() : L(n) ? n.getChildrenSize() : 0; if (r === "start" ? e.offset !== 0 : e.offset !== i) return !1; for (let s = n; !s.is(t); ) { if (r === "start" ? s.getPreviousSibling() : s.getNextSibling()) return !1; @@ -22916,110 +23421,110 @@ function Vd(e, t, r) { } return !0; } -function ji(e) { +function Hi(e) { for (let t = e; t; t = t.getParent()) - if (ae(t)) return t; + if (le(t)) return t; } -function dE(e) { +function sA(e) { const t = e.isBackward(), r = t ? e.focus : e.anchor, n = t ? e.anchor : e.focus, i = /* @__PURE__ */ new Set(); for (const s of e.getNodes()) { - const o = ji(s); + const o = Hi(s); o && i.add(o); } return [...i].filter((s) => { - const o = ji(r.getNode())?.is(s) ?? !1, a = ji(n.getNode())?.is(s) ?? !1; - return !(o && !Vd(r, s, "start") || a && !Vd(n, s, "end")); + const o = Hi(r.getNode())?.is(s) ?? !1, a = Hi(n.getNode())?.is(s) ?? !1; + return !(o && !of(r, s, "start") || a && !of(n, s, "end")); }); } -function oc(e) { +function gc(e) { const t = e.wholeParaDeleteExpected; if (!t) return; const r = w(); - if (!(!N(r) || r.isCollapsed())) - for (const n of dE(r)) t.add(n.getKey()); + if (!(!P(r) || r.isCollapsed())) + for (const n of sA(r)) t.add(n.getKey()); } -function fE(e) { +function oA(e) { const t = e.collapsedDeleteCaretParas; if (!t) return; const r = w(); - if (!N(r) || !r.isCollapsed()) return; - const n = ji(r.focus.getNode()); + if (!P(r) || !r.isCollapsed()) return; + const n = Hi(r.focus.getNode()); n && t.add(n.getKey()); } -function pE(e) { +function aA(e) { const t = w(); - !N(t) || t.isCollapsed() || t.getNodes().some((r) => O(r)) && (oc(e), t.removeText()); + !P(t) || t.isCollapsed() || t.getNodes().some((r) => O(r)) && (gc(e), t.removeText()); } -const hE = new RegExp( +const cA = new RegExp( String.raw`^\\\+?([${er}]+)(?:[ \u00A0]|$)` ); -function gE(e, t) { - const r = hE.exec(e.getTextContent()); - return !!r && ql(r[1], t); -} -function mE(e, t) { - if (!gi(t.viewOptions)) return; - if (Ft(e.getFirstChild())) { - uE(e, t); +function lA(e, t) { + const r = cA.exec(e.getTextContent()); + return !!r && Bl(r[1], t); +} +function uA(e, t) { + if (!bi(t.viewOptions)) return; + if (zt(e.getFirstChild())) { + iA(e, t); return; } if (t.splitExpected.current) { - if (gE(e, t.getMarker)) return; - $l(e), t.logger?.debug(`[MarkerEdit] injected prefix for split para "${e.getMarker()}"`); + if (lA(e, t.getMarker)) return; + Wl(e), t.logger?.debug(`[MarkerEdit] injected prefix for split para "${e.getMarker()}"`); return; } if (e.isEmpty()) { const n = t.wholeParaDeleteExpected?.has(e.getKey()) ?? !1, i = t.collapsedDeleteCaretParas?.has(e.getKey()) ?? !1; if (!n && !i) return; - if (t.wholeParaDeleteExpected?.delete(e.getKey()), t.collapsedDeleteCaretParas?.delete(e.getKey()), !e.getParent()?.getChildren().some((o) => ae(o) && !o.is(e))) { - no(e, cr), t.logger?.debug("[MarkerEdit] whole-para delete of the last para: reset to \\p"); + if (t.wholeParaDeleteExpected?.delete(e.getKey()), t.collapsedDeleteCaretParas?.delete(e.getKey()), !e.getParent()?.getChildren().some((o) => le(o) && !o.is(e))) { + lo(e, lr), t.logger?.debug("[MarkerEdit] whole-para delete of the last para: reset to \\p"); return; } e.remove(), t.logger?.debug("[MarkerEdit] removed para whose whole representation was deleted"); return; } const r = e.getPreviousSibling(); - if (ae(r)) { - const n = e.getChildren().filter((a) => !Nn(a)), i = w(); + if (le(r)) { + const n = e.getChildren().filter((a) => !qn(a)), i = w(); let s = !1; - if (N(i) && i.isCollapsed()) { + if (P(i) && i.isCollapsed()) { const a = i.anchor.getNode(); - a.is(e) ? s = !0 : ji(a)?.is(e) && (s = !n.some( - (c) => a.is(c) || F(c) && a.getParents().some((l) => l.is(c)) + a.is(e) ? s = !0 : Hi(a)?.is(e) && (s = !n.some( + (c) => a.is(c) || L(c) && a.getParents().some((l) => l.is(c)) )); } const o = r.getChildrenSize(); r.append(...n), e.remove(), s && Qt(r, o), t.logger?.debug("[MarkerEdit] merged marker-deleted para into previous"); return; } - no(e, cr); + lo(e, lr); } -function yE(e) { +function dA(e) { const t = e.getUnknownAttributes(); if (!t) return; - const r = ar(t, po(e.getMarker())); + const r = cr(t, yo(e.getMarker())); return r === "" ? void 0 : r; } -function bE(e) { - const t = e.getChildren().filter((s) => !O(s) && ne(s, oe) !== "attribute"), r = t[0]; - r && v(r) && r.getTextContent().startsWith(I) && r.setTextContent(r.getTextContent().slice(1)); - const n = yE(e); - n && t.push(pe(n)); +function fA(e) { + const t = e.getChildren().filter((s) => !O(s) && ie(s, ae) !== "attribute"), r = t[0]; + r && S(r) && r.getTextContent().startsWith(R) && r.setTextContent(r.getTextContent().slice(1)); + const n = dA(e); + n && t.push(he(n)); let i = e; for (const s of t) i.insertAfter(s), i = s; e.remove(); } -function kE(e, t) { +function pA(e, t) { const r = e.getChildren(), n = r.some((s) => O(s) && s.getMarkerSyntax() === "opening"); if (e.getIsCollapsed() !== !0) { if (n) return; const s = e.getCaller(), o = s !== "" && r.some( - (c) => v(c) && !O(c) && c.getTextContent() === At(s) - ), a = ui(e).some(({ node: c }) => O(c)); + (c) => S(c) && !O(c) && c.getTextContent() === Pt(s) + ), a = pi(e).some(({ node: c }) => O(c)); if (!o && !a) return; r.forEach((c) => { - O(c) || (v(c) && c.getTextContent() === At(s) && c.setTextContent(` ${s} `), e.insertBefore(c)); + O(c) || (S(c) && c.getTextContent() === Pt(s) && c.setTextContent(` ${s} `), e.insertBefore(c)); }), e.remove(), t.logger?.debug( "[MarkerEdit] unwrapped expanded note whose opening glyph was deleted (content preserved)" ); @@ -23028,93 +23533,93 @@ function kE(e, t) { const i = r.some((s) => O(s) && s.getMarkerSyntax() === "closing"); n !== i && (e.remove(), t.logger?.debug("[MarkerEdit] removed collapsed note with damaged glyph pair")); } -function TE(e, t) { +function hA(e, t) { if (e.isEmpty()) return; const r = e.getFirstChild(); if (!(O(r) && r.getMarkerSyntax() === "opening")) { - bE(e), t.logger?.debug(`[MarkerEdit] unwrapped char span "${e.getMarker()}"`); + fA(e), t.logger?.debug(`[MarkerEdit] unwrapped char span "${e.getMarker()}"`); return; } const i = e.getUnknownAttributes()?.closed !== "false", s = e.getChildren().some((o) => O(o) && o.getMarkerSyntax() === "closing"); i && !s && Vt(e, t); } -function Hg(e, t, r) { - if (!O(e.getFirstChild()) && r?.markerMode === "editable" && gi(r)) { - no(e, t); +function gm(e, t, r) { + if (!O(e.getFirstChild()) && r?.markerMode === "editable" && bi(r)) { + lo(e, t); return; } - Ph(e, t); + zh(e, t); } -function Gg() { +function mm() { const e = w(); - if (!N(e)) return "declined"; + if (!P(e)) return "declined"; if (!e.isCollapsed()) { - const t = Jg(e); - return t !== "removed" ? t : (ac(), "handled"); + const t = ym(e); + return t !== "removed" ? t : (mc(), "handled"); } - return ac() ? "handled" : "declined"; + return mc() ? "handled" : "declined"; } -function xE(e, t) { +function gA(e, t) { if (!t) return e; - const r = JM.exec(e); + const r = iE.exec(e); if (!r) return e; const n = r[1]; return n === "c" || n === "v" || t(n)?.type !== b.Paragraph ? e : e.slice(r[0].length); } -function Wd(e, t) { +function af(e, t) { const r = w(); - if (!N(r)) return "declined"; + if (!P(r)) return "declined"; if (r.isCollapsed()) { - if (!Yg()) + if (!bm()) return "declined"; } else { - const s = Jg(r); + const s = ym(r); if (s !== "removed") return s; } const [n, ...i] = e.map( - (s) => xE(s, t) + (s) => gA(s, t) ); - Hd(n ?? ""); + cf(n ?? ""); for (const s of i) - ac(), Hd(s); + mc(), cf(s); return "handled"; } -function _E(e) { +function mA(e) { const t = e.getRootElement(), r = t?.ownerDocument.getSelection(), n = r?.anchorNode; if (!t || !r || !n || !t.contains(n)) return !1; - const i = li(n); + const i = fi(n); if (!i) return !1; const s = Xt(i); - if (!s || s.getIsCollapsed() !== !1 || s.is(i) || !v(i) || O(i)) return !1; + if (!s || s.getIsCollapsed() !== !1 || s.is(i) || !S(i) || O(i)) return !1; const o = Math.min(r.anchorOffset, i.getTextContentSize()); return i.select(o, o), !0; } -function Jg(e) { +function ym(e) { const t = Xt(e.anchor.getNode()), r = Xt(e.focus.getNode()), n = t?.getIsCollapsed() === !1, i = r?.getIsCollapsed() === !1; - return !n && !i ? "declined" : (e.removeText(), CE() ? "removed" : "needs-plain-split"); + return !n && !i ? "declined" : (e.removeText(), yA() ? "removed" : "needs-plain-split"); } -function Hd(e) { +function cf(e) { if (e === "") return; const t = w(); - N(t) && t.insertText(e); + P(t) && t.insertText(e); } -function CE() { +function yA() { const e = w(); - if (!N(e) || !e.isCollapsed()) return !1; + if (!P(e) || !e.isCollapsed()) return !1; const t = Xt(e.anchor.getNode()); return !t || t.getIsCollapsed() !== !1 ? !1 : t.getChildren().some((r) => O(r) && r.getMarkerSyntax() === "opening"); } -function Yg() { +function bm() { const e = w(); - if (!N(e) || !e.isCollapsed()) return; + if (!P(e) || !e.isCollapsed()) return; const t = e.anchor.getNode(), r = Xt(t); if (!(!r || r.getIsCollapsed() !== !1 || r.is(t))) return r; } -function ac() { +function mc() { const e = w(); - if (!N(e) || !e.isCollapsed()) return !1; - const t = e.anchor.getNode(), r = Yg(); + if (!P(e) || !e.isCollapsed()) return !1; + const t = e.anchor.getNode(), r = bm(); if (!r) return !1; let n = t; for (; !r.is(n.getParent()); ) { @@ -23122,9 +23627,9 @@ function ac() { if (!l) return !1; n = l; } - const i = xr("fp", { closed: "false" }); - i.append(ot("fp")); - const s = v(t) && !O(t) ? t : void 0, o = e.anchor.offset, a = s?.getTextContentSize() ?? 0, c = s !== void 0 && s.is(n); + const i = Mr("fp", { closed: "false" }); + i.append(lt("fp")); + const s = S(t) && !O(t) ? t : void 0, o = e.anchor.offset, a = s?.getTextContentSize() ?? 0, c = s !== void 0 && s.is(n); if (s && !c) { if (o <= 0) s.insertBefore(i); else if (o >= a) s.insertAfter(i); @@ -23132,73 +23637,73 @@ function ac() { const [, l] = s.splitText(o); l.insertBefore(i); } - ni(i, { renderGlyphs: !0 }); + si(i, { renderGlyphs: !0 }); } else { const l = s && o < a ? [o === 0 ? s : s.splitText(o)[1]] : []; n.insertAfter(i); - const [u] = l; - u && (Ak(u), i.append(u)); + const [d] = l; + d && (Bk(d), i.append(d)); } - return i.getChildren().every(O) && i.append(pe(Dt)), Xg(i), !0; + return i.getChildren().every(O) && i.append(he(Ut)), km(i), !0; } -function Xg(e) { +function km(e) { const t = e.getChildren().find((r) => !O(r)); - if (v(t)) { - const r = t.getTextContent().startsWith(I) ? 1 : 0; + if (S(t)) { + const r = t.getTextContent().startsWith(R) ? 1 : 0; t.select(r, r); return; } - if (F(t)) { - Xg(t); + if (L(t)) { + km(t); return; } e.selectEnd(); } -function SE(e) { +function bA(e) { const t = []; let r = e; for (; r; ) - D(r) && t.push(r.getMarker()), r = r.getParent(); + I(r) && t.push(r.getMarker()), r = r.getParent(); return t; } -function vE(e) { +function kA(e) { const t = e.getTopLevelElement(), r = []; - for (const n of Fe().getChildren()) { + for (const n of Ke().getChildren()) { if (t && n.is(t)) break; - (pt(n) || Ge(n) || ae(n)) && r.push(n.getMarker()); + (ht(n) || Je(n) || le(n)) && r.push(n.getMarker()); } return r; } -function ME(e) { +function TA(e) { let t = e; - for (; F(t); ) { + for (; L(t); ) { const r = t.getFirstChild(); if (!r) break; t = r; } return t; } -function EE(e, t, r) { +function xA(e, t, r) { const n = e.getFirstChild(); if (!n) return !1; let i = n; - if (Ft(n)) { + if (zt(n)) { if (t.is(n)) return !0; - if (i = n.getNextSibling(), i && Nn(i)) { + if (i = n.getNextSibling(), i && qn(i)) { if (t.is(i)) return !0; i = i.getNextSibling(); } } - return i ? t.is(ME(i)) && r === 0 : !1; + return i ? t.is(TA(i)) && r === 0 : !1; } -function AE(e, t, r) { +function _A(e, t, r) { const n = e.getFirstChild(); - if (!n || !Ft(n)) return !1; + if (!n || !zt(n)) return !1; if (t.is(n)) return !0; const i = n.getNextSibling(); - return i !== null && Nn(i) && t.is(i) && r === 0; + return i !== null && qn(i) && t.is(i) && r === 0; } -function PE() { +function CA() { if (typeof window > "u" || typeof window.getSelection != "function") return; const e = window.getSelection(); if (!e || e.rangeCount === 0) return; @@ -23207,67 +23712,67 @@ function PE() { const { x: r, y: n, width: i, height: s } = t.getBoundingClientRect(); return { x: r, y: n, width: i, height: s }; } -function NE() { +function vA() { const e = w(); - if (!N(e)) return; - const t = e.focus.getNode(), r = e.focus.offset, n = !e.isCollapsed(), i = nt(t, ae), s = !n && (!i || AE(i, t, r)) ? "paragraph" : "character", o = Xt(t); + if (!P(e)) return; + const t = e.focus.getNode(), r = e.focus.offset, n = !e.isCollapsed(), i = rt(t, le), s = !n && (!i || _A(i, t, r)) ? "paragraph" : "character", o = Xt(t); return { source: s, paraMarker: i?.getMarker(), - previousParaMarkers: vE(t), - openCharMarkers: SE(t), + previousParaMarkers: kA(t), + openCharMarkers: bA(t), noteMarker: o?.getMarker(), hasTextSelection: n, // The trailing edge of a canonical closing glyph counts as AFTER the marker, not inside it // (see $isPointInMarkerGlyphText) — Enter there opens the paragraph menu exactly as at the // end of a plain-text paragraph. - inMarkerText: Hc(t, r), - anchorRect: PE() + inMarkerText: Zc(t, r), + anchorRect: CA() }; } -function OE() { +function SA() { const e = w(); - if (!N(e) || !e.isCollapsed()) return; + if (!P(e) || !e.isCollapsed()) return; const t = e.anchor.getNode(); - if (!v(t) || O(t)) return; - const r = e.anchor.offset, n = t.getTextContent().slice(0, r), i = YM.exec(n); + if (!S(t) || O(t)) return; + const r = e.anchor.offset, n = t.getTextContent().slice(0, r), i = sE.exec(n); i && t.spliceText(r - i[0].length, i[0].length, "", !0); } -function wE(e, t, r) { - Hg(e, t, r), Rl(e); +function MA(e, t, r) { + gm(e, t, r), Vl(e); } -function qE(e, t, r) { +function EA(e, t, r) { const n = w(); - if (!N(n)) return; - const i = n.focus.getNode(), s = nt(i, ae); - if (t === "backslash" && s && EE(s, i, n.focus.offset)) { - wE(s, e, r); + if (!P(n)) return; + const i = n.focus.getNode(), s = rt(i, le); + if (t === "backslash" && s && xA(s, i, n.focus.offset)) { + MA(s, e, r); return; } - Zg(e, r); + xm(e, r); } -function RE(e, t) { +function AA(e, t) { const r = w(); - return !N(r) || !r.isCollapsed() ? !1 : (r.insertText(`\\${e}${t?.trailingSpace === !1 ? "" : " "}`), !0); + return !P(r) || !r.isCollapsed() ? !1 : (r.insertText(`\\${e}${t?.trailingSpace === !1 ? "" : " "}`), !0); } -function Qg(e) { +function Tm(e) { const t = w(); - return N(t) ? (t.insertText(`\\${e}*`), !0) : !1; + return P(t) ? (t.insertText(`\\${e}*`), !0) : !1; } -function $E(e, t, r, n) { - if (N(w()) || n.logger?.warn( +function PA(e, t, r, n) { + if (P(w()) || n.logger?.warn( "$applyMarkerMenuSelection: no range selection — cleanup/insert will no-op (editor blurred?)" - ), t.literalPrefixLanded && OE(), e.kind === "closeTag") { - Qg(e.marker.replace(/\*$/, "")); + ), t.literalPrefixLanded && SA(), e.kind === "closeTag") { + Tm(e.marker.replace(/\*$/, "")); return; } - if (e.marker === "fp" && Gg() !== "declined") return; - if (e.kind === "paragraph" && Ze.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers)) { - qE(e.marker, t.trigger, n.viewOptions); + if (e.marker === "fp" && mm() !== "declined") return; + if (e.kind === "paragraph" && nt.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers)) { + EA(e.marker, t.trigger, n.viewOptions); return; } - if (Ee.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers)) - return hg( + if (Pe.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers)) + return Eg( e.marker, r, n.expandedNoteKeyRef, @@ -23275,7 +23780,7 @@ function $E(e, t, r, n) { n.nodeOptions, n.logger ); - ec( + oc( e.marker, n.expandedNoteKeyRef, n.viewOptions, @@ -23283,122 +23788,122 @@ function $E(e, t, r, n) { n.logger, void 0, n.styleInfo - ).action({ editor: Zn(), reference: r }); + ).action({ editor: ti(), reference: r }); } -function Zg(e, t) { +function xm(e, t) { const r = w(); - if (!N(r)) return; - const n = gi(t); - if (fg()) { + if (!P(r)) return; + const n = bi(t); + if (Sg()) { const s = w(); - if (!N(s)) return; - const o = nt(s.anchor.getNode(), ae); + if (!P(s)) return; + const o = rt(s.anchor.getNode(), le); if (!o) return; - o.setMarker(e), n && $l(o); + o.setMarker(e), n && Wl(o); return; } const i = r.insertParagraph(); - ae(i) && (n ? no(i, e) : i.setMarker(e)); + le(i) && (n ? lo(i, e) : i.setMarker(e)); } -function IE() { +function NA() { const [e] = ce(); - return z(() => e.registerCommand(Sf, () => !0, Tt), [e]), null; + return F(() => e.registerCommand(Lf, () => !0, xt), [e]), null; } -function LE(e, t) { +function OA(e, t) { if (!e.startsWith("\\")) return !0; - const r = VM.exec(e)?.[1]; - return r === void 0 ? !1 : !ql(r, t); + const r = eE.exec(e)?.[1]; + return r === void 0 ? !1 : !Bl(r, t); } -function em(e, t) { - if (e.getMarkerSyntax() !== "opening" || !LE(e.getTextContent(), t)) return; +function _m(e, t) { + if (e.getMarkerSyntax() !== "opening" || !OA(e.getTextContent(), t)) return; const r = e.getParent(); - if (!ae(r)) return; + if (!le(r)) return; const n = t(r.getMarker())?.type; if (n !== void 0 && n !== b.Unknown || r.getFirstChild()?.is(e) !== !0) return; const i = r.getPreviousSibling(); - if (ae(i)) + if (le(i)) return [i, r]; } -function tm(e, t) { - const r = em(e, t.getMarker); - return r !== void 0 && Fg(r, t); +function Cm(e, t) { + const r = _m(e, t.getMarker); + return r !== void 0 && cm(r, t); } -function DE(e, t) { +function wA(e, t) { const r = w(); - N(r) && [r.anchor, r.focus].forEach((n) => { + P(r) && [r.anchor, r.focus].forEach((n) => { n.key === e.getKey() && n.offset > t && n.set(e.getKey(), t, "text"); }); } -function rm(e) { - const t = WM.exec(e); +function vm(e) { + const t = tE.exec(e); if (!t) return; const r = 1 + t[1].length; return { start: r, end: r + t[2].length }; } -function UE(e) { +function qA(e) { const t = w(); - if (!N(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return; - const r = rm(e.getTextContent()); + if (!P(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return; + const r = vm(e.getTextContent()); if (!r) return; const { offset: n } = t.anchor; if (!(n < r.start || n > r.end)) return n - r.start; } -function FE(e) { +function RA(e) { const t = w(); - if (!N(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return; + if (!P(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return; const r = e.getNextSibling(); - if (j(e.getParent()) && v(r)) { + if (K(e.getParent()) && S(r)) { const n = r.getNextSibling(); - if (D(n)) { - Vc(n); + if (I(n)) { + Xc(n); return; } } - v(r) ? r.select(1, 1) : e.select(e.getTextContentSize(), e.getTextContentSize()); + S(r) ? r.select(1, 1) : e.select(e.getTextContentSize(), e.getTextContentSize()); } -function Gd(e, t) { +function lf(e, t) { if (t !== void 0) { - const r = e.getLatest(), n = rm(r.getTextContent()); + const r = e.getLatest(), n = vm(r.getTextContent()); if (n) { const i = Math.min(n.start + t, n.end); r.select(i, i); return; } } - FE(e); + RA(e); } -function Jd(e, t) { +function uf(e, t) { return e.replace(/^\+/, "") !== t.replace(/^\+/, ""); } -function nm(e, t, r) { +function Sm(e, t, r) { if (t.startsWith("+") !== e.getNested()) return Vt(e, r); - const n = UE(e), i = e.getParent(); - if (ae(i)) { - if (!ql(t, r.getMarker)) - return tm(e, r) ? (r.logger?.debug( + const n = qA(e), i = e.getParent(); + if (le(i)) { + if (!Bl(t, r.getMarker)) + return Cm(e, r) ? (r.logger?.debug( `[MarkerEdit] unknown-split paragraph rejoined its predecessor on rename to "${t}"` ), !0) : Vt(e, r); const s = e.getMarker(); - return i.setMarker(t), e.setMarker(t), Jd(s, t) && Gd(e, n), r.logger?.debug(`[MarkerEdit] para marker renamed to "${t}"`), !0; + return i.setMarker(t), e.setMarker(t), uf(s, t) && lf(e, n), r.logger?.debug(`[MarkerEdit] para marker renamed to "${t}"`), !0; } - if (D(i) || j(i)) { + if (I(i) || K(i)) { const s = t.replace(/^\+/, ""); - if (!(D(i) ? aE(t, r.getMarker) : Ee.isValidMarker(s))) + if (!(I(i) ? tA(t, r.getMarker) : Pe.isValidMarker(s))) return Vt(e, r); const a = e.getMarker(); if (i.getMarker() !== a) return Vt(e, r); i.setMarker(s); const c = i.getChildren().filter(O).filter((l) => l.getMarkerSyntax() === "closing" && l.getMarker() === a).at(-1); - return c && (DE(c, rt(s, c.getNested()).length), c.setMarker(s)), e.setMarker(s), Jd(a, s) && Gd(e, n), r.logger?.debug(`[MarkerEdit] ${i.getType()} marker renamed to "${s}"`), !0; + return c && (wA(c, ot(s, c.getNested()).length), c.setMarker(s)), e.setMarker(s), uf(a, s) && lf(e, n), r.logger?.debug(`[MarkerEdit] ${i.getType()} marker renamed to "${s}"`), !0; } return Vt(e, r); } -function zE(e) { +function $A(e) { const t = w(); - if (!N(t)) return !1; + if (!P(t)) return !1; const r = t.isCollapsed() ? [t.anchor] : [t.anchor, t.focus]; for (const n of r) { const i = n.getNode(); @@ -23416,29 +23921,29 @@ function zE(e) { } return !!(!t.isCollapsed() && t.getNodes().some((n) => n.is(e))); } -function KE(e, t) { +function LA(e, t) { const r = e.getTextContent(); - if (Xr(e)) { + if (rn(e)) { t.pendingKeys.delete(e.getKey()); return; } - if (Le(e.getParent()) && Uc(e)) { + if (De(e.getParent()) && Vc(e)) { t.pendingKeys.delete(e.getKey()); return; } - if (!t.pendingKeys.has(e.getKey()) && !zE(e)) { - $k(e), t.logger?.debug( + if (!t.pendingKeys.has(e.getKey()) && !$A(e)) { + Xk(e), t.logger?.debug( `[MarkerEdit] healed machine-drifted glyph bytes back to "${e.getTextContent()}"` ); return; } if (e.getMarkerSyntax() === "opening") { - const n = jM.exec(r); + const n = QM.exec(r); if (n) { - t.pendingKeys.delete(e.getKey()), nm(e, n[1], t); + t.pendingKeys.delete(e.getKey()), Sm(e, n[1], t); return; } - if (BM.test(r)) { + if (ZM.test(r)) { t.pendingKeys.delete(e.getKey()), Vt(e, t); return; } @@ -23446,28 +23951,28 @@ function KE(e, t) { return; } if (e.getMarkerSyntax() === "closing") { - const n = e.getParent(), i = rt(e.getMarker(), e.getNested()); - if (D(n) && e.getMarker() === n.getMarker() && n.getLastChild()?.is(e) && r.startsWith(i) && r.length > i.length) { - const s = w(), o = N(s) && s.isCollapsed() && s.anchor.key === e.getKey() && s.anchor.offset > i.length ? s.anchor.offset - i.length : void 0, a = pe(r.slice(i.length)); + const n = e.getParent(), i = ot(e.getMarker(), e.getNested()); + if (I(n) && e.getMarker() === n.getMarker() && n.getLastChild()?.is(e) && r.startsWith(i) && r.length > i.length) { + const s = w(), o = P(s) && s.isCollapsed() && s.anchor.key === e.getKey() && s.anchor.offset > i.length ? s.anchor.offset - i.length : void 0, a = he(r.slice(i.length)); e.setTextContent(i), n.insertAfter(a), o !== void 0 && a.select(o, o), t.pendingKeys.delete(e.getKey()); return; } } t.pendingKeys.add(e.getKey()); } -function jE(e, t) { +function IA(e, t) { if (e.getTextContent() === "") { t.pendingKeys.delete(e.getKey()), e.remove(); return; } - if (Jp(e)) { + if (ah(e)) { t.pendingKeys.delete(e.getKey()); return; } t.pendingKeys.add(e.getKey()); } -function im(e) { - if (!If(e)?.length) +function Mm(e) { + if (!Gf(e)?.length) throw new Error(`marker "${e}" declares no leading attributes in the markers map`); return { // `\m`, separator, value word, then either nothing-yet (unterminated), or a @@ -23493,18 +23998,18 @@ function im(e) { valueTerminated: new RegExp(`^\\\\${e}[  ]+([^  \\\\]+)[  ]+$`) }; } -const wi = im("v"), BE = im("c"), Yd = /^[ \u00A0]*$/; -function Xd(e, t, r) { +const Li = Mm("v"), DA = Mm("c"), df = /^[ \u00A0]*$/; +function ff(e, t, r) { const n = e.getNextSibling(); - if (v(n) && n.getType() === Ke.getType() && n.getMode() === "normal" && ne(n, oe) !== "attribute") { + if (S(n) && n.getType() === Be.getType() && n.getMode() === "normal" && ie(n, ae) !== "attribute") { n.setTextContent(t + n.getTextContent()), r !== void 0 && n.select(r, r); return; } - const i = pe(t); + const i = he(t); e.insertAfter(i), r !== void 0 && i.select(r, r); } -function VE(e, t) { - const r = e.getTextContent(), n = Lt("v", e.getNumber()); +function UA(e, t) { + const r = e.getTextContent(), n = Dt("v", e.getNumber()); if (r === n) { t.pendingKeys.delete(e.getKey()); return; @@ -23512,28 +24017,28 @@ function VE(e, t) { const i = /^[ \u00A0]+/.exec(r); if (i) { const c = r.slice(i[0].length); - if (wi.midEdit.test(c)) { + if (Li.midEdit.test(c)) { t.pendingKeys.add(e.getKey()); return; } - const l = wi.valueAndRest.exec(c); - if (l && Yd.test(l[2] ?? "")) { + const l = Li.valueAndRest.exec(c); + if (l && df.test(l[2] ?? "")) { t.pendingKeys.delete(e.getKey()), l[1] !== e.getNumber() && e.setNumber(l[1]); return; } } - if (wi.midEdit.test(r)) { + if (Li.midEdit.test(r)) { t.pendingKeys.add(e.getKey()); return; } - const s = wi.valueAndRest.exec(r); + const s = Li.valueAndRest.exec(r); if (!s) { - const c = wi.markerRest.exec(r); + const c = Li.markerRest.exec(r); if (c) { - const [, l, u, d] = c, f = w(), p = N(f) && f.isCollapsed() && f.anchor.key === e.getKey() ? f.anchor.offset : void 0; - t.pendingKeys.delete(e.getKey()), e.setNumber(u), e.setTextContent(Lt("v", u)); - const m = p !== void 0 && p >= l.length ? Math.min(p - l.length, d.length) : void 0; - Xd(e, d, m); + const [, l, d, u] = c, p = w(), f = P(p) && p.isCollapsed() && p.anchor.key === e.getKey() ? p.anchor.offset : void 0; + t.pendingKeys.delete(e.getKey()), e.setNumber(d), e.setTextContent(Dt("v", d)); + const g = f !== void 0 && f >= l.length ? Math.min(f - l.length, u.length) : void 0; + ff(e, u, g); return; } t.pendingKeys.delete(e.getKey()), Vt(e, t); @@ -23544,16 +24049,16 @@ function VE(e, t) { t.pendingKeys.add(e.getKey()); return; } - if (t.pendingKeys.delete(e.getKey()), Yd.test(a ?? "")) { + if (t.pendingKeys.delete(e.getKey()), df.test(a ?? "")) { o !== e.getNumber() && e.setNumber(o); return; } - e.setNumber(o), e.setTextContent(Lt("v", o)), a && Xd(e, a, a.length); + e.setNumber(o), e.setTextContent(Dt("v", o)), a && ff(e, a, a.length); } -const WE = /^[ \u00A0]+([^ \u00A0\\]+)[ \u00A0]+$/; -function HE(e, t) { +const FA = /^[ \u00A0]+([^ \u00A0\\]+)[ \u00A0]+$/; +function zA(e, t) { const r = e.getParent(); - if (!j(r) || r.getIsCollapsed() !== !1 || !If(r.getMarker())?.includes("caller")) return !1; + if (!K(r) || r.getIsCollapsed() !== !1 || !Gf(r.getMarker())?.includes("caller")) return !1; const n = r.getChildren(); let i = 0; for (; i < n.length; ) { @@ -23563,190 +24068,190 @@ function HE(e, t) { } if (!e.is(n[i])) return !1; const s = e.getTextContent(); - if (s === At(r.getCaller())) + if (s === Pt(r.getCaller())) return t.pendingKeys.delete(e.getKey()), !0; - const o = WE.exec(s); + const o = FA.exec(s); if (!o) return !1; const [, a] = o; - return t.pendingKeys.delete(e.getKey()), r.setCaller(a), e.setTextContent(At(a)), !0; + return t.pendingKeys.delete(e.getKey()), r.setCaller(a), e.setTextContent(Pt(a)), !0; } -function GE(e) { +function KA(e) { if (e.getChildrenSize() === 0) { e.remove(); return; } const t = e.getFirstChild(); - if (!v(t)) return; - const r = Lt("c", e.getNumber()), n = t.getTextContent(); + if (!S(t)) return; + const r = Dt("c", e.getNumber()), n = t.getTextContent(); if (n === r) return; - const i = /^[ \u00A0]+/.exec(n), s = i ? n.slice(i[0].length) : n, o = BE.valueTerminated.exec(s); + const i = /^[ \u00A0]+/.exec(n), s = i ? n.slice(i[0].length) : n, o = DA.valueTerminated.exec(s); o && o[1] !== e.getNumber() && e.setNumber(o[1]); } -function sm(e) { - if (Be(e)) { - const { wrapper: t } = ko(e); +function Em(e) { + if (He(e)) { + const { wrapper: t } = Co(e); return t && t.getChildrenSize() === 0 ? [t] : []; } - if (j(e)) { - const { wrapper: t } = $p(e); + if (K(e)) { + const { wrapper: t } = Hp(e); return t && t.getChildrenSize() === 0 ? [t] : []; } - if (Pe(e)) { - const t = [], r = Ip(e); + if (we(e)) { + const t = [], r = Gp(e); r.wrapper && r.wrapper.getChildrenSize() === 0 && t.push(r.wrapper); - const n = Dp(e); + const n = Yp(e); return n.wrapper && n.wrapper.getChildrenSize() === 0 && t.push(n.wrapper), t; } - if (Oe(e)) { - const t = [], r = Yi(e, "va"); + if (Ae(e)) { + const t = [], r = es(e, "va"); r.wrapper && r.wrapper.getChildrenSize() === 0 && t.push(r.wrapper); - const n = r.wrapper ?? r.closer ?? e, i = Yi(n, "vp"); + const n = r.wrapper ?? r.closer ?? e, i = es(n, "vp"); return i.wrapper && i.wrapper.getChildrenSize() === 0 && t.push(i.wrapper), t; } return []; } -function JE(e) { +function jA(e) { const t = w(); - if (!N(t) || !t.isCollapsed()) return !1; + if (!P(t) || !t.isCollapsed()) return !1; const r = t.anchor.getNode(); - return sm(e).some((n) => r.is(n)); + return Em(e).some((n) => r.is(n)); } -function YE(e, t, r = "departure") { +function BA(e, t, r = "departure") { let n = !1; const i = r === "departure"; - if (i && ae(e) && Pp(e)) + if (i && le(e) && zp(e)) return t.pendingKeys.add(e.getKey()), { handled: !0, mutated: !1 }; - for (const l of Qi) - if (l.settleScope !== "none" && l.ownerPredicate(e) && gs(l, e) && (i || JE(e))) + for (const l of rs) + if (l.settleScope !== "none" && l.ownerPredicate(e) && Ts(l, e) && (i || jA(e))) return t.pendingKeys.add(e.getKey()), { handled: !0, mutated: !1 }; - for (const l of sm(e)) + for (const l of Em(e)) l.remove(), n = !0; let s = !1; - if (D(e)) { - const l = Fk(e); - l !== void 0 && xb(l) && (zp(e), s = !0, n = !0); + if (I(e)) { + const l = rT(e); + l !== void 0 && Ib(l) && (Zp(e), s = !0, n = !0); } let o = !1, a = !1, c = !1; - for (const l of Qi) + for (const l of rs) if (l.settleScope !== "none" && l.ownerPredicate(e)) { - if (RT(l, e)) { - es(l, e), a = !0, n = !0; + if (YT(l, e)) { + is(l, e), a = !0, n = !0; continue; } if (l.deletionPolicy === "none") { o = !0; continue; } - if (l.deletionPolicy === "remove-owner" && ih(l, e)) + if (l.deletionPolicy === "remove-owner" && mh(l, e)) return e.remove(), { handled: !0, mutated: !0 }; - Co(l, l.scanPieces(e), l.expectedPieces(e)) && (c = !0); + Eo(l, l.scanPieces(e), l.expectedPieces(e)) && (c = !0); } return (a || s) && !c ? { handled: !0, mutated: n } : { handled: o, mutated: n }; } -function Qd(e) { - return v(e) && e.getType() === Ke.getType() && e.getMode() === "normal" && ne(e, oe) !== "attribute"; +function pf(e) { + return S(e) && e.getType() === Be.getType() && e.getMode() === "normal" && ie(e, ae) !== "attribute"; } -function XE(e) { +function VA(e) { const t = /* @__PURE__ */ new Set(); if (e === void 0) return t; t.add(e); - const r = se(e); + const r = oe(e); if (!r?.isAttached()) return t; - for (let n = r.getPreviousSibling(); n && Qd(n); n = n.getPreviousSibling()) + for (let n = r.getPreviousSibling(); n && pf(n); n = n.getPreviousSibling()) t.add(n.getKey()); - for (let n = r.getNextSibling(); n && Qd(n); n = n.getNextSibling()) + for (let n = r.getNextSibling(); n && pf(n); n = n.getNextSibling()) t.add(n.getKey()); return t; } -function As(e, t, r = "departure") { +function qs(e, t, r = "departure") { let n = !1; if (e.pendingKeys.size === 0) return n; - const i = XE(t), s = [...e.pendingKeys].filter((a) => !i.has(a)), o = /* @__PURE__ */ new Set(); + const i = VA(t), s = [...e.pendingKeys].filter((a) => !i.has(a)), o = /* @__PURE__ */ new Set(); for (const a of s) { - const c = se(a); + const c = oe(a); if (!c?.isAttached()) { e.pendingKeys.delete(a); continue; } if (O(c)) { e.pendingKeys.delete(a); - const p = c.getTextContent(); - if (Xr(c)) continue; - const m = Ag.exec(p); - c.getMarkerSyntax() === "opening" && m ? n = nm(c, m[1], e) || n : r === "idle" && Bd(c, e.getMarker, e.viewOptions) ? e.pendingKeys.add(a) : tm(c, e) ? (n = !0, e.logger?.debug( + const f = c.getTextContent(); + if (rn(c)) continue; + const g = Ug.exec(f); + c.getMarkerSyntax() === "opening" && g ? n = Sm(c, g[1], e) || n : r === "idle" && sf(c, e.getMarker, e.viewOptions) ? e.pendingKeys.add(a) : Cm(c, e) ? (n = !0, e.logger?.debug( "[MarkerEdit] unknown-split paragraph rejoined its predecessor on marker degradation" )) : n = Vt(c, e) || n; continue; } - const l = _n(c)?.owner, u = l?.isAttached() ? l : c, d = u.getKey(); - if (o.has(d)) { - a !== d && e.pendingKeys.delete(a); + const l = Sn(c)?.owner, d = l?.isAttached() ? l : c, u = d.getKey(); + if (o.has(u)) { + a !== u && e.pendingKeys.delete(a); continue; } - if (i.has(d)) { - e.pendingKeys.delete(a), e.pendingKeys.add(d); + if (i.has(u)) { + e.pendingKeys.delete(a), e.pendingKeys.add(u); continue; } - e.pendingKeys.delete(a), a !== d && e.pendingKeys.delete(d), o.add(d); - const f = YE(u, e, r); - if (n = f.mutated || n, !f.handled) { - if (r === "idle" && Bd(u, e.getMarker, e.viewOptions)) { - e.pendingKeys.add(d); + e.pendingKeys.delete(a), a !== u && e.pendingKeys.delete(u), o.add(u); + const p = BA(d, e, r); + if (n = p.mutated || n, !p.handled) { + if (r === "idle" && sf(d, e.getMarker, e.viewOptions)) { + e.pendingKeys.add(u); continue; } - n = Vt(u, e) || n; + n = Vt(d, e) || n; } } return n; } -function om(e) { - if (Qr(e.getNextSibling())) return !0; +function Am(e) { + if (Ir(e.getNextSibling())) return !0; for (let t = e.getParent(); t; t = t.getParent()) - if (D(t)) return Di(t) !== void 0; + if (I(t)) return Ki(t) !== void 0; return !1; } -function QE(e) { - const t = _n(e); +function WA(e) { + const t = Sn(e); if (!t) return !1; - const r = xn(t.kind); - return !Co( + const r = vn(t.kind); + return !Eo( r, r.scanPieces(t.owner), r.expectedPieces(t.owner) ); } -function Zd(e) { +function hf(e) { for (let t = e.getParent(); t; t = t.getParent()) - if (pt(t) || De(t) || Qp(t)) return !0; + if (ht(t) || Ue(t) || uh(t)) return !0; return !1; } -function ZE(e, t) { - const r = e.getTextContent(), n = ne(e, oe), i = e.getParent(); - if (n !== "attribute" && Pe(i)) { - r.replace(/^[ \u00A0]+/, "") === Lt("c", i.getNumber()) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey()); +function HA(e, t) { + const r = e.getTextContent(), n = ie(e, ae), i = e.getParent(); + if (n !== "attribute" && we(i)) { + r.replace(/^[ \u00A0]+/, "") === Dt("c", i.getNumber()) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey()); return; } - if (HE(e, t)) return; + if (zA(e, t)) return; if (n === "attribute") { - QE(e) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey()); + WA(e) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey()); return; } if (!r.includes("\\")) { - if (r.includes("|") && om(e)) t.pendingKeys.add(e.getKey()); - else if (r.includes("//") && !Zd(e)) + if (r.includes("|") && Am(e)) t.pendingKeys.add(e.getKey()); + else if (r.includes("//") && !hf(e)) t.pendingKeys.add(e.getKey()); - else if (Up(e)) t.pendingKeys.add(e.getKey()); - else if (Pe(as(e))) t.pendingKeys.add(e.getKey()); + else if (Xp(e)) t.pendingKeys.add(e.getKey()); + else if (we(fs(e))) t.pendingKeys.add(e.getKey()); else { const a = e.getParent(); - D(a) && Kp(a) && t.pendingKeys.add(a.getKey()), t.pendingKeys.delete(e.getKey()); + I(a) && eh(a) && t.pendingKeys.add(a.getKey()), t.pendingKeys.delete(e.getKey()); } return; } - if (Zd(e)) return; - const s = w(), o = N(s) && s.isCollapsed() && s.anchor.key === e.getKey() ? r.slice(0, s.anchor.offset) : r; - if (HM.test(o)) { - if (Nb(r)) { + if (hf(e)) return; + const s = w(), o = P(s) && s.isCollapsed() && s.anchor.key === e.getKey() ? r.slice(0, s.anchor.offset) : r; + if (rE.test(o)) { + if (Wb(r)) { t.pendingKeys.add(e.getKey()); return; } @@ -23758,359 +24263,89 @@ function ZE(e, t) { } else t.pendingKeys.add(e.getKey()); } -function eA(e, t) { - return e.deletionPolicy !== "remove-owner" || e.byteFormat.writer !== "read-only" ? !1 : ih(e, t); +function GA(e, t) { + return e.deletionPolicy !== "remove-owner" || e.byteFormat.writer !== "read-only" ? !1 : mh(e, t); } -function tA(e) { +function JA(e) { const t = (r) => { if (O(r)) { - Xr(r) || e.pendingKeys.add(r.getKey()); + rn(r) || e.pendingKeys.add(r.getKey()); return; } - if (Qr(r)) { - Jp(r) || e.pendingKeys.add(r.getKey()); + if (Ir(r)) { + ah(r) || e.pendingKeys.add(r.getKey()); return; } - for (const n of Qi) - n.settleScope !== "none" && n.ownerPredicate(r) && (gs(n, r) || eA(n, r)) && e.pendingKeys.add(r.getKey()); - if (Oe(r)) { - r.getTextContent() !== Lt("v", r.getNumber()) && e.pendingKeys.add(r.getKey()); + for (const n of rs) + n.settleScope !== "none" && n.ownerPredicate(r) && (Ts(n, r) || GA(n, r)) && e.pendingKeys.add(r.getKey()); + if (Ae(r)) { + r.getTextContent() !== Dt("v", r.getNumber()) && e.pendingKeys.add(r.getKey()); return; } - if (v(r)) { - if (r.getType() !== Ke.getType() || ne(r, oe) === "attribute") return; + if (S(r)) { + if (r.getType() !== Be.getType() || ie(r, ae) === "attribute") return; const n = r.getParent(); - if (Pe(n)) { - r.getTextContent() !== Lt("c", n.getNumber()) && e.pendingKeys.add(r.getKey()); + if (we(n)) { + r.getTextContent() !== Dt("c", n.getNumber()) && e.pendingKeys.add(r.getKey()); return; } const i = r.getTextContent(); - (i.includes("\\") || i.includes("|") && om(r) || i.includes("//") || Up(r) !== void 0) && e.pendingKeys.add(r.getKey()); + (i.includes("\\") || i.includes("|") && Am(r) || i.includes("//") || Xp(r) !== void 0) && e.pendingKeys.add(r.getKey()); return; } - if (D(r)) { + if (I(r)) { r.getChildren().forEach(t); return; } - if (!De(r) && !pt(r)) { - if (Le(r) && r.getChildrenSize() === 0) { - const n = _n(r)?.owner; + if (!Ue(r) && !ht(r)) { + if (De(r) && r.getChildrenSize() === 0) { + const n = Sn(r)?.owner; n && e.pendingKeys.add(n.getKey()); return; } - F(r) && r.getChildren().forEach(t); - } - }; - Fe().getChildren().forEach(t); -} -const io = "usfm:", am = "usfmopen", cm = "usfmclosed"; -function rA(e) { - return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} -const nA = new RegExp( - [io, am, cm].map(rA).join("|") -), iA = "\uFEFF", sA = /^usfm_(.+)$/; -function oA(e) { - return e.nodeType === Node.ELEMENT_NODE; -} -function aA(e) { - return e.replace( - /%([0-9a-fA-F]{4})/g, - (t, r) => String.fromCharCode(Number.parseInt(r, 16)) - ); -} -function cA(e) { - return e.startsWith(io) ? aA(e.slice(io.length)).replace(/\r\n?|\n/g, " ") : ""; -} -function lm(e) { - for (const t of e.classList) { - const r = sA.exec(t); - if (r) return r[1]; - } -} -function lA(e) { - const t = lm(e); - if (t !== void 0) - return e.classList.contains("nested") ? `+${t}` : t; -} -function uA(e) { - const t = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_COMMENT); - for (let r = t.nextNode(); r; r = t.nextNode()) - if ((r.nodeValue ?? "").startsWith(io)) return !0; - for (const r of e.querySelectorAll("*")) { - const { classList: n } = r; - if (!(!n.contains(am) && !n.contains(cm)) && lm(r) !== void 0) - return !0; - } - return !1; -} -function um(e, t, r) { - if (e.nodeType === Node.TEXT_NODE) { - t || r.push(e.nodeValue ?? ""); - return; - } - if (e.nodeType === Node.COMMENT_NODE) { - r.push(cA(e.nodeValue ?? "")); - return; - } - if (!oA(e)) return; - const { classList: n } = e, i = (u) => e.childNodes.forEach((d) => um(d, u, r)); - if (t) { - i(!n.contains("include")); - return; - } - if (n.contains("exclude")) { - i(!0); - return; - } - const s = e.tagName.toLowerCase(); - if (s === "br") { - r.push(` -`); - return; - } - if (s === "span" && e.getAttribute("class") === "attribute") { - r.push(e.textContent ?? ""); - return; - } - const o = s === "div" || s === "tr", a = o || s === "span" || s === "th" || s === "td" ? lA(e) : void 0, c = a !== void 0 && n.contains("usfmopen"), l = a !== void 0 && n.contains("usfmclosed"); - o && r.push(` -`), (c || l) && r.push(`\\${a} `), i(!1), l && r.push(`\\${a}*`), o && r.push(` -`); -} -function dA(e) { - if (!nA.test(e)) return; - const { body: t } = new DOMParser().parseFromString(e, "text/html"); - if (t.querySelectorAll("script,style,template").forEach((n) => n.remove()), !uA(t)) return; - const r = []; - return t.childNodes.forEach((n) => um(n, !1, r)), r.join("").replaceAll(iA, "").replaceAll(I, " ").replace(/\r\n?/g, ` -`).replace(/\n+/g, ` -`).replace(/^\n|\n$/g, ""); -} -function fA(e) { - const t = e.getTextContent(); - if (!t.includes(" ")) return; - const r = ne(e, oe); - if (r === "attribute" || r === fr) return; - for (let o = e.getParent(); o; o = o.getParent()) - if (pt(o) || Pe(o) || De(o)) return; - const n = t.startsWith(I) && D(e.getParent()), i = n ? t.slice(1) : t, s = (n ? I : "") + i.replace(/ (?=[ \u00A0])/g, I).replace(new RegExp("(?<=\\u00A0) ", "g"), I); - s !== t && e.setTextContent(s); -} -function pA(e) { - const { body: t } = new DOMParser().parseFromString(e, "text/html"); - return t.querySelectorAll("script,style,template").forEach((r) => r.remove()), t.querySelectorAll("br").forEach((r) => r.replaceWith(` -`)), t.querySelectorAll("p,div,li,td,th,tr,h1,h2,h3,h4,h5,h6,blockquote,pre").forEach((r) => r.after(` -`)), (t.textContent ?? "").replace(/\n+/g, ` -`).replace(/^\n|\n$/g, ""); -} -function hA(e, t) { - if (!e) return !1; - try { - const r = JSON.parse(e); - if (typeof r != "object" || r === null) return !1; - const { namespace: n, nodes: i } = r; - return n === t && Array.isArray(i); - } catch { - return !1; - } -} -function cc(e, t) { - const r = e && typeof e == "object" && "clipboardData" in e ? e.clipboardData : void 0; - if (!r) return; - const n = (a) => a.replace(/\r\n?/g, ` -`), i = n(r.getData("text/plain")), s = r.getData("text/html"), o = s ? dA(s) : void 0; - return { - text: o ? n(o) : i || (s ? n(pA(s)) : ""), - isInternal: hA( - r.getData("application/x-lexical-editor"), - t - ) - }; -} -const ef = String.raw`\\(?:\+?[${er}]+\*?|\*)`, gA = new RegExp( - String.raw`(?<=${ef})\u00A0|\u00A0(?=${ef})`, - "g" -); -function Il(e) { - return e.replace(/^\u00A0+/gm, (t) => " ".repeat(t.length)).replace(gA, " ").replaceAll(I, "~"); -} -const dm = new RegExp( - String.raw`\\c(?![${er}])[ \u00A0]*[^\s\\]*`, - "g" -), fm = new RegExp(String.raw`\\id(?![${er}])[^\n\\]*`, "g"), mA = new RegExp( - String.raw`^(?:${dm.source}|${fm.source})` -); -function Ll(e) { - return e.split(` -`).map((t) => { - const r = t.replace(dm, "").replace(fm, ""); - return mA.test(t) && r.trim() === "" && t.trim() !== "" ? void 0 : r; - }).filter((t) => t !== void 0).join(` -`); -} -function lc(e) { - if (v(e) && ne(e, oe) === "attribute") return !0; - for (let t = e; t; t = t.getParent()) - if (Le(t)) return !0; - return !1; -} -function yA(e) { - return lc(e.anchor.getNode()) || lc(e.focus.getNode()); -} -function bA(e) { - const { anchor: t, focus: r } = e; - return t.key === r.key && lc(t.getNode()); -} -function kA(e, t) { - const n = bA(e) ? t : Il(Ll(t)); - n && e.insertText(n.replace(/\n/g, " ")); -} -function TA(e, t = !1, r = () => { -}) { - const n = cc(e, Zn()._config.namespace); - if (!n) return !1; - const i = w(), s = N(i) && yA(i); - if (!s && n.isInternal || t && N(i) && Qn(i)) - return !1; - const { text: o } = n; - if (!o || !N(i)) return !1; - if (e?.preventDefault(), s) - return kA(i, o), !0; - const a = Il(Ll(o)); - if (!a) return !0; - const c = a.split(` -`); - if (t) - return i.insertText(c.join(" ")), !0; - if (c.length < 2) - return i.insertText(a), !0; - r(), i.isCollapsed() || i.removeText(); - const l = Zn(); - return c.forEach((u, d) => { - if (d > 0 && l.dispatchCommand(Rs, void 0), u === "") return; - const f = w(); - N(f) && f.insertText(u); - }), !0; -} -function xA(e) { - if (e.getTextContent() !== I) return !1; - const t = e.getParent(); - return j(t) ? !St(e.getPreviousSibling()) : !1; -} -function _A(e, t) { - if (t || e.getTextContent() !== I) return ""; - const r = e.getParent(); - if (!j(r) || !St(e.getPreviousSibling())) return ""; - const n = r.getCategory(); - return n ? `\\cat ${n}\\cat*` : ""; -} -function CA(e) { - const t = e.getParent(); - return (j(t) ? t.getCaller() : void 0) || Bi; -} -function SA(e) { - const t = e.getParent(); - return !t || Gr(t) === void 0; -} -function pm(e) { - const t = e.getNodes(); - if (t.length === 0) return ""; - const r = t[0], n = t[t.length - 1], { anchor: i, focus: s } = e, o = i.isBefore(s), [a, c] = kc(e); - let l = "", u = !0; - for (const d of t) { - if (F(d) && !d.isInline()) { - !u && SA(d) && (l += ` -`), u = !d.isEmpty(); - continue; + L(r) && r.getChildren().forEach(t); } - if (u = !1, St(d)) - (d !== n || !e.isCollapsed()) && (l += (d === r ? "" : " ") + CA(d)); - else if (v(d)) { - let f = d.getTextContent(); - d === r ? d === n ? (i.type !== "element" || s.type !== "element" || s.offset === i.offset) && (f = a < c ? f.slice(a, c) : f.slice(c, a)) : f = o ? f.slice(a) : f.slice(c) : d === n && (f = o ? f.slice(0, c) : f.slice(0, a)), l += xA(d) ? "" : f.replaceAll(I, " ") + _A(d, d === n); - } else (uo(d) || us(d)) && (d !== n || !e.isCollapsed()) && (l += d.getTextContent().replaceAll(I, " ")); - } - return l; -} -function hm(e) { - return e.split(` -`).map((t) => t.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")).map( - (t) => t ? `

${t}

` : "

" - ).join(""); -} -function vA(e) { - return e.getNodes().some( - (t) => [t, ...t.getParents()].some( - (r) => pt(r) || Pe(r) - ) - ); -} -function MA(e) { - const t = w(); - if (!N(t) || t.isCollapsed()) return; - const r = pm(t), n = { - "text/plain": r, - "text/html": hm(r) }; - if (Po() || vA(t)) return n; - const i = Dy(e); - return i && (n["application/x-lexical-editor"] = i), n; -} -function tf(e, t, r) { - const n = w(); - if (!N(n) || n.isCollapsed()) - return (!e || !("clipboardData" in e)) && !zh(); - const i = MA(t); - return i ? gm(e, t, n, i, r) : !1; + Ke().getChildren().forEach(t); } -function gm(e, t, r, n, i) { - const s = !n["text/plain"], o = i && t.isEditable(); - if (!e || !("clipboardData" in e)) - return s || Uy(t, null, n), o && r.removeText(), !0; - if (e.clipboardData == null) return !1; - if (e.preventDefault(), !s) - for (const [a, c] of Object.entries(n)) e.clipboardData.setData(a, c); - return o && r.removeText(), !0; -} -const mm = vf( +const Pm = If( "COMMIT_PENDING_MARKERS_COMMAND" ); -function ka(e) { +function va(e) { const t = e(); - return jr(kf), jr(Ff), t; + return Gr(Of), Gr(Qf), t; } -const rf = 8, EA = 1e3; -function Wn(e, t) { - const r = Oe(e) ? ["va", "vp"] : Be(e) ? ["milestone"] : j(e) ? ["cat"] : ( +const gf = 8, YA = 1e3; +function Jn(e, t) { + const r = Ae(e) ? ["va", "vp"] : He(e) ? ["milestone"] : K(e) ? ["cat"] : ( // A chapter's two runs must be driven in this order — `\cp`'s scan and insertion // anchor both depend on `\ca`'s wrapper already being in place, the same dependency // a verse's `\vp` has on `\va`. ["ca", "cp"] ); for (const n of r) - UT(xn(n), e, t.pendingKeys); + tx(vn(n), e, t.pendingKeys); } -function AA(e, t) { +function XA(e, t) { const r = (n, i) => { - if (i.updateTags.has(Cc) || i.updateTags.has(Vi)) return; + if (i.updateTags.has(Pc) || i.updateTags.has(Ji)) return; const s = []; i.prevEditorState.read(() => { for (const [o, a] of n) { if (a !== "destroyed") continue; - const c = se(o); + const c = oe(o); if (!c) continue; - const l = _n(c); + const l = Sn(c); l && s.push({ owner: l.owner, kind: l.kind }); } }), s.length !== 0 && e.getEditorState().read(() => { for (const { owner: o, kind: a } of s) { - const c = se(o.getKey()); - c?.isAttached() && xn(a).expectedPieces(c).wantsRun && t.pendingKeys.add(c.getKey()); + const c = oe(o.getKey()); + c?.isAttached() && vn(a).expectedPieces(c).wantsRun && t.pendingKeys.add(c.getKey()); } }); }; - return Ve( + return Fe( // Registered for the four node classes a display-run piece (or a whole run // wrapper) can be — a plain TextNode (a char span's `|…` run, a verse's `\va`/`\vp` value, a // milestone's attribute text), a MarkerNode (a run's opening/closing glyphs, which @@ -24121,80 +24356,97 @@ function AA(e, t) { // subclass does not make the TextNode registration see it, mirroring the transform dispatch // the TextNode catch-all transform's own comment documents — so each class needs its own // registration. - e.registerMutationListener(Ke, r), - e.registerMutationListener(hr, r), - e.registerMutationListener(Er, r), - e.registerMutationListener(Ar, r) + e.registerMutationListener(Be, r), + e.registerMutationListener(gr, r), + e.registerMutationListener(wr, r), + e.registerMutationListener(qr, r) ); } -function uc(e, t) { +const QA = (e) => Lo(e).length; +function yc(e, t) { if (e.structureProtectionMode !== "protected") return !1; const r = w(); - return r ? t ? Jh(r, t) : N(r) && Qn(r) : !1; + return r ? t ? cg(r, t) : P(r) && ei(r) : !1; } -function PA(e, t, r) { - return Ve( +function ZA(e, t, r) { + return Fe( e.registerCommand( - yr, + _r, (n) => { - if (Po() || uc(t)) return !1; - const i = cc(n, e._config.namespace); + if (qo() || yc(t)) return !1; + const i = lc(n, e._config.namespace); if (!i || !i.text.includes(` `)) return !1; - const s = r ? Il(Ll(i.text)) : i.text; + const s = r ? wl(ql(i.text)) : i.text; if (s.includes(` `)) { const o = s.split(` `); - let a = Wd(o, t.getMarker); - if (a === "declined" && _E(e) && (a = Wd(o, t.getMarker)), a === "handled") + let a = af(o, t.getMarker); + if (a === "declined" && mA(e) && (a = af(o, t.getMarker)), a === "handled") return n?.preventDefault(), !0; } return !1; }, - Wt + tt ), e.registerCommand( - yr, + _r, (n) => { - const i = cc(n, e._config.namespace); + const i = lc(n, e._config.namespace); if (!i || i.isInternal || !i.text) return !1; const s = i.text.split(` `); - if (s.length < 2 || !aM()) return !1; + if (s.length < 2 || !CM()) return !1; const o = w(); - return t.structureProtectionMode === "protected" && N(o) && Qn(o) ? !1 : (n?.preventDefault(), N(o) && !o.isCollapsed() && o.removeText(), s.forEach((a, c) => { - if (c > 0 && e.dispatchCommand(Rs, void 0), a === "") return; + return t.structureProtectionMode === "protected" && P(o) && ei(o) ? !1 : (n?.preventDefault(), P(o) && !o.isCollapsed() && o.removeText(), s.forEach((a, c) => { + if (c > 0 && e.dispatchCommand(Us, void 0), a === "") return; const l = w(); - N(l) && l.insertText(a); + P(l) && l.insertText(a); }), !0); }, - Ne + qe ), e.registerCommand( - yr, + _r, () => (t.splitExpected.current = !0, !1), - Tt + xt ) ); } -function NA({ +function e1({ viewOptions: e, getMarker: t, logger: r, markerSettleDelayMs: n, - structureProtectionMode: i = "off" + structureProtectionMode: i = "off", + copyLimit: s }) { - const [s] = ce(), o = e?.markerMode === "editable", a = !!e && Ao(e), c = Z(void 0), l = Z(n); - return z(() => { - l.current = n; - const u = c.current; - u && (e && (u.viewOptions = e), u.getMarker = t ?? lr, u.logger = r, u.structureProtectionMode = i); - }, [e, t, r, n, i]), z(() => { - if (!o || !e) return; - const u = { + const [o] = ce(), a = e?.markerMode === "editable", c = !!e && wo(e), l = Q(void 0), d = Q(n), u = Q(s), p = Q(a && c); + return F(() => { + d.current = n, u.current = s, p.current = a && c; + const f = l.current; + f && (e && (f.viewOptions = e), f.getMarker = t ?? ur, f.logger = r, f.structureProtectionMode = i); + }, [ + e, + t, + r, + n, + i, + s, + a, + c + ]), F(() => { + const f = (g) => p.current && Ig(g, u.current, QA); + return Fe( + o.registerCommand(Hr, f, tt), + o.registerCommand(Wt, f, tt) + ); + }, [o]), F(() => { + if (!a || !e) return; + const f = { viewOptions: e, - getMarker: t ?? lr, + getMarker: t ?? ur, pendingKeys: /* @__PURE__ */ new Set(), splitExpected: { current: !1 }, wholeParaDeleteExpected: /* @__PURE__ */ new Set(), @@ -24203,46 +24455,46 @@ function NA({ logger: r, structureProtectionMode: i }; - c.current = u; - const d = NT(s, u.pendingKeys); - let f, p = !1, m, g = !1, y = !1, k = 0; - const _ = () => k < rf ? !1 : (u.logger?.warn( - `[MarkerEdit] settle cascade exceeded ${rf} consecutive mutating passes; leaving ${u.pendingKeys.size} node(s) pending. This is a rebuild that never reaches a fixed point — pending keys: ${[...u.pendingKeys].join(", ")}` - ), !0), S = (M, q = "departure") => { - s.update(() => { - k = ka( - () => As(u, M, q) - ) ? k + 1 : 0; + l.current = f; + const g = WT(o, f.pendingKeys); + let h, y = !1, k, _ = !1, C = !1, M = 0; + const E = () => M < gf ? !1 : (f.logger?.warn( + `[MarkerEdit] settle cascade exceeded ${gf} consecutive mutating passes; leaving ${f.pendingKeys.size} node(s) pending. This is a rebuild that never reaches a fixed point — pending keys: ${[...f.pendingKeys].join(", ")}` + ), !0), z = (N, B = "departure") => { + o.update(() => { + M = va( + () => qs(f, N, B) + ) ? M + 1 : 0; }); }; - let P; - const A = () => { - if (P !== void 0 && clearTimeout(P), P = void 0, y || u.pendingKeys.size === 0) return; - const M = l.current ?? EA; - M < 0 || (P = setTimeout(() => { - P = void 0, !(y || u.pendingKeys.size === 0) && (p || _() || S(void 0, "idle")); - }, M)); - }, B = Ve( - s.registerNodeTransform(hr, (M) => { - if (s.isComposing()) return; - KE(M, u); - const q = _n(M); - q && (Oe(q.owner) || j(q.owner) || Pe(q.owner) || Be(q.owner) && ko(q.owner).wrapper === void 0) && Wn(q.owner, u); + let W; + const j = () => { + if (W !== void 0 && clearTimeout(W), W = void 0, C || f.pendingKeys.size === 0) return; + const N = d.current ?? YA; + N < 0 || (W = setTimeout(() => { + W = void 0, !(C || f.pendingKeys.size === 0) && (y || E() || z(void 0, "idle")); + }, N)); + }, U = Fe( + o.registerNodeTransform(gr, (N) => { + if (o.isComposing()) return; + LA(N, f); + const B = Sn(N); + B && (Ae(B.owner) || K(B.owner) || we(B.owner) || He(B.owner) && Co(B.owner).wrapper === void 0) && Jn(B.owner, f); }), - s.registerNodeTransform(dt, (M) => { - s.isComposing() || (VE(M, u), Wn(M, u)); + o.registerNodeTransform(ft, (N) => { + o.isComposing() || (UA(N, f), Jn(N, f)); }), - s.registerNodeTransform(Nt, (M) => { - s.isComposing() || (GE(M), M.isAttached() && Wn(M, u)); + o.registerNodeTransform(Ot, (N) => { + o.isComposing() || (KA(N), N.isAttached() && Jn(N, f)); }), - s.registerNodeTransform(Ze, (M) => { - s.isComposing() || mE(M, u); + o.registerNodeTransform(nt, (N) => { + o.isComposing() || uA(N, f); }), - s.registerNodeTransform(me, (M) => { - if (!s.isComposing()) { - TE(M, u); - for (const q of ["separator", "char"]) - M.isAttached() && gs(xn(q), M) && u.pendingKeys.add(M.getKey()); + o.registerNodeTransform(be, (N) => { + if (!o.isComposing()) { + hA(N, f); + for (const B of ["separator", "char"]) + N.isAttached() && Ts(vn(B), N) && f.pendingKeys.add(N.getKey()); } }), // Self-healing milestone display run (the shared $syncDisplayRun driver, @@ -24258,29 +24510,29 @@ function NA({ // representation, so deleting all of it must delete the milestone, not resurrect the run) // — the sync leaves it alone and the milestone is pended for the caret-departure settle // ($resolvePendingMarkers). - s.registerNodeTransform(Jt, (M) => { - s.isComposing() || Wn(M, u); + o.registerNodeTransform(Jt, (N) => { + o.isComposing() || Jn(N, f); }), - s.registerNodeTransform(Ar, (M) => { - if (s.isComposing()) return; - const q = _n(M); - q && (Be(q.owner) || Oe(q.owner) || j(q.owner) || Pe(q.owner)) && Wn(q.owner, u); + o.registerNodeTransform(qr, (N) => { + if (o.isComposing()) return; + const B = Sn(N); + B && (He(B.owner) || Ae(B.owner) || K(B.owner) || we(B.owner)) && Jn(B.owner, f); }), - s.registerNodeTransform(Ee, (M) => { - s.isComposing() || (kE(M, u), Wn(M, u)); + o.registerNodeTransform(Pe, (N) => { + o.isComposing() || (pA(N, f), Jn(N, f)); }), // Unmatched-marker bytes are editable text in this mode; their edits pend and settle // exactly like closer-glyph edits (see $unmatchedNodeTransform). Its own registration — // Lexical dispatches transforms by exact node type, so neither the TextNode catch-all // below nor the MarkerNode transform above ever fires for this subclass. - s.registerNodeTransform(Or, (M) => { - s.isComposing() || jE(M, u); + o.registerNodeTransform(Lr, (N) => { + o.isComposing() || IA(N, f); }), // Plain-TextNode catch-all for typed/pasted literal backslash sequences (Tier 2). // Lexical dispatches transforms by exact node type, so this never fires for // MarkerNode/VerseNode subclasses — TextSpacingPlugin relies on the same fact. - s.registerNodeTransform(Ke, (M) => { - s.isComposing() || ZE(M, u); + o.registerNodeTransform(Be, (N) => { + o.isComposing() || HA(N, f); }), // Plain TextNodes can't emit a DOM class from node state the way // ImmutableTypedTextNode does in createDOM(), so a char span's own `|…` attribute run @@ -24311,319 +24563,346 @@ function NA({ // attributeDisplay.utils.ts) gets only the generic dim `.attribute` class below, not the // marker-specific `usfm_va`/`usfm_vp` superscript coloring, until the wrap lands — a brief, // imperceptible gap in a transient shape nothing at rest builds anymore. - s.registerMutationListener( - Ke, - (M) => { - s.getEditorState().read(() => { - for (const [q, $] of M) { - if ($ === "destroyed") continue; - const J = se(q); - !J || ne(J, oe) !== "attribute" || Le(J.getParent()) || s.getElementByKey(q)?.classList.add("attribute"); + o.registerMutationListener( + Be, + (N) => { + o.getEditorState().read(() => { + for (const [B, re] of N) { + if (re === "destroyed") continue; + const X = oe(B); + !X || ie(X, ae) !== "attribute" || De(X.getParent()) || o.getElementByKey(B)?.classList.add("attribute"); } }); }, { skipInitialization: !1 } ), - AA(s, u), - ...a ? [ - s.registerNodeTransform(Ke, (M) => { - s.isComposing() || fA(M); + XA(o, f), + ...c ? [ + o.registerNodeTransform(Be, (N) => { + o.isComposing() || mE(N); }), - s.registerCommand( - fo, - (M) => tf( + o.registerCommand( + Hr, + (N) => Xd( // COPY_COMMAND's payload is `ClipboardEvent | KeyboardEvent | null`. A plain // `event instanceof ClipboardEvent` narrows this correctly in real browsers, // but jsdom (our test environment) doesn't implement `ClipboardEvent` at all — // `instanceof` against the undefined global throws — so this duck-checks the // one property `$handleCopyForStandardView` actually needs instead. - M && typeof M == "object" && "clipboardData" in M ? M : null, - s, - !1 + N && typeof N == "object" && "clipboardData" in N ? N : null, + o, + !1, + u.current ), - Ne + qe ), - s.registerCommand( - yn, - (M) => ( + o.registerCommand( + Wt, + (N) => ( // A structure-protected document's cut of a selection `StructureKeyboardPlugin` // refuses to replace is that plugin's to refuse: it registers CUT at CRITICAL for // exactly that reason, so it has already had its turn by the time this claim runs // and a selection reaching here is one it allowed. - tf( - M && typeof M == "object" && "clipboardData" in M ? M : null, - s, - !0 + Xd( + N && typeof N == "object" && "clipboardData" in N ? N : null, + o, + !0, + u.current ) ), - Ne + qe ), - s.registerCommand( - yr, - (M) => TA( + o.registerCommand( + _r, + (N) => vE( // Same jsdom-safe duck-check as COPY above. - M && typeof M == "object" && "clipboardData" in M ? M : null, - u.structureProtectionMode === "protected", + N && typeof N == "object" && "clipboardData" in N ? N : null, + f.structureProtectionMode === "protected", // Consumed by $paraMarkerDeletionTransform below, same as the // INSERT_PARAGRAPH_COMMAND and LOW-priority PASTE_COMMAND handlers arm it for // the paste paths that reach them — this HIGH-priority claim reaches the // former only from its second line on, and the latter never. () => { - u.splitExpected.current = !0; + f.splitExpected.current = !0; } ), - Ne + qe ) ] : [], - s.registerCommand( - yn, - () => (!uc(u) && !Po() && oc(u), !1), - Wt + o.registerCommand( + Wt, + () => (!yc(f) && !qo() && gc(f), !1), + tt ), - s.registerCommand( - Tc, - () => (s.isComposing() || pE(u), !1), - Jn + o.registerCommand( + Mc, + () => (o.isComposing() || aA(f), !1), + Wr ), - s.registerCommand( - lo, - () => (p = !1, k = 0, A(), !1), - Tt + o.registerCommand( + mo, + () => (y = !1, M = 0, j(), !1), + xt ), - s.registerCommand( - vr, - (M) => (p = !1, k = 0, A(), (M.key === "Backspace" || M.key === "Delete") && !uc(u, Vh(M)) && (oc(u), fE(u), queueMicrotask(() => { - u.wholeParaDeleteExpected?.clear(), u.collapsedDeleteCaretParas?.clear(); - })), s.isComposing() || !M.ctrlKey || M.altKey || M.shiftKey || M.metaKey || M.key !== " " && M.code !== "Space" || !oM() ? !1 : (M.preventDefault(), !0)), - Ne + o.registerCommand( + Nr, + (N) => (y = !1, M = 0, j(), (N.key === "Backspace" || N.key === "Delete") && !yc(f, ig(N)) && (gc(f), oA(f), queueMicrotask(() => { + f.wholeParaDeleteExpected?.clear(), f.collapsedDeleteCaretParas?.clear(); + })), o.isComposing() || !N.ctrlKey || N.altKey || N.shiftKey || N.metaKey || N.key !== " " && N.code !== "Space" || !_M() ? !1 : (N.preventDefault(), !0)), + qe ), - s.registerCommand( - _f, - (M) => { - const q = Gg(); - q === "needs-plain-split" && s.dispatchCommand(Rs, void 0); - const $ = q !== "declined" || FT(); - return $ && M?.preventDefault(), As(u), $; + o.registerCommand( + Rf, + (N) => { + const B = mm(); + B === "needs-plain-split" && o.dispatchCommand(Us, void 0); + const re = B !== "declined" || rx(); + return re && N?.preventDefault(), qs(f), re; }, - Ne + qe ), - s.registerCommand( - Rs, - () => (u.splitExpected.current = !0, fg()), - Ne + o.registerCommand( + Us, + () => (f.splitExpected.current = !0, Sg()), + qe ), - PA(s, u, a), - s.registerCommand( - mm, + ZA(o, f, c), + o.registerCommand( + Pm, () => { - if (p) return !0; - const M = s.getRootElement(), q = M?.ownerDocument, $ = !!M && !!q && q.hasFocus() && M.contains(q.activeElement); - let J; - if ($) { - const Q = w(); - J = N(Q) ? Q.focus.key : f; + if (y) return !0; + const N = o.getRootElement(), B = N?.ownerDocument, re = !!N && !!B && B.hasFocus() && N.contains(B.activeElement); + let X; + if (re) { + const ye = w(); + X = P(ye) ? ye.focus.key : h; } - return ka(() => As(u, J)), !0; + return va(() => qs(f, X)), !0; }, - Tt + xt ), - s.registerCommand( - _c, + o.registerCommand( + Ac, () => { - if (p) return !1; - const M = w(), q = N(M) ? M.focus.key : f; - return ka(() => As(u, q)), !1; + if (y) return !1; + const N = w(), B = P(N) ? N.focus.key : h; + return va(() => qs(f, B)), !1; }, - Tt + xt ), - s.registerUpdateListener(({ editorState: M, tags: q }) => { - u.splitExpected.current = !1, u.wholeParaDeleteExpected?.clear(), u.collapsedDeleteCaretParas?.clear(), u.rebuildAttempted.clear(); - const $ = M.read(() => { - const Q = w(); - return N(Q) ? Q.focus.key : void 0; - }), J = m; - if ($ !== void 0 && (m = $), q.has(Cc)) { - u.pendingKeys.clear(), M.read(() => tA(u)), p = !0, $ !== void 0 && (f = $); + o.registerUpdateListener(({ editorState: N, tags: B }) => { + f.splitExpected.current = !1, f.wholeParaDeleteExpected?.clear(), f.collapsedDeleteCaretParas?.clear(), f.rebuildAttempted.clear(); + const re = N.read(() => { + const ye = w(); + return P(ye) ? ye.focus.key : void 0; + }), X = k; + if (re !== void 0 && (k = re), B.has(Pc)) { + f.pendingKeys.clear(), N.read(() => JA(f)), y = !0, re !== void 0 && (h = re); return; } - if (q.has(Br)) { - $ !== void 0 && $ !== J && (p = !0); + if (B.has(Jr)) { + re !== void 0 && re !== X && (y = !0); return; } - p || ($ !== void 0 && (f = $), A(), !(g || $ === void 0) && [...u.pendingKeys].some((Q) => Q !== $) && (g = !0, queueMicrotask(() => { - g = !1, !y && (_() || S(f)); + y || (re !== void 0 && (h = re), j(), !(_ || re === void 0) && [...f.pendingKeys].some((ye) => ye !== re) && (_ = !0, queueMicrotask(() => { + _ = !1, !C && (E() || z(h)); }))); }) ); return () => { - y = !0, P !== void 0 && clearTimeout(P), P = void 0, d(), B(), c.current = void 0; + C = !0, W !== void 0 && clearTimeout(W), W = void 0, g(), U(), l.current = void 0; }; - }, [s, o, a]), null; + }, [o, a, c]), null; } -const OA = ["status_unknown", "status_invalid"], ym = { +const t1 = ["status_unknown", "status_invalid"], Nm = { unknown: "This marker is not in the stylesheet!", invalid: "This marker is not valid here!" -}, wA = Object.values(ym); -function qA(e, t) { +}, r1 = Object.values(Nm); +function n1(e, t) { e.classList.toggle("status_unknown", t === "unknown"), e.classList.toggle("status_invalid", t === "invalid"); - const r = ym[t]; + const r = Nm[t]; e.getAttribute("aria-description") !== r && (e.setAttribute("aria-description", r), e.title = r); } -function nf(e) { - e.classList.remove(...OA), e.removeAttribute("aria-description"), wA.includes(e.title) && e.removeAttribute("title"); +function mf(e) { + e.classList.remove(...t1), e.removeAttribute("aria-description"), r1.includes(e.title) && e.removeAttribute("title"); } -function RA(e, t, r, n) { - const i = (a) => a.read(() => Fe().getChildrenKeys()), s = i(t), o = i(e); +function i1(e, t, r, n) { + const i = (a) => a.read(() => Ke().getChildrenKeys()), s = i(t), o = i(e); if (!(s.length !== o.length || s.some((a, c) => a !== o[c]))) return e.read(() => { const a = /* @__PURE__ */ new Set(), c = (l) => { - const u = se(l)?.getTopLevelElement(); - u && a.add(u.getKey()); + const d = oe(l)?.getTopLevelElement(); + d && a.add(d.getKey()); }; for (const l of r.keys()) c(l); for (const l of n) c(l); return a; }); } -function $A(e) { - const t = se(e), r = t?.getTopLevelElement(); +function s1(e) { + const t = oe(e), r = t?.getTopLevelElement(); return !t || !r ? !1 : t.getKey() === r.getKey() ? !0 : O(t) && t.getParent()?.getKey() === r.getKey(); } -function IA({ +function o1({ viewOptions: e, styleInfo: t, logger: r }) { const [n] = ce(), i = e?.markerMode === "editable"; - return z(() => { + return F(() => { if (!i) return; - const s = t ?? Vs; + const s = t ?? Js; let o = /* @__PURE__ */ new Map(); const a = (l) => { n.isComposing() || n.getEditorState().read(() => { - const u = wM(s, l); - let d = u; + const d = $E(s, l); + let u = d; if (l) { - d = new Map(u); - for (const [f, p] of o) { - if (d.has(f) || $A(f)) continue; - const m = se(f)?.getTopLevelElement(); - !m || l.has(m.getKey()) || d.set(f, p); + u = new Map(d); + for (const [p, f] of o) { + if (u.has(p) || s1(p)) continue; + const g = oe(p)?.getTopLevelElement(); + !g || l.has(g.getKey()) || u.set(p, f); } } - for (const [f] of o) { - if (d.has(f)) continue; - const p = n.getElementByKey(f); - p && nf(p); + for (const [p] of o) { + if (u.has(p)) continue; + const f = n.getElementByKey(p); + f && mf(f); } - for (const [f, p] of d) { - const m = n.getElementByKey(f); - m && qA(m, p); + for (const [p, f] of u) { + const g = n.getElementByKey(p); + g && n1(g, f); } - o = d, r?.debug(`[MarkerValidation] pass: ${d.size} flagged`); + o = u, r?.debug(`[MarkerValidation] pass: ${u.size} flagged`); }); }; a(); const c = n.registerUpdateListener( - ({ editorState: l, prevEditorState: u, dirtyElements: d, dirtyLeaves: f }) => { - d.size === 0 && f.size === 0 || a( - RA(l, u, d, f) + ({ editorState: l, prevEditorState: d, dirtyElements: u, dirtyLeaves: p }) => { + u.size === 0 && p.size === 0 || a( + i1(l, d, u, p) ); } ); return () => { c(); for (const [l] of o) { - const u = n.getElementByKey(l); - u && nf(u); + const d = n.getElementByKey(l); + d && mf(d); } }; }, [n, i, t, r]), null; } -function LA(e, t) { - const r = ll(ol), n = tl(); +function Sa(e, t) { + const r = gl(fl), n = al(); if (!r || !n?.end) return; - const i = Os.deserializeEditorState(e.getEditorState(), t); + const i = Ls.deserializeEditorState(e.getEditorState(), t); if (!i) return; - const s = $y({ + const s = Yy({ namespace: "markers-view-copy", - nodes: [Qe, ...nl], + nodes: [et, ...ll], onError: (a) => { throw a; } }); return s.parseEditorState( - Cr.serializeEditorState(i, r) + Ar.serializeEditorState(i, r) ).read( () => { - const a = Mo(n); - return a ? pm(a) : void 0; + const a = No(n); + return a ? Lo(a) : void 0; }, { editor: s } ); } -function DA({ viewOptions: e }) { - const [t] = ce(); - return z(() => { - const r = (n, i) => { - const s = w(); - if (!N(s) || s.isCollapsed()) return !1; - const o = LA(t, e); - return o === void 0 ? !1 : gm( +function a1({ + viewOptions: e, + copyLimit: t +}) { + const [r] = ce(), n = Q(t), i = Q(e); + return F(() => { + n.current = t, i.current = e; + }, [t, e]), F(() => { + const s = () => { + const c = i.current; + return c?.markerMode === "visible" ? c : void 0; + }, o = (c, l) => { + const d = s(); + if (!d) return !1; + const u = w(); + if (!P(u) || u.isCollapsed()) return !1; + const p = Sa(r, d); + return p === void 0 ? !1 : Rl( // COPY_COMMAND's payload is `ClipboardEvent | KeyboardEvent | null`, and jsdom (our test // environment) has no `ClipboardEvent` for `instanceof` to narrow against, so this // duck-checks the one property `$writeCopyPayload` needs. `in` throws on a non-object, so // the type check comes first. - n && typeof n == "object" && "clipboardData" in n ? n : null, - t, - s, - { "text/plain": o, "text/html": hm(o) }, - i + c && typeof c == "object" && "clipboardData" in c ? c : null, + r, + u, + { "text/plain": p, "text/html": ao(p) }, + l, + { + copyLimit: n.current, + $payloadFor: () => { + const f = Sa(r, d) ?? ""; + return { "text/plain": f, "text/html": ao(f) }; + } + } ); + }, a = (c) => { + const l = s(); + return l ? Ig(c, n.current, (d) => { + const u = Sa(r, l); + return u === void 0 ? d.getTextContent().length : u.length; + }) : !1; }; - return Ve( - t.registerCommand(fo, (n) => r(n, !1), Ne), - t.registerCommand(yn, (n) => r(n, !0), Ne) + return Fe( + r.registerCommand(Hr, a, tt), + r.registerCommand(Wt, a, tt), + r.registerCommand(Hr, (c) => o(c, !1), qe), + r.registerCommand(Wt, (c) => o(c, !0), qe) ); - }, [t, e]), null; + }, [r]), null; } -function bm(e, t, r) { +function Om(e, t, r) { const n = Math.min(e.length, t.length); for (let i = 0; i < n; i++) { const s = e[i], o = t[i]; r.set(s.getKey(), { node: o, siblings: t }); - const a = Sr(o); - a && F(s) && bm(s.getChildren(), a, r); + const a = Pr(o); + a && L(s) && Om(s.getChildren(), a, r); } } -function km(e, t) { +function wm(e, t) { let r = 0; const n = (i) => { for (let s = 0; s < i.length; s++) { - const o = i[s], a = Sr(o); + const o = i[s], a = Pr(o); if (a) { n(a); continue; } - const c = ci(o); - if (c === void 0 || !c.includes(tt)) continue; - const l = c.split(tt), u = []; - for (let d = 0; d < l.length; d++) { - const f = l[d]; - if (d > 0 && u.push(...t[r++] ?? []), f.length > 0) { - const p = { + const c = ui(o); + if (c === void 0 || !c.includes(st)) continue; + const l = c.split(st), d = []; + for (let u = 0; u < l.length; u++) { + const p = l[u]; + if (u > 0 && d.push(...t[r++] ?? []), p.length > 0) { + const f = { ...o, - text: f + text: p }; - u.push(p); + d.push(f); } } - i.splice(s, 1, ...u), s += u.length - 1; + i.splice(s, 1, ...d), s += d.length - 1; } }; n(e); } -function Tm(e, t, r) { +function qm(e, t, r) { const n = []; for (const i of e.sentinels) { const s = []; @@ -24637,41 +24916,41 @@ function Tm(e, t, r) { } return n; } -function xm(e, t) { +function Rm(e, t) { const r = []; for (const n of e) - wg(n, t) || ((ae(n) || D(n)) && r.push(n.getMarker()), F(n) && r.push(...xm(n.getChildren(), t))); + em(n, t) || ((le(n) || I(n)) && r.push(n.getMarker()), L(n) && r.push(...Rm(n.getChildren(), t))); return r; } -function _m(e) { +function $m(e) { const t = []; for (const r of e) { - const n = Pl(r); + const n = Fl(r); (n === "para" || n === "char") && t.push(r.marker ?? ""); - const i = Sr(r); - i && t.push(..._m(i)); + const i = Pr(r); + i && t.push(...$m(i)); } return t; } -function Dl(e, t, r) { - const n = xm(e, r), i = _m(t); +function Hl(e, t, r) { + const n = Rm(e, r), i = $m(t); return n.length === i.length && n.every((s, o) => s === i[o]); } -function UA(e, t) { +function c1(e, t) { if (!e || e.input.run.length === 0) return; const r = w(); let n, i; - if (N(r)) { + if (P(r)) { if (!r.isCollapsed()) return; n = r.focus.getNode(), i = r.focus.offset; } else if (t) - n = se(t.key), i = t.offset; + n = oe(t.key), i = t.offset; else return; - if (!(!v(n) || !n.isAttached()) && !(e.nodeKey !== void 0 && n.getKey() !== e.nodeKey) && n.getTextContent().slice(0, i).endsWith(e.input.run)) + if (!(!S(n) || !n.isAttached()) && !(e.nodeKey !== void 0 && n.getKey() !== e.nodeKey) && n.getTextContent().slice(0, i).endsWith(e.input.run)) return { node: n, caretOffset: i, run: e.input.run }; } -function Ul(e, t) { +function Gl(e, t) { const r = t.node.getKey(), n = e.spans.find( (o) => !o.isSentinel && o.key === r ); @@ -24679,133 +24958,133 @@ function Ul(e, t) { const i = n.start + t.caretOffset, s = i - t.run.length; return s < n.start || e.text.slice(s, i) !== t.run ? e.text : e.text.slice(0, s) + e.text.slice(i); } -function FA(e, t, r, n, i) { +function l1(e, t, r, n, i) { const { viewOptions: s, getMarker: o, logger: a } = r; if (e.length === 0) return; const c = { text: "", spans: [], sentinels: [] }; for (const y of e) { - const k = Nl(y, o, s); + const k = zl(y, o, s); if (!k) return; c.text.length > 0 && (c.text += " "); const _ = c.text.length; k.spans.forEach( - (S) => c.spans.push({ ...S, start: S.start + _, end: S.end + _ }) + (C) => c.spans.push({ ...C, start: C.start + _, end: C.end + _ }) ), c.sentinels.push(...k.sentinels), c.text += k.text; } - const l = i ? Ul(c, i) : c.text, u = Mr(l, { + const l = i ? Gl(c, i) : c.text, d = Or(l, { getMarker: o }); - if (u.length === 0) return; - if (qn(u) !== c.sentinels.length) { + if (d.length === 0) return; + if (Ln(d) !== c.sentinels.length) { a?.warn("[MarkerEdit] Settled USJ skipped: sentinel/preserved-node count mismatch"); return; } - const d = Cr.serializeEditorState( - { type: kr, version: br, content: u }, + const u = Ar.serializeEditorState( + { type: vr, version: Cr, content: d }, s ).root.children; - if (qo(d) !== c.sentinels.length) { + if (Do(u) !== c.sentinels.length) { a?.warn( "[MarkerEdit] Settled USJ skipped: serialized sentinel/preserved-node count mismatch" ); return; } - const f = Tm(c, t, n); - if (!f) { + const p = qm(c, t, n); + if (!p) { a?.warn("[MarkerEdit] Settled USJ skipped: a preserved node had no serialized form"); return; } - if (ki(d, o) === bi(e, o) && Dl(e, d, o)) { + if (_i(u, o) === xi(e, o) && Hl(e, u, o)) { a?.debug("[MarkerEdit] Settled USJ skipped: rebuild is a no-op (fixed point)"); return; } - km(d, f); - const m = zA(e), g = Cm(d); - for (let y = 0; y < m.length && y < g.length; y++) - m[y].sid !== void 0 && g[y].number === m[y].number && (g[y].sid = m[y].sid); - return d; + wm(u, p); + const g = u1(e), h = Lm(u); + for (let y = 0; y < g.length && y < h.length; y++) + g[y].sid !== void 0 && h[y].number === g[y].number && (h[y].sid = g[y].sid); + return u; } -function zA(e) { +function u1(e) { const t = [], r = (n) => { - Oe(n) ? t.push({ number: n.getNumber(), sid: n.getSid() }) : F(n) && n.getChildren().forEach(r); + Ae(n) ? t.push({ number: n.getNumber(), sid: n.getSid() }) : L(n) && n.getChildren().forEach(r); }; return e.forEach(r), t; } -function Cm(e) { +function Lm(e) { const t = []; for (const r of e) { - bp(r) && t.push(r); - const n = Sr(r); - n && t.push(...Cm(n)); + Np(r) && t.push(r); + const n = Pr(r); + n && t.push(...Lm(n)); } return t; } -function KA(e, t, r, n, i) { - const { viewOptions: s, getMarker: o, logger: a } = r, c = zg(e, o, s); +function d1(e, t, r, n, i) { + const { viewOptions: s, getMarker: o, logger: a } = r, c = lm(e, o, s); if (!c) return; - const { out: l, contentNodes: u } = c; - if (u.length === 0) return; - const d = i ? Ul(l, i) : l.text, f = Mr(d, { + const { out: l, contentNodes: d } = c; + if (d.length === 0) return; + const u = i ? Gl(l, i) : l.text, p = Or(u, { getMarker: o, isNoteContext: !0 }); - if (f.length === 0) return; - if (qn(f) !== l.sentinels.length) { + if (p.length === 0) return; + if (Ln(p) !== l.sentinels.length) { a?.warn("[MarkerEdit] Settled note USJ skipped: sentinel/preserved-node count mismatch"); return; } - const [p] = f; - if (f.length !== 1 || typeof p != "object" || p.type !== "para") { + const [f] = p; + if (p.length !== 1 || typeof f != "object" || f.type !== "para") { a?.warn("[MarkerEdit] Settled note USJ skipped: unexpected tokenized shape"); return; } - const m = p.content ?? [], g = Kg(m), y = e.getCategory() !== g, k = Ng(e, m, g, s); + const g = f.content ?? [], h = um(g), y = e.getCategory() !== h, k = Qg(e, g, h, s); if (k.failure !== void 0) { k.failure === "shape" ? a?.warn("[MarkerEdit] Settled note USJ skipped: unexpected serialized shape") : k.failure === "caller" && a?.warn("[MarkerEdit] Settled note USJ skipped: serialized note lacks the caller"); return; } const _ = k.children; - if (qo(_) !== l.sentinels.length) { + if (Do(_) !== l.sentinels.length) { a?.warn( "[MarkerEdit] Settled note USJ skipped: serialized sentinel/preserved-node count mismatch" ); return; } - const S = Tm(l, t, n); - if (!S) { + const C = qm(l, t, n); + if (!C) { a?.warn("[MarkerEdit] Settled note USJ skipped: a preserved node had no serialized form"); return; } - if (ki(_, o) === bi(u, o) && Dl(u, _, o)) { + if (_i(_, o) === xi(d, o) && Hl(d, _, o)) { if (y) - return { rebuilt: void 0, contentNodes: u, category: g, categoryChanged: y }; + return { rebuilt: void 0, contentNodes: d, category: h, categoryChanged: y }; a?.debug("[MarkerEdit] Settled note USJ skipped: rebuild is a no-op (fixed point)"); return; } - return km(_, S), { rebuilt: _, contentNodes: u, category: g, categoryChanged: y }; + return wm(_, C), { rebuilt: _, contentNodes: d, category: h, categoryChanged: y }; } -function sf(e) { +function yf(e) { return e.$?.textType; } -function jA(e, t) { +function f1(e, t) { const r = e, n = t; - return r.type === "text" && n.type === "text" && r.format === n.format && r.style === n.style && r.mode === n.mode && r.detail === n.detail && sf(e) === sf(t); + return r.type === "text" && n.type === "text" && r.format === n.format && r.style === n.style && r.mode === n.mode && r.detail === n.detail && yf(e) === yf(t); } -function BA(e) { +function p1(e) { const t = []; for (const r of e) { - const n = se(r); - n?.isAttached() && De(n) && n.getTag() === "optbreak" && n.getChildrenSize() === 0 && t.push(n); + const n = oe(r); + n?.isAttached() && Ue(n) && n.getTag() === "optbreak" && n.getChildrenSize() === 0 && t.push(n); } return t; } -function VA(e) { +function h1(e) { if (!O(e) || e.getMarkerSyntax() !== "opening") return; const t = e.getParent(); - if (!j(t)) return; + if (!K(t)) return; const r = e.getTextContent(); - if (Xr(e)) return; - const n = Ag.exec(r); + if (rn(e)) return; + const n = Ug.exec(r); if (!n) return; const i = n[1]; if (i.startsWith("+")) return; @@ -24813,175 +25092,175 @@ function VA(e) { if (t.getMarker() === s) return { glyph: e, note: t, oldMarker: s, newMarker: i }; } -function of(e, t) { +function bf(e, t) { const r = e; - r.marker = t, r.text = Rg(t, r.markerSyntax, r.nested); + r.marker = t, r.text = rm(t, r.markerSyntax, r.nested); } -function WA(e, t) { +function g1(e, t) { const { glyph: r, note: n, oldMarker: i, newMarker: s } = e; - if (!Ee.isValidMarker(s)) return; + if (!Pe.isValidMarker(s)) return; const o = t.get(n.getKey()); o && (o.node.marker = s); const a = t.get(r.getKey()); - a && of(a.node, s); - const c = n.getChildren().filter(O).filter((u) => u.getMarkerSyntax() === "closing" && u.getMarker() === i).at(-1), l = c && t.get(c.getKey()); - l && of(l.node, s); + a && bf(a.node, s); + const c = n.getChildren().filter(O).filter((d) => d.getMarkerSyntax() === "closing" && d.getMarker() === i).at(-1), l = c && t.get(c.getKey()); + l && bf(l.node, s); } -function HA(e, t, r) { - const { viewOptions: n, getMarker: i, logger: s } = t, o = Vg(e, i, n); +function m1(e, t, r) { + const { viewOptions: n, getMarker: i, logger: s } = t, o = pm(e, i, n); if (!o) return; - const a = r ? Ul(o, r) : o.text, c = Mr(a, { + const a = r ? Gl(o, r) : o.text, c = Or(a, { getMarker: i }), [l] = c; if (c.length === 0 || typeof l != "object" || l.type !== "chapter") return; - if (qn(c) !== 0) { + if (Ln(c) !== 0) { s?.warn("[MarkerEdit] Settled chapter USJ skipped: unexpected preserved-node placeholder"); return; } e.getSid() !== void 0 && (l.sid = e.getSid()); - const u = Cr.serializeEditorState( - { type: kr, version: br, content: c }, + const d = Ar.serializeEditorState( + { type: vr, version: Cr, content: c }, n ).root.children; - if (u.length === 0) return; - const d = [e, ...$o(e)]; - if (!(e.getNumber() !== (l.number ?? "") || e.getAltnumber() !== l.altnumber || e.getPubnumber() !== l.pubnumber) && ki(u, i) === bi(d, i) && Dl(d, u, i)) { + if (d.length === 0) return; + const u = [e, ...Fo(e)]; + if (!(e.getNumber() !== (l.number ?? "") || e.getAltnumber() !== l.altnumber || e.getPubnumber() !== l.pubnumber) && _i(d, i) === xi(u, i) && Hl(u, d, i)) { s?.debug("[MarkerEdit] Settled chapter USJ skipped: rebuild is a no-op (fixed point)"); return; } - return u; + return d; } -function GA(e, t, r, n, i) { - const s = UA(n, i); +function y1(e, t, r, n, i) { + const s = c1(n, i); if (t.size === 0 && !s) return; - const o = /* @__PURE__ */ new Map(), a = [], c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), d = (y) => { - j(y) ? c.set(y.getKey(), y) : Pe(y) ? l.set(y.getKey(), y) : o.set(y.getKey(), [y]); + const o = /* @__PURE__ */ new Map(), a = [], c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), u = (y) => { + K(y) ? c.set(y.getKey(), y) : we(y) ? l.set(y.getKey(), y) : o.set(y.getKey(), [y]); }; for (const y of t) { - const k = se(y); + const k = oe(y); if (!k?.isAttached()) continue; - const _ = as(k); + const _ = fs(k); if (_) { - if (d(_), O(k)) { - const S = em(k, r.getMarker); - S && a.push(S); + if (u(_), O(k)) { + const C = _m(k, r.getMarker); + C && a.push(C); } - if (j(_)) { - const S = VA(k); - S && u.set(_.getKey(), S); + if (K(_)) { + const C = h1(k); + C && d.set(_.getKey(), C); } } } - const f = /* @__PURE__ */ new Set(); + const p = /* @__PURE__ */ new Set(); for (const y of a) - y.some((k) => f.has(k.getKey())) || (y.forEach((k) => { - f.add(k.getKey()), o.delete(k.getKey()); + y.some((k) => p.has(k.getKey())) || (y.forEach((k) => { + p.add(k.getKey()), o.delete(k.getKey()); }), o.set(y[0].getKey(), y)); if (s) { - const y = as(s.node); - y && d(y); + const y = fs(s.node); + y && u(y); } - const p = BA(t); - if (o.size === 0 && c.size === 0 && l.size === 0 && p.length === 0) + const f = p1(t); + if (o.size === 0 && c.size === 0 && l.size === 0 && f.length === 0) return; - const m = new Set(p.map((y) => y.getKey())), g = /* @__PURE__ */ new Map(); - bm(Fe().getChildren(), e.root.children, g); - for (const y of u.values()) WA(y, g); + const g = new Set(f.map((y) => y.getKey())), h = /* @__PURE__ */ new Map(); + Om(Ke().getChildren(), e.root.children, h); + for (const y of d.values()) g1(y, h); for (const y of c.values()) { - const k = g.get(y.getKey()), _ = k ? Sr(k.node) : void 0; + const k = h.get(y.getKey()), _ = k ? Pr(k.node) : void 0; if (!k || !_) continue; - const S = KA(y, g, r, m, s); - if (!S) continue; - if (S.categoryChanged) { - const B = k.node; - S.category === void 0 ? delete B.category : B.category = S.category; - } - if (!S.rebuilt) continue; - const P = g.get(S.contentNodes[0].getKey()); - if (!P) continue; - const A = _.indexOf(P.node); - A < 0 || _.splice(A, S.contentNodes.length, ...S.rebuilt); + const C = d1(y, h, r, g, s); + if (!C) continue; + if (C.categoryChanged) { + const z = k.node; + C.category === void 0 ? delete z.category : z.category = C.category; + } + if (!C.rebuilt) continue; + const M = h.get(C.contentNodes[0].getKey()); + if (!M) continue; + const E = _.indexOf(M.node); + E < 0 || _.splice(E, C.contentNodes.length, ...C.rebuilt); } for (const y of o.values()) { - const k = g.get(y[0].getKey()); + const k = h.get(y[0].getKey()); if (!k) continue; - const _ = FA(y, g, r, m, s); + const _ = l1(y, h, r, g, s); if (!_) continue; - const S = k.siblings.indexOf(k.node); - S < 0 || k.siblings.splice(S, y.length, ..._); + const C = k.siblings.indexOf(k.node); + C < 0 || k.siblings.splice(C, y.length, ..._); } for (const y of l.values()) { - const k = g.get(y.getKey()); + const k = h.get(y.getKey()); if (!k) continue; - const _ = 1 + $o(y).length, S = HA(y, r, s); - if (!S) continue; - const P = k.siblings.indexOf(k.node); - P < 0 || k.siblings.splice(P, _, ...S); + const _ = 1 + Fo(y).length, C = m1(y, r, s); + if (!C) continue; + const M = k.siblings.indexOf(k.node); + M < 0 || k.siblings.splice(M, _, ...C); } - for (const y of p) { - const k = g.get(y.getKey()); + for (const y of f) { + const k = h.get(y.getKey()); if (!k) continue; const _ = k.siblings.indexOf(k.node); if (_ < 0) continue; k.siblings.splice(_, 1); - const S = k.siblings[_ - 1], P = k.siblings[_], A = S && ci(S), B = P && ci(P); - S && P && A !== void 0 && B !== void 0 && jA(S, P) && (S.text = A + B, k.siblings.splice(_, 1)); + const C = k.siblings[_ - 1], M = k.siblings[_], E = C && ui(C), z = M && ui(M); + C && M && E !== void 0 && z !== void 0 && f1(C, M) && (C.text = E + z, k.siblings.splice(_, 1)); } - return ig(e, r.viewOptions); + return yg(e, r.viewOptions); } -function JA({ +function b1({ viewOptions: e, logger: t }) { - const [r] = ce(), n = gi(e) && (e?.markerMode === "visible" || (e?.hasGutterParaMarkers ?? !1)); - return z(() => { + const [r] = ce(), n = bi(e) && (e?.markerMode === "visible" || (e?.hasGutterParaMarkers ?? !1)); + return F(() => { if (n) return r.registerNodeTransform( - Ze, - (i) => YA(i, t) + nt, + (i) => k1(i, t) ); }, [r, n, t]), null; } -function YA(e, t) { - e.getMarker() !== cr && (e.isEmpty() || Ft(e.getFirstChild()) || (t?.debug( - `[ParaMarkerPrefixGuard] Resetting paragraph "${e.getMarker()}" → "${cr}" (key ${e.getKey()})` - ), e.setMarker(cr))); +function k1(e, t) { + e.getMarker() !== lr && (e.isEmpty() || zt(e.getFirstChild()) || (t?.debug( + `[ParaMarkerPrefixGuard] Resetting paragraph "${e.getMarker()}" → "${lr}" (key ${e.getKey()})` + ), e.setMarker(lr))); } -function XA({ +function T1({ scrRef: e, onScrRefChange: t }) { - const [r] = ce(), n = Z({ + const [r] = ce(), n = Q({ phase: "idle", pendingEchoes: [], scrRef: e, onScrRefChange: t, sawDocument: !1 }); - return z(() => { + return F(() => { const i = n.current, s = i.scrRef; - i.scrRef = e, i.onScrRefChange = t, so(s, e) || QA(i, r, e); - }, [r, e, t]), z( + i.scrRef = e, i.onScrRefChange = t, uo(s, e) || x1(i, r, e); + }, [r, e, t]), F( () => r.registerMutationListener( - Ut, + Ft, (i, { prevEditorState: s }) => { const o = [...i.values()]; if (o.every((c) => c === "destroyed")) return; - const a = dc(r); - af(n.current, r, a, { + const a = bc(r); + kf(n.current, r, a, { hasCreated: o.includes("created"), hasDestroyed: o.includes("destroyed"), - isSameDocumentReload: Ps(s) === Ps(r.getEditorState()) + isSameDocumentReload: Rs(s) === Rs(r.getEditorState()) }); }, { skipInitialization: !1 } ), [r] - ), z(() => { + ), F(() => { const i = (a) => a.read( () => new Set( - Fe().getChildren().filter(Ge).map((c) => c.getKey()) + Ke().getChildren().filter(Je).map((c) => c.getKey()) ) ); let s; @@ -24989,15 +25268,15 @@ function XA({ const c = r.getEditorState(); if (s === c) return; s = c; - const u = a === c ? /* @__PURE__ */ new Set() : i(a), d = i(c), f = [...d].some((p) => !u.has(p)); - f && (dc(r) || af(n.current, r, void 0, { - hasCreated: f, - hasDestroyed: [...u].some((p) => !d.has(p)), - isSameDocumentReload: Ps(a) === Ps(c) + const d = a === c ? /* @__PURE__ */ new Set() : i(a), u = i(c), p = [...u].some((f) => !d.has(f)); + p && (bc(r) || kf(n.current, r, void 0, { + hasCreated: p, + hasDestroyed: [...d].some((f) => !u.has(f)), + isSameDocumentReload: Rs(a) === Rs(c) })); }; - return Ve( - ...[Nt, pr].map( + return Fe( + ...[Ot, hr].map( (a) => r.registerMutationListener( a, (c, { prevEditorState: l }) => o(l), @@ -25005,140 +25284,140 @@ function XA({ ) ) ); - }, [r]), z( + }, [r]), F( () => r.registerCommand( - ur, + dr, () => { const i = n.current; - return i.phase === "idle" && r1(i, e1()), !1; + return i.phase === "idle" && S1(i, C1()), !1; }, - Tt + xt ), [r] - ), z(() => { + ), F(() => { const i = (s) => { [...s.values()].some( (a) => a === "created" || a === "destroyed" - ) && queueMicrotask(() => r.dispatchCommand(ur, void 0)); + ) && queueMicrotask(() => r.dispatchCommand(dr, void 0)); }; - return Ve( - r.registerMutationListener(Ct, i), - r.registerMutationListener(dt, i) + return Fe( + r.registerMutationListener(vt, i), + r.registerMutationListener(ft, i) ); - }, [r]), z(() => { - const i = () => o1(n.current); + }, [r]), F(() => { + const i = () => P1(n.current); return r.registerRootListener((s, o) => { o?.removeEventListener("pointerdown", i), o?.removeEventListener("keydown", i), o?.removeEventListener("beforeinput", i), s?.addEventListener("pointerdown", i), s?.addEventListener("keydown", i), s?.addEventListener("beforeinput", i); }); }, [r]), null; } -function QA(e, t, r) { - if (ZA(e, r)) return; +function x1(e, t, r) { + if (_1(e, r)) return; e.phase = "navigating", e.pendingEchoes.length = 0; - const n = dc(t); - (!n || n === r.book) && t.update(() => Sm(r.chapterNum, r.verseNum), { - tag: Br + const n = bc(t); + (!n || n === r.book) && t.update(() => Im(r.chapterNum, r.verseNum), { + tag: Jr }); } -function ZA(e, t) { - const r = e.pendingEchoes.findIndex((n) => so(n, t)); +function _1(e, t) { + const r = e.pendingEchoes.findIndex((n) => uo(n, t)); return r < 0 ? !1 : (e.pendingEchoes.splice(0, r + 1), e.phase = "idle", !0); } -function e1() { - const e = w(), t = $c(e); +function C1() { + const e = w(), t = zc(e); if (!t) return; - const r = Fl(), n = xp(t); + const r = Jl(), n = qp(t); if (!n && !r) return; const i = n ? parseInt(n.getNumber() ?? "1", 10) : 1; if (Number.isNaN(i)) return; - const s = Yc(t, e), { verseNum: o, verse: a } = lx(s ?? void 0, e); + const s = rl(t, e), { verseNum: o, verse: a } = Sx(s ?? void 0, e); if (!Number.isNaN(o)) return { book: r?.getCode() || void 0, chapterNum: i, verseNum: o, verse: a }; } -function dc(e) { - return e.getEditorState().read(() => Fl()?.getCode() || void 0); +function bc(e) { + return e.getEditorState().read(() => Jl()?.getCode() || void 0); } -function Fl() { - return Fe().getChildren().find(pt); +function Jl() { + return Ke().getChildren().find(ht); } -function af(e, t, r, n) { +function kf(e, t, r, n) { const i = n.hasCreated && !e.sawDocument; if (n.hasCreated && (e.sawDocument = !0), e.phase === "navigating") { - n.hasCreated && (!r || r === e.scrRef.book) && Ta(e, t); + n.hasCreated && (!r || r === e.scrRef.book) && Ma(e, t); return; } - n.hasCreated && n.hasDestroyed ? (n.isSameDocumentReload || Ta(e, t), e.phase = "navigating") : i && Ta(e, t), r && r !== e.scrRef.book && Em(e, { ...e.scrRef, book: r }) && (e.phase = "navigating"); + n.hasCreated && n.hasDestroyed ? (n.isSameDocumentReload || Ma(e, t), e.phase = "navigating") : i && Ma(e, t), r && r !== e.scrRef.book && Fm(e, { ...e.scrRef, book: r }) && (e.phase = "navigating"); } -function Ta(e, t) { +function Ma(e, t) { queueMicrotask(() => { t.update( - () => Sm(e.scrRef.chapterNum, e.scrRef.verseNum), - { tag: Br } + () => Im(e.scrRef.chapterNum, e.scrRef.verseNum), + { tag: Jr } ); }); } -function Sm(e, t) { - const r = $c(w()), n = Xc(r)?.getNumber(), i = xp(r); - if ((i ? parseInt(i.getNumber() ?? "1", 10) : 1) === e && n && (Ep(n) ? Mm(t, n) : parseInt(n, 10) === t)) +function Im(e, t) { + const r = zc(w()), n = nl(r)?.getNumber(), i = qp(r); + if ((i ? parseInt(i.getNumber() ?? "1", 10) : 1) === e && n && (Up(n) ? Um(t, n) : parseInt(n, 10) === t)) return; - const o = Fe().getChildren(), a = Tp(o, e); + const o = Ke().getChildren(), a = wp(o, e); if (!a) return; - const c = Ck(o, a), l = mk(c, !0); - _k(c, l); - let u; + const c = Uk(o, a), l = wk(c, !0); + Dk(c, l); + let d; try { - u = ix(c, t); + d = Tx(c, t); } catch { return; } - u && (ae(u) ? !v(u.getFirstChild()) && yi(u) || Qt(u, 0) : t1(u)); + d && (le(d) ? !S(d.getFirstChild()) && Ti(d) || Qt(d, 0) : v1(d)); } -function t1(e) { +function v1(e) { const t = e.getParent(); if (!t) return; const r = e.getIndexWithinParent() + 1, n = t.getChildAtIndex(r); - if (!n || ge(n)) { + if (!n || me(n)) { Qt(t, r); return; } - const i = _o(t, r); + const i = Mo(t, r); if (i) { i.select(0, 0); return; } - if (v(e)) { + if (S(e)) { const o = e.getTextContentSize(); e.select(o, o); return; } - const s = F(n) && !j(n) ? vm(n) : void 0; + const s = L(n) && !K(n) ? Dm(n) : void 0; s ? s.select(0, 0) : Qt(t, r); } -function vm(e) { +function Dm(e) { const t = e.getFirstChild(); - if (v(t)) return t; - if (F(t) && !j(t)) return vm(t); + if (S(t)) return t; + if (L(t) && !K(t)) return Dm(t); } -function Ps(e) { +function Rs(e) { return e.read(() => { - const t = Fe().getChildren().find(Ge); - return `${Fl()?.getCode() ?? ""}|${t?.getNumber() ?? ""}`; + const t = Ke().getChildren().find(Je); + return `${Jl()?.getCode() ?? ""}|${t?.getNumber() ?? ""}`; }); } -function r1(e, t) { - e.phase !== "navigating" && t && (n1(t, e.scrRef) || Em(e, i1(t, e.scrRef))); +function S1(e, t) { + e.phase !== "navigating" && t && (M1(t, e.scrRef) || Fm(e, E1(t, e.scrRef))); } -function n1(e, t) { - return e.book && e.book !== t.book || e.chapterNum !== t.chapterNum ? !1 : e.verse ? Mm(t.verseNum, e.verse) : t.verseNum === e.verseNum; +function M1(e, t) { + return e.book && e.book !== t.book || e.chapterNum !== t.chapterNum ? !1 : e.verse ? Um(t.verseNum, e.verse) : t.verseNum === e.verseNum; } -function Mm(e, t) { +function Um(e, t) { try { - return Ic(e, t); + return Kc(e, t); } catch { return !1; } } -function i1(e, t) { +function E1(e, t) { const r = { book: e.book || t.book, chapterNum: e.chapterNum, @@ -25146,26 +25425,26 @@ function i1(e, t) { }; return e.verse != null && (r.verse = e.verse), t.versificationStr != null && (r.versificationStr = t.versificationStr), r; } -const s1 = 8; -function Em(e, t) { - return so(t, e.scrRef) || e.pendingEchoes.some((r) => so(r, t)) ? !1 : (e.pendingEchoes.push(t), e.pendingEchoes.length > s1 && e.pendingEchoes.shift(), e.onScrRefChange(t), !0); +const A1 = 8; +function Fm(e, t) { + return uo(t, e.scrRef) || e.pendingEchoes.some((r) => uo(r, t)) ? !1 : (e.pendingEchoes.push(t), e.pendingEchoes.length > A1 && e.pendingEchoes.shift(), e.onScrRefChange(t), !0); } -function so(e, t) { +function uo(e, t) { return e.book === t.book && e.chapterNum === t.chapterNum && e.verseNum === t.verseNum && (e.verse ?? void 0) === (t.verse ?? void 0); } -function o1(e) { +function P1(e) { e.phase = "idle"; } -function a1(e) { - return pt(e) ? `${e.__code}` : Pe(e) ? `${e.__marker} "${e.__number}"` : D(e) ? `${e.__marker}` : fs(e) ? `${e.__marker} "${e.__number}"` : St(e) ? `${e.__caller}` : wn(e) ? `${e.__marker} "${e.__number}"` : j(e) ? `${e.__marker} "${e.__caller}"` + (e.__isCollapsed ? " (collapsed)" : " (expanded)") : ae(e) ? `${e.__marker}` : v(e) ? `"${e.__text}"${c1(e)}` : Ce(e) ? `ids: [ ${JSON.stringify(e.getTypedIDs())} ]` : Oe(e) ? `${e.__marker} "${e.__number}"` : ""; +function N1(e) { + return ht(e) ? `${e.__code}` : we(e) ? `${e.__marker} "${e.__number}"` : I(e) ? `${e.__marker}` : ys(e) ? `${e.__marker} "${e.__number}"` : St(e) ? `${e.__caller}` : $n(e) ? `${e.__marker} "${e.__number}"` : K(e) ? `${e.__marker} "${e.__caller}"` + (e.__isCollapsed ? " (collapsed)" : " (expanded)") : le(e) ? `${e.__marker}` : S(e) ? `"${e.__text}"${O1(e)}` : Ce(e) ? `ids: [ ${JSON.stringify(e.getTypedIDs())} ]` : Ae(e) ? `${e.__marker} "${e.__number}"` : ""; } -function c1(e) { - return e.__state ? " " + JSON.stringify(e.__state.toJSON()[ds]) : ""; +function O1(e) { + return e.__state ? " " + JSON.stringify(e.__state.toJSON()[gs]) : ""; } -function l1() { +function w1() { const [e] = ce(); - return /* @__PURE__ */ C( - Fy, + return /* @__PURE__ */ v( + rb, { viewClassName: "tree-view-output", treeTypeButtonClassName: "debug-treetype-button", @@ -25173,51 +25452,51 @@ function l1() { timeTravelButtonClassName: "debug-timetravel-button", timeTravelPanelSliderClassName: "debug-timetravel-panel-slider", timeTravelPanelButtonClassName: "debug-timetravel-panel-button", - customPrintNode: a1, + customPrintNode: N1, editor: e } ); } -const Am = gf(null), cf = 4; -function u1({ +const zm = Ef(null), Tf = 4; +function q1({ children: e, className: t, onClick: r, title: n }) { - const i = Z(null), s = mf(Am); + const i = Q(null), s = Af(zm); if (s === null) throw new Error("DropDownItem must be used within a DropDown"); const { registerItem: o } = s; - return z(() => { + return F(() => { i && i.current && o(i); - }, [i, o]), /* @__PURE__ */ C("button", { className: t, onClick: r, ref: i, title: n, type: "button", children: e }); + }, [i, o]), /* @__PURE__ */ v("button", { className: t, onClick: r, ref: i, title: n, type: "button", children: e }); } -function d1({ +function R1({ children: e, dropDownRef: t, onClose: r }) { - const [n, i] = de(), [s, o] = de(), a = he( - (u) => { - i((d) => d ? [...d, u] : [u]); + const [n, i] = fe(), [s, o] = fe(), a = ge( + (d) => { + i((u) => u ? [...u, d] : [d]); }, [i] - ), c = (u) => { + ), c = (d) => { if (!n) return; - const d = u.key; - ["Escape", "ArrowUp", "ArrowDown", "Tab"].includes(d) && u.preventDefault(), d === "Escape" || d === "Tab" ? r() : d === "ArrowUp" ? o((f) => { - if (!f) return n[0]; - const p = n.indexOf(f) - 1; - return n[p === -1 ? n.length - 1 : p]; - }) : d === "ArrowDown" && o((f) => f ? n[n.indexOf(f) + 1] : n[0]); - }, l = ze(() => ({ registerItem: a }), [a]); - return z(() => { - const u = s ?? n?.[0]; - u?.current && u.current.focus(); - }, [n, s]), /* @__PURE__ */ C(Am.Provider, { value: l, children: /* @__PURE__ */ C("div", { className: "dropdown", ref: t, onKeyDown: c, children: e }) }); -} -function f1({ + const u = d.key; + ["Escape", "ArrowUp", "ArrowDown", "Tab"].includes(u) && d.preventDefault(), u === "Escape" || u === "Tab" ? r() : u === "ArrowUp" ? o((p) => { + if (!p) return n[0]; + const f = n.indexOf(p) - 1; + return n[f === -1 ? n.length - 1 : f]; + }) : u === "ArrowDown" && o((p) => p ? n[n.indexOf(p) + 1] : n[0]); + }, l = je(() => ({ registerItem: a }), [a]); + return F(() => { + const d = s ?? n?.[0]; + d?.current && d.current.focus(); + }, [n, s]), /* @__PURE__ */ v(zm.Provider, { value: l, children: /* @__PURE__ */ v("div", { className: "dropdown", ref: t, onKeyDown: c, children: e }) }); +} +function $1({ disabled: e = !1, buttonLabel: t, buttonAriaLabel: r, @@ -25226,65 +25505,65 @@ function f1({ children: s, stopCloseOnClickSelf: o }) { - const a = Z(null), c = Z(null), [l, u] = de(!1), d = () => { - u(!1), c && c.current && c.current.focus(); + const a = Q(null), c = Q(null), [l, d] = fe(!1), u = () => { + d(!1), c && c.current && c.current.focus(); }; - return z(() => { - const f = c.current, p = a.current; - if (l && f !== null && p !== null) { - const { top: m, left: g } = f.getBoundingClientRect(); - p.style.top = `${m + f.offsetHeight + cf}px`, p.style.left = `${Math.min(g, window.innerWidth - p.offsetWidth - 20)}px`; - } - }, [a, c, l]), z(() => { - const f = c.current; - if (f !== null && l) { - const p = (m) => { - const g = m.target; - o && a.current && a.current.contains(g) || f.contains(g) || u(!1); + return F(() => { + const p = c.current, f = a.current; + if (l && p !== null && f !== null) { + const { top: g, left: h } = p.getBoundingClientRect(); + f.style.top = `${g + p.offsetHeight + Tf}px`, f.style.left = `${Math.min(h, window.innerWidth - f.offsetWidth - 20)}px`; + } + }, [a, c, l]), F(() => { + const p = c.current; + if (p !== null && l) { + const f = (g) => { + const h = g.target; + o && a.current && a.current.contains(h) || p.contains(h) || d(!1); }; - return document.addEventListener("click", p), () => { - document.removeEventListener("click", p); + return document.addEventListener("click", f), () => { + document.removeEventListener("click", f); }; } return () => { }; - }, [a, c, l, o]), z(() => { - const f = () => { + }, [a, c, l, o]), F(() => { + const p = () => { if (l) { - const p = c.current, m = a.current; - if (p !== null && m !== null) { - const { top: g } = p.getBoundingClientRect(), y = g + p.offsetHeight + cf; - y !== m.getBoundingClientRect().top && (m.style.top = `${y}px`); + const f = c.current, g = a.current; + if (f !== null && g !== null) { + const { top: h } = f.getBoundingClientRect(), y = h + f.offsetHeight + Tf; + y !== g.getBoundingClientRect().top && (g.style.top = `${y}px`); } } }; - return document.addEventListener("scroll", f), () => { - document.removeEventListener("scroll", f); + return document.addEventListener("scroll", p), () => { + document.removeEventListener("scroll", p); }; - }, [c, a, l]), /* @__PURE__ */ Te(gn, { children: [ - /* @__PURE__ */ Te( + }, [c, a, l]), /* @__PURE__ */ xe(bn, { children: [ + /* @__PURE__ */ xe( "button", { type: "button", disabled: e, "aria-label": r || t, className: n, - onClick: () => u(!l), + onClick: () => d(!l), ref: c, children: [ - i && /* @__PURE__ */ C("span", { className: i }), - t && /* @__PURE__ */ C("span", { className: "text dropdown-button-text", children: t }), - /* @__PURE__ */ C("i", { className: "chevron-down" }) + i && /* @__PURE__ */ v("span", { className: i }), + t && /* @__PURE__ */ v("span", { className: "text dropdown-button-text", children: t }), + /* @__PURE__ */ v("i", { className: "chevron-down" }) ] } ), - l && hn( - /* @__PURE__ */ C(d1, { dropDownRef: a, onClose: d, children: s }), + l && yn( + /* @__PURE__ */ v(R1, { dropDownRef: a, onClose: u, children: s }), document.body ) ] }); } -const fc = { +const kc = { m: "m - Paragraph - Margin - No First Line Indent", ms: "ms - Heading - Major Section Level 1", nb: "nb - Paragraph - No Break with Previous Paragraph", @@ -25295,8 +25574,8 @@ const fc = { r: "r - Heading - Parallel References", s: "s - Heading - Section Level 1" // do not allow `b - Poetry - Stanza Break (Blank Line)` here to avoid a USFM validity issue. -}, pc = { - ...fc, +}, Tc = { + ...kc, // File / header cl: "cl - Chapter - Publishing Label", h: "h - File - Header", @@ -25401,27 +25680,27 @@ const fc = { // Tables tr: "tr - Table - Row" }; -function p1({ +function L1({ editorRef: e, blockMarker: t, disabled: r = !1 }) { - return /* @__PURE__ */ C( - f1, + return /* @__PURE__ */ v( + $1, { disabled: r, buttonClassName: "toolbar-item block-controls", - buttonIconClassName: "icon block-marker " + h1(t), - buttonLabel: g1(t), + buttonIconClassName: "icon block-marker " + I1(t), + buttonLabel: D1(t), buttonAriaLabel: "Formatting options for block type", - children: Object.keys(fc).map((n) => /* @__PURE__ */ Te( - u1, + children: Object.keys(kc).map((n) => /* @__PURE__ */ xe( + q1, { - className: "item block-marker " + m1(t === n), + className: "item block-marker " + U1(t === n), onClick: () => e.current?.formatPara(n), children: [ - /* @__PURE__ */ C("i", { className: "icon block-marker " + n }), - /* @__PURE__ */ C("span", { className: "text usfm_" + n, children: fc[n] }) + /* @__PURE__ */ v("i", { className: "icon block-marker " + n }), + /* @__PURE__ */ v("span", { className: "text usfm_" + n, children: kc[n] }) ] }, n @@ -25429,28 +25708,28 @@ function p1({ } ); } -function h1(e) { - return e && e in pc ? e : "ban"; +function I1(e) { + return e && e in Tc ? e : "ban"; } -function g1(e) { - return e && e in pc ? pc[e] : "No Style"; +function D1(e) { + return e && e in Tc ? Tc[e] : "No Style"; } -function m1(e) { +function U1(e) { return e ? "active dropdown-item-active" : ""; } -function lf() { - return /* @__PURE__ */ C("div", { className: "divider" }); +function xf() { + return /* @__PURE__ */ v("div", { className: "divider" }); } -const y1 = En(function({ editorRef: t, isReadonly: r = !1, onStateChange: n }, i) { - const [s] = ce(), [o, a] = de(s), [c, l] = de(), [u, d] = de(!1), [f, p] = de(!1), m = he( +const F1 = Nn(function({ editorRef: t, isReadonly: r = !1, onStateChange: n }, i) { + const [s] = ce(), [o, a] = fe(s), [c, l] = fe(), [d, u] = fe(!1), [p, f] = fe(!1), g = ge( ({ - canUndo: g, + canUndo: h, canRedo: y, blockMarker: k, contextMarker: _ }) => { - d(g), p(y), l(k), n?.({ - canUndo: g, + u(h), f(y), l(k), n?.({ + canUndo: h, canRedo: y, blockMarker: k, contextMarker: _ @@ -25458,61 +25737,61 @@ const y1 = En(function({ editorRef: t, isReadonly: r = !1, onStateChange: n }, i }, [n] ); - return z(() => s.registerCommand( - ur, - (g, y) => (a(y), !1), - Wt - ), [s]), /* @__PURE__ */ Te(gn, { children: [ - /* @__PURE__ */ C(Bh, { onStateChange: m }), - /* @__PURE__ */ Te("div", { className: "toolbar", children: [ - /* @__PURE__ */ C( + return F(() => s.registerCommand( + dr, + (h, y) => (a(y), !1), + tt + ), [s]), /* @__PURE__ */ xe(bn, { children: [ + /* @__PURE__ */ v(ng, { onStateChange: g }), + /* @__PURE__ */ xe("div", { className: "toolbar", children: [ + /* @__PURE__ */ v( "button", { - disabled: !u || r, + disabled: !d || r, onClick: () => { - o.dispatchCommand(Mf, void 0); + o.dispatchCommand(Df, void 0); }, - title: $s ? "Undo (⌘Z)" : "Undo (Ctrl+Z)", + title: ri ? "Undo (⌘Z)" : "Undo (Ctrl+Z)", type: "button", className: "toolbar-item spaced", "aria-label": "Undo", - children: /* @__PURE__ */ C("i", { className: "format undo" }) + children: /* @__PURE__ */ v("i", { className: "format undo" }) } ), - /* @__PURE__ */ C( + /* @__PURE__ */ v( "button", { - disabled: !f || r, + disabled: !p || r, onClick: () => { - o.dispatchCommand(Ef, void 0); + o.dispatchCommand(Uf, void 0); }, - title: $s ? "Redo (⌘Y)" : "Redo (Ctrl+Y)", + title: ri ? "Redo (⌘Y)" : "Redo (Ctrl+Y)", type: "button", className: "toolbar-item", "aria-label": "Redo", - children: /* @__PURE__ */ C("i", { className: "format redo" }) + children: /* @__PURE__ */ v("i", { className: "format redo" }) } ), - /* @__PURE__ */ C(lf, {}), - o === s && /* @__PURE__ */ Te(gn, { children: [ - /* @__PURE__ */ C( - p1, + /* @__PURE__ */ v(xf, {}), + o === s && /* @__PURE__ */ xe(bn, { children: [ + /* @__PURE__ */ v( + L1, { editorRef: t, blockMarker: c, disabled: r } ), - /* @__PURE__ */ C(lf, {}) + /* @__PURE__ */ v(xf, {}) ] }), - /* @__PURE__ */ C("div", { ref: i, className: "end-container" }) + /* @__PURE__ */ v("div", { ref: i, className: "end-container" }) ] }) ] }); -}), b1 = Eo(), k1 = {}, T1 = {}; -function x1() { - return /* @__PURE__ */ C("div", { className: "editor-placeholder", children: "Enter some Scripture..." }); +}), z1 = Oo(), K1 = {}, j1 = {}; +function B1() { + return /* @__PURE__ */ v("div", { className: "editor-placeholder", children: "Enter some Scripture..." }); } -const Pm = En(function({ +const Km = Nn(function({ defaultUsj: t, scrRef: r, onScrRefChange: n, @@ -25522,612 +25801,615 @@ const Pm = En(function({ options: a, logger: c, children: l -}, u) { - const d = Z(null), f = Z(null), p = Z(null), m = Z(t), g = Z(void 0), y = Z(void 0), k = Z(void 0), _ = Z(void 0), S = Z(!1), [P, A] = de(t), [B, M] = de(0), [q, $] = de(), { - isReadonly: J = !1, - structureProtectionMode: Q = "off", - hasExternalUI: Me = !1, - hasSpellCheck: re = !1, - textDirection: qe = "ltr", - markerMenuTrigger: be = "\\", +}, d) { + const u = Q(null), p = Q(null), f = Q(null), g = Q(t), h = Q(void 0), y = Q(void 0), k = Q(void 0), _ = Q(void 0), C = Q(!1), [M, E] = fe(t), [z, W] = fe(0), [j, U] = fe(), { + isReadonly: N = !1, + structureProtectionMode: B = "off", + hasExternalUI: re = !1, + hasSpellCheck: X = !1, + textDirection: ye = "ltr", + markerMenuTrigger: ke = "\\", view: tr, - nodes: Re, - debug: en = !1, - contextMenu: gr, - styleInfo: vt, - markerSettleDelayMs: te - } = a ?? T1, E = tr ?? b1, Y = ns(E) && (E.markerMode !== "hidden" || !E.hasSpacing || E.hasGutterParaMarkers || E.hasActiveTextFocusBox) ? { - ...E, + nodes: $e, + debug: sn = !1, + contextMenu: mr, + styleInfo: Mt, + markerSettleDelayMs: ne, + copyLimit: A + } = a ?? j1, G = tr ?? z1, de = as(G) && (G.markerMode !== "hidden" || !G.hasSpacing || G.hasGutterParaMarkers || G.hasActiveTextFocusBox) ? { + ...G, markerMode: "hidden", hasSpacing: !0, hasGutterParaMarkers: !1, hasActiveTextFocusBox: !1 - } : E, le = Z(Y); - wt(le.current, Y) || (le.current = Y); - const W = le.current, xe = ze(() => Re ?? k1, [Re]), ht = ze(() => gr, [gr]), zt = ze( - () => JT(vt ?? Vs), - [vt] - ), gt = Z(c); - wt(gt.current, c) || (gt.current = c); - const We = gt.current, ct = ns(W), ue = J || ct, Rn = Y !== E; - z(() => { - ct && !J && We?.error( + } : G, Ee = Q(de); + qt(Ee.current, de) || (Ee.current = de); + const ee = Ee.current, Se = je(() => $e ?? K1, [$e]), yr = je(() => mr, [mr]), wt = je( + () => dx(Mt ?? Js), + [Mt] + ), on = Q(c); + qt(on.current, c) || (on.current = c); + const We = on.current, ue = as(ee), gt = N || ue, _e = de !== G; + F(() => { + ue && !N && We?.error( "Editor: the block verse layout is read-only; ignoring `isReadonly: false`. Set `isReadonly: true` alongside `verseLayout: 'block'`." - ), Rn && We?.warn( + ), _e && We?.warn( "Editor: a visible `markerMode`, `hasSpacing: false`, `hasGutterParaMarkers` and `hasActiveTextFocusBox` are not supported with the block verse layout and are ignored." - ), W?.markerMode === "visible" && !J && We?.warn( + ), ee?.markerMode === "visible" && !N && We?.warn( "Editor: `markerMode: 'visible'` renders markers as read-only glyphs, but the editor is editable and no marker repair runs there. Pass `isReadonly: true` alongside it." ); - }, [ct, J, Rn, We, W?.markerMode]); - const ye = Z(null), Ti = ze(() => { - if (W.markerMode !== "editable") return; - const R = vt ?? Vs; + }, [ue, N, _e, We, ee?.markerMode]); + const br = Q(null), Oe = je(() => { + if (ee.markerMode !== "editable") return; + const q = Mt ?? Js; return { - getContext: () => ye.current?.getMarkerMenuContext(), + getContext: () => br.current?.getMarkerMenuContext(), // The context object is always one this same harness produced via `getContext()` above // (never externally supplied), so it really is a full `MarkerMenuContext` at runtime - // the cast bridges shared-react's structural `MarkerMenuContextLike` back to it. - getItems: (K) => FM( - R, - K, - xe.extraValidMarkers + getItems: (V) => jE( + q, + V, + Se.extraValidMarkers ), - getEnterItems: (K) => zM( - R, - K, - xe.extraValidMarkers + getEnterItems: (V) => BE( + q, + V, + Se.extraValidMarkers ), - apply: (K, G) => { - const X = ye.current; - X && (G.trigger === "enter" ? X.splitParagraphWithMarker(K.marker) : X.applyMarkerMenuSelection(K, G)); + apply: (V, J) => { + const Z = br.current; + Z && (J.trigger === "enter" ? Z.splitParagraphWithMarker(V.marker) : Z.applyMarkerMenuSelection(V, J)); }, - commitTypedCloser: (K) => { - ye.current?.commitTypedCloser(K); + commitTypedCloser: (V) => { + br.current?.commitTypedCloser(V); } }; - }, [W, vt, xe.extraValidMarkers]), ve = (R) => { - S.current || (S.current = !0, gt.current?.warn( - `Editor: cannot ${R} in the block verse layout; its paragraphs are split across verse blocks, so editor content indexes do not match the source USJ.` + }, [ee, Mt, Se.extraValidMarkers]), kr = (q) => { + C.current || (C.current = !0, on.current?.warn( + `Editor: cannot ${q} in the block verse layout; its paragraphs are split across verse blocks, so editor content indexes do not match the source USJ.` )); - }, wr = (R) => { - if (ct) + }, Ci = (q) => { + if (ue) throw new Error( - `Cannot ${R} in the block verse layout; it is a read-only view whose structure does not match the source USJ.` + `Cannot ${q} in the block verse layout; it is a read-only view whose structure does not match the source USJ.` ); - }, qr = (R) => { - if (wr(R), ue) throw new Error(`Cannot ${R} in readonly mode`); - }, rr = ze( + }, mt = (q) => { + if (Ci(q), gt) throw new Error(`Cannot ${q} in readonly mode`); + }, xs = je( () => ({ namespace: "platformEditor", - theme: { ..._g, showCharMarkerTitles: W.showCharMarkerTitles }, - editable: !ue, + theme: { ...$g, showCharMarkerTitles: ee.showCharMarkerTitles }, + editable: !gt, editorState: void 0, // Handling of errors during update - onError(R) { - throw R; + onError(q) { + throw q; }, // Registered per layout so an editor that isn't using block verse never holds its node. - nodes: [Qe, ...ct ? a_ : nl] + nodes: [et, ...ue ? C_ : ll] }), - [ue, ct, W.showCharMarkerTitles] + [gt, ue, ee.showCharMarkerTitles] ); - Os.initialize(We); - function Rr(R) { - if (R !== void 0 && !cM(R, xe.extraValidMarkers)) - throw new Error(`Unsupported character marker '${R}'`); - } - const tn = he(() => { - const R = d.current; - if (!R) return m.current; - const K = Ou(R), G = y.current; - if ((!K || K.size === 0) && !G) return m.current; - const X = R.getEditorState(), _e = X.toJSON(); - return X.read( - () => GA( - _e, - K ?? /* @__PURE__ */ new Set(), - { viewOptions: W, getMarker: zt, logger: We }, - G, + Ls.initialize(We); + function Tr(q) { + if (q !== void 0 && !vM(q, Se.extraValidMarkers)) + throw new Error(`Unsupported character marker '${q}'`); + } + const In = ge(() => { + const q = u.current; + if (!q) return g.current; + const V = Uu(q), J = y.current; + if ((!V || V.size === 0) && !J) return g.current; + const Z = q.getEditorState(), Me = Z.toJSON(); + return Z.read( + () => y1( + Me, + V ?? /* @__PURE__ */ new Set(), + { viewOptions: ee, getMarker: wt, logger: We }, + J, k.current ) - ) ?? m.current; - }, [W, zt, We]), xi = { + ) ?? g.current; + }, [ee, wt, We]), vi = { focus() { - d.current?.focus(); + u.current?.focus(); }, isFocused() { - const R = d.current?.getRootElement(); - return !!R && R.ownerDocument.activeElement === R; + const q = u.current?.getRootElement(); + return !!q && q.ownerDocument.activeElement === q; }, undo() { - d.current?.dispatchCommand(Mf, void 0); + u.current?.dispatchCommand(Df, void 0); }, redo() { - d.current?.dispatchCommand(Ef, void 0); + u.current?.dispatchCommand(Uf, void 0); }, // Both leave the clipboard untouched when nothing is selected, rather than writing a // placeholder over it — `ClipboardPlugin`'s guard claims the command (shared-react's // `registerEmptyCopyGuard`). Going through `copySelection`/`cutSelection` rather than // dispatching here keeps that one seam named. cut() { - qr("cut"), d.current && pl(d.current); + mt("cut"), u.current && kl(u.current); }, copy() { - d.current && fl(d.current); + u.current && bl(u.current); }, paste() { - qr("paste"), d.current && hl(d.current); + mt("paste"), u.current && Tl(u.current); }, pastePlainText() { - qr("paste as plain text"), d.current && gl(d.current); + mt("paste as plain text"), u.current && xl(u.current); }, getUsj() { - return tn(); + return In(); }, commitPendingMarkerEdits() { - d.current?.update( + u.current?.update( () => { - d.current?.dispatchCommand(mm, void 0); + u.current?.dispatchCommand(Pm, void 0); }, { discrete: !0 } ); }, - setTransientInput(R) { - if (!R) { + setTransientInput(q) { + if (!q) { y.current = void 0; return; } - const K = d.current?.getEditorState().read(() => { - const G = w(); - return N(G) && G.isCollapsed() ? G.focus.key : void 0; + const V = u.current?.getEditorState().read(() => { + const J = w(); + return P(J) && J.isCollapsed() ? J.focus.key : void 0; }); - y.current = { input: R, nodeKey: K ?? k.current?.key }; + y.current = { input: q, nodeKey: V ?? k.current?.key }; }, - setUsj(R) { - if (!wt(m.current, R)) { - m.current = R, y.current = void 0; - const K = wt(P, R); - A(R), K && M((G) => G + 1); + setUsj(q) { + if (!qt(g.current, q)) { + g.current = q, y.current = void 0; + const V = qt(M, q); + E(q), V && W((J) => J + 1); } }, - applyUpdate(R, K = "remote") { - if (ct && K === "remote") { - gt.current?.error( + applyUpdate(q, V = "remote") { + if (ue && V === "remote") { + on.current?.error( "Editor: ignoring a remote update in the block verse layout; reload the view with the new USJ instead." ); return; } - wr("apply an update"), d.current?.update( + Ci("apply an update"), u.current?.update( () => { - K === "remote" && jr(Vi), R_(R, W, xe, We); + V === "remote" && Gr(Ji), Y_(q, ee, Se, We); }, { discrete: !0 } ); - const G = d.current?.getEditorState(); - if (!G) return; - const X = Os.deserializeEditorState(G, W); - if (X) { - const _e = !wt(m.current, X); - if (_e && (m.current = X), _e || !wt(P, X)) { - const lt = ju(R, G, "apply"); - _.current = X, s?.(X, R, K, lt); + const J = u.current?.getEditorState(); + if (!J) return; + const Z = Ls.deserializeEditorState(J, ee); + if (Z) { + const Me = !qt(g.current, Z); + if (Me && (g.current = Z), Me || !qt(M, Z)) { + const Xe = Xu(q, J, "apply"); + _.current = Z, s?.(Z, q, V, Xe); } } }, - replaceEmbedUpdate(R, K) { - const G = d.current?.read(() => xx(R, K)); - G ? this.applyUpdate(G) : c?.warn( - `replaceEmbedUpdate: no embed found for key "${R}" — update dropped (stale key after a setUsj reload?)` + replaceEmbedUpdate(q, V) { + const J = u.current?.read(() => Ix(q, V)); + J ? this.applyUpdate(J) : c?.warn( + `replaceEmbedUpdate: no embed found for key "${q}" — update dropped (stale key after a setUsj reload?)` ); }, getSelection() { - if (ct) { - ve("get the selection"); + if (ue) { + kr("get the selection"); return; } - return d.current?.read(tl); + return u.current?.read(al); }, - setSelection(R) { - if (ct) { - ve("set the selection"); + setSelection(q) { + if (ue) { + kr("set the selection"); return; } - d.current?.update(() => { - const K = Mo(R); - K !== void 0 && (ei(K), jr(Uf)); + u.current?.update(() => { + const V = No(q); + V !== void 0 && (Tn(V), Gr(Xf)); }); }, - setAnnotation(R, K, G, X, _e) { - if (ct) { - ve("set an annotation"); + setAnnotation(q, V, J, Z, Me) { + if (ue) { + kr("set an annotation"); return; } - let lt, Ot, rn, nn; - typeof X == "function" || X === void 0 ? (lt = X, Ot = _e) : (lt = X.onClick, Ot = X.onRemove, rn = X.onMouseEnter, nn = X.onMouseLeave), f.current?.setAnnotation( - R, - ku(K), - G, - lt, - Ot, - rn, - nn + let Xe, nr, an, Si; + typeof Z == "function" || Z === void 0 ? (Xe = Z, nr = Me) : (Xe = Z.onClick, nr = Z.onRemove, an = Z.onMouseEnter, Si = Z.onMouseLeave), p.current?.setAnnotation( + q, + Eu(V), + J, + Xe, + nr, + an, + Si ); }, - removeAnnotation(R, K) { - f.current?.removeAnnotation(ku(R), K); + removeAnnotation(q, V) { + p.current?.removeAnnotation(Eu(q), V); }, - formatPara(R) { - qr("format a paragraph"), d.current?.update( + formatPara(q) { + mt("format a paragraph"), u.current?.update( () => { - const K = w(); - if (!N(K)) { + const V = w(); + if (!P(V)) { c?.warn( - `formatPara refused: no range selection to retag with "${R}" (restore the caret before applying, as the marker palettes do)` + `formatPara refused: no range selection to retag with "${q}" (restore the caret before applying, as the marker palettes do)` ); return; } - jy(K, () => Ji(R)); - const G = w(); - if (!N(G)) return; - const X = /* @__PURE__ */ new Set(); - G.getNodes().forEach((_e) => { - const lt = _e.getTopLevelElement(); - ae(lt) && X.add(lt); - }), X.forEach((_e) => Hg(_e, R, W)); + sb(V, () => Zi(q)); + const J = w(); + if (!P(J)) return; + const Z = /* @__PURE__ */ new Set(); + J.getNodes().forEach((Me) => { + const Xe = Me.getTopLevelElement(); + le(Xe) && Z.add(Xe); + }), Z.forEach((Me) => gm(Me, q, ee)); }, { discrete: !0 } ); }, - getElementByKey(R) { - return d.current?.read( - () => d.current?.getElementByKey(R) ?? void 0 + getElementByKey(q) { + return u.current?.read( + () => u.current?.getElementByKey(q) ?? void 0 ); }, - removeCharacterMarker(R) { - if (ue) throw new Error("Cannot remove character marker in readonly mode"); - Rr(R); - let K = !1; - return d.current?.update( + removeCharacterMarker(q) { + if (gt) throw new Error("Cannot remove character marker in readonly mode"); + Tr(q); + let V = !1; + return u.current?.update( () => { - const G = w(); - N(G) && (K = mg(G, R, W)); + const J = w(); + P(J) && (V = Pg(J, q, ee)); }, { discrete: !0 } - ), K; + ), V; }, - replaceCharacterMarker(R, K) { - if (ue) throw new Error("Cannot replace character marker in readonly mode"); - Rr(R), Rr(K); - let G = !1; - return d.current?.update( + replaceCharacterMarker(q, V) { + if (gt) throw new Error("Cannot replace character marker in readonly mode"); + Tr(q), Tr(V); + let J = !1; + return u.current?.update( () => { - const X = w(); - N(X) && (G = TM(X, R, K)); + const Z = w(); + P(Z) && (J = LM(Z, q, V)); }, { discrete: !0 } - ), G; + ), J; }, - extendCharacterMarker(R, K) { - if (ue) throw new Error("Cannot extend character marker in readonly mode"); - Rr(R), K?.forEach( - (X) => Rr(X) + extendCharacterMarker(q, V) { + if (gt) throw new Error("Cannot extend character marker in readonly mode"); + Tr(q), V?.forEach( + (Z) => Tr(Z) ); - let G = !1; - return d.current?.update( + let J = !1; + return u.current?.update( () => { - const X = w(); - N(X) && (G = xM( - X, - R, - K, - W + const Z = w(); + P(Z) && (J = IM( + Z, + q, + V, + ee )); }, { discrete: !0 } - ), G; + ), J; }, - insertMarker(R) { - if (ue) throw new Error("Cannot insert marker in readonly mode"); + insertMarker(q) { + if (gt) throw new Error("Cannot insert marker in readonly mode"); if (!r) throw new Error("Cannot insert marker without a scripture reference (scrRef)"); - if (!d.current) return; - if (!Za(R, xe.extraValidMarkers)) - throw new Error(`Unsupported marker '${R}'`); - const K = ec( - R, - g, - W, - xe, + if (!u.current) return; + if (!sc(q, Se.extraValidMarkers)) + throw new Error(`Unsupported marker '${q}'`); + const V = oc( + q, + h, + ee, + Se, We, void 0, - vt + Mt ); - return K.action({ editor: d.current, reference: r }), K.getInsertedNoteKey?.(); + return V.action({ editor: u.current, reference: r }), V.getInsertedNoteKey?.(); }, getMarkerMenuContext() { - if (!J) - return d.current?.getEditorState().read(() => NE()); + if (!N) + return u.current?.getEditorState().read(() => vA()); }, - applyMarkerMenuSelection(R, K) { - if (J) throw new Error("Cannot apply marker menu selection in readonly mode"); + applyMarkerMenuSelection(q, V) { + if (N) throw new Error("Cannot apply marker menu selection in readonly mode"); if (!r) throw new Error( "Cannot apply marker menu selection without a scripture reference (scrRef)" ); - if (!d.current) return; - if (R.kind !== "closeTag" && !Za(R.marker, xe.extraValidMarkers)) - throw new Error(`Unsupported marker '${R.marker}'`); - let G; - return d.current.update(() => { - G = $E(R, K, r, { - expandedNoteKeyRef: g, - viewOptions: W, - nodeOptions: xe, + if (!u.current) return; + if (q.kind !== "closeTag" && !sc(q.marker, Se.extraValidMarkers)) + throw new Error(`Unsupported marker '${q.marker}'`); + let J; + return u.current.update(() => { + J = PA(q, V, r, { + expandedNoteKeyRef: h, + viewOptions: ee, + nodeOptions: Se, logger: c, - styleInfo: vt + styleInfo: Mt }); - }), G; + }), J; }, - splitParagraphWithMarker(R) { - if (J) throw new Error("Cannot split paragraph in readonly mode"); - d.current && d.current.update(() => { - Zg(R, W); + splitParagraphWithMarker(q) { + if (N) throw new Error("Cannot split paragraph in readonly mode"); + u.current && u.current.update(() => { + xm(q, ee); }); }, - commitTypedMarker(R, K) { - if (J) throw new Error("Cannot commit a typed marker in readonly mode"); - if (!d.current) return !1; - let G = !1; - return d.current.update(() => { - G = RE(R, K), G || c?.warn( + commitTypedMarker(q, V) { + if (N) throw new Error("Cannot commit a typed marker in readonly mode"); + if (!u.current) return !1; + let J = !1; + return u.current.update(() => { + J = AA(q, V), J || c?.warn( "commitTypedMarker refused: requires a collapsed range selection (wrap a selection via applyMarkerMenuSelection instead)" ); - }), G; - }, - commitTypedCloser(R) { - if (J) throw new Error("Cannot commit a typed closing marker in readonly mode"); - if (!d.current) return !1; - let K = !1; - return d.current.update(() => { - K = Qg(R), K || c?.warn( + }), J; + }, + commitTypedCloser(q) { + if (N) throw new Error("Cannot commit a typed closing marker in readonly mode"); + if (!u.current) return !1; + let V = !1; + return u.current.update(() => { + V = Tm(q), V || c?.warn( "commitTypedCloser refused: requires a range selection to commit the closer at" ); - }), K; + }), V; }, - insertNote(R, K, G) { - qr("insert a note"), d.current?.update( + insertNote(q, V, J) { + mt("insert a note"), u.current?.update( () => { - const X = Th( - R, - K, - G, + const Z = wh( + q, + V, + J, r, - W, - xe, + ee, + Se, We ); - X && !X.getIsCollapsed() && (g.current = X.getKey()); + Z && !Z.getIsCollapsed() && (h.current = Z.getKey()); }, { discrete: !0 } ); }, - selectNote(R) { - d.current?.update(() => { - const K = Ju(R); - K && (i_(K, W), K.getIsCollapsed() || (g.current = K.getKey())); + selectNote(q) { + u.current?.update(() => { + const V = nd(q); + V && (T_(V, ee), V.getIsCollapsed() || (h.current = V.getKey())); }); }, - getNoteOps(R) { - return d.current?.read(() => { - const K = Ju(R); - if (K) - return Zc(K); + getNoteOps(q) { + return u.current?.read(() => { + const V = nd(q); + if (V) + return sl(V); }); }, get toolbarEndRef() { - return p; + return f; } }; - ye.current = xi, gc(u, () => xi), z(() => { - const R = d.current; - if (R) - return R.registerUpdateListener(({ editorState: K }) => { - K.read(() => { - const G = w(); - if (!N(G) || !G.isCollapsed()) return; - const X = G.focus.getNode(); - v(X) && (k.current = { key: X.getKey(), offset: G.focus.offset }); + br.current = vi, _c(d, () => vi), F(() => { + const q = u.current; + if (q) + return q.registerUpdateListener(({ editorState: V }) => { + V.read(() => { + const J = w(); + if (!P(J) || !J.isCollapsed()) return; + const Z = J.focus.getNode(); + S(Z) && (k.current = { key: Z.getKey(), offset: J.focus.offset }); }); }); }, []); - const ms = he( - (R, K, G, X) => { - if (ct) return; - const _e = Os.deserializeEditorState(R, W); - if (_e) { - const lt = !wt(m.current, _e); - if (lt && (m.current = _e), lt || !wt(P, _e)) { - const Ot = ju(X, R); - _.current = _e, s?.(_e, X, "local", Ot); + const rr = ge( + (q, V, J, Z) => { + if (ue) return; + const Me = Ls.deserializeEditorState(q, ee); + if (Me) { + const Xe = !qt(g.current, Me); + if (Xe && (g.current = Me), Xe || !qt(M, Me)) { + const nr = Xu(Z, q); + _.current = Me, s?.(Me, Z, "local", nr); } } }, - [P, s, W, ct] + [M, s, ee, ue] ); - z(() => { - const R = d.current; - if (!(!R || !s)) - return R.registerUpdateListener(({ tags: K, dirtyElements: G, dirtyLeaves: X }) => { - !K.has(Cc) && (G.size === 0 && X.size === 0 || K.has(Vi) || !Ou(R)?.size) || queueMicrotask(() => { - const _e = tn(); - !_e || wt(_.current, _e) || (_.current = _e, s(_e, void 0, "local", void 0)); + F(() => { + const q = u.current; + if (!(!q || !s)) + return q.registerUpdateListener(({ tags: V, dirtyElements: J, dirtyLeaves: Z }) => { + !V.has(Pc) && (J.size === 0 && Z.size === 0 || V.has(Ji) || !Uu(q)?.size) || queueMicrotask(() => { + const Me = In(); + !Me || qt(_.current, Me) || (_.current = Me, s(Me, void 0, "local", void 0)); }); }); - }, [s, tn]); - const Kt = he( - (R) => { - $(R.contextMarker), o?.(R); + }, [s, In]); + const Kt = ge( + (q) => { + U(q.contextMarker), o?.(q); }, [o] ); return ( // A Lexical editor's node types are fixed when it is created, so switching layouts has to // recreate it. The key never changes for the inline layouts, which leave `verseLayout` unset. - /* @__PURE__ */ Te(Nf, { initialConfig: rr, children: [ - /* @__PURE__ */ C(DC, { isEditable: !ue }), - /* @__PURE__ */ Te("div", { className: "editor-container", children: [ - Me ? /* @__PURE__ */ C(Bh, { onStateChange: Kt }) : /* @__PURE__ */ C( + /* @__PURE__ */ xe(Kf, { initialConfig: xs, children: [ + /* @__PURE__ */ v(ev, { isEditable: !gt }), + /* @__PURE__ */ xe("div", { className: "editor-container", children: [ + re ? /* @__PURE__ */ v(ng, { onStateChange: Kt }) : /* @__PURE__ */ v( "div", { - className: "editor-toolbar-container" + (ue ? "-readonly" : "-editable"), - children: /* @__PURE__ */ C( - y1, + className: "editor-toolbar-container" + (gt ? "-readonly" : "-editable"), + children: /* @__PURE__ */ v( + F1, { - ref: p, - editorRef: ye, - isReadonly: ue, + ref: f, + editorRef: br, + isReadonly: gt, onStateChange: Kt } ) } ), - /* @__PURE__ */ Te("div", { className: "editor-inner", children: [ - /* @__PURE__ */ C(wf, { editorRef: d }), - /* @__PURE__ */ C( - Ky, + /* @__PURE__ */ xe("div", { className: "editor-inner", children: [ + /* @__PURE__ */ v(Bf, { editorRef: u }), + /* @__PURE__ */ v( + ib, { - contentEditable: /* @__PURE__ */ C( - Of, + contentEditable: /* @__PURE__ */ v( + jf, { - className: `editor-input usfm ${q_(W).join(" ")}${W.hasGutterParaMarkers ? " psc-gutter-markers" : ""}${W.hasActiveTextFocusBox ? " psc-active-focus" : ""}`, - spellCheck: re + className: `editor-input usfm ${J_(ee).join(" ")}${ee.hasGutterParaMarkers ? " psc-gutter-markers" : ""}${ee.hasActiveTextFocusBox ? " psc-active-focus" : ""}`, + spellCheck: X } ), - placeholder: /* @__PURE__ */ C(x1, {}), - ErrorBoundary: qf + placeholder: /* @__PURE__ */ v(B1, {}), + ErrorBoundary: Vf } ), - Me && /* @__PURE__ */ C(LC, {}), - /* @__PURE__ */ C(Rf, {}), - r && n && /* @__PURE__ */ C(XA, { scrRef: r, onScrRefChange: n }), - r && !Me && /* @__PURE__ */ C( - ov, + re && /* @__PURE__ */ v(ZC, {}), + /* @__PURE__ */ v(Wf, {}), + r && n && /* @__PURE__ */ v(T1, { scrRef: r, onScrRefChange: n }), + r && !re && /* @__PURE__ */ v( + _S, { - trigger: be, + trigger: ke, scrRef: r, - contextMarker: q, - getMarkerAction: (R) => ec( - R, - g, - W, - xe, + contextMarker: j, + getMarkerAction: (q) => oc( + q, + h, + ee, + Se, We, void 0, - vt + Mt ), - editableHarness: Ti + editableHarness: Oe } ), - /* @__PURE__ */ C( - zC, + /* @__PURE__ */ v( + nv, { - scripture: P, - scriptureRef: m, - nodeOptions: xe, - editorAdaptor: Cr, - viewOptions: W, + scripture: M, + scriptureRef: g, + nodeOptions: Se, + editorAdaptor: Ar, + viewOptions: ee, logger: We }, - B + z ), - /* @__PURE__ */ C(aS, { onChange: i }), - /* @__PURE__ */ C( - E_, + /* @__PURE__ */ v(Cv, { onChange: i }), + /* @__PURE__ */ v( + j_, { - onChange: ms, + onChange: rr, ignoreSelectionChange: !0, ignoreHistoryMergeTagChange: !0, - ignoreTags: ub + ignoreTags: Mb } ), - /* @__PURE__ */ C(MM, { viewOptions: W }), - /* @__PURE__ */ C(v_, { ref: f, logger: We }), - /* @__PURE__ */ C(oC, { viewOptions: W }), - /* @__PURE__ */ C(TC, {}), - /* @__PURE__ */ C(MC, {}), - W?.markerMode !== "editable" && /* @__PURE__ */ C(EC, { logger: We }), - /* @__PURE__ */ C(OC, { options: ht }), - /* @__PURE__ */ C(IC, {}), - /* @__PURE__ */ C(FC, {}), - /* @__PURE__ */ C(IE, {}), - /* @__PURE__ */ C( - NA, + /* @__PURE__ */ v(KM, { viewOptions: ee }), + /* @__PURE__ */ v(z_, { ref: p, logger: We }), + /* @__PURE__ */ v(_C, { viewOptions: ee }), + /* @__PURE__ */ v(LC, {}), + /* @__PURE__ */ v(KC, {}), + ee?.markerMode !== "editable" && /* @__PURE__ */ v(jC, { logger: We }), + /* @__PURE__ */ v(HC, { options: yr }), + /* @__PURE__ */ v(NE, { limit: A }), + /* @__PURE__ */ v(QC, {}), + /* @__PURE__ */ v(rv, {}), + /* @__PURE__ */ v(NA, {}), + /* @__PURE__ */ v( + e1, { - viewOptions: W, - getMarker: zt, + viewOptions: ee, + getMarker: wt, logger: We, - markerSettleDelayMs: te, - structureProtectionMode: Q + markerSettleDelayMs: ne, + structureProtectionMode: B, + copyLimit: A } ), - W?.markerMode === "visible" && /* @__PURE__ */ C(DA, { viewOptions: W }), - /* @__PURE__ */ C( - IA, + /* @__PURE__ */ v(a1, { viewOptions: ee, copyLimit: A }), + /* @__PURE__ */ v( + o1, { - styleInfo: vt, - viewOptions: W, + styleInfo: Mt, + viewOptions: ee, logger: We } ), - /* @__PURE__ */ C( - KC, + /* @__PURE__ */ v( + iv, { - expandedNoteKeyRef: g, - nodeOptions: xe, - viewOptions: W, + expandedNoteKeyRef: h, + nodeOptions: Se, + viewOptions: ee, logger: We } ), - /* @__PURE__ */ C(oS, {}), - /* @__PURE__ */ C(tC, {}), - /* @__PURE__ */ C(X_, {}), - /* @__PURE__ */ C(JA, { viewOptions: W, logger: We }), - /* @__PURE__ */ C(cS, {}), - /* @__PURE__ */ C(HS, { structureProtectionMode: Q }), - /* @__PURE__ */ C(GS, { textDirection: qe }), - /* @__PURE__ */ C(YS, {}), - /* @__PURE__ */ C(iv, {}), + /* @__PURE__ */ v(_v, {}), + /* @__PURE__ */ v(yC, {}), + /* @__PURE__ */ v(pC, {}), + /* @__PURE__ */ v(b1, { viewOptions: ee, logger: We }), + /* @__PURE__ */ v(vv, {}), + /* @__PURE__ */ v(lS, { structureProtectionMode: B }), + /* @__PURE__ */ v(uS, { textDirection: ye }), + /* @__PURE__ */ v(fS, {}), + /* @__PURE__ */ v(TS, {}), l ] }), - en && /* @__PURE__ */ C(l1, {}) + sn && /* @__PURE__ */ v(w1, {}) ] }) - ] }, W.verseLayout ?? "inline") + ] }, ee.verseLayout ?? "inline") ); -}), PP = En(function(t, r) { +}), ZP = Nn(function(t, r) { const { children: n, ...i } = t; - return /* @__PURE__ */ C(Pm, { ref: r, ...i }); + return /* @__PURE__ */ v(Km, { ref: r, ...i }); }); -function Nm() { +function jm() { return Math.random().toString(36).replace(/[^a-z]+/g, "").substr(0, 5); } -function oo(e, t, r, n, i) { +function fo(e, t, r, n, i) { return { author: t, content: e, deleted: i === void 0 ? !1 : i, - id: r === void 0 ? Nm() : r, + id: r === void 0 ? jm() : r, timeStamp: n === void 0 ? performance.timeOrigin + performance.now() : n, type: "comment" }; } -function Om(e, t, r) { +function Bm(e, t, r) { return { comments: t, - id: r === void 0 ? Nm() : r, + id: r === void 0 ? jm() : r, quote: e, type: "thread" }; } -function uf(e) { +function _f(e) { return { comments: Array.from(e.comments), id: e.id, @@ -26135,7 +26417,7 @@ function uf(e) { type: "thread" }; } -function _1(e) { +function V1(e) { return { author: e.author, content: "[Deleted Comment]", @@ -26145,12 +26427,12 @@ function _1(e) { type: "comment" }; } -function xa(e) { +function Ea(e) { const t = e._changeListeners; for (const r of t) r(); } -class C1 { +class W1 { _editor; _comments; _changeListeners; @@ -26187,7 +26469,7 @@ class C1 { * @param comments - The new Comments array. */ setComments(t) { - this._comments = t, xa(this); + this._comments = t, Ea(this); } /** * Adds a comment or thread to the store. @@ -26202,14 +26484,14 @@ class C1 { for (let o = 0; o < i.length; o++) { const a = i[o]; if (a.type === "thread" && a.id === r.id) { - const c = uf(a); + const c = _f(a); i.splice(o, 1, c); const l = n !== void 0 ? n : c.comments.length; if (this.isCollaborative() && s !== null) { - const u = s.get(o).get("comments"); + const d = s.get(o).get("comments"); this._withRemoteTransaction(() => { - const d = this._createCollabSharedMap(t); - u.insert(l, [d]); + const u = this._createCollabSharedMap(t); + d.insert(l, [u]); }); } c.comments.splice(l, 0, t); @@ -26223,7 +26505,7 @@ class C1 { s.insert(o, [a]); }), i.splice(o, 0, t); } - this._comments = i, xa(this); + this._comments = i, Ea(this); } /** * Deletes a comment or thread from the store. @@ -26239,13 +26521,13 @@ class C1 { for (let o = 0; o < n.length; o++) { const a = n[o]; if (a.type === "thread" && a.id === r.id) { - const c = uf(a); + const c = _f(a); n.splice(o, 1, c); const l = c.comments; if (s = l.indexOf(t), this.isCollaborative() && i !== null) { - const u = i.get(o).get("comments"), d = s; + const d = i.get(o).get("comments"), u = s; this._withRemoteTransaction(() => { - u.delete(d); + d.delete(u); }); } l.splice(s, 1); @@ -26256,9 +26538,9 @@ class C1 { s = n.indexOf(t), this.isCollaborative() && i !== null && this._withRemoteTransaction(() => { i.delete(s); }), n.splice(s, 1); - return this._comments = n, xa(this), t.type === "comment" ? { + return this._comments = n, Ea(this), t.type === "comment" ? { index: s, - markedComment: _1(t) + markedComment: V1(t) } : null; } /** @@ -26288,16 +26570,16 @@ class C1 { // eslint-disable-next-line @typescript-eslint/no-explicit-any _getCollabComments() { const t = this._collabProvider; - return t !== null ? t.doc.get("comments", ou) : null; + return t !== null ? t.doc.get("comments", hu) : null; } // eslint-disable-next-line @typescript-eslint/no-explicit-any _createCollabSharedMap(t) { - const r = new au(), n = t.type, i = t.id; + const r = new gu(), n = t.type, i = t.id; if (r.set("type", n), r.set("id", i), n === "comment") r.set("author", t.author), r.set("content", t.content), r.set("deleted", t.deleted), r.set("timeStamp", t.timeStamp); else { r.set("quote", t.quote); - const s = new ou(); + const s = new hu(); t.comments.forEach((o, a) => { const c = this._createCollabSharedMap(o); s.insert(a, [c]); @@ -26321,51 +26603,51 @@ class C1 { } catch { } }, s = this._editor.registerCommand( - ib, + Tb, (a) => (n !== void 0 && i !== void 0 && (a ? (this.logger?.info("Comments connected!"), n()) : (this.logger?.info("Comments disconnected!"), i())), !1), - Tt + xt ), o = (a, c) => { if (c.origin !== this) { for (const l of a) - if (l instanceof sb) { - const u = l.target, d = l.delta; - let f = 0; - for (const p of d) { - const m = p.insert, g = p.retain, y = p.delete, k = u.parent, _ = u === r ? void 0 : k instanceof au && this._comments.find((S) => S.id === k.get("id")); - if (Array.isArray(m)) { - const S = f; - m.slice().reverse().forEach((P) => { - const A = P.get("id"), M = P.get("type") === "thread" ? Om( - P.get("quote"), - P.get("comments").toArray().map( - (q) => oo( - q.get("content"), - q.get("author"), - q.get("id"), - q.get("timeStamp"), - q.get("deleted") + if (l instanceof xb) { + const d = l.target, u = l.delta; + let p = 0; + for (const f of u) { + const g = f.insert, h = f.retain, y = f.delete, k = d.parent, _ = d === r ? void 0 : k instanceof gu && this._comments.find((C) => C.id === k.get("id")); + if (Array.isArray(g)) { + const C = p; + g.slice().reverse().forEach((M) => { + const E = M.get("id"), W = M.get("type") === "thread" ? Bm( + M.get("quote"), + M.get("comments").toArray().map( + (j) => fo( + j.get("content"), + j.get("author"), + j.get("id"), + j.get("timeStamp"), + j.get("deleted") ) ), - A - ) : oo( - P.get("content"), - P.get("author"), - A, - P.get("timeStamp"), - P.get("deleted") + E + ) : fo( + M.get("content"), + M.get("author"), + E, + M.get("timeStamp"), + M.get("deleted") ); this._withLocalTransaction(() => { - this.addComment(M, _, S); + this.addComment(W, _, C); }); }); - } else if (typeof g == "number") - f += g; + } else if (typeof h == "number") + p += h; else if (typeof y == "number") - for (let S = 0; S < y; S++) { - const P = _ === void 0 || _ === !1 ? this._comments[f] : _.comments[f]; + for (let C = 0; C < y; C++) { + const M = _ === void 0 || _ === !1 ? this._comments[p] : _.comments[p]; this._withLocalTransaction(() => { - this.deleteCommentOrThread(P, _); - }), f++; + this.deleteCommentOrThread(M, _); + }), p++; } } } @@ -26376,35 +26658,35 @@ class C1 { }); } } -function S1(e) { - const [t, r] = de(e.getComments()); - return z(() => e.registerOnChange(() => { +function H1(e) { + const [t, r] = fe(e.getComments()); + return F(() => e.registerOnChange(() => { r(e.getComments()); }), [e]), t; } -function v1({ +function G1({ onClose: e, children: t, title: r, closeOnClickOutside: n }) { - const i = Z(null); - return z(() => { + const i = Q(null); + return F(() => { i.current !== null && i.current.focus(); - }, []), z(() => { + }, []), F(() => { let s = null; const o = (l) => { l.key === "Escape" && e(); }, a = (l) => { - const u = l.target; - i.current !== null && !i.current.contains(u) && n && e(); + const d = l.target; + i.current !== null && !i.current.contains(d) && n && e(); }, c = i.current; return c !== null && (s = c.parentElement, s !== null && s.addEventListener("click", a)), window.addEventListener("keydown", o), () => { window.removeEventListener("keydown", o), s !== null && s?.removeEventListener("click", a); }; - }, [n, e]), /* @__PURE__ */ C("div", { className: "Modal__overlay", role: "dialog", children: /* @__PURE__ */ Te("div", { className: "Modal__modal", tabIndex: -1, ref: i, children: [ - /* @__PURE__ */ C("h2", { className: "Modal__title", children: r }), - /* @__PURE__ */ C( + }, [n, e]), /* @__PURE__ */ v("div", { className: "Modal__overlay", role: "dialog", children: /* @__PURE__ */ xe("div", { className: "Modal__modal", tabIndex: -1, ref: i, children: [ + /* @__PURE__ */ v("h2", { className: "Modal__title", children: r }), + /* @__PURE__ */ v( "button", { className: "Modal__closeButton", @@ -26414,29 +26696,29 @@ function v1({ children: "X" } ), - /* @__PURE__ */ C("div", { className: "Modal__content", children: t }) + /* @__PURE__ */ v("div", { className: "Modal__content", children: t }) ] }) }); } -function M1({ +function J1({ onClose: e, children: t, title: r, closeOnClickOutside: n = !1 }) { - return hn( - /* @__PURE__ */ C(v1, { onClose: e, title: r, closeOnClickOutside: n, children: t }), + return yn( + /* @__PURE__ */ v(G1, { onClose: e, title: r, closeOnClickOutside: n, children: t }), document.body ); } -function wm() { - const [e, t] = de(null), r = he(() => { +function Vm() { + const [e, t] = fe(null), r = ge(() => { t(null); - }, []), n = ze(() => { + }, []), n = je(() => { if (e === null) return null; const { title: s, content: o, closeOnClickOutside: a } = e; - return /* @__PURE__ */ C(M1, { onClose: r, title: s, closeOnClickOutside: a, children: o }); - }, [e, r]), i = he( + return /* @__PURE__ */ v(J1, { onClose: r, title: s, closeOnClickOutside: a, children: o }); + }, [e, r]), i = ge( (s, o, a = !1) => { t({ closeOnClickOutside: a, @@ -26448,14 +26730,14 @@ function wm() { ); return [n, i]; } -const E1 = { - ..._g, +const Y1 = { + ...$g, paragraph: "CommentEditorTheme__paragraph" }; -function A1(...e) { +function X1(...e) { return e.filter(Boolean).join(" "); } -function Yr({ +function tn({ "data-test-id": e, children: t, className: r, @@ -26464,11 +26746,11 @@ function Yr({ small: s, title: o }) { - return /* @__PURE__ */ C( + return /* @__PURE__ */ v( "button", { disabled: i, - className: A1( + className: X1( "Button__root", i && "Button__disabled", s && "Button__small", @@ -26482,46 +26764,46 @@ function Yr({ } ); } -function P1({ +function Q1({ className: e }) { - return /* @__PURE__ */ C(Of, { className: e || "ContentEditable__root" }); + return /* @__PURE__ */ v(jf, { className: e || "ContentEditable__root" }); } -function N1({ +function Z1({ children: e, className: t }) { - return /* @__PURE__ */ C("div", { className: t || "Placeholder__root", children: e }); + return /* @__PURE__ */ v("div", { className: t || "Placeholder__root", children: e }); } -const df = vf("INSERT_INLINE_COMMAND"); -function O1({ +const Cf = If("INSERT_INLINE_COMMAND"); +function eP({ anchorKey: e, editor: t, showComments: r, onAddComment: n }) { - const i = Z(null), s = he(() => { + const i = Q(null), s = ge(() => { const o = i.current, a = t.getRootElement(), c = t.getElementByKey(e); if (o !== null && a !== null && c !== null) { - const { right: l } = a.getBoundingClientRect(), { top: u } = c.getBoundingClientRect(); - o.style.left = `${l - 20}px`, o.style.top = `${u - 30}px`; + const { right: l } = a.getBoundingClientRect(), { top: d } = c.getBoundingClientRect(); + o.style.left = `${l - 20}px`, o.style.top = `${d - 30}px`; } }, [e, t]); - return z(() => (window.addEventListener("resize", s), () => { + return F(() => (window.addEventListener("resize", s), () => { window.removeEventListener("resize", s); - }), [t, s]), cs(() => { + }), [t, s]), ps(() => { s(); - }, [e, t, r, s]), /* @__PURE__ */ C("div", { className: "CommentPlugin_AddCommentBox", ref: i, children: /* @__PURE__ */ C("button", { className: "CommentPlugin_AddCommentBox_button", onClick: n, children: /* @__PURE__ */ C("i", { className: "icon add-comment" }) }) }); + }, [e, t, r, s]), /* @__PURE__ */ v("div", { className: "CommentPlugin_AddCommentBox", ref: i, children: /* @__PURE__ */ v("button", { className: "CommentPlugin_AddCommentBox_button", onClick: n, children: /* @__PURE__ */ v("i", { className: "icon add-comment" }) }) }); } -function w1({ onEscape: e }) { +function tP({ onEscape: e }) { const [t] = ce(); - return z(() => t.registerCommand( - Sf, + return F(() => t.registerCommand( + Lf, (r) => e(r), - Jn + Wr ), [t, e]), null; } -function qm({ +function Wm({ className: e, autoFocus: t, onEscape: r, @@ -26529,122 +26811,122 @@ function qm({ editorRef: i, placeholder: s = "Type a comment..." }) { - return /* @__PURE__ */ C(Nf, { initialConfig: { + return /* @__PURE__ */ v(Kf, { initialConfig: { namespace: "Commenting", nodes: [], onError: (a) => { throw a; }, - theme: E1 - }, children: /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox_EditorContainer", children: [ - /* @__PURE__ */ C( - tb, + theme: Y1 + }, children: /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentInputBox_EditorContainer", children: [ + /* @__PURE__ */ v( + yb, { - contentEditable: /* @__PURE__ */ C(P1, { className: e }), - placeholder: /* @__PURE__ */ C(N1, { children: s }), - ErrorBoundary: qf + contentEditable: /* @__PURE__ */ v(Q1, { className: e }), + placeholder: /* @__PURE__ */ v(Z1, { children: s }), + ErrorBoundary: Vf } ), - /* @__PURE__ */ C(eb, { onChange: n }), - /* @__PURE__ */ C(Rf, {}), - t !== !1 && /* @__PURE__ */ C(Xy, {}), - /* @__PURE__ */ C(w1, { onEscape: r }), - /* @__PURE__ */ C(Qy, {}), - i !== void 0 && /* @__PURE__ */ C(wf, { editorRef: i }) + /* @__PURE__ */ v(mb, { onChange: n }), + /* @__PURE__ */ v(Wf, {}), + t !== !1 && /* @__PURE__ */ v(pb, {}), + /* @__PURE__ */ v(tP, { onEscape: r }), + /* @__PURE__ */ v(hb, {}), + i !== void 0 && /* @__PURE__ */ v(Bf, { editorRef: i }) ] }) }); } -function Rm(e, t) { - return he( +function Hm(e, t) { + return ge( (r, n) => { r.read(() => { - e(rb()), t(!nb(n.isComposing(), !0)); + e(bb()), t(!kb(n.isComposing(), !0)); }); }, [t, e] ); } -function q1({ +function rP({ editor: e, cancelAddComment: t, submitAddComment: r }) { - const [n, i] = de(""), [s, o] = de(!1), a = Z(null), c = ze( + const [n, i] = fe(""), [s, o] = fe(!1), a = Q(null), c = je( () => ({ container: document.createElement("div"), elements: [] }), [] - ), l = Z(null), u = Im(), d = he(() => { + ), l = Q(null), d = Jm(), u = ge(() => { e.getEditorState().read(() => { - const g = w(); - if (N(g)) { - l.current = g.clone(); - const y = g.anchor, k = g.focus, _ = By( + const h = w(); + if (P(h)) { + l.current = h.clone(); + const y = h.anchor, k = h.focus, _ = ob( e, y.getNode(), y.offset, k.getNode(), k.offset - ), S = a.current; - if (_ !== null && S !== null) { - const { left: P, bottom: A, width: B } = _.getBoundingClientRect(), M = Vy(e, _); - let q = M.length === 1 ? P + B / 2 - 125 : P - 125; - q < 10 && (q = 10), S.style.left = `${q}px`, S.style.top = `${A + 20 + (window.pageYOffset || document.documentElement.scrollTop)}px`; - const $ = M.length, { container: J } = c, Q = c.elements, Me = Q.length; - for (let re = 0; re < $; re++) { - const qe = M[re]; - let be = Q[re]; - be === void 0 && (be = document.createElement("span"), Q[re] = be, J.appendChild(be)); - const Re = `position:absolute;top:${qe.top + (window.pageYOffset || document.documentElement.scrollTop)}px;left:${qe.left}px;height:${qe.height}px;width:${qe.width}px;background-color:rgba(255, 212, 0, 0.3);pointer-events:none;z-index:5;`; - be.style.cssText = Re; + ), C = a.current; + if (_ !== null && C !== null) { + const { left: M, bottom: E, width: z } = _.getBoundingClientRect(), W = ab(e, _); + let j = W.length === 1 ? M + z / 2 - 125 : M - 125; + j < 10 && (j = 10), C.style.left = `${j}px`, C.style.top = `${E + 20 + (window.pageYOffset || document.documentElement.scrollTop)}px`; + const U = W.length, { container: N } = c, B = c.elements, re = B.length; + for (let X = 0; X < U; X++) { + const ye = W[X]; + let ke = B[X]; + ke === void 0 && (ke = document.createElement("span"), B[X] = ke, N.appendChild(ke)); + const $e = `position:absolute;top:${ye.top + (window.pageYOffset || document.documentElement.scrollTop)}px;left:${ye.left}px;height:${ye.height}px;width:${ye.width}px;background-color:rgba(255, 212, 0, 0.3);pointer-events:none;z-index:5;`; + ke.style.cssText = $e; } - for (let re = Me - 1; re >= $; re--) { - const qe = Q[re]; - J.removeChild(qe), Q.pop(); + for (let X = re - 1; X >= U; X--) { + const ye = B[X]; + N.removeChild(ye), B.pop(); } } } }); }, [e, c]); - cs(() => { - d(); - const g = c.container, y = document.body; - return y !== null ? (y.appendChild(g), () => { - y.removeChild(g); + ps(() => { + u(); + const h = c.container, y = document.body; + return y !== null ? (y.appendChild(h), () => { + y.removeChild(h); }) : () => { }; - }, [c.container, d]), z(() => (window.addEventListener("resize", d), () => { - window.removeEventListener("resize", d); - }), [d]); - const f = (g) => (g.preventDefault(), t(), !0), p = () => { + }, [c.container, u]), F(() => (window.addEventListener("resize", u), () => { + window.removeEventListener("resize", u); + }), [u]); + const p = (h) => (h.preventDefault(), t(), !0), f = () => { if (s) { - let g = e.getEditorState().read(() => { + let h = e.getEditorState().read(() => { const y = l.current; return y ? y.getTextContent() : ""; }); - g.length > 100 && (g = g.slice(0, 99) + "…"), r( - Om(g, [oo(n, u)]), + h.length > 100 && (h = h.slice(0, 99) + "…"), r( + Bm(h, [fo(n, d)]), !0, void 0, l.current ), l.current = null; } - }, m = Rm(i, o); - return /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox", ref: a, children: [ - /* @__PURE__ */ C( - qm, + }, g = Hm(i, o); + return /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentInputBox", ref: a, children: [ + /* @__PURE__ */ v( + Wm, { className: "CommentPlugin_CommentInputBox_Editor", - onEscape: f, - onChange: m + onEscape: p, + onChange: g } ), - /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox_Buttons", children: [ - /* @__PURE__ */ C(Yr, { onClick: t, className: "CommentPlugin_CommentInputBox_Button", children: "Cancel" }), - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentInputBox_Buttons", children: [ + /* @__PURE__ */ v(tn, { onClick: t, className: "CommentPlugin_CommentInputBox_Button", children: "Cancel" }), + /* @__PURE__ */ v( + tn, { - onClick: p, + onClick: f, disabled: !s, className: "CommentPlugin_CommentInputBox_Button primary", children: "Comment" @@ -26653,15 +26935,15 @@ function q1({ ] }) ] }); } -function R1({ +function nP({ submitAddComment: e, thread: t, placeholder: r }) { - const [n, i] = de(""), [s, o] = de(!1), a = Z(null), c = Im(), l = Rm(i, o); - return /* @__PURE__ */ Te(gn, { children: [ - /* @__PURE__ */ C( - qm, + const [n, i] = fe(""), [s, o] = fe(!1), a = Q(null), c = Jm(), l = Hm(i, o); + return /* @__PURE__ */ xe(bn, { children: [ + /* @__PURE__ */ v( + Wm, { className: "CommentPlugin_CommentsPanel_Editor", autoFocus: !1, @@ -26671,36 +26953,36 @@ function R1({ placeholder: r } ), - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ v( + tn, { className: "CommentPlugin_CommentsPanel_SendButton", onClick: () => { if (s) { - e(oo(n, c), !1, t); - const d = a.current; - d !== null && d.dispatchCommand(Iy, void 0); + e(fo(n, c), !1, t); + const u = a.current; + u !== null && u.dispatchCommand(Xy, void 0); } }, disabled: !s, - children: /* @__PURE__ */ C("i", { className: "send" }) + children: /* @__PURE__ */ v("i", { className: "send" }) } ) ] }); } -function $m({ +function Gm({ commentOrThread: e, deleteCommentOrThread: t, onClose: r, thread: n = void 0 }) { - return /* @__PURE__ */ Te(gn, { children: [ + return /* @__PURE__ */ xe(bn, { children: [ "Are you sure you want to delete this ", e.type, "?", - /* @__PURE__ */ Te("div", { className: "Modal__content", children: [ - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ xe("div", { className: "Modal__content", children: [ + /* @__PURE__ */ v( + tn, { onClick: () => { t(e, n), r(); @@ -26709,8 +26991,8 @@ function $m({ } ), " ", - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ v( + tn, { onClick: () => { r(); @@ -26721,57 +27003,57 @@ function $m({ ] }) ] }); } -function ff({ +function vf({ comment: e, deleteComment: t, thread: r, rtf: n }) { - const [i, s] = de(0); - z(() => { - const u = () => { + const [i, s] = fe(0); + F(() => { + const d = () => { s(performance.timeOrigin + performance.now()); }; - u(); - const d = window.setInterval(u, 6e4); + d(); + const u = window.setInterval(d, 6e4); return () => { - window.clearInterval(d); + window.clearInterval(u); }; }, []); - const o = Math.round((e.timeStamp - i) / 1e3), a = Math.round(o / 60), [c, l] = wm(); - return /* @__PURE__ */ Te("li", { className: "CommentPlugin_CommentsPanel_List_Comment", children: [ - /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentsPanel_List_Details", children: [ - /* @__PURE__ */ C("span", { className: "CommentPlugin_CommentsPanel_List_Comment_Author", children: e.author }), - /* @__PURE__ */ Te("span", { className: "CommentPlugin_CommentsPanel_List_Comment_Time", children: [ + const o = Math.round((e.timeStamp - i) / 1e3), a = Math.round(o / 60), [c, l] = Vm(); + return /* @__PURE__ */ xe("li", { className: "CommentPlugin_CommentsPanel_List_Comment", children: [ + /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentsPanel_List_Details", children: [ + /* @__PURE__ */ v("span", { className: "CommentPlugin_CommentsPanel_List_Comment_Author", children: e.author }), + /* @__PURE__ */ xe("span", { className: "CommentPlugin_CommentsPanel_List_Comment_Time", children: [ "· ", o > -10 ? "Just now" : n.format(a, "minute") ] }) ] }), - /* @__PURE__ */ C("p", { className: e.deleted ? "CommentPlugin_CommentsPanel_DeletedComment" : "", children: e.content }), - !e.deleted && /* @__PURE__ */ Te(gn, { children: [ - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ v("p", { className: e.deleted ? "CommentPlugin_CommentsPanel_DeletedComment" : "", children: e.content }), + !e.deleted && /* @__PURE__ */ xe(bn, { children: [ + /* @__PURE__ */ v( + tn, { onClick: () => { - l("Delete Comment", (u) => /* @__PURE__ */ C( - $m, + l("Delete Comment", (d) => /* @__PURE__ */ v( + Gm, { commentOrThread: e, deleteCommentOrThread: t, thread: r, - onClose: u + onClose: d } )); }, className: "CommentPlugin_CommentsPanel_List_DeleteButton", - children: /* @__PURE__ */ C("i", { className: "delete" }) + children: /* @__PURE__ */ v("i", { className: "delete" }) } ), c ] }) ] }); } -function $1({ +function iP({ activeIDs: e, comments: t, deleteCommentOrThread: r, @@ -26779,7 +27061,7 @@ function $1({ submitAddComment: i, markNodeMap: s }) { - const [o] = ce(), [a, c] = de(0), [l, u] = wm(), d = ze( + const [o] = ce(), [a, c] = fe(0), [l, d] = Vm(), u = je( () => new Intl.RelativeTimeFormat("en", { localeMatcher: "best fit", numeric: "auto", @@ -26787,25 +27069,25 @@ function $1({ }), [] ); - return z(() => { - const f = setTimeout(() => { + return F(() => { + const p = setTimeout(() => { c(a + 1); }, 1e4); return () => { - clearTimeout(f); + clearTimeout(p); }; - }, [a]), /* @__PURE__ */ C("ul", { className: "CommentPlugin_CommentsPanel_List", ref: n, children: t.map((f) => { - const p = f.id; - return f.type === "thread" ? /* @__PURE__ */ Te( + }, [a]), /* @__PURE__ */ v("ul", { className: "CommentPlugin_CommentsPanel_List", ref: n, children: t.map((p) => { + const f = p.id; + return p.type === "thread" ? /* @__PURE__ */ xe( "li", { onClick: () => { - const g = s.get(p); - if (g !== void 0 && (e === null || e.indexOf(p) === -1)) { + const h = s.get(f); + if (h !== void 0 && (e === null || e.indexOf(f) === -1)) { const y = document.activeElement; o.update( () => { - const k = Array.from(g)[0], _ = se(k); + const k = Array.from(h)[0], _ = oe(k); Ce(_) && _.selectStart(); }, { @@ -26816,76 +27098,76 @@ function $1({ ); } }, - className: `CommentPlugin_CommentsPanel_List_Thread ${s.has(p) ? "interactive" : ""} ${e.indexOf(p) === -1 ? "" : "active"}`, + className: `CommentPlugin_CommentsPanel_List_Thread ${s.has(f) ? "interactive" : ""} ${e.indexOf(f) === -1 ? "" : "active"}`, children: [ - /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentsPanel_List_Thread_QuoteBox", children: [ - /* @__PURE__ */ Te("blockquote", { className: "CommentPlugin_CommentsPanel_List_Thread_Quote", children: [ + /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentsPanel_List_Thread_QuoteBox", children: [ + /* @__PURE__ */ xe("blockquote", { className: "CommentPlugin_CommentsPanel_List_Thread_Quote", children: [ "> ", - /* @__PURE__ */ C("span", { children: f.quote }) + /* @__PURE__ */ v("span", { children: p.quote }) ] }), - /* @__PURE__ */ C( - Yr, + /* @__PURE__ */ v( + tn, { onClick: () => { - u("Delete Thread", (g) => /* @__PURE__ */ C( - $m, + d("Delete Thread", (h) => /* @__PURE__ */ v( + Gm, { - commentOrThread: f, + commentOrThread: p, deleteCommentOrThread: r, - onClose: g + onClose: h } )); }, className: "CommentPlugin_CommentsPanel_List_DeleteButton", - children: /* @__PURE__ */ C("i", { className: "delete" }) + children: /* @__PURE__ */ v("i", { className: "delete" }) } ), l ] }), - /* @__PURE__ */ C("ul", { className: "CommentPlugin_CommentsPanel_List_Thread_Comments", children: f.comments.map((g) => /* @__PURE__ */ C( - ff, + /* @__PURE__ */ v("ul", { className: "CommentPlugin_CommentsPanel_List_Thread_Comments", children: p.comments.map((h) => /* @__PURE__ */ v( + vf, { - comment: g, + comment: h, deleteComment: r, - thread: f, - rtf: d + thread: p, + rtf: u }, - g.id + h.id )) }), - /* @__PURE__ */ C("div", { className: "CommentPlugin_CommentsPanel_List_Thread_Editor", children: /* @__PURE__ */ C( - R1, + /* @__PURE__ */ v("div", { className: "CommentPlugin_CommentsPanel_List_Thread_Editor", children: /* @__PURE__ */ v( + nP, { submitAddComment: i, - thread: f, + thread: p, placeholder: "Reply to comment..." } ) }) ] }, - p - ) : /* @__PURE__ */ C( - ff, + f + ) : /* @__PURE__ */ v( + vf, { - comment: f, + comment: p, deleteComment: r, - rtf: d + rtf: u }, - p + f ); }) }); } -function I1({ +function sP({ activeIDs: e, deleteCommentOrThread: t, comments: r, submitAddComment: n, markNodeMap: i }) { - const s = Z(null), o = r.length === 0; - return /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentsPanel", children: [ - /* @__PURE__ */ C("h2", { className: "CommentPlugin_CommentsPanel_Heading", children: "Comments" }), - o ? /* @__PURE__ */ C("div", { className: "CommentPlugin_CommentsPanel_Empty", children: "No Comments" }) : /* @__PURE__ */ C( - $1, + const s = Q(null), o = r.length === 0; + return /* @__PURE__ */ xe("div", { className: "CommentPlugin_CommentsPanel", children: [ + /* @__PURE__ */ v("h2", { className: "CommentPlugin_CommentsPanel_Heading", children: "Comments" }), + o ? /* @__PURE__ */ v("div", { className: "CommentPlugin_CommentsPanel_Empty", children: "No Comments" }) : /* @__PURE__ */ v( + iP, { activeIDs: e, comments: r, @@ -26897,11 +27179,11 @@ function I1({ ) ] }); } -function Im() { - const e = $f(), { yjsDocMap: t, name: r } = e; +function Jm() { + const e = Hf(), { yjsDocMap: t, name: r } = e; return t.has("comments") ? r : "Scripture User"; } -function L1({ +function oP({ providerFactory: e, setCommentStore: t, onChange: r, @@ -26909,189 +27191,189 @@ function L1({ commentContainerRef: i, logger: s }) { - const o = $f(), [a] = ce(), c = ze(() => { - const q = new C1(a, s); - return r && q.registerOnChange(r), t?.(q), q; - }, [a, s, r, t]), l = S1(c), u = ze(() => /* @__PURE__ */ new Map(), []), [d, f] = de(), [p, m] = de([]), [g, y] = de(!1), [k, _] = de(!1), { yjsDocMap: S } = o; - z(() => { + const o = Hf(), [a] = ce(), c = je(() => { + const j = new W1(a, s); + return r && j.registerOnChange(r), t?.(j), j; + }, [a, s, r, t]), l = H1(c), d = je(() => /* @__PURE__ */ new Map(), []), [u, p] = fe(), [f, g] = fe([]), [h, y] = fe(!1), [k, _] = fe(!1), { yjsDocMap: C } = o; + F(() => { if (e) { - const q = e("comments", S); - return c.registerCollaboration(q); + const j = e("comments", C); + return c.registerCollaboration(j); } return () => { }; - }, [c, e, S]); - const P = he(() => { + }, [c, e, C]); + const M = ge(() => { a.update(() => { - const q = w(); - q !== null && (q.dirty = !0); + const j = w(); + j !== null && (j.dirty = !0); }), y(!1); - }, [a]), A = he( - (q, $) => { - if (q.type === "comment") { - const J = c.deleteCommentOrThread(q, $); - if (!J) + }, [a]), E = ge( + (j, U) => { + if (j.type === "comment") { + const N = c.deleteCommentOrThread(j, U); + if (!N) return; - const { markedComment: Q, index: Me } = J; - c.addComment(Q, $, Me); + const { markedComment: B, index: re } = N; + c.addComment(B, U, re); } else { - c.deleteCommentOrThread(q); - const J = $ !== void 0 ? $.id : q.id, Q = u.get(J); - Q !== void 0 && setTimeout(() => { + c.deleteCommentOrThread(j); + const N = U !== void 0 ? U.id : j.id, B = d.get(N); + B !== void 0 && setTimeout(() => { a.update(() => { - for (const Me of Q) { - const re = se(Me); - Ce(re) && (re.deleteID(Kr, J), re.hasNoIDsForEveryType() && Us(re)); + for (const re of B) { + const X = oe(re); + Ce(X) && (X.deleteID(Vr, N), X.hasNoIDsForEveryType() && js(X)); } }); }); } }, - [c, a, u] - ), B = he( - (q, $, J, Q) => { - c.addComment(q, J), $ && (a.update(() => { - N(Q) && Zf(Q, Kr, q.id); + [c, a, d] + ), z = ge( + (j, U, N, B) => { + c.addComment(j, N), U && (a.update(() => { + P(B) && dp(B, Vr, j.id); }), y(!1)); }, [c, a] ); - z(() => { - const q = []; - let $; - for (const J of p) { - const Q = u.get(J); - if (Q !== void 0) - for (const Me of Q) { - const re = a.getElementByKey(Me); - re !== null && (re.classList.add("selected"), q.push(re), $ = window.setTimeout(() => { + F(() => { + const j = []; + let U; + for (const N of f) { + const B = d.get(N); + if (B !== void 0) + for (const re of B) { + const X = a.getElementByKey(re); + X !== null && (X.classList.add("selected"), j.push(X), U = window.setTimeout(() => { _(!0); }, 0)); } } return () => { - $ !== void 0 && window.clearTimeout($); - for (const J of q) - J.classList.remove("selected"); + U !== void 0 && window.clearTimeout(U); + for (const N of j) + N.classList.remove("selected"); }; - }, [p, a, u]), z(() => { - if (!a.hasNodes([Qe])) + }, [f, a, d]), F(() => { + if (!a.hasNodes([et])) throw new Error("CommentPlugin: TypedMarkNode not registered on editor!"); - const q = /* @__PURE__ */ new Map(); - return Ve( - Pf( + const j = /* @__PURE__ */ new Map(); + return Fe( + zf( a, - Qe, - ($) => Hi($.getTypedIDs()), - ($, J) => { - for (const [Q, Me] of Object.entries($.getTypedIDs())) - Me.forEach((re) => { - J.addID(Q, re); + et, + (U) => Xi(U.getTypedIDs()), + (U, N) => { + for (const [B, re] of Object.entries(U.getTypedIDs())) + re.forEach((X) => { + N.addID(B, X); }); } ), a.registerMutationListener( - Qe, - ($) => { + et, + (U) => { a.getEditorState().read(() => { - for (const [J, Q] of $) { - const Me = se(J); - let re = []; - Q === "destroyed" ? re = q.get(J) ?? [] : Ce(Me) && (re = Me.getTypedIDs()[Kr] ?? []); - for (const qe of re) { - let be = u.get(qe); - q.set(J, re), Q === "destroyed" ? be !== void 0 && (be.delete(J), be.size === 0 && u.delete(qe)) : (be === void 0 && (be = /* @__PURE__ */ new Set(), u.set(qe, be)), be.has(J) || be.add(J)); + for (const [N, B] of U) { + const re = oe(N); + let X = []; + B === "destroyed" ? X = j.get(N) ?? [] : Ce(re) && (X = re.getTypedIDs()[Vr] ?? []); + for (const ye of X) { + let ke = d.get(ye); + j.set(N, X), B === "destroyed" ? ke !== void 0 && (ke.delete(N), ke.size === 0 && d.delete(ye)) : (ke === void 0 && (ke = /* @__PURE__ */ new Set(), d.set(ye, ke)), ke.has(N) || ke.add(N)); } } }); }, { skipInitialization: !1 } ), - a.registerUpdateListener(({ editorState: $, tags: J }) => { - $.read(() => { - const Q = w(); - let Me = !1, re = !1; - if (N(Q)) { - const qe = Q.anchor.getNode(); - if (v(qe)) { - const be = Wb(qe, Kr, Q.anchor.offset) ?? []; - be !== null && (m(be), Me = !0), Q.isCollapsed() || (f(qe.getKey()), re = !0); + a.registerUpdateListener(({ editorState: U, tags: N }) => { + U.read(() => { + const B = w(); + let re = !1, X = !1; + if (P(B)) { + const ye = B.anchor.getNode(); + if (S(ye)) { + const ke = ck(ye, Vr, B.anchor.offset) ?? []; + ke !== null && (g(ke), re = !0), B.isCollapsed() || (p(ye.getKey()), X = !0); } } - Me || m((qe) => qe.length === 0 ? qe : []), re || f(null), !J.has("collaboration") && N(Q) && y(!1); + re || g((ye) => ye.length === 0 ? ye : []), X || p(null), !N.has("collaboration") && P(B) && y(!1); }); }), a.registerCommand( - df, + Cf, () => { - const $ = window.getSelection(); - return $ !== null && $.removeAllRanges(), y(!0), !0; + const U = window.getSelection(); + return U !== null && U.removeAllRanges(), y(!0), !0; }, - mn + kn ) ); - }, [a, u]); - const M = () => { - a.dispatchCommand(df, void 0); + }, [a, d]); + const W = () => { + a.dispatchCommand(Cf, void 0); }; - return /* @__PURE__ */ Te(gn, { children: [ - g && hn( - /* @__PURE__ */ C( - q1, + return /* @__PURE__ */ xe(bn, { children: [ + h && yn( + /* @__PURE__ */ v( + rP, { editor: a, - cancelAddComment: P, - submitAddComment: B + cancelAddComment: M, + submitAddComment: z } ), document.body ), - d != null && !g && hn( - /* @__PURE__ */ C( - O1, + u != null && !h && yn( + /* @__PURE__ */ v( + eP, { - anchorKey: d, + anchorKey: u, editor: a, showComments: k, - onAddComment: M + onAddComment: W } ), document.body ), - n !== null && hn( - /* @__PURE__ */ C( - Yr, + n !== null && yn( + /* @__PURE__ */ v( + tn, { className: `CommentPlugin_ShowCommentsButton ${k ? "active" : ""}`, onClick: () => _(!k), title: k ? "Hide Comments" : "Show Comments", - children: /* @__PURE__ */ C("i", { className: "comments" }) + children: /* @__PURE__ */ v("i", { className: "comments" }) } ), n?.current ?? document.body ), - k && hn( - /* @__PURE__ */ C( - I1, + k && yn( + /* @__PURE__ */ v( + sP, { comments: l, - submitAddComment: B, - deleteCommentOrThread: A, - activeIDs: p, - markNodeMap: u + submitAddComment: z, + deleteCommentOrThread: E, + activeIDs: f, + markNodeMap: d } ), i?.current ?? document.body ) ] }); } -function D1() { - const e = Z(void 0), t = he((r) => { +function aP() { + const e = Q(void 0), t = ge((r) => { e.current = r; }, []); return [e, t]; } -function U1(e, t) { +function cP(e, t) { const r = t.current?.getComments() ?? [], n = r?.map((s) => s.id), i = e.map((s) => { const o = n.findIndex((a) => a === s); return o !== void 0 && o >= 0 ? r[o] : { @@ -27114,21 +27396,21 @@ function U1(e, t) { e.includes(s.id) || i.push(s); }), i && t.current?.setComments(i); } -function F1(e, t) { - z(() => { +function lP(e, t) { + F(() => { e.options ??= {}, e.options.nodes ??= {}, e.options.nodes.addMissingComments = (r) => { - U1(r, t); + cP(r, t); }; }, [t, e]); } -const NP = En(function(t, r) { - const n = Z(null), i = Z(!0), s = Z(null), [o, a] = de(null), { children: c, onCommentChange: l, onUsjChange: u, showCommentsContainerRef: d, ...f } = t, { logger: p, options: { isReadonly: m, view: g } = {} } = t, y = (m ?? !1) || ns(g), [k, _] = D1(); - F1(f, k), z(() => { +const eN = Nn(function(t, r) { + const n = Q(null), i = Q(!0), s = Q(null), [o, a] = fe(null), { children: c, onCommentChange: l, onUsjChange: d, showCommentsContainerRef: u, ...p } = t, { logger: f, options: { isReadonly: g, view: h } = {} } = t, y = (g ?? !1) || as(h), [k, _] = aP(); + lP(p, k), F(() => { if (process.env.NODE_ENV !== "production") { - const A = "@eten-tech-foundation/platform-editor: Marginal is deprecated and will be removed in a future release."; - p?.warn(A), p || console.warn(A); + const E = "@eten-tech-foundation/platform-editor: Marginal is deprecated and will be removed in a future release."; + f?.warn(E), f || console.warn(E); } - }, [p]), gc(r, () => ({ + }, [f]), _c(r, () => ({ focus() { n.current?.focus(); }, @@ -27159,203 +27441,203 @@ const NP = En(function(t, r) { commitPendingMarkerEdits() { n.current?.commitPendingMarkerEdits(); }, - setTransientInput(A) { - n.current?.setTransientInput(A); + setTransientInput(E) { + n.current?.setTransientInput(E); }, - setUsj(A) { - n.current?.setUsj(A); + setUsj(E) { + n.current?.setUsj(E); }, - applyUpdate(A, B) { - n.current?.applyUpdate(A, B); + applyUpdate(E, z) { + n.current?.applyUpdate(E, z); }, - replaceEmbedUpdate(A, B) { - return n.current?.replaceEmbedUpdate(A, B); + replaceEmbedUpdate(E, z) { + return n.current?.replaceEmbedUpdate(E, z); }, getSelection() { return n.current?.getSelection(); }, - setSelection(A) { - n.current?.setSelection(A); + setSelection(E) { + n.current?.setSelection(E); }, - setAnnotation(A, B, M, q, $) { - typeof q == "function" || q === void 0 ? n.current?.setAnnotation(A, B, M, q, $) : n.current?.setAnnotation(A, B, M, q); + setAnnotation(E, z, W, j, U) { + typeof j == "function" || j === void 0 ? n.current?.setAnnotation(E, z, W, j, U) : n.current?.setAnnotation(E, z, W, j); }, - removeAnnotation(A, B) { - n.current?.removeAnnotation(A, B); + removeAnnotation(E, z) { + n.current?.removeAnnotation(E, z); }, - formatPara(A) { - n.current?.formatPara(A); + formatPara(E) { + n.current?.formatPara(E); }, - getElementByKey(A) { - return n.current?.getElementByKey(A); + getElementByKey(E) { + return n.current?.getElementByKey(E); }, - removeCharacterMarker(A) { - return n.current?.removeCharacterMarker(A) ?? !1; + removeCharacterMarker(E) { + return n.current?.removeCharacterMarker(E) ?? !1; }, - replaceCharacterMarker(A, B) { - return n.current?.replaceCharacterMarker(A, B) ?? !1; + replaceCharacterMarker(E, z) { + return n.current?.replaceCharacterMarker(E, z) ?? !1; }, - extendCharacterMarker(A, B) { - return n.current?.extendCharacterMarker(A, B) ?? !1; + extendCharacterMarker(E, z) { + return n.current?.extendCharacterMarker(E, z) ?? !1; }, - insertMarker(A) { - return n.current?.insertMarker(A); + insertMarker(E) { + return n.current?.insertMarker(E); }, getMarkerMenuContext() { return n.current?.getMarkerMenuContext(); }, - applyMarkerMenuSelection(A, B) { - return n.current?.applyMarkerMenuSelection(A, B); + applyMarkerMenuSelection(E, z) { + return n.current?.applyMarkerMenuSelection(E, z); }, - splitParagraphWithMarker(A) { - n.current?.splitParagraphWithMarker(A); + splitParagraphWithMarker(E) { + n.current?.splitParagraphWithMarker(E); }, - commitTypedMarker(A, B) { - return n.current?.commitTypedMarker(A, B) ?? !1; + commitTypedMarker(E, z) { + return n.current?.commitTypedMarker(E, z) ?? !1; }, - commitTypedCloser(A) { - return n.current?.commitTypedCloser(A) ?? !1; + commitTypedCloser(E) { + return n.current?.commitTypedCloser(E) ?? !1; }, - insertNote(A, B, M) { - n.current?.insertNote(A, B, M); + insertNote(E, z, W) { + n.current?.insertNote(E, z, W); }, - selectNote(A) { - n.current?.selectNote(A); + selectNote(E) { + n.current?.selectNote(E); }, - getNoteOps(A) { - return n.current?.getNoteOps(A); + getNoteOps(E) { + return n.current?.getNoteOps(E); }, - setComments(A) { - k.current?.setComments(A), i.current = !0; + setComments(E) { + k.current?.setComments(E), i.current = !0; }, get toolbarEndRef() { return o; } })); - const S = he( - (A, B, M, q) => { - if (!u) return; - const $ = k.current?.getComments(); - u(A, $, B, M, q); - }, - [k, u] - ), P = he(() => { + const C = ge( + (E, z, W, j) => { + if (!d) return; + const U = k.current?.getComments(); + d(E, U, z, W, j); + }, + [k, d] + ), M = ge(() => { if (!l || i.current) { i.current = !1; return; } - const A = k.current?.getComments(); - l(A); + const E = k.current?.getComments(); + l(E); }, [k, i, l]); - return z(() => (a(n.current?.toolbarEndRef ?? null), () => a(null)), []), /* @__PURE__ */ C(Zy, { children: /* @__PURE__ */ Te(Pm, { ref: n, onUsjChange: S, ...f, children: [ - /* @__PURE__ */ C( - L1, + return F(() => (a(n.current?.toolbarEndRef ?? null), () => a(null)), []), /* @__PURE__ */ v(gb, { children: /* @__PURE__ */ xe(Km, { ref: n, onUsjChange: C, ...p, children: [ + /* @__PURE__ */ v( + oP, { setCommentStore: _, - onChange: P, - showCommentsContainerRef: y ? null : d ?? o, + onChange: M, + showCommentsContainerRef: y ? null : u ?? o, commentContainerRef: s, - logger: f.logger + logger: p.logger } ), - /* @__PURE__ */ C("div", { ref: s, className: "comment-container" }) + /* @__PURE__ */ v("div", { ref: s, className: "comment-container" }) ] }) }); }); -function pn(e) { +function mn(e) { return e.toFixed(3).replace(/0+$/, "").replace(/\.$/, ""); } -function z1(e) { +function uP(e) { return e.replace(/["\\\n\r\f<>]/g, (t) => t === ` ` ? "\\a " : t === "\r" ? "\\d " : t === "\f" ? "\\c " : t === "<" ? "\\3C " : t === ">" ? "\\3E " : `\\${t}`); } -function K1(e) { +function dP(e) { return typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape(e) : e.replace(/[^\w-]/g, (t) => `\\${t}`); } -const j1 = /^[#\w().,%/\s-]+$/; -function mr(e) { +const fP = /^[#\w().,%/\s-]+$/; +function xr(e) { return e != null; } -const B1 = { +const pP = { left: "left", right: "right", center: "center", both: "justify" -}, V1 = { +}, hP = { left: "right", right: "left" -}, W1 = "var(--usj-font-fallback, serif)"; -function Lm(e, t) { +}, gP = "var(--usj-font-fallback, serif)"; +function Ym(e, t) { const r = [e]; - return t && t !== e && r.push(t), `font-family: ${r.map((i) => `"${z1(i)}"`).join(", ")}, ${W1}`; + return t && t !== e && r.push(t), `font-family: ${r.map((i) => `"${uP(i)}"`).join(", ")}, ${gP}`; } -const hc = ".editor-input.usfm", H1 = /^[\w.#[\]="':()>+~*,\s-]+$/; -function G1(e) { - return H1.test(e) ? e : (console.warn( - `[generateUsjCss] Ignoring unsafe containerSelector "${e}"; using "${hc}".` - ), hc); +const xc = ".editor-input.usfm", mP = /^[\w.#[\]="':()>+~*,\s-]+$/; +function yP(e) { + return mP.test(e) ? e : (console.warn( + `[generateUsjCss] Ignoring unsafe containerSelector "${e}"; using "${xc}".` + ), xc); } -function J1(e, t, r, n, i) { +function bP(e, t, r, n, i) { const s = []; - if (t.fontName && s.push(Lm(t.fontName, i)), t.bold && s.push("font-weight: bold"), t.italic && s.push("font-style: italic"), t.color && (j1.test(t.color) ? s.push(`color: ${t.color}`) : console.warn( + if (t.fontName && s.push(Ym(t.fontName, i)), t.bold && s.push("font-weight: bold"), t.italic && s.push("font-style: italic"), t.color && (fP.test(t.color) ? s.push(`color: ${t.color}`) : console.warn( `[generateUsjCss] Skipping unsafe color "${t.color}" for marker "${e}".` - )), mr(t.fontSize) && t.fontSize > 0 && s.push(`font-size: ${Math.floor(t.fontSize * 100 / 12)}%`), mr(t.firstLineIndent) && s.push(`text-indent: ${pn(t.firstLineIndent * 20 * r)}vw`), mr(t.leftMargin) && t.leftMargin >= 0 && s.push(`margin-${n ? "right" : "left"}: ${pn(t.leftMargin * 20 * r)}vw`), mr(t.rightMargin) && t.rightMargin >= 0 && s.push( - `margin-${n ? "left" : "right"}: ${pn(t.rightMargin * 20 * r)}vw` - ), mr(t.spaceBefore) && t.spaceBefore >= 0 && s.push(`margin-top: ${pn(t.spaceBefore * r)}pt`), mr(t.spaceAfter) && t.spaceAfter >= 0 && s.push(`margin-bottom: ${pn(t.spaceAfter * r)}pt`), t.lineSpacing === 1 ? s.push("line-height: 1.5") : t.lineSpacing === 2 && s.push("line-height: 2"), t.subscript ? s.push("vertical-align: text-bottom", "font-size: 66%") : t.superscript && s.push("vertical-align: text-top", "font-size: 66%"), t.underline && s.push("text-decoration: underline"), t.smallCaps && s.push("font-variant: small-caps"), t.justification) { - const o = B1[n ? V1[t.justification] ?? t.justification : t.justification]; + )), xr(t.fontSize) && t.fontSize > 0 && s.push(`font-size: ${Math.floor(t.fontSize * 100 / 12)}%`), xr(t.firstLineIndent) && s.push(`text-indent: ${mn(t.firstLineIndent * 20 * r)}vw`), xr(t.leftMargin) && t.leftMargin >= 0 && s.push(`margin-${n ? "right" : "left"}: ${mn(t.leftMargin * 20 * r)}vw`), xr(t.rightMargin) && t.rightMargin >= 0 && s.push( + `margin-${n ? "left" : "right"}: ${mn(t.rightMargin * 20 * r)}vw` + ), xr(t.spaceBefore) && t.spaceBefore >= 0 && s.push(`margin-top: ${mn(t.spaceBefore * r)}pt`), xr(t.spaceAfter) && t.spaceAfter >= 0 && s.push(`margin-bottom: ${mn(t.spaceAfter * r)}pt`), t.lineSpacing === 1 ? s.push("line-height: 1.5") : t.lineSpacing === 2 && s.push("line-height: 2"), t.subscript ? s.push("vertical-align: text-bottom", "font-size: 66%") : t.superscript && s.push("vertical-align: text-top", "font-size: 66%"), t.underline && s.push("text-decoration: underline"), t.smallCaps && s.push("font-variant: small-caps"), t.justification) { + const o = pP[n ? hP[t.justification] ?? t.justification : t.justification]; o && s.push(`text-align: ${o}`); } return t.textProperties?.includes("verse") && s.push("white-space: nowrap", "unicode-bidi: embed"), s; } -const pf = { c: 150, ca: 133, cp: 150 }; -function hf(e, t) { - return e && mr(e.fontSize) && e.fontSize > 0 ? Math.floor(e.fontSize * 100 / 12) : t; +const Sf = { c: 150, ca: 133, cp: 150 }; +function Mf(e, t) { + return e && xr(e.fontSize) && e.fontSize > 0 ? Math.floor(e.fontSize * 100 / 12) : t; } -function Y1(e, t) { +function kP(e, t) { if (["c", "ca", "cp"].filter((i) => { const s = e.markers[i]; - return s && mr(s.fontSize) && s.fontSize > 0; + return s && xr(s.fontSize) && s.fontSize > 0; }).length === 0) return []; - const n = hf(e.markers.c, pf.c); + const n = Mf(e.markers.c, Sf.c); return ["ca", "cp"].map((i) => { - const s = hf( + const s = Mf( e.markers[i], - pf[i] - ), o = pn(s * 100 / n); + Sf[i] + ), o = mn(s * 100 / n); return `${t} .usfm_c .usfm_${i}.usfm_${i} { font-size: ${o}%; }`; }); } -function OP(e, t = {}) { - const { zoom: r = 1, rtl: n = !1, containerSelector: i = hc } = t, s = G1(i), o = [], a = []; - e.defaultFont && a.push(Lm(e.defaultFont)), mr(e.defaultFontSize) && e.defaultFontSize > 0 && a.push(`font-size: ${pn(e.defaultFontSize * r)}pt`), a.length > 0 && o.push(`${s} { ${a.join("; ")}; }`); +function tN(e, t = {}) { + const { zoom: r = 1, rtl: n = !1, containerSelector: i = xc } = t, s = yP(i), o = [], a = []; + e.defaultFont && a.push(Ym(e.defaultFont)), xr(e.defaultFontSize) && e.defaultFontSize > 0 && a.push(`font-size: ${mn(e.defaultFontSize * r)}pt`), a.length > 0 && o.push(`${s} { ${a.join("; ")}; }`); for (const [c, l] of Object.entries(e.markers)) { - const u = J1(c, l, r, n, e.defaultFont); - u.length > 0 && o.push(`${s} .usfm_${K1(c)} { ${u.join("; ")}; }`); + const d = bP(c, l, r, n, e.defaultFont); + d.length > 0 && o.push(`${s} .usfm_${dP(c)} { ${d.join("; ")}; }`); } - return o.push(...Y1(e, s)), o.join(` + return o.push(...kP(e, s)), o.join(` `); } export { - Ah as BLOCK_VERSE_VIEW_MODE, + Fh as BLOCK_VERSE_VIEW_MODE, T as CategoryType, - PP as Editorial, - Bi as GENERATOR_NOTE_CALLER, - Lf as HIDDEN_NOTE_CALLER, - NP as Marginal, + ZP as Editorial, + Gi as GENERATOR_NOTE_CALLER, + Jf as HIDDEN_NOTE_CALLER, + eN as Marginal, b as MarkerType, - Eh as PARAGRAPH_STRUCTURE_VIEW_MODE, - ol as STANDARD_VIEW_MODE, - Vs as defaultStyleInfo, - AP as directionToNames, - m_ as filterAndRankItems, - OP as generateUsjCss, - MP as getDefaultViewMode, - Eo as getDefaultViewOptions, - zM as getEnterMenuItems, - FM as getMarkerMenuItems, - EP as getViewMode, - ll as getViewOptions, - ns as isBlockVerseLayout, - Fr as isInsertEmbedOpOfType, - P_ as viewModeToViewNames + Uh as PARAGRAPH_STRUCTURE_VIEW_MODE, + fl as STANDARD_VIEW_MODE, + Js as defaultStyleInfo, + QP as directionToNames, + w_ as filterAndRankItems, + tN as generateUsjCss, + YP as getDefaultViewMode, + Oo as getDefaultViewOptions, + BE as getEnterMenuItems, + jE as getMarkerMenuItems, + XP as getViewMode, + gl as getViewOptions, + as as isBlockVerseLayout, + jr as isInsertEmbedOpOfType, + V_ as viewModeToViewNames }; //# sourceMappingURL=index.js.map diff --git a/packages/platform/dist/index.js.map b/packages/platform/dist/index.js.map index 7556555c3..4f857f8a1 100644 --- a/packages/platform/dist/index.js.map +++ b/packages/platform/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../../../libs/shared/dist/converters/usfm/emptyUsfmNodes.js","../../../libs/shared/dist/converters/usfm/leadingAttributes.js","../../../libs/shared/dist/nodes/usj/node-constants.js","../../../libs/shared/dist/nodes/usj/MilestoneNode.js","../../../libs/shared/dist/nodes/usj/NoteNode.js","../../../libs/shared/dist/utils/usfm/usfmTypes.js","../../../libs/shared/dist/utils/usfm/usfmMarkers.js","../../../libs/shared/dist/utils/usfm/usfmMarkersOverwrites.js","../../../libs/shared/dist/utils/usfm/getMarker.js","../../../libs/shared/dist/converters/usfm/usfmFragmentToUsj.js","../../../libs/shared/dist/nodes/collab/delta.state.js","../../../libs/shared/dist/nodes/features/ImmutableTypedTextNode.js","../../../libs/shared/dist/nodes/features/TypedMarkNode.js","../../../libs/shared/dist/nodes/features/UnknownNode.js","../../../libs/shared/dist/nodes/usj/AttributeRunNode.js","../../../libs/shared/dist/nodes/usj/BookNode.js","../../../libs/shared/dist/nodes/usj/ChapterNode.js","../../../libs/shared/dist/nodes/usj/CharNode.js","../../../libs/shared/dist/nodes/usj/ImmutableChapterNode.js","../../../libs/shared/dist/nodes/usj/ImpliedParaNode.js","../../../libs/shared/dist/nodes/usj/ParaNode.js","../../../libs/shared/dist/nodes/usj/VerseNode.js","../../../libs/shared/dist/plugins/CursorHandler/core/utils/constants.js","../../../libs/shared/dist/plugins/CursorHandler/core/utils/index.js","../../../libs/shared/dist/nodes/usj/node.utils.js","../../../libs/shared/dist/nodes/features/MarkerNode.js","../../../libs/shared/dist/nodes/usj/attributeDisplay.utils.js","../../../libs/shared/dist/nodes/usj/nestedGlyphs.utils.js","../../../libs/shared/dist/nodes/usj/markerSeparators.utils.js","../../../libs/shared/dist/nodes/features/unknownUsfm.utils.js","../../../libs/shared/dist/displayRun/displayRunRegistry.js","../../../libs/shared/dist/displayRun/displayRunOwner.utils.js","../../../libs/shared/dist/nodes/features/ImmutableUnmatchedNode.js","../../../libs/shared/dist/nodes/usj/ImmutableTableNode.js","../../../libs/shared/dist/nodes/usj/ImmutableTableRowNode.js","../../../libs/shared/dist/nodes/usj/ImmutableTableCellNode.js","../../../libs/shared/dist/nodes/usj/caretBoundaries.utils.js","../../../libs/shared/dist/nodes/usj/charGlyphs.utils.js","../../../libs/shared/dist/nodes/usj/charStack.utils.js","../../../libs/shared/dist/nodes/usj/pendedDisplayOwners.utils.js","../../../libs/shared/dist/nodes/usj/displayRunSync.utils.js","../../../libs/shared/dist/nodes/usj/glyphPositions.utils.js","../../../libs/shared/dist/nodes/usj/VerseBlockNode.js","../../../libs/shared/dist/nodes/usj/index.js","../../../libs/shared/dist/utils/usfm/defaultStyleInfo.js","../../../libs/shared/dist/utils/usfm/styleInfo.js","../../../libs/shared/dist/utils/usj/contentToLexicalNode.js","../../../libs/shared-react/dist/nodes/usj/ImmutableVerseNode.js","../../../libs/shared-react/dist/nodes/usj/node-react.utils.js","../../../libs/shared-react/dist/plugins/usj/collab/rich-text-ot.model.js","../../../libs/shared-react/dist/plugins/usj/collab/delta-common.utils.js","../../../libs/shared-react/dist/plugins/usj/collab/editor-delta.adaptor.js","../../../libs/shared-react/dist/nodes/usj/ImmutableNoteCallerNode.js","../../../libs/shared-react/dist/plugins/usj/annotation/selection.utils.js","../../../libs/shared-react/dist/nodes/usj/note.utils.js","../../../libs/shared-react/dist/nodes/usj/index.js","../../../libs/shared-react/dist/plugins/FloatingBox/FloatingBox.js","../../../libs/shared-react/dist/plugins/FloatingBox/useFloatingPosition.js","../../../libs/shared-react/dist/plugins/FloatingBox/useCursorCoords.js","../../../libs/shared-react/dist/plugins/FloatingBox/FloatingBoxAtCursor.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/MenuContext.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/useMenuCore.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/Root.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/Option.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/Options.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/filterAndRankItems.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/index.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/useFilteredItems.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/useMenuActions.js","../../../libs/shared-react/dist/plugins/NodesMenu/Menu/useLexicalMenuNavigation.js","../../../libs/shared-react/dist/plugins/NodesMenu/LexicalMenuNavigation.js","../../../libs/shared-react/dist/plugins/NodesMenu/NodeSelectionMenu.js","../../../libs/shared-react/dist/plugins/NodesMenu/index.js","../../../libs/shared-react/dist/plugins/PerfNodesItems/useUsfmMarkersForMenu.js","../../../libs/shared-react/dist/plugins/usj/annotation/AnnotationPlugin.js","../../../libs/shared-react/dist/plugins/usj/collab/DeltaOnChangePlugin.js","../../../libs/shared-react/dist/views/view-mode.model.js","../../../libs/shared-react/dist/views/view-options.utils.js","../../../libs/shared-react/dist/plugins/usj/collab/delta-apply-update.utils.js","../../../libs/shared-react/dist/plugins/usj/ParaMarkerPrefixCursorGuardPlugin.js","../../../libs/shared-react/dist/plugins/usj/OpaqueBlockGuardPlugin.js","../../../libs/shared-react/dist/plugins/usj/ArrowNavigationPlugin.js","../../../libs/shared-react/dist/plugins/usj/CharNodePlugin.js","../../../libs/shared-react/dist/plugins/usj/clipboard.utils.js","../../../libs/shared-react/dist/plugins/usj/ClipboardPlugin.js","../../../libs/shared-react/dist/plugins/usj/CommandMenuPlugin.js","../../../libs/shared-react/dist/plugins/usj/ContextMenuPlugin.js","../../../libs/shared-react/dist/plugins/usj/DecoratorBoundarySelectionPlugin.js","../../../libs/shared-react/dist/plugins/usj/DisableHistoryShortcutsPlugin.js","../../../libs/shared-react/dist/plugins/usj/EditablePlugin.js","../../../libs/shared-react/dist/plugins/usj/transientCaretHost.js","../../../libs/shared-react/dist/plugins/usj/EmptyVerseCaretGuardPlugin.js","../../../libs/shared-react/dist/plugins/usj/LoadStatePlugin.js","../../../libs/shared-react/dist/plugins/usj/NoteNodePlugin.js","../../../libs/shared-react/dist/plugins/usj/NoteShellCaretGuardPlugin.js","../../../libs/shared-react/dist/plugins/usj/OnSelectionChangePlugin.js","../../../libs/shared-react/dist/plugins/usj/ParaNodePlugin.js","../../../libs/shared-react/dist/plugins/usj/StateChangePlugin.js","../../../libs/shared-react/dist/plugins/usj/structureKeyboard.utils.js","../../../node_modules/.pnpm/dompurify@3.4.13/node_modules/dompurify/dist/purify.es.mjs","../../../libs/shared-react/dist/plugins/usj/StructureKeyboardPlugin.js","../../../libs/shared-react/dist/plugins/usj/text-direction.model.js","../../../libs/shared-react/dist/plugins/usj/TextDirectionPlugin.js","../../../libs/shared-react/dist/plugins/usj/TextSpacingPlugin.js","../../../libs/shared-react/dist/plugins/usj/TrailingNoteCaretGuardPlugin.js","../../../libs/shared-react/dist/plugins/UsfmNodesMenuPlugin.js","../../../libs/shared-react/dist/plugins/usj/UsjNodesMenuPlugin.js","../src/editor/markerEdit/whitespaceDisplay.utils.ts","../src/editor/adaptors/editor-usj.adaptor.ts","../src/editor/adaptors/verse-block.utils.ts","../src/editor/adaptors/usj-editor.adaptor.ts","../src/editor/markerEdit/charFormatting.utils.ts","../src/editor/adaptors/usj-marker-action.utils.ts","../src/editor/editor.theme.ts","../src/editor/ActiveTextPlugin.tsx","../src/editor/markerEdit/markerValidation.utils.ts","../src/editor/markerMenu/markerItemSource.ts","../src/editor/markerEdit/markerName.pattern.ts","../src/editor/markerEdit/settleShared.utils.ts","../src/editor/markerEdit/tier2Rebuild.utils.ts","../src/editor/markerEdit/markerKind.utils.ts","../src/editor/markerEdit/markerEditDeletion.utils.ts","../src/editor/markerEdit/applyParaMarker.utils.ts","../src/editor/markerEdit/markerEditNote.utils.ts","../src/editor/markerMenu/markerMenuContext.utils.ts","../src/editor/markerMenu/markerMenuApply.utils.ts","../src/editor/EscapeKeyPlugin.tsx","../src/editor/markerEdit/markerEditTier1.utils.ts","../src/editor/markerEdit/markerEditTier2Trigger.utils.ts","../src/editor/markerEdit/paratext9Clipboard.utils.ts","../src/editor/markerEdit/whitespaceDisplay.plugin.utils.ts","../src/editor/markerEdit/MarkerEditPlugin.tsx","../src/editor/markerEdit/MarkerValidationPlugin.tsx","../src/editor/MarkersViewCopyPlugin.tsx","../src/editor/markerEdit/virtualSettle.utils.ts","../src/editor/ParaMarkerPrefixGuardPlugin.tsx","../src/editor/ScriptureReferencePlugin.tsx","../src/editor/TreeViewPlugin.tsx","../src/editor/toolbar/DropDown.tsx","../src/editor/toolbar/BlockFormatDropDown.tsx","../src/editor/toolbar/ToolbarPlugin.tsx","../src/editor/Editor.tsx","../src/Editorial.tsx","../src/marginal/comments/commenting.ts","../src/marginal/comments/ui/Modal.tsx","../src/marginal/comments/use-modal.hook.tsx","../src/marginal/comments/comment-editor.theme.ts","../src/marginal/comments/join-classes.util.ts","../src/marginal/comments/ui/Button.tsx","../src/marginal/comments/ui/ContentEditable.tsx","../src/marginal/comments/ui/Placeholder.tsx","../src/marginal/comments/CommentPlugin.tsx","../src/marginal/comments/use-comment-store-ref.hook.tsx","../src/marginal/comments/use-missing-comments-props.hook.tsx","../src/marginal/Marginal.tsx","../src/editor/generateUsjCss.ts"],"sourcesContent":["import { $applyNodeReplacement, $createParagraphNode, $insertNodes, $isRootOrShadowRoot, $parseSerializedNode, } from \"lexical\";\nimport { $wrapNodeInElement } from \"@lexical/utils\";\nexport const $createNodeFromSerializedNode = (serializedNode) => {\n return $applyNodeReplacement($parseSerializedNode(serializedNode));\n};\nexport const $insertUsfmNode = (serializedNode) => {\n const newNode = $createNodeFromSerializedNode(serializedNode);\n $insertNodes([newNode]);\n if ($isRootOrShadowRoot(newNode.getParentOrThrow())) {\n $wrapNodeInElement(newNode, $createParagraphNode).selectEnd();\n }\n return newNode;\n};\n","/**\n * The markers map's `leadingAttributes` relation — which markers take a value written directly\n * after the marker, separated only by whitespace, and stored as an attribute rather than as\n * text: `\\v`/`\\c`'s number, the note-marker family's caller, `\\id`'s code.\n *\n * ONE rule follows from the declaration, with no per-marker exceptions: whitespace between the\n * marker and its leading-attribute value is structural and collapses to one, so `\\v 5` is\n * verse 5, `\\f +` has caller `+`, and `\\id MAT` has code `MAT`. The fragment tokenizer's own\n * word scan (`getNextWord`, usfmFragmentToUsj.ts) already implements the collapse; this module\n * is the declarative side, so Tier-1 glyph arms (platform's marker-edit engine) can consume the\n * same marker/attribute knowledge instead of hardcoding per-marker lists.\n *\n * The table is a VENDORED SLICE of paranext-core's\n * `lib/platform-bible-utils/src/scripture/markers-maps/markers-map-3.0.model.ts`\n * (`USFM_MARKERS_MAP.markers`, every `leadingAttributes` field verbatim; 3.1 declares the\n * identical set) — the source of truth lives there; re-copy when it changes (the same\n * convention as `attributeMarkersMapAgreement.test.ts`'s slice and the vendored\n * `testUsfmCorpus` fixtures). scripture-editors has no dependency on `platform-bible-utils`,\n * so a live import is not available.\n */\n/** Vendored from `USFM_MARKERS_MAP.markers`: every marker with a `leadingAttributes` field,\n * verbatim, ORDERED by the order the values must appear in after the marker. */\nconst LEADING_ATTRIBUTES = {\n c: [\"number\"],\n ef: [\"caller\"],\n efe: [\"caller\"],\n ex: [\"caller\"],\n f: [\"caller\"],\n fe: [\"caller\"],\n id: [\"code\"],\n v: [\"number\"],\n x: [\"caller\"],\n};\n/**\n * The ordered leading-attribute names the markers map declares for `marker`, or `undefined`\n * for a marker that declares none. Read-only map data — callable from any context.\n */\nexport function leadingAttributeNames(marker) {\n return LEADING_ATTRIBUTES[marker];\n}\n","/** This file avoids a circular dependency between `CharNode.ts` and `node.utils.ts`. */\n/** Non-breaking space (U+00A0). */\nexport const NBSP = \"\\u00A0\";\n/** Zero-width space (U+200B). */\nexport const ZWSP = \"\\u200B\";\nexport const EMPTY_CHAR_PLACEHOLDER_TEXT = NBSP;\nexport const NODE_ATTRIBUTE_PREFIX = `${NBSP}|`;\nexport const PARA_MARKER_DEFAULT = \"p\";\n/**\n * Note caller will be auto-generated.\n * @public\n */\nexport const GENERATOR_NOTE_CALLER = \"+\";\n/**\n * Hidden note caller will not be auto-generated, and will not be displayed in some views.\n * @public\n */\nexport const HIDDEN_NOTE_CALLER = \"-\";\nexport const CHAPTER_CLASS_NAME = \"chapter\";\nexport const VERSE_CLASS_NAME = \"verse\";\nexport const INVALID_CLASS_NAME = \"invalid\";\nexport const TEXT_SPACING_CLASS_NAME = \"text-spacing\";\nexport const FORMATTED_FONT_CLASS_NAME = \"formatted-font\";\nexport const MARKER_MODE_CLASS_NAME_PREFIX = \"marker-\";\nexport const EXTERNAL_USJ_MUTATION_TAG = \"external-usj-mutation\";\nexport const SELECTION_CHANGE_TAG = \"selection-change\";\nexport const CURSOR_CHANGE_TAG = \"cursor-change\";\nexport const ANNOTATION_CHANGE_TAG = \"annotation-change\";\nexport const DELTA_CHANGE_TAG = \"delta-change\";\n/**\n * Marks a commit that carries Lexical's `HISTORY_MERGE_TAG` yet still CHANGES the document — a\n * marker-edit settle, which must never become its own undo entry but is a real content change the\n * host has to see. The merge tag carries two meanings that normally coincide (\"do not push a\n * history entry\" and \"nothing to report\"); this tag is how such a commit says it means only the\n * first, so USJ-change consumers do not skip it.\n */\nexport const MARKER_SETTLE_TAG = \"marker-settle\";\n/** Tags that should not be present when handling a USJ change. */\nexport const blackListedChangeTags = [\n EXTERNAL_USJ_MUTATION_TAG,\n SELECTION_CHANGE_TAG,\n CURSOR_CHANGE_TAG,\n ANNOTATION_CHANGE_TAG,\n DELTA_CHANGE_TAG,\n];\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/ms/index.html */\nimport { $applyNodeReplacement, DecoratorNode, } from \"lexical\";\nexport const STARTING_MS_COMMENT_MARKER = \"zmsc-s\";\nexport const ENDING_MS_COMMENT_MARKER = \"zmsc-e\";\n/** Milestone markers used to mark a comment annotation */\nconst milestoneCommentMarkers = [STARTING_MS_COMMENT_MARKER, ENDING_MS_COMMENT_MARKER];\n/** @see https://docs.usfm.bible/usfm/3.1/ms/index.html */\nconst VALID_MILESTONE_MARKERS = [\n \"ts-s\",\n \"ts-e\",\n \"t-s\",\n \"t-e\",\n \"ts\",\n \"qt1-s\",\n \"qt1-e\",\n \"qt2-s\",\n \"qt2-e\",\n \"qt3-s\",\n \"qt3-e\",\n \"qt4-s\",\n \"qt4-e\",\n \"qt5-s\",\n \"qt5-e\",\n \"qt-s\",\n \"qt-e\",\n // custom markers used for annotations\n STARTING_MS_COMMENT_MARKER,\n ENDING_MS_COMMENT_MARKER,\n];\nexport const MILESTONE_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const MS_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"sid\",\n \"eid\",\n \"content\",\n];\n/**\n * The milestone `MarkerObject` properties that are never attribute bytes. Everything ELSE a\n * milestone carries — `sid` and `eid` just as much as an unknown attribute like `who` — is written\n * into its `|…` run, so those are the names that have an order relative to one another. Derived\n * from {@link MS_MARKER_OBJECT_PROPS} so the two cannot drift.\n */\nexport const MS_NON_ATTRIBUTE_PROPS = MS_MARKER_OBJECT_PROPS.filter((property) => property !== \"sid\" && property !== \"eid\");\nexport class MilestoneNode extends DecoratorNode {\n __marker;\n __sid;\n __eid;\n __unknownAttributes;\n __attributeOrder;\n // `attributeOrder` rides AFTER `key`, never ahead of it: a node's key is the last argument\n // every Lexical node constructor took before this field existed, and slotting the new field\n // ahead of it would silently reinterpret an existing 5-argument call's NodeKey as the order\n // list (TypeScript consumers get a compile error; JavaScript consumers get corruption). Same\n // rule as MarkerNode's constructor.\n constructor(marker = \"\", sid, eid, unknownAttributes, key, attributeOrder) {\n super(key);\n this.__marker = marker;\n this.__sid = sid;\n this.__eid = eid;\n this.__unknownAttributes = unknownAttributes;\n this.__attributeOrder = attributeOrder;\n }\n static getType() {\n return \"ms\";\n }\n static clone(node) {\n const { __marker, __sid, __eid, __unknownAttributes, __attributeOrder, __key } = node;\n return new MilestoneNode(__marker, __sid, __eid, __unknownAttributes, __key, __attributeOrder);\n }\n static importJSON(serializedNode) {\n return $createMilestoneNode().updateFromJSON(serializedNode);\n }\n static isValidMarker(marker, extraValidMarkers) {\n return (marker !== undefined &&\n (VALID_MILESTONE_MARKERS.includes(marker) ||\n marker.startsWith(\"z\") ||\n (extraValidMarkers?.includes(marker) ?? false)));\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setSid(serializedNode.sid)\n .setEid(serializedNode.eid)\n .setUnknownAttributes(serializedNode.unknownAttributes)\n .setAttributeOrder(serializedNode.attributeOrder);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setSid(sid) {\n if (this.__sid === sid)\n return this;\n const self = this.getWritable();\n self.__sid = sid;\n return self;\n }\n getSid() {\n const self = this.getLatest();\n return self.__sid;\n }\n setEid(eid) {\n if (this.__eid === eid)\n return this;\n const self = this.getWritable();\n self.__eid = eid;\n return self;\n }\n getEid() {\n const self = this.getLatest();\n return self.__eid;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n setAttributeOrder(attributeOrder) {\n const self = this.getWritable();\n self.__attributeOrder = attributeOrder;\n return self;\n }\n /**\n * The authored attribute order this milestone was loaded with, or `undefined` when its order is\n * the canonical one. Feed it to `milestoneAttributes` (attributeDisplay.utils.ts) — every site\n * that turns a milestone back into bytes or back into USJ goes through there, so the order\n * cannot be honored in one place and dropped in another.\n */\n getAttributeOrder() {\n const self = this.getLatest();\n return self.__attributeOrder;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(this.__type, `usfm_${this.__marker}`);\n return dom;\n }\n updateDOM() {\n // Returning false tells Lexical that this node does not need its\n // DOM element replacing with a new copy from createDOM.\n return false;\n }\n /**\n * A milestone paints nothing of its own: in editable marker mode its `\\qt-s …\\*` glyphs are real\n * sibling nodes, and in the other modes an `ImmutableTypedTextNode` carries them.\n *\n * Keep this payload EMPTY. A decorator payload with STABLE IDENTITY is unsound for any node that\n * can be re-parented, and a milestone can be — it rides a Tier-2 paragraph rebuild as a preserved\n * sentinel and is moved into the freshly created paragraph, whose children Lexical builds new\n * elements for. Lexical skips notifying its decorator listener whenever the payload is unchanged\n * (`reconcileDecorator` bails on `currentDecorators[key] === decorator`, and equal strings always\n * compare equal), so `@lexical/react`'s portal would stay bound to the OLD, detached element and\n * the live one would render nothing from then on. `\"\"` is what makes that harmless here: there is\n * nothing to lose. Giving this node visible payload would reintroduce exactly that defect — render\n * such bytes from `createDOM` instead, the way `ImmutableTypedTextNode` does.\n */\n decorate() {\n return \"\";\n }\n exportJSON() {\n return {\n type: this.getType(),\n marker: this.getMarker(),\n sid: this.getSid(),\n eid: this.getEid(),\n unknownAttributes: this.getUnknownAttributes(),\n attributeOrder: this.getAttributeOrder(),\n version: MILESTONE_VERSION,\n };\n }\n // Mutation\n isKeyboardSelectable() {\n return false;\n }\n}\nexport function isMilestoneCommentMarker(marker) {\n return milestoneCommentMarkers.includes(marker);\n}\nexport function $createMilestoneNode(marker, sid, eid, unknownAttributes, attributeOrder) {\n return $applyNodeReplacement(new MilestoneNode(marker, sid, eid, unknownAttributes, undefined, attributeOrder));\n}\nexport function $isMilestoneNode(node) {\n return node instanceof MilestoneNode;\n}\nexport function isSerializedMilestoneNode(node) {\n return node?.type === MilestoneNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/note/index.html */\nimport { GENERATOR_NOTE_CALLER, HIDDEN_NOTE_CALLER } from \"./node-constants.js\";\nimport { $applyNodeReplacement, ElementNode, isHTMLElement, } from \"lexical\";\nexport const DEFAULT_NOTE_MARKER = \"f\";\n/** @see https://docs.usfm.bible/usfm/3.1/note/index.html */\nconst VALID_NOTE_MARKERS = [\n // Footnote\n DEFAULT_NOTE_MARKER,\n \"fe\",\n \"ef\",\n \"efe\",\n // Cross Reference\n \"x\",\n \"ex\",\n];\n/**\n * Classifies a note marker into its caller family, matching PT9's Standard view rule\n * (Paratext repo `ParatextInternalShared/ScriptureViews/Standard.xslt` lines 446-449): a note is a\n * footnote iff its style starts with `f` or starts with `ef`; EVERY other note style — `x`, `ex`,\n * and any custom note marker — uses the cross-reference caller sequence.\n *\n * Note the deliberate consequence for custom markers: a custom note marker that does not start\n * with `f`/`ef` (e.g. `zfn`) classifies as a cross-reference — auto-callers from the\n * cross-reference sequence, hidden-caller default — exactly as PT9 renders it, even if the marker\n * is semantically a footnote.\n *\n * @param marker - The note marker to classify (e.g. `f`, `x`, `ef`, or a custom marker).\n * @returns `\"footnote\"` for `f*`/`ef*` markers, `\"crossref\"` for everything else.\n */\nexport function getNoteKind(marker) {\n return marker.startsWith(\"f\") || marker.startsWith(\"ef\") ? \"footnote\" : \"crossref\";\n}\n/** List of known properties of `MarkerObject` */\nexport const NOTE_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"caller\",\n \"category\",\n \"content\",\n];\nexport const NOTE_VERSION = 1;\nexport class NoteNode extends ElementNode {\n __marker;\n __caller;\n __isCollapsed;\n __category;\n __unknownAttributes;\n constructor(marker = DEFAULT_NOTE_MARKER, caller, isCollapsed = true, category, unknownAttributes, key) {\n super(key);\n this.__marker = marker;\n this.__caller =\n caller ?? (getNoteKind(marker) === \"crossref\" ? HIDDEN_NOTE_CALLER : GENERATOR_NOTE_CALLER);\n this.__isCollapsed = isCollapsed;\n this.__category = category;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"note\";\n }\n static clone(node) {\n const { __marker, __caller, __isCollapsed, __category, __unknownAttributes, __key } = node;\n return new NoteNode(__marker, __caller, __isCollapsed, __category, __unknownAttributes, __key);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isNoteElement(node))\n return null;\n return {\n conversion: $convertNoteElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createNoteNode().updateFromJSON(serializedNode);\n }\n static isValidMarker(marker, extraValidMarkers) {\n return (marker !== undefined &&\n (VALID_NOTE_MARKERS.includes(marker) ||\n (extraValidMarkers?.includes(marker) ?? false)));\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setCaller(serializedNode.caller)\n .setIsCollapsed(serializedNode.isCollapsed)\n .setCategory(serializedNode.category)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setCaller(caller) {\n if (this.__caller === caller)\n return this;\n const self = this.getWritable();\n self.__caller = caller;\n return self;\n }\n getCaller() {\n const self = this.getLatest();\n return self.__caller;\n }\n setIsCollapsed(isCollapsed) {\n if (this.__isCollapsed === isCollapsed)\n return this;\n const self = this.getWritable();\n self.__isCollapsed = isCollapsed;\n return self;\n }\n toggleIsCollapsed() {\n const self = this.getWritable();\n self.__isCollapsed = !self.__isCollapsed;\n return self;\n }\n getIsCollapsed() {\n const self = this.getLatest();\n return self.__isCollapsed;\n }\n setCategory(category) {\n if (this.__category === category)\n return this;\n const self = this.getWritable();\n self.__category = category;\n return self;\n }\n getCategory() {\n const self = this.getLatest();\n return self.__category;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(this.__type, `usfm_${this.__marker}`, this.__isCollapsed ? \"collapsed\" : \"expanded\");\n dom.setAttribute(\"data-caller\", this.__caller);\n // The stylesheet's `+`-caller counter rules select on the caller family, not per-marker\n // classes, so custom note markers number correctly (see `getNoteKind` for the PT9 rule).\n dom.setAttribute(\"data-note-kind\", getNoteKind(this.__marker));\n return dom;\n }\n updateDOM(prevNode, dom) {\n // Returning true tells Lexical that this node needs its DOM element\n // replacing with a new copy from createDOM.\n if (prevNode.__isCollapsed !== this.__isCollapsed)\n return true;\n if (prevNode.__marker !== this.__marker) {\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.remove(`usfm_${prevNode.__marker}`);\n dom.classList.add(`usfm_${this.__marker}`);\n dom.setAttribute(\"data-note-kind\", getNoteKind(this.__marker));\n }\n if (prevNode.__caller !== this.__caller)\n dom.setAttribute(\"data-caller\", this.__caller);\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(this.getType(), `usfm_${this.getMarker()}`, this.getIsCollapsed() ? \"collapsed\" : \"expanded\");\n element.setAttribute(\"data-caller\", this.getCaller());\n element.setAttribute(\"data-note-kind\", getNoteKind(this.getMarker()));\n }\n return { element };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n caller: this.getCaller(),\n isCollapsed: this.getIsCollapsed(),\n category: this.getCategory(),\n unknownAttributes: this.getUnknownAttributes(),\n version: NOTE_VERSION,\n };\n }\n // Mutation\n canBeEmpty() {\n return false;\n }\n isInline() {\n return true;\n }\n}\nfunction $convertNoteElement(element) {\n const marker = element.getAttribute(\"data-marker\") ?? \"f\";\n const caller = element.getAttribute(\"data-caller\") ?? \"\";\n const isCollapsed = element.classList.contains(\"collapsed\");\n const node = $createNoteNode(marker, caller, isCollapsed);\n return { node };\n}\nexport function $createNoteNode(marker, caller, isCollapsed, category, unknownAttributes) {\n return $applyNodeReplacement(new NoteNode(marker, caller, isCollapsed, category, unknownAttributes));\n}\nfunction isNoteElement(node) {\n if (!node)\n return false;\n const marker = node.getAttribute(\"data-marker\") ?? \"\";\n return NoteNode.isValidMarker(marker) && node.classList.contains(NoteNode.getType());\n}\nexport function $isNoteNode(node) {\n return node instanceof NoteNode;\n}\nexport function isSerializedNoteNode(node) {\n return node?.type === NoteNode.getType();\n}\n","/** Generated file using `nx generate markers-data` with 'tools/usfm-markers/src/generators/markers-data/data/usfm.sty' */\n/** @public */\nexport var CategoryType;\n(function (CategoryType) {\n CategoryType[\"FileIdentification\"] = \"FileIdentification\";\n CategoryType[\"Headers\"] = \"Headers\";\n CategoryType[\"Remarks\"] = \"Remarks\";\n CategoryType[\"Introduction\"] = \"Introduction\";\n CategoryType[\"DivisionMarks\"] = \"DivisionMarks\";\n CategoryType[\"Paragraphs\"] = \"Paragraphs\";\n CategoryType[\"Poetry\"] = \"Poetry\";\n CategoryType[\"TitlesHeadings\"] = \"TitlesHeadings\";\n CategoryType[\"Tables\"] = \"Tables\";\n CategoryType[\"CenterTables\"] = \"CenterTables\";\n CategoryType[\"RightTables\"] = \"RightTables\";\n CategoryType[\"Lists\"] = \"Lists\";\n CategoryType[\"Footnotes\"] = \"Footnotes\";\n CategoryType[\"CrossReferences\"] = \"CrossReferences\";\n CategoryType[\"SpecialText\"] = \"SpecialText\";\n CategoryType[\"CharacterStyling\"] = \"CharacterStyling\";\n CategoryType[\"Breaks\"] = \"Breaks\";\n CategoryType[\"SpecialFeatures\"] = \"SpecialFeatures\";\n CategoryType[\"PeripheralReferences\"] = \"PeripheralReferences\";\n CategoryType[\"PeripheralMaterials\"] = \"PeripheralMaterials\";\n CategoryType[\"Uncategorized\"] = \"Uncategorized\";\n})(CategoryType || (CategoryType = {}));\n/** @public */\nexport var MarkerType;\n(function (MarkerType) {\n MarkerType[\"Paragraph\"] = \"Paragraph\";\n MarkerType[\"Character\"] = \"Character\";\n MarkerType[\"Note\"] = \"Note\";\n MarkerType[\"Milestone\"] = \"Milestone\";\n MarkerType[\"Unknown\"] = \"Unknown\";\n})(MarkerType || (MarkerType = {}));\n","/** Generated file using `nx generate markers-data` with 'tools/usfm-markers/src/generators/markers-data/data/usfm.sty' */\nimport { CategoryType, MarkerType } from \"./usfmTypes.js\";\nexport const usfmMarkers = {\n id: {\n category: CategoryType.FileIdentification,\n type: MarkerType.Paragraph,\n description: \"File identification information (BOOKID, FILENAME, EDITOR, MODIFICATION DATE)\",\n hasEndMarker: false,\n children: {\n FileIdentification: [\"usfm\", \"ide\"],\n Headers: [\"h\", \"h1\", \"h2\", \"h3\", \"toc1\", \"toc2\", \"toc3\"],\n Remarks: [\"rem\", \"sts\", \"restore\"],\n Introduction: [\n \"imt\",\n \"imt1\",\n \"imt2\",\n \"imt3\",\n \"imt4\",\n \"imte\",\n \"imte1\",\n \"imte2\",\n \"is\",\n \"is1\",\n \"is2\",\n \"iot\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ior\",\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"ib\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"iex\",\n \"ie\",\n ],\n DivisionMarks: [\"c\", \"cl\"],\n TitlesHeadings: [\"mt\", \"mt1\", \"mt2\", \"mt3\", \"mt4\"],\n },\n },\n usfm: {\n category: CategoryType.FileIdentification,\n type: MarkerType.Paragraph,\n description: \"File markup version information\",\n hasEndMarker: false,\n children: undefined,\n },\n ide: {\n category: CategoryType.FileIdentification,\n type: MarkerType.Paragraph,\n description: \"File encoding information\",\n hasEndMarker: false,\n children: {\n Remarks: [\"rem\", \"sts\"],\n },\n },\n h: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Running header text for a book (basic)\",\n hasEndMarker: false,\n children: {\n Headers: [\"toc1\", \"toc2\", \"toc3\", \"toca1\", \"toca2\", \"toca3\"],\n },\n },\n h1: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Running header text\",\n hasEndMarker: false,\n children: {\n Headers: [\"toc1\", \"toc2\", \"toc3\", \"toca1\", \"toca2\", \"toca3\"],\n },\n },\n h2: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Running header text, left side of page\",\n hasEndMarker: false,\n children: {\n Headers: [\"toc1\", \"toc2\", \"toc3\", \"toca1\", \"toca2\", \"toca3\"],\n },\n },\n h3: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Running header text, right side of page\",\n hasEndMarker: false,\n children: {\n Headers: [\"toc1\", \"toc2\", \"toc3\", \"toca1\", \"toca2\", \"toca3\"],\n },\n },\n toc1: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Long table of contents text\",\n hasEndMarker: false,\n children: undefined,\n },\n toc2: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Short table of contents text\",\n hasEndMarker: false,\n children: undefined,\n },\n toc3: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Book Abbreviation\",\n hasEndMarker: false,\n children: undefined,\n },\n toca1: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Alternative language long table of contents text\",\n hasEndMarker: false,\n children: undefined,\n },\n toca2: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Alternative language short table of contents text\",\n hasEndMarker: false,\n children: undefined,\n },\n toca3: {\n category: CategoryType.Headers,\n type: MarkerType.Paragraph,\n description: \"Alternative language book Abbreviation\",\n hasEndMarker: false,\n children: undefined,\n },\n rem: {\n category: CategoryType.Remarks,\n type: MarkerType.Paragraph,\n description: \"Comments and remarks\",\n hasEndMarker: false,\n children: undefined,\n },\n sts: {\n category: CategoryType.Remarks,\n type: MarkerType.Paragraph,\n description: \"Status of this file\",\n hasEndMarker: false,\n children: undefined,\n },\n restore: {\n category: CategoryType.Remarks,\n type: MarkerType.Paragraph,\n description: \"Project restore information\",\n hasEndMarker: false,\n children: undefined,\n },\n imt: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title, level 1 (if single level) (basic)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imt1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imt2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imt3: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title, level 3\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imt4: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title, level 4 (usually within parenthesis)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imte: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title at introduction end, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imte1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title at introduction end, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n imte2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction major title at introduction end, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n is: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction section heading, level 1 (if single level) (basic)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n CharacterStyling: [\"no\"],\n },\n },\n is1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction section heading, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n is2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction section heading, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n SpecialText: [\"bk\"],\n },\n },\n iot: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline title (basic)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CharacterStyling: [\"no\"],\n },\n },\n io: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline text, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"ior\", \"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n io1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline text, level 1 (if multiple levels) (basic)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"ior\", \"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n io2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline text, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"ior\", \"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n io3: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline text, level 3\",\n hasEndMarker: false,\n children: {\n Introduction: [\"ior\", \"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n io4: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction outline text, level 4\",\n hasEndMarker: false,\n children: {\n Introduction: [\"ior\", \"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ior: {\n category: CategoryType.Introduction,\n type: MarkerType.Character,\n description: \"Introduction references range for outline entry; for marking references separately\",\n hasEndMarker: true,\n children: undefined,\n },\n ip: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph (basic)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n im: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph, with no first line indent (may occur after poetry)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ipi: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph, indented, with first line indent\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n imi: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph text, indented, with no first line indent\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ili: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ili1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ili2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ipq: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph, quote from the body text\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n imq: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph, quote from the body text, with no first line indent\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ipr: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction prose paragraph, right aligned\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ib: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction blank line\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n },\n },\n iq: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction poetry text, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n iq1: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction poetry text, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n iq2: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction poetry text, level 2\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n iq3: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction poetry text, level 3\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n iex: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction explanatory or bridge text (e.g. explanation of missing book in Short Old Testament)\",\n hasEndMarker: false,\n children: {\n Introduction: [\"iqt\"],\n CharacterStyling: [\"no\"],\n },\n },\n iqt: {\n category: CategoryType.Introduction,\n type: MarkerType.Character,\n description: \"For quoted scripture text appearing in the introduction\",\n hasEndMarker: true,\n children: undefined,\n },\n ie: {\n category: CategoryType.Introduction,\n type: MarkerType.Paragraph,\n description: \"Introduction ending marker\",\n hasEndMarker: false,\n children: undefined,\n },\n c: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Paragraph,\n description: \"Chapter number (necessary for normal Paratext operation)\",\n hasEndMarker: false,\n children: {\n DivisionMarks: [\"ca\", \"cp\", \"cl\", \"cd\"],\n Paragraphs: [\"p\", \"m\", \"po\", \"pr\", \"cls\", \"pi\", \"pi1\", \"pi2\", \"pi3\", \"pc\", \"mi\", \"nb\"],\n Poetry: [\"q\", \"q1\", \"q2\", \"q3\", \"q4\", \"qc\", \"qr\", \"qa\", \"qd\", \"b\"],\n TitlesHeadings: [\n \"mte\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"ms3\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"r\",\n \"sp\",\n \"d\",\n \"sd\",\n \"sd1\",\n \"sd2\",\n \"sd3\",\n \"sd4\",\n ],\n Lists: [\"lh\", \"li\", \"li1\", \"li2\", \"li3\", \"li4\", \"lf\", \"lim\", \"lim1\", \"lim2\", \"lim3\", \"lim4\"],\n Footnotes: [\"f\", \"fe\"],\n SpecialText: [\"lit\"],\n Breaks: [\"pb\"],\n },\n },\n ca: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Character,\n description: \"Second (alternate) chapter number (for coding dual versification; useful for places where different traditions of chapter breaks need to be supported in the same translation)\",\n hasEndMarker: true,\n children: undefined,\n },\n cp: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Paragraph,\n description: \"Published chapter number (chapter string that should appear in the published text)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\"],\n },\n },\n cl: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Paragraph,\n description: \"Chapter label used for translations that add a word such as 'Chapter' before chapter numbers (e.g. Psalms). The subsequent text is the chapter label.\",\n hasEndMarker: false,\n children: undefined,\n },\n cd: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Paragraph,\n description: \"Chapter Description (Publishing option D, e.g. in Russian Bibles)\",\n hasEndMarker: false,\n children: {\n DivisionMarks: [\"vp\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n v: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Character,\n description: \"A verse number (Necessary for normal paratext operation) (basic)\",\n hasEndMarker: false,\n children: undefined,\n },\n va: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Character,\n description: \"Second (alternate) verse number (for coding dual numeration in Psalms; see also NRSV Exo 22.1-4)\",\n hasEndMarker: true,\n children: undefined,\n },\n vp: {\n category: CategoryType.DivisionMarks,\n type: MarkerType.Character,\n description: \"Published verse marker (verse string that should appear in the published text)\",\n hasEndMarker: true,\n children: undefined,\n },\n p: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, with first line indent (basic)\",\n hasEndMarker: false,\n children: {\n Paragraphs: [\"pmo\", \"pm\", \"pmc\", \"pmr\"],\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n m: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, with no first line indent (may occur after poetry) (basic)\",\n hasEndMarker: false,\n children: {\n Paragraphs: [\"pmo\", \"pm\", \"pmc\", \"pmr\"],\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n po: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Letter opening\",\n hasEndMarker: false,\n children: {\n Paragraphs: [\"pmo\", \"pm\", \"pmc\", \"pmr\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pr: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Text refrain (paragraph text, right aligned)\",\n hasEndMarker: false,\n children: {\n Paragraphs: [\"pmo\", \"pm\", \"pmc\", \"pmr\"],\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n cls: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Letter Closing\",\n hasEndMarker: false,\n children: {\n SpecialText: [\"tl\", \"sig\", \"pn\", \"png\", \"addpn\", \"add\"],\n },\n },\n pmo: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Embedded text opening\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pm: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Embedded text paragraph\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pmc: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Embedded text closing\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pmr: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Embedded text refrain (e.g. Then all the people shall say, 'Amen!')\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pi: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, level 1 indent (if single level), with first line indent; often used for discourse (basic)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pi1: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, level 1 indent (if multiple levels), with first line indent; often used for discourse\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pi2: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, level 2 indent, with first line indent; often used for discourse\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pi3: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, level 3 indent, with first line indent; often used for discourse\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n pc: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, centered (for Inscription)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n mi: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, indented, with no first line indent; often used for discourse\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n nb: {\n category: CategoryType.Paragraphs,\n type: MarkerType.Paragraph,\n description: \"Paragraph text, with no break from previous paragraph text (at chapter boundary) (basic)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n q: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, level 1 indent (if single level)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n q1: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, level 1 indent (if multiple levels) (basic)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n q2: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, level 2 indent (basic)\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n q3: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, level 3 indent\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n q4: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, level 4 indent\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qc: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, centered\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qr: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, Right Aligned\",\n hasEndMarker: false,\n children: {\n Poetry: [\"qs\", \"qac\", \"qm\", \"qm1\", \"qm2\", \"qm3\"],\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qs: {\n category: CategoryType.Poetry,\n type: MarkerType.Character,\n description: \"Poetry text, Selah\",\n hasEndMarker: true,\n children: {\n Footnotes: [\"f\"],\n CrossReferences: [\"x\"],\n },\n },\n qa: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, Acrostic marker/heading\",\n hasEndMarker: false,\n children: undefined,\n },\n qac: {\n category: CategoryType.Poetry,\n type: MarkerType.Character,\n description: \"Poetry text, Acrostic markup of the first character of a line of acrostic poetry\",\n hasEndMarker: true,\n children: undefined,\n },\n qm: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, embedded, level 1 indent (if single level)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qm1: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, embedded, level 1 indent (if multiple levels)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qm2: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, embedded, level 2 indent\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qm3: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text, embedded, level 3 indent\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n qd: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"A Hebrew musical performance annotation, similar in content to Hebrew descriptive title.\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte1\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n b: {\n category: CategoryType.Poetry,\n type: MarkerType.Paragraph,\n description: \"Poetry text stanza break (e.g. stanza break) (basic)\",\n hasEndMarker: false,\n children: undefined,\n },\n mt: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"The main title of the book (if single level)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\"],\n CrossReferences: [\"x\"],\n },\n },\n mt1: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"The main title of the book (if multiple levels) (basic)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\"],\n CrossReferences: [\"x\"],\n },\n },\n mt2: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A secondary title usually occurring before the main title (basic)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\"],\n CrossReferences: [\"x\"],\n },\n },\n mt3: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A secondary title occurring after the main title\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\"],\n CrossReferences: [\"x\"],\n },\n },\n mt4: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A small secondary title sometimes occurring within parentheses\",\n hasEndMarker: false,\n children: undefined,\n },\n mte: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"The main title of the book repeated at the end of the book, level 1 (if single level)\",\n hasEndMarker: false,\n children: undefined,\n },\n mte1: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"The main title of the book repeated at the end of the book, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mte2\"],\n },\n },\n mte2: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A secondary title occurring before or after the 'ending' main title\",\n hasEndMarker: false,\n children: undefined,\n },\n ms: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A major section division heading, level 1 (if single level) (basic)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mr\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ms1: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A major section division heading, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mr\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ms2: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A major section division heading, level 2\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mr\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n ms3: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A major section division heading, level 3\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"mr\"],\n Footnotes: [\"f\", \"fe\"],\n },\n },\n mr: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A major section division references range heading (basic)\",\n hasEndMarker: false,\n children: undefined,\n },\n s: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section heading, level 1 (if single level) (basic)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"sr\", \"r\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n s1: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section heading, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"sr\", \"r\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n s2: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section heading, level 2 (e.g. Proverbs 22-24)\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"sr\", \"r\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n s3: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section heading, level 3 (e.g. Genesis 'The First Day')\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"sr\", \"r\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"no\", \"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n s4: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section heading, level 4\",\n hasEndMarker: false,\n children: {\n TitlesHeadings: [\"sr\", \"r\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n sr: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A section division references range heading\",\n hasEndMarker: false,\n children: undefined,\n },\n r: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Parallel reference(s) (basic)\",\n hasEndMarker: false,\n children: undefined,\n },\n sp: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A heading, to identify the speaker (e.g. Job)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n d: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"A Hebrew text heading, to provide description (e.g. Psalms)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n sd: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Vertical space used to divide the text into sections, level 1 (if single level)\",\n hasEndMarker: false,\n children: undefined,\n },\n sd1: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Vertical space used to divide the text into sections, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: undefined,\n },\n sd2: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Vertical space used to divide the text into sections, level 2\",\n hasEndMarker: false,\n children: undefined,\n },\n sd3: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Vertical space used to divide the text into sections, level 3\",\n hasEndMarker: false,\n children: undefined,\n },\n sd4: {\n category: CategoryType.TitlesHeadings,\n type: MarkerType.Paragraph,\n description: \"Vertical space used to divide the text into sections, level 4\",\n hasEndMarker: false,\n children: undefined,\n },\n lh: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"List header (introductory remark)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n li: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n li1: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n li2: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 2\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n li3: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 3\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n li4: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"A list entry, level 4\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lf: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"List footer (concluding remark)\",\n hasEndMarker: false,\n children: {\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lim: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"An embedded list entry, level 1 (if single level)\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lim1: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"An embedded list entry, level 1 (if multiple levels)\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lim2: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"An embedded list entry, level 2\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lim3: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"An embedded list item, level 3\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n lim4: {\n category: CategoryType.Lists,\n type: MarkerType.Paragraph,\n description: \"An embedded list entry, level 4\",\n hasEndMarker: false,\n children: {\n Lists: [\"litl\", \"lik\", \"liv\", \"liv1\", \"liv2\", \"liv3\", \"liv4\", \"liv5\"],\n Footnotes: [\"f\", \"fe\", \"fm\"],\n CrossReferences: [\"x\", \"xt\", \"rq\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n litl: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"List entry total text\",\n hasEndMarker: true,\n children: undefined,\n },\n lik: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry key text\",\n hasEndMarker: true,\n children: undefined,\n },\n liv: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 1 content (if single value)\",\n hasEndMarker: true,\n children: undefined,\n },\n liv1: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 1 content (if multiple values)\",\n hasEndMarker: true,\n children: undefined,\n },\n liv2: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 2 content\",\n hasEndMarker: true,\n children: undefined,\n },\n liv3: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 3 content\",\n hasEndMarker: true,\n children: undefined,\n },\n liv4: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 4 content\",\n hasEndMarker: true,\n children: undefined,\n },\n liv5: {\n category: CategoryType.Lists,\n type: MarkerType.Character,\n description: \"Structured list entry value 5 content\",\n hasEndMarker: true,\n children: undefined,\n },\n f: {\n category: CategoryType.Footnotes,\n type: MarkerType.Note,\n description: \"A Footnote text item (basic)\",\n hasEndMarker: true,\n children: {\n Footnotes: [\"fr\", \"ft\", \"fk\", \"fq\", \"fqa\", \"fl\", \"fw\", \"fp\", \"fv\", \"fdc\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n fe: {\n category: CategoryType.Footnotes,\n type: MarkerType.Note,\n description: \"An Endnote text item\",\n hasEndMarker: true,\n children: {\n Footnotes: [\"fr\", \"ft\", \"fk\", \"fq\", \"fqa\", \"fl\", \"fw\", \"fp\", \"fv\", \"fdc\"],\n CrossReferences: [\"xt\"],\n SpecialText: [\n \"qt\",\n \"nd\",\n \"tl\",\n \"dc\",\n \"bk\",\n \"sig\",\n \"pn\",\n \"png\",\n \"addpn\",\n \"wj\",\n \"k\",\n \"sls\",\n \"ord\",\n \"add\",\n ],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n fr: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"The origin reference for the footnote (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n ft: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"Footnote text, Protocanon (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n fk: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A footnote keyword (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n fq: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A footnote scripture quote or alternate rendering (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n fqa: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A footnote alternate rendering for a portion of scripture text\",\n hasEndMarker: true,\n children: undefined,\n },\n fl: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A footnote label text item, for marking or 'labelling' the type or alternate translation being provided in the note.\",\n hasEndMarker: true,\n children: undefined,\n },\n fw: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A footnote witness list, for distinguishing a list of sigla representing witnesses in critical editions.\",\n hasEndMarker: true,\n children: undefined,\n },\n fp: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A Footnote additional paragraph marker\",\n hasEndMarker: true,\n children: undefined,\n },\n fv: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"A verse number within the footnote text\",\n hasEndMarker: true,\n children: undefined,\n },\n fdc: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"Footnote text, applies to Deuterocanon only\",\n hasEndMarker: true,\n children: undefined,\n },\n fm: {\n category: CategoryType.Footnotes,\n type: MarkerType.Character,\n description: \"An additional footnote marker location for a previous footnote\",\n hasEndMarker: true,\n children: undefined,\n },\n x: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Note,\n description: \"A list of cross references (basic)\",\n hasEndMarker: true,\n children: {\n CrossReferences: [\"xo\", \"xop\", \"xt\", \"xta\", \"xk\", \"xq\", \"xot\", \"xnt\", \"xdc\"],\n CharacterStyling: [\"it\", \"bd\", \"bdit\", \"em\", \"sc\", \"sup\"],\n },\n },\n xo: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"The cross reference origin reference (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n xop: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"Published cross reference origin reference (origin reference that should appear in the published text)\",\n hasEndMarker: true,\n children: undefined,\n },\n xt: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"The cross reference target reference(s), protocanon only (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n xta: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"Cross reference target references added text\",\n hasEndMarker: true,\n children: undefined,\n },\n xk: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"A cross reference keyword\",\n hasEndMarker: true,\n children: undefined,\n },\n xq: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"A cross-reference quotation from the scripture text\",\n hasEndMarker: true,\n children: undefined,\n },\n xot: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"Cross-reference target reference(s), Old Testament only\",\n hasEndMarker: true,\n children: undefined,\n },\n xnt: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"Cross-reference target reference(s), New Testament only\",\n hasEndMarker: true,\n children: undefined,\n },\n xdc: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"Cross-reference target reference(s), Deuterocanon only\",\n hasEndMarker: true,\n children: undefined,\n },\n rq: {\n category: CategoryType.CrossReferences,\n type: MarkerType.Character,\n description: \"A cross-reference indicating the source text for the preceding quotation.\",\n hasEndMarker: true,\n children: undefined,\n },\n qt: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For Old Testament quoted text appearing in the New Testament (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n nd: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For name of deity (basic)\",\n hasEndMarker: true,\n children: undefined,\n },\n tl: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For transliterated words\",\n hasEndMarker: true,\n children: undefined,\n },\n dc: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"Deuterocanonical/LXX additions or insertions in the Protocanonical text\",\n hasEndMarker: true,\n children: undefined,\n },\n bk: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For the quoted name of a book\",\n hasEndMarker: true,\n children: undefined,\n },\n sig: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For the signature of the author of an Epistle\",\n hasEndMarker: true,\n children: undefined,\n },\n pn: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For a proper name\",\n hasEndMarker: true,\n children: undefined,\n },\n png: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For a geographic proper name\",\n hasEndMarker: true,\n children: undefined,\n },\n addpn: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For chinese words to be dot underline & underline\",\n hasEndMarker: true,\n children: undefined,\n },\n wj: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For marking the words of Jesus\",\n hasEndMarker: true,\n children: undefined,\n },\n k: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For a keyword\",\n hasEndMarker: true,\n children: undefined,\n },\n sls: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"To represent where the original text is in a secondary language or from an alternate text source\",\n hasEndMarker: true,\n children: undefined,\n },\n ord: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For the text portion of an ordinal number\",\n hasEndMarker: true,\n children: undefined,\n },\n add: {\n category: CategoryType.SpecialText,\n type: MarkerType.Character,\n description: \"For a translational addition to the text\",\n hasEndMarker: true,\n children: undefined,\n },\n lit: {\n category: CategoryType.SpecialText,\n type: MarkerType.Paragraph,\n description: \"For a comment or note inserted for liturgical use\",\n hasEndMarker: false,\n children: undefined,\n },\n no: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, use normal text\",\n hasEndMarker: true,\n children: undefined,\n },\n it: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, use italic text\",\n hasEndMarker: true,\n children: undefined,\n },\n bd: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, use bold text\",\n hasEndMarker: true,\n children: undefined,\n },\n bdit: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, use bold + italic text\",\n hasEndMarker: true,\n children: undefined,\n },\n em: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, use emphasized text style\",\n hasEndMarker: true,\n children: undefined,\n },\n sc: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, for small capitalization text\",\n hasEndMarker: true,\n children: undefined,\n },\n sup: {\n category: CategoryType.CharacterStyling,\n type: MarkerType.Character,\n description: \"A character style, for superscript text. Typically for use in critical edition footnotes.\",\n hasEndMarker: true,\n children: undefined,\n },\n pb: {\n category: CategoryType.Breaks,\n type: MarkerType.Paragraph,\n description: \"Page Break used for new reader portions and children's bibles where content is controlled by the page\",\n hasEndMarker: false,\n children: undefined,\n },\n};\n","import { CategoryType, MarkerType } from \"./usfmTypes.js\";\nconst paragraphChildren = {\n DivisionMarks: { add: [\"v\", \"c\"], remove: [] },\n Paragraphs: { add: [\"p\"], remove: [] },\n Poetry: { add: [\"q\", \"q1\", \"q2\", \"q3\", \"q4\", \"b\"], remove: [] },\n TitlesHeadings: {\n add: [\n \"mte\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"ms3\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"r\",\n \"sp\",\n \"d\",\n \"sd\",\n \"sd1\",\n \"sd2\",\n \"sd3\",\n \"sd4\",\n ],\n remove: [],\n },\n};\nconst usfmMarkersOverwrites = {\n p: { children: paragraphChildren },\n q: { children: paragraphChildren },\n q1: { children: paragraphChildren },\n q2: { children: paragraphChildren },\n q3: { children: paragraphChildren },\n q4: { children: paragraphChildren },\n b: { children: paragraphChildren },\n qm: {\n children: {\n Paragraphs: { add: [\"p\"], remove: [] },\n },\n },\n c: {\n type: MarkerType.Paragraph,\n children: null,\n },\n v: {\n children: null,\n },\n // The following are attribute-bearing character markers present in usfm.sty and in\n // CharNode's VALID_CHAR_MARKERS, but absent from the generated usfmMarkers data.\n // They are defined here as complete entries rather than hand-edited into the\n // generated file, which would be silently lost on regeneration.\n w: {\n category: CategoryType.SpecialFeatures,\n type: MarkerType.Character,\n description: \"A wordlist/glossary/dictionary entry marker for study/analysis purposes\",\n hasEndMarker: true,\n },\n rb: {\n category: CategoryType.SpecialFeatures,\n type: MarkerType.Character,\n description: \"A ruby glossing marker for study/analysis purposes\",\n hasEndMarker: true,\n },\n jmp: {\n category: CategoryType.SpecialFeatures,\n type: MarkerType.Character,\n description: \"A hyperlink marker for study/analysis purposes\",\n hasEndMarker: true,\n },\n // The generated table has no `fig`, but `usfm.sty` does (and so does the stylesheet data every\n // project supplies). Without an entry here, a document parsed BEFORE its project stylesheet\n // resolves falls back to this table, reads `\\fig` as an unknown marker, and breaks the figure\n // into its own paragraph with the closer stranded as unmatched.\n fig: {\n category: CategoryType.SpecialFeatures,\n type: MarkerType.Character,\n description: \"Illustration [Columns to span, height, filename, caption text]\",\n hasEndMarker: true,\n },\n};\nexport default usfmMarkersOverwrites;\n","import { usfmMarkers } from \"./usfmMarkers.js\";\nimport usfmMarkersOverwrites from \"./usfmMarkersOverwrites.js\";\n//NOTE: We can make this reusable if we agree on a common usfmMarkers object for all editors and use the overwrites objects as a parameter for this function.\nfunction getMarker(marker) {\n // Own-property lookups: both tables are plain object literals, so a bare index resolves\n // Object.prototype members — a marker named `constructor`, `toString`, `__proto__`, … answered\n // with a Function, which spreads to a truthy Marker carrying no category/type/hasEndMarker.\n // Callers read a truthy result as \"this marker is known\", so a stray `\\constructor` typed by a\n // user classified as a real marker with an undefined type. (`createMarkerLookup` in\n // styleInfo.ts guards its own table the same way.)\n const baseMarker = Object.hasOwn(usfmMarkers, marker) ? usfmMarkers[marker] : undefined;\n const overwrite = Object.hasOwn(usfmMarkersOverwrites, marker)\n ? usfmMarkersOverwrites[marker]\n : undefined;\n if (!baseMarker) {\n // The overwrites file can ADD markers the generated data lacks — but with no base to fill\n // gaps, only an overwrite carrying the FULL required Marker shape may stand alone. A partial\n // overwrite (e.g. `type` set but no category/description/hasEndMarker) is not a valid Marker,\n // so the `as Marker` cast would lie about it; refuse rather than return a malformed object.\n if (overwrite?.category !== undefined &&\n overwrite.type !== undefined &&\n overwrite.description !== undefined &&\n overwrite.hasEndMarker !== undefined)\n // A COPY: the table entry is module-level shared state, and handing it out lets any caller\n // that edits the returned Marker corrupt that marker for the whole process.\n return { ...overwrite };\n return undefined;\n }\n if (!overwrite) {\n return baseMarker;\n }\n let mergedChildren = baseMarker.children ? { ...baseMarker.children } : undefined;\n if (overwrite.children === null) {\n mergedChildren = undefined;\n }\n if (overwrite.children) {\n mergedChildren = mergedChildren || {};\n for (const [category, modification] of Object.entries(overwrite.children)) {\n const categoryType = category;\n if (modification === null) {\n // Remove the entire category if it exists\n Reflect.deleteProperty(mergedChildren, categoryType);\n }\n else {\n // Update children for this category\n let currentChildren = mergedChildren[categoryType] || [];\n if (modification.remove) {\n currentChildren = currentChildren.filter((child) => !modification.remove.includes(child));\n }\n if (modification.add) {\n currentChildren = [...new Set([...currentChildren, ...modification.add])];\n }\n if (currentChildren.length > 0) {\n mergedChildren[categoryType] = currentChildren;\n }\n else {\n Reflect.deleteProperty(mergedChildren, categoryType);\n }\n }\n }\n // If mergedChildren is empty, set it to undefined\n if (Object.keys(mergedChildren).length === 0) {\n mergedChildren = undefined;\n }\n }\n return {\n ...baseMarker,\n ...overwrite,\n children: mergedChildren,\n };\n}\nexport default getMarker;\n","/**\n * StyleInfo-driven USFM fragment tokenizer for Tier 2 paragraph re-tokenization.\n * Reference semantics: ParatextData `UsfmToken.Tokenize` —\n * fragment-level tokenization only; document-level validation stays out. Marker\n * kinds come from the bundled usfm.sty-derived `usfmMarkers` map via `getMarker`\n * by default, or from `options.getMarker` (a project `StyleInfo`-backed\n * `MarkerLookup`) when supplied — classification is stylesheet-first:\n * a marker the lookup KNOWS is classified by its declared type, full stop.\n *\n * Markers the effective stylesheet does NOT know (`kind === undefined`) fall\n * back to name-pattern heuristics — `NoteNode.isValidMarker`, and for\n * milestones only the stylesheet-family names (`\\qt#-s/-e`, `\\ts-s/-e`, plus\n * the `zmsc-*` comment markers; see `isMilestoneHeuristicName`) — and\n * failing those, resolve by context exactly like PT9's\n * `UsfmParser.DetermineUnknownTokenType`: PARAGRAPH in body text, CHARACTER\n * inside a note (`options.isNoteContext`), except `esb`/`esbe`, which PT9\n * always treats as paragraphs (`UsfmToken.cs:405-421`). An unmatched closer\n * (known or unknown marker, no open frame to close) becomes a\n * `{ type: \"unmatched\", marker: \"*\" }` USJ object (PT9 `sink.Unmatched`,\n * `UsxUsfmParserSink.cs:262-266`), not literal text.\n *\n * Literal-text degradation remains only for fragments the\n * tokenizer cannot confidently parse at all: a bare `\\`, or an unterminated\n * milestone (stylesheet-declared, or matching the suffix convention above).\n * Content between a milestone's marker and its `\\*` is NOT literal: a\n * milestone cannot hold content, so it ends where the content begins, the\n * content follows it as a sibling, and the author's `\\*` becomes an unmatched\n * element. A stray `\\*` with no milestone to close is NOT literal either — it\n * becomes an unmatched element (see above).\n *\n * Figures, tables, sidebars, and peripheral divisions assemble to their faithful\n * USJ shapes at the assembly level, marker-name driven (they are parser-level\n * structures in ParatextData, independent of stylesheet classification):\n * `\\fig …\\fig*` folds to an inline `figure` object (USFM's `src` attribute\n * renamed to USX/USJ's `file`), `\\tr` plus `t[hc][rc]#(-#)` cell markers build\n * `table` → `table:row` → `table:cell` with name-derived `align`/`colspan`,\n * `\\esb`…`\\esbe` wraps the following blocks in a `sidebar` (`\\cat` directly\n * after `\\esb` folds to its `category`), and `\\periph` opens a `periph`\n * division that takes every following block until the next `\\periph`, a\n * chapter, or the fragment end — its marker line splitting at the first `|`\n * into the division title (`alt`) and an ordinary attribute list. Anything off\n * the clean shapes — nested markup or positional (USFM 2.0) attributes in a\n * figure, a missing `\\fig*`, a cell marker with no open row, a periph\n * attribute list that does not parse — degrades to the plain char/para output\n * the marker classification produces on its own.\n *\n * Input is USFM text: `~` means NBSP; U+FFFC sentinels (atomic-node placeholders\n * from the Tier 2 fragment builder) ride through as ordinary text characters.\n */\nimport { NBSP, PARA_MARKER_DEFAULT, ZWSP } from \"../../nodes/usj/node-constants.js\";\nimport { isMilestoneCommentMarker } from \"../../nodes/usj/MilestoneNode.js\";\nimport { NoteNode } from \"../../nodes/usj/NoteNode.js\";\nimport getMarker from \"../../utils/usfm/getMarker.js\";\nimport { MarkerType } from \"../../utils/usfm/usfmTypes.js\";\nconst VERSE_MARKER = \"v\";\nconst CHAPTER_MARKER = \"c\";\nconst FIGURE_MARKER = \"fig\";\nconst TABLE_ROW_MARKER = \"tr\";\nconst SIDEBAR_MARKER = \"esb\";\nconst SIDEBAR_END_MARKER = \"esbe\";\nconst PERIPH_MARKER = \"periph\";\n/** The USJ property a periph's marker-line title lands on — the same name a pipe pair would use,\n * which is why a line carrying both spellings is refused rather than resolved. */\nconst PERIPH_TITLE_ATTRIBUTE = \"alt\";\n/**\n * Table cell marker names: `t` + header/cell (`h`/`c`) + optional alignment infix (`r`/`c`) +\n * starting column + optional span end column (`th1`, `tc13`, `thr5`, `thc3-4`, `tcr1-4`).\n * ParatextData derives the whole cell shape from the name alone — no stylesheet entry needed.\n */\nconst TABLE_CELL_MARKER_REGEX = /^t[hc]([rc]?)(\\d+)(?:-(\\d+))?$/;\n/** Cell alignment from the marker-name infix after `t[hc]`: `th1`/`tc1` → start,\n * `thc1`/`tcc1` → center, `thr1`/`tcr1` → end (ParatextData's name-derived alignment). */\nconst TABLE_CELL_ALIGN_BY_INFIX = {\n \"\": \"start\",\n c: \"center\",\n r: \"end\",\n};\n/**\n * Whether a cell-marker name match is a cell ParatextData recognizes. A rangeless cell\n * covers columns 1–12 spelled with no leading zeros: usfm.sty declares exactly\n * th1–th12/tc1–tc12 (and their r/c variants) and ParatextData's tag lookup is by LITERAL\n * name (`ScrStylesheet.GetTagIndex` is a string-keyed dictionary, no numeric parse), so\n * `\\tc13` and `\\tc01` are both unknown markers. A RANGED cell is narrower: ParatextData's\n * range test (`ScrStylesheet.IsCellRange`, regex `^(t[ch][cr]?[1-5])-([2-5])$` plus\n * `colSpan >= 2`) takes only single-digit spans that start in columns 1–5, end in 2–5, and\n * grow left-to-right — `\\thc4-2` (reversed), `\\tc2-2` (no growth), and `\\thc11-13`\n * (multi-digit columns) are all unknown markers too.\n */\nfunction isRecognizedTableCell(cellMatch) {\n const [, , spanStart, spanEnd] = cellMatch;\n if (!spanEnd)\n return /^(?:[1-9]|1[0-2])$/.test(spanStart);\n return (/^[1-5]$/.test(spanStart) && /^[2-5]$/.test(spanEnd) && Number(spanEnd) > Number(spanStart));\n}\n/**\n * PT9 `UsfmToken.IsNonSemanticWhiteSpace`: everything .NET counts as whitespace EXCEPT IDEOGRAPHIC\n * SPACE (U+3000), plus ZWSP (U+200B). U+3000 is excluded so it survives as authored content, and\n * ZWJ/ZWNJ are deliberately absent — Paratext treats them as content, not spacing.\n */\nfunction isNonSemanticWhiteSpace(ch) {\n const code = ch.charCodeAt(0);\n // TAB, LF, VT, FF, CR — the control characters .NET counts as whitespace\n if (code >= 0x09 && code <= 0x0d)\n return true;\n return (code === 0x20 || // SPACE\n code === 0x85 || // NEXT LINE\n code === 0xa0 || // NO-BREAK SPACE\n code === 0x1680 || // OGHAM SPACE MARK\n (code >= 0x2000 && code <= 0x200a) || // EN QUAD through HAIR SPACE\n code === 0x2028 || // LINE SEPARATOR\n code === 0x2029 || // PARAGRAPH SEPARATOR\n code === 0x202f || // NARROW NO-BREAK SPACE\n code === 0x205f || // MEDIUM MATHEMATICAL SPACE\n code === 0x200b // ZERO WIDTH SPACE\n );\n}\n/**\n * PT9 `CharExtensions.IsInvisibleCharOrWhitespace`: the invisible characters Paratext supports\n * explicitly. Used only to collapse a character repeated immediately after itself.\n */\nconst INVISIBLE_CHAR_OR_WHITESPACE = /[\\u200D\\u2003\\u2002\\u0020\\u00A0\\u202F\\u2009\\u200A\\u3000\\u200B\\u200C\\u2060\\u200E\\u200F]/;\n/**\n * Collapse whitespace runs (PT9 `UsfmToken.RegularizeSpaces`); keep U+FFFC.\n *\n * A run collapses to its FIRST character kept verbatim, not to a plain space: NBSP stays NBSP and a\n * ZWSP between two words stays ZWSP. That distinction is not cosmetic — ZWSP is the word-break\n * character in Thai, Khmer, and Lao, so flattening it to a space would rewrite those texts on every\n * settle. `platform-bible-utils`' `normalizeScriptureSpaces` is the same algorithm; the two are\n * independent ports of one Paratext function and must agree.\n *\n * The one deliberate departure from PT9: a run containing a line break collapses to `\"\\n\"` rather\n * than `\" \"`, so the attribute-marker fold logic can tell STRUCTURAL whitespace (line-end, folds\n * across: `\\ca 1 ca\\ca*` ⏎ `\\cp 1 cp`) from same-line spaces (content per Paratext, blocking the\n * fold: `\\va 12 va\\va* \\vp` keeps vp standalone). Content text normalizes the `\"\\n\"` back to `\" \"`\n * in `toUsjText`.\n *\n * Exported so the Paratext test vectors it must reproduce can be asserted directly, without the\n * marker-boundary handling of a full fragment parse in between.\n */\nexport function regularizeSpaces(text) {\n let result = \"\";\n let lastCharWasSpace = false;\n // Placeholder previous character, so the first iteration cannot report a duplicate\n let prevCh = \"\\0\";\n // Where the character standing in for the run being collapsed was written, so a line break\n // arriving later in the same run can promote it to \"\\n\" after the fact\n let runIndex = -1;\n for (let i = 0; i < text.length; i += 1) {\n const ch = text[i];\n if (ch.charCodeAt(0) < 32) {\n // Control characters, CR/LF, and TAB become spaces\n if (!lastCharWasSpace) {\n runIndex = result.length;\n result += \" \";\n }\n lastCharWasSpace = true;\n }\n else if (!lastCharWasSpace &&\n ch === ZWSP &&\n i + 1 < text.length &&\n isNonSemanticWhiteSpace(text[i + 1])) {\n // ZWSP is redundant when a space follows it\n }\n else if (isNonSemanticWhiteSpace(ch)) {\n // Keep the run's first space exactly as authored\n if (!lastCharWasSpace) {\n runIndex = result.length;\n result += ch;\n }\n lastCharWasSpace = true;\n }\n else if (INVISIBLE_CHAR_OR_WHITESPACE.test(ch) && ch === prevCh) {\n // An invisible character repeated immediately collapses to one\n }\n else {\n result += ch;\n lastCharWasSpace = false;\n runIndex = -1;\n }\n if ((ch === \"\\n\" || ch === \"\\r\") && runIndex >= 0) {\n result = `${result.slice(0, runIndex)}\\n${result.slice(runIndex + 1)}`;\n }\n prevCh = ch;\n }\n return result;\n}\n/**\n * The tokenize-identity predicate for an engine-owned separator: whether deleting the structural\n * space that ends a marker's name leaves the surrounding bytes tokenizing IDENTICALLY, given the\n * bytes that follow the separator. Defined here, beside {@link scanMarkerName}, because it IS that\n * scan's rule read backwards — the two must never drift.\n *\n * | Following bytes | Without the separator | Same meaning? |\n * | --- | --- | --- |\n * | `\\wj stuff` | `\\nd\\wj stuff` | yes — the name scan stops at `\\` either way. Heal. |\n * | `\\|x=\"y\"` | `\\nd\\|x=\"y\"` | yes — the name scan stops at `\\|` either way. Heal. |\n * | `things` | `\\ndthings` | no — the marker is now `ndthings`. Rename. |\n * | `*stuff` | `\\nd*stuff` | no — that is a CLOSING marker. Do not heal. |\n *\n * The `*` row is why this is defined by MEANING and not by a terminator character class: `*` is\n * one of the scan's terminators, but it ends the token AND changes what the token is, so an\n * allowlist built from terminators would wrongly heal it and silently prevent the user from\n * typing a closer.\n *\n * The question is local — it depends only on the first following byte — so callers get an O(1)\n * answer for the common shapes and fall back to a scoped re-tokenization only when this returns\n * `false` (the bytes then genuinely mean something new).\n *\n * @param followingBytes - The displayed bytes immediately after the separator's site.\n * @returns `true` when removing the separator cannot change the token stream.\n */\nexport function separatorRemovalTokenizesIdentically(followingBytes) {\n if (followingBytes.length === 0)\n return true;\n const ch = followingBytes[0];\n if (ch === \"*\")\n return false; // completes a closing marker\n if (ch === \"\\\\\" || ch === \"|\")\n return true; // the name scan stops here either way\n if (/[\\s\\u200B]/.test(ch))\n return true; // more whitespace: the run still separates\n return false; // a name character: the marker name grows through it\n}\n/** Marker name chars per PT9 scan: stop at `\\`, `|`, whitespace; `*` ends and is included. */\nfunction scanMarkerName(fragment, start) {\n let index = start;\n while (index < fragment.length) {\n const ch = fragment[index];\n if (ch === \"\\\\\" || ch === \"|\")\n break;\n if (ch === \"*\") {\n index++;\n break;\n }\n if (/[\\s\\u200B]/.test(ch))\n break;\n index++;\n }\n return { name: fragment.slice(start, index), next: index };\n}\n/** Milestone names the Paratext stylesheet family declares: `\\qt-s/-e`, `\\qt1-s`…`\\qt5-e`,\n * `\\ts-s/-e`. */\nconst STYLESHEET_MILESTONE_NAME_REGEX = /^(?:qt[1-5]?|ts)-[se]$/;\n/**\n * Milestone-name heuristic for markers ABSENT from the effective stylesheet: only names the\n * Paratext stylesheet family actually declares as milestones, plus the editor's own annotation\n * comment markers — so the heuristic predicts what ParatextData's stylesheet-driven parse\n * produces for the same bytes. Deliberately EXCLUDED: bare `ts`, `t-s`, `t-e` — syntactically\n * valid milestones, but no stylesheet declares them, so ParatextData parses them as unknown\n * markers (paragraph in body text) and any milestone produced here would flip to that shape on\n * the next chapter read. Also excluded: `MilestoneNode.isValidMarker`'s generic `z`-prefix\n * wildcard, which would classify any custom.sty-style marker (e.g. `\\zfoo`) as a milestone and\n * keep unknown-marker resolution (`DetermineUnknownTokenType`) from ever seeing it. A project\n * that declares any of these in custom.sty gets them classified stylesheet-first with no code\n * change.\n */\nexport function isMilestoneHeuristicName(name) {\n return STYLESHEET_MILESTONE_NAME_REGEX.test(name) || isMilestoneCommentMarker(name);\n}\n/** PT9 `GetNextWord`: skip leading whitespace, take up to whitespace or `\\`. */\nfunction getNextWord(fragment, start) {\n let index = start;\n while (index < fragment.length && /[\\s\\u00A0\\u200B]/.test(fragment[index]))\n index++;\n const wordStart = index;\n while (index < fragment.length && !/[\\s\\u00A0\\u200B\\\\]/.test(fragment[index]))\n index++;\n const word = fragment.slice(wordStart, index);\n while (index < fragment.length && /[\\s\\u00A0\\u200B]/.test(fragment[index]))\n index++;\n return { word, next: index };\n}\nfunction tokenize(fragment, getMarkerFn, isNoteContext) {\n const tokens = [];\n let index = 0;\n // PT9 resolves an unknown marker against the OPEN ELEMENT STACK, not against the fragment it\n // started in (`State.Stack.Exists(e => e.Type == Note)`, UsfmParser.cs). A note opened inside\n // this fragment puts one on that stack just as surely as being handed note content does, so\n // track it: without this, an unknown marker inside `\\f ... \\f*` classifies as a PARAGRAPH and\n // tears the note in half, stranding its closer.\n let openNoteMarker;\n const pushText = (text) => {\n if (!text)\n return;\n const prev = tokens[tokens.length - 1];\n if (prev?.kind === \"text\")\n prev.text += text;\n else\n tokens.push({ kind: \"text\", text });\n };\n // `//` mid-text is USFM's discretionary line break (PT9 tokenizes it wherever it appears,\n // spec-blind — even inside a URL); the surrounding text is kept byte-exact.\n const pushTextWithOptbreaks = (text) => {\n const parts = text.split(\"//\");\n parts.forEach((part, partIndex) => {\n if (partIndex > 0)\n tokens.push({ kind: \"optbreak\" });\n pushText(part);\n });\n };\n while (index < fragment.length) {\n if (fragment[index] !== \"\\\\\") {\n const nextMarker = fragment.indexOf(\"\\\\\", index);\n const end = nextMarker === -1 ? fragment.length : nextMarker;\n pushTextWithOptbreaks(regularizeSpaces(fragment.slice(index, end)));\n index = end;\n continue;\n }\n const rawStart = index;\n const { name, next } = scanMarkerName(fragment, index + 1);\n index = next;\n if (name === \"\") {\n // Bare `\\` — literal (degradation property).\n pushText(fragment.slice(rawStart, index));\n continue;\n }\n if (name === \"*\") {\n // Stray `\\*` with no milestone to close (milestone closes are consumed by\n // scanMilestone): PT9 sink.Unmatched — route through the end-token path so it becomes\n // an `{ type: \"unmatched\", marker: \"*\" }` object, which serializes back to `\\*`.\n tokens.push({ kind: \"end\", marker: \"\" });\n continue;\n }\n if (name.endsWith(\"*\")) {\n if (name.slice(0, -1) === openNoteMarker)\n openNoteMarker = undefined;\n tokens.push({ kind: \"end\", marker: name.slice(0, -1) });\n continue;\n }\n // Consume the separator whitespace after an opening marker (PT9 skips it) — all leading\n // whitespace, not just a single space.\n const consumeSeparator = () => {\n while (index < fragment.length && /[\\s\\u00A0\\u200B]/.test(fragment[index]))\n index++;\n };\n if (name === VERSE_MARKER) {\n const { word, next: afterWord } = getNextWord(fragment, index);\n index = afterWord;\n tokens.push({ kind: \"verse\", number: word });\n continue;\n }\n if (name === CHAPTER_MARKER) {\n const { word, next: afterWord } = getNextWord(fragment, index);\n index = afterWord;\n openNoteMarker = undefined; // PT9 CloseAll: a chapter empties the element stack\n tokens.push({ kind: \"chapter\", number: word });\n continue;\n }\n // Stylesheet-first (PT9: the stylesheet always classifies; our pattern\n // heuristics only stand in for markers ABSENT from the effective sheet).\n const isNested = name.startsWith(\"+\");\n const clean = isNested ? name.slice(1) : name;\n const kind = getMarkerFn(clean)?.type;\n if (kind === MarkerType.Note || (kind === undefined && NoteNode.isValidMarker(name))) {\n const { word, next: afterWord } = getNextWord(fragment, index);\n index = afterWord;\n openNoteMarker = name;\n tokens.push({ kind: \"note\", marker: name, caller: word || \"+\" });\n continue;\n }\n if (kind === MarkerType.Milestone || (kind === undefined && isMilestoneHeuristicName(name))) {\n const milestone = scanMilestone(fragment, rawStart, name, index);\n if (milestone) {\n tokens.push(milestone.token);\n if (milestone.ejectedText)\n pushText(milestone.ejectedText);\n index = milestone.next;\n }\n else {\n // Not `\\*`-terminated: keep the raw text through the next `\\` (PT9 behavior).\n const endOfText = fragment.indexOf(\"\\\\\", index);\n const end = endOfText === -1 ? fragment.length : endOfText;\n pushText(fragment.slice(rawStart, end));\n index = end;\n }\n continue;\n }\n if (kind === MarkerType.Paragraph) {\n consumeSeparator();\n tokens.push({ kind: \"para\", marker: name });\n }\n else if (kind === MarkerType.Character) {\n consumeSeparator();\n tokens.push({ kind: \"charOpen\", marker: clean, isNested });\n }\n else if (attributeMarker(clean)) {\n // Attribute markers (ca/cp/va/vp/cat) are parser-level in ParatextData, not stylesheet\n // entries — classify them by their fixed shape when the sheet doesn't know them, so\n // the assembly loop can fold them onto their target (or keep them standalone).\n consumeSeparator();\n if (attributeMarker(clean)?.shape === \"para\")\n tokens.push({ kind: \"para\", marker: name });\n else\n tokens.push({ kind: \"charOpen\", marker: clean, isNested });\n }\n else {\n // Unknown to the effective stylesheet: PT9 resolves by context\n // (DetermineUnknownTokenType): PARAGRAPH in body text, CHARACTER inside\n // a note; `esb`/`esbe` are explicitly paragraphs (UsfmToken.cs:405-421).\n // A non-`+` char run closes any open char style unconditionally, same as\n // a known Character token (UsfmParser.cs:247).\n consumeSeparator();\n const insideNote = isNoteContext || openNoteMarker !== undefined;\n if (!insideNote || name === SIDEBAR_MARKER || name === SIDEBAR_END_MARKER)\n tokens.push({ kind: \"para\", marker: name });\n else\n tokens.push({ kind: \"charOpen\", marker: clean, isNested });\n }\n }\n return tokens;\n}\n/**\n * USFM attribute markers: markers that describe the PREVIOUS marker and become an attribute on\n * it in USX/USJ when they directly follow it (whitespace only between, plain-text content) —\n * `\\c 1 \\ca 2\\ca*` → `chapter{altnumber:\"2\"}`. Standalone occurrences (not adjacent to a\n * supporting target, or carrying markup in their content) stay ordinary markers, exactly as\n * ParatextData emits them. The relation data is parser-level in ParatextData (not in any\n * stylesheet), so it is hardcoded here; `cat` supports two target types — an open note\n * (`f`/`fe`/`x`/`ef`/`efe`/`ex`) and an `esb` sidebar — so `targetTypes` is a list.\n *\n * This table models the PARSER, keyed by USJ node type, deliberately matching ParatextData's\n * fold-at-parse behavior (which is keyed by token TYPE, not by a host-marker list). The markers\n * map in paranext-core (`markers-map-3.0.model.ts`) models the SERIALIZER and keys the same\n * relation by host MARKER NAME. The two agree on every shared fact — the agreement test beside\n * this module (`attributeMarkersMapAgreement.test.ts`) pins that, and pins the keying\n * difference explicitly. Parser-only behaviors (a same-line space before the marker blocks the\n * fold; markup in the content aborts it; an empty span never folds) stay local to this\n * converter and are pinned in its own tests.\n */\nexport const ATTRIBUTE_MARKERS = {\n ca: { attrName: \"altnumber\", targetTypes: [\"chapter\"], shape: \"char\" },\n cp: { attrName: \"pubnumber\", targetTypes: [\"chapter\"], shape: \"para\" },\n va: { attrName: \"altnumber\", targetTypes: [\"verse\"], shape: \"char\" },\n vp: { attrName: \"pubnumber\", targetTypes: [\"verse\"], shape: \"char\" },\n cat: { attrName: \"category\", targetTypes: [\"note\", \"sidebar\"], shape: \"char\" },\n};\n/**\n * The attribute-marker entry for `marker`, or undefined if there is none.\n *\n * Goes through `Object.hasOwn` rather than indexing directly: a marker whose name collides with an\n * `Object.prototype` member (`\\toString`, `\\constructor`) would otherwise resolve to the inherited\n * function and read as a declared attribute marker.\n */\nfunction attributeMarker(marker) {\n return Object.hasOwn(ATTRIBUTE_MARKERS, marker) ? ATTRIBUTE_MARKERS[marker] : undefined;\n}\n/**\n * Whether `marker` is one this parser folds onto a host node ({@link ATTRIBUTE_MARKERS}).\n *\n * The distinction a stylesheet cannot make: an attribute marker's round trip is defined by this\n * table, not by usfm.sty, so the tokenizer re-derives one whether or not the stylesheet declares\n * it. `\\cat` is the case that matters — it is the only attribute marker usfm.sty omits, so a\n * stylesheet-keyed \"can the engine re-derive this span?\" test is the one place the two records\n * disagree, and it reads `\\cat` as an unknown custom marker.\n */\nexport function isAttributeMarker(marker) {\n return attributeMarker(marker) !== undefined;\n}\nconst ATTRIBUTE_PAIR_REGEX = /([-\\w]+)\\s*=\\s*\"(.*?)\"/g;\n/**\n * Whitespace run containing a line break, inside attribute text. ParatextData regularizes\n * text BEFORE attribute parsing ever sees it (`UsfmToken.Tokenize` calls `RegularizeSpaces`\n * — control whitespace becomes deduplicated plain spaces — and only then hands the text to\n * `HandleAttributes`), so a line break inside an attribute span reaches its `.*?`-based\n * value pattern as a plain space, never as a newline. Milestone and figure attribute text\n * here is sliced from the raw fragment (or still carries the collapsed `\"\\n\"` run marker),\n * so the same normalization must run before matching — without it the value capture (`.`\n * never matches a line terminator) fails on a wrapped value and the whole string would\n * leak into the default attribute.\n */\nconst ATTRIBUTE_LINE_BREAK_RUN_REGEX = /[\\s\\u200B]*[\\n\\r][\\s\\u200B]*/g;\n/**\n * USFM 3 default attribute per marker (subset; unmapped bare values stay literal).\n *\n * `xt`/`jmp` use the USFM/USX/USJ **3.0** name `link-href`: this pipeline pins USJ 3.0 (chapter\n * data round-trips through ParatextData, and the host downgrades 3.1), and every 3.0 consumer —\n * ParatextData serialization, checks, link handling — reads `link-href`. USFM 3.1 renamed it to\n * `href`; switch these when the pipeline moves to 3.1.\n */\nconst DEFAULT_MARKER_ATTRIBUTES = {\n w: \"lemma\",\n rb: \"gloss\",\n xt: \"link-href\",\n jmp: \"link-href\",\n};\n/**\n * Character marker default attribute lookup for USFM 3 (shared with display builders).\n * Returns the default attribute name for a given character marker, or undefined if the marker\n * has no default attribute.\n */\nexport function defaultMarkerAttribute(marker) {\n return DEFAULT_MARKER_ATTRIBUTES[marker];\n}\n/**\n * USJ structural keys that a parsed attribute may never overwrite: the callers merge parsed\n * attributes straight onto the node object (`Object.assign` / spread), so an attribute literally\n * named `type`, `marker`, or `content` would clobber the node's own identity or replace its\n * content array with a string. Such attributes are dropped.\n */\nconst RESERVED_NODE_KEYS = new Set([\"type\", \"marker\", \"content\"]);\n/**\n * Whether `pairs` consume all of `text` apart from whitespace — i.e. the attribute list parsed\n * completely rather than partly. Each match must start where the previous one ended (whitespace\n * aside), and the last must reach the end.\n */\nfunction pairsCoverList(text, pairs) {\n let cursor = 0;\n for (const pair of pairs) {\n const start = pair.index;\n if (start === undefined)\n return false;\n if (text.slice(cursor, start).trim() !== \"\")\n return false;\n cursor = start + pair[0].length;\n }\n return text.slice(cursor).trim() === \"\";\n}\nfunction parseAttributeText(attributeText, marker, defaultAttributeName = DEFAULT_MARKER_ATTRIBUTES[marker]) {\n const regularizedText = attributeText.replace(ATTRIBUTE_LINE_BREAK_RUN_REGEX, \" \");\n // Null-prototype accumulator: on a plain `{}`, `attributes[\"__proto__\"] = value` hits\n // Object.prototype's accessor and is a silent no-op, so an attribute literally named\n // `__proto__` would parse successfully and lose its pair. An own property on a null-prototype\n // object survives spread and JSON like any other key.\n const attributes = Object.create(null);\n const pairs = [...regularizedText.matchAll(ATTRIBUTE_PAIR_REGEX)];\n if (pairs.length > 0) {\n // The pairs must account for the WHOLE list, with nothing but whitespace between and after\n // them. The regex is global, so without this it happily skips over anything it cannot match:\n // `gloss=\"st\"uff\"` matched `gloss=\"st\"` and the leftover `uff\"` vanished, taking bytes the\n // author wrote and could see. USFM has no way to escape a quote inside a value, so such a list\n // is genuinely ambiguous and there is no reading of it to salvage — refusing the whole list\n // hands it back to the caller, which keeps it as literal text (`extractAttributes`). That is\n // Paratext 9's behavior, and the only lossless one: every byte stays on screen and in the file,\n // where the author can see what is wrong and fix it.\n if (!pairsCoverList(regularizedText, pairs))\n return undefined;\n // An EMPTY value refuses the whole list too, matching Paratext 9. `|who=\"\"` is not a reading\n // Paratext will ever agree with, so parsing it here would make the editor the only thing in the\n // pipeline that believes the attribute exists — and the byte the author actually wrote is the\n // literal text, which the refusal keeps. Note this is the opposite call from the USJ side, where\n // an empty value arriving as real state is honoured and written out; there it is unambiguous\n // data, here it is bytes Paratext reads as text.\n if (pairs.some((pair) => pair[2] === \"\"))\n return undefined;\n for (const [, name, value] of pairs) {\n if (!RESERVED_NODE_KEYS.has(name))\n attributes[name] = value;\n }\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n }\n // Bare (default-attribute) value: keep it byte-exact (past the line-break regularization\n // above), whitespace included — ParatextData treats the space before the closing marker as\n // part of the value (`\\w marker|stuff \\w*` → lemma=\"stuff \"; `\\qt-s |TJ \\*` → who=\"TJ \").\n // The USFM 3 spec calls that space structural, but Paratext keeps it as content, and this\n // pipeline round-trips through ParatextData.\n if (regularizedText.trim() && defaultAttributeName)\n return { [defaultAttributeName]: regularizedText };\n return undefined;\n}\n/**\n * Default attribute for stylesheet-family milestone names (USFM 3.0): quotation starts take\n * `who`, every `-e` end takes `eid`, and other starts (`\\ts-s`, comment markers) take `sid`.\n */\nexport function milestoneDefaultAttribute(name) {\n if (name.endsWith(\"-e\"))\n return \"eid\";\n return name.startsWith(\"qt\") ? \"who\" : \"sid\";\n}\n/**\n * Whether re-tokenizing `text` would EJECT bytes out of a milestone — the shapes where a milestone\n * ends early and content it cannot hold follows it as a sibling, leaving the author's `\\*`\n * unmatched.\n *\n * Answered by running the real tokenizer rather than re-deriving the conditions, so it cannot drift\n * from {@link scanMilestone}. Callers use this to defer such a rebuild to the settle instead of\n * applying it mid-keystroke, because ejection MOVES bytes and doing that under the caret rearranges\n * the line the user is still typing on. Only ejection earns that deferral: a well-formed milestone\n * rearranges nothing and applies where it stands, the instant its `\\*` is typed.\n *\n * The ejected shape is BOTH halves together — content immediately after the milestone AND the\n * author's own `\\*` stranded past that content as an unmatched closing marker. Asking only for\n * the first half counts ordinary body text following a well-formed milestone, which is the common\n * case and not an ejection at all; the leftover unmatched `\\*` is the observable that tells the\n * two apart. The content run may tokenize into several items (an ejected list containing `//`\n * splits around an optbreak), so the closer is looked for anywhere past it rather than adjacent.\n */\nexport function milestoneEjectionPending(text) {\n const content = usfmFragmentToUsjContent(text)[0];\n const items = typeof content === \"object\" && \"content\" in content ? content.content : undefined;\n if (!items)\n return false;\n return items.some((item, index) => {\n if (typeof item !== \"object\" || item.type !== \"ms\")\n return false;\n if (typeof items[index + 1] !== \"string\")\n return false;\n return items\n .slice(index + 2)\n .some((later) => typeof later !== \"string\" && later.type === \"unmatched\" && later.marker === \"*\");\n });\n}\n/**\n * An attribute list sitting AFTER a closed milestone that belongs back INSIDE it — the shape\n * ejection leaves behind once the author repairs the bytes. `\\qt1-s\\*|who=\"person\"\\*` reads as\n * the milestone the author meant, `\\qt1-s |who=\"person\"\\*`.\n *\n * Two conditions, and both are load-bearing.\n *\n * The author's own trailing `\\*` is what marks the run as a milestone's list rather than body\n * text: content that merely begins with `|` has no closer after it, so this can never quietly eat\n * ordinary bytes. Whitespace between the milestone's closer and the `|` is tolerated — it is the\n * separator the author would have typed inside the milestone anyway.\n *\n * The list must PARSE, and that is what keeps absorption and ejection from chasing each other.\n * Ejection puts an unparseable list OUTSIDE the milestone ({@link scanMilestone}); absorbing one\n * back in would produce bytes that eject again, and the two would trade the same run forever.\n * `|who=\"\"` does not parse, so the ejected spelling stays ejected and the pair terminates.\n *\n * Returns the parsed list and where scanning resumes (past its `\\*`), or undefined for every\n * shape that is not this one.\n */\nfunction scanAbsorbableAttributes(fragment, start, name) {\n let index = start;\n while (index < fragment.length && /[\\s\\u00A0\\u200B]/.test(fragment[index]))\n index++;\n if (fragment[index] !== \"|\")\n return undefined;\n const closeIndex = fragment.indexOf(\"\\\\\", index);\n if (closeIndex === -1 || fragment.slice(closeIndex, closeIndex + 2) !== \"\\\\*\")\n return undefined;\n const attributes = parseAttributeText(fragment.slice(index + 1, closeIndex), name, milestoneDefaultAttribute(name));\n if (!attributes)\n return undefined;\n return { attributes, next: closeIndex + 2 };\n}\n/**\n * A milestone must be terminated by `\\*` (PT9 `MilestoneEnded`); attributes may\n * follow a `|` between the marker and the `\\*`.\n */\nfunction scanMilestone(fragment, _rawStart, name, index) {\n const closeIndex = fragment.indexOf(\"\\\\\", index);\n if (closeIndex === -1 || fragment.slice(closeIndex, closeIndex + 2) !== \"\\\\*\")\n return undefined;\n // Slices up to the FIRST backslash at or after `index`, so `between` cannot contain one: the\n // milestone's attribute region ends at whatever markup follows it.\n const between = fragment.slice(index, closeIndex);\n const pipeIndex = between.indexOf(\"|\");\n let attributes;\n if (pipeIndex >= 0) {\n attributes = parseAttributeText(between.slice(pipeIndex + 1), name, milestoneDefaultAttribute(name));\n // A list with CONTENT that will not parse becomes CONTENT, and a milestone cannot hold\n // content — so the milestone ends immediately and the bytes follow it as siblings. Resuming\n // the scan at the `|` is what produces that: the milestone token closes here, the unparsed\n // bytes are read as ordinary text, and the author's `\\*` is left to scan as an unmatched\n // closing marker. `\\qt1-s |who=\"\"\\*` therefore reads exactly as `\\qt1-s\\*|who=\"\"\\*` already\n // does — a closed milestone, the literal text, and one unmatched `\\*` — which is Paratext 9's\n // reading and keeps every byte.\n //\n // Refusing the whole token instead would lose the milestone the author did write, and building\n // it while dropping the attribute bytes would lose those; this loses neither. A bare `|` with\n // nothing after it is not this case — there are no bytes to keep, and dropping it is the\n // ratified answer for a `|` typed into a milestone glyph.\n if (!attributes && between.slice(pipeIndex + 1).trim() !== \"\") {\n // Content BEFORE the `|` is ejected here too, for the same reason the valid-attribute path\n // below ejects it: it is bytes the milestone cannot hold, and resuming the scan at the `|`\n // would skip straight past it. `\\\\qt1-s things|who=\"\"\\\\*` keeps `things` as a sibling\n // rather than deleting it, and still converges — re-tokenizing the ejected spelling\n // `\\\\qt1-s\\\\*things|who=\"\"\\\\*` reaches the same tree.\n const beforePipe = between.slice(0, pipeIndex);\n return {\n token: { kind: \"milestone\", marker: name },\n next: index + pipeIndex,\n ejectedText: beforePipe.trim() !== \"\" ? beforePipe.replace(/^[ \\u00A0]/, \"\") : undefined,\n };\n }\n }\n // Content a milestone cannot hold — the bytes before a valid attribute list, or, when there is\n // no `|` at all, everything between the marker and the closer. A milestone is self-closing, so\n // it ENDS where that content begins: the content follows it as a sibling, and the author's `\\*`\n // is left to scan as an unmatched closing marker. Valid attributes stay, because they are\n // genuinely the milestone's own; only what it cannot hold moves.\n //\n // That is what ParatextData writes to disk — `\\qt1-s stuff\\*` is saved as `\\qt1-s\\*stuff\\*` —\n // so the two spellings must tokenize IDENTICALLY for a reload to be a fixed point. Reading the\n // stray content as literal text instead dissolves the milestone the author did write, leaving\n // the editor disagreeing with the file.\n //\n // Resuming at `closeIndex` is what leaves the `\\*` unmatched, and the leading separator space\n // belongs to the marker rather than the content, so it goes with the marker.\n const ejected = pipeIndex >= 0 ? between.slice(0, pipeIndex) : between;\n if (ejected.trim() !== \"\")\n return {\n token: { kind: \"milestone\", marker: name, attributes },\n next: closeIndex,\n ejectedText: ejected.replace(/^[ \\u00A0]/, \"\"),\n };\n // A repaired attribute list left sitting past the closer folds back in, its values overwriting\n // any the milestone already carries: the list the author just fixed is the one they mean. Only\n // the clean path absorbs: when content was ejected above, the `\\*` is deliberately left to scan\n // as an unmatched closer, so there is no \"past the closer\" position to absorb from.\n const absorbed = scanAbsorbableAttributes(fragment, closeIndex + 2, name);\n if (absorbed)\n return {\n token: {\n kind: \"milestone\",\n marker: name,\n attributes: { ...attributes, ...absorbed.attributes },\n },\n next: absorbed.next,\n };\n return { token: { kind: \"milestone\", marker: name, attributes }, next: closeIndex + 2 };\n}\n/** Convert `~` to NBSP for USJ text content (PT9 read-side `UsfmParser` behavior), and\n * normalize the line-break marker `regularizeSpaces` preserved back to a plain space. */\nfunction toUsjText(text) {\n return text.replaceAll(\"\\n\", \" \").replaceAll(\"~\", NBSP);\n}\n/** Get (and lazily initialize) a marker object's content array without a non-null assertion. */\nfunction getContent(object) {\n if (!object.content)\n object.content = [];\n return object.content;\n}\nexport function usfmFragmentToUsjContent(fragment, options) {\n const result = [];\n const isNoteContext = options?.isNoteContext ?? false;\n let para;\n let note;\n const charStack = [];\n // Char-stack depth at the moment the open note started: frames BELOW it enclose the note\n // (USX nests the note inside them and the span continues after it — `\\wj a \\f …\\f* b\\wj*`\n // puts both the note and \" b\" inside the wj span); frames AT/ABOVE it were opened inside\n // the note's content and close with it. Only meaningful while `note` is set.\n let noteBaseDepth = 0;\n // ---- opaque-structure state (tables, sidebars) ----\n // Current open table and its open row. `\\tr` creates both (consecutive rows share one\n // table); a cell marker then points `para` at the cell object, so ALL ordinary content\n // handling (text, char spans, notes, verses) lands inside the cell unchanged. While the\n // table is open, `para` is always its open row or a cell of that row.\n let table;\n let tableRow;\n // Current open sidebar: `\\esb`…`\\esbe` wraps subsequent top-level blocks (paragraphs and\n // tables). Implicit close (fragment end or a chapter token) marks it closed=\"false\" —\n // ParatextData auto-closes sidebars at the chapter boundary.\n let sidebar;\n // Current open peripheral division: `\\periph` opens one and takes every following block into\n // it. Nothing closes a division but the next `\\periph`, a chapter boundary, or the end of the\n // fragment — USFM gives a division marker no closing bytes at all, so unlike a sidebar there is\n // no terminated/implicit distinction to record. The marker LINE's own text (the division title\n // and its attribute list) is captured first; see `periphCapture`.\n let periph;\n /** Where top-level blocks (paragraphs, tables) land: an open sidebar's content, else an open\n * peripheral division's content, else the fragment result. */\n const blockTarget = () => {\n if (sidebar)\n return getContent(sidebar);\n if (periph)\n return getContent(periph);\n return result;\n };\n // True between a chapter token and the next opened block: loose content there sits at the\n // DOCUMENT ROOT in ParatextData's output, not in an implied paragraph — text typed after\n // `\\c 1` saves as its own ` text` line, and 2SA-2's unclosed `\\ca` strands a root char.\n // Fragment-LEADING bare content keeps the implied-`\\p` wrap (the note-content rebuild\n // depends on unwrapping it), so this is scoped to post-chapter position only.\n let atChapterRootScope = false;\n const container = () => {\n if (note) {\n // Inside the note, only frames opened WITHIN it receive content; the enclosing\n // frames are suspended until the note closes.\n if (charStack.length > noteBaseDepth)\n return getContent(charStack[charStack.length - 1].object);\n return getContent(note);\n }\n if (charStack.length > 0)\n return getContent(charStack[charStack.length - 1].object);\n if (!para) {\n if (atChapterRootScope && !isNoteContext)\n return blockTarget();\n para = { type: \"para\", marker: PARA_MARKER_DEFAULT, content: [] };\n blockTarget().push(para);\n }\n return getContent(para);\n };\n const pushContent = (item) => {\n const target = container();\n if (typeof item === \"string\" && typeof target[target.length - 1] === \"string\") {\n target[target.length - 1] = target[target.length - 1] + item;\n }\n else {\n target.push(item);\n }\n };\n // Implicit close: every still-open char span gets closed=\"false\", mirroring ParatextData\n // (a span only stays unmarked when the user's own `\\marker*` terminated it).\n const markImplicitlyClosed = (fromIndex) => {\n for (let i = fromIndex; i < charStack.length; i += 1) {\n const object = charStack[i].object;\n object.closed = \"false\";\n }\n };\n const closeCharStack = () => {\n markImplicitlyClosed(0);\n charStack.length = 0;\n };\n const closeNote = (terminated) => {\n if (!note)\n return;\n // Chars opened INSIDE the note close with it (implicitly); frames below the note\n // boundary survive — the enclosing span continues after the note.\n if (charStack.length > noteBaseDepth) {\n markImplicitlyClosed(noteBaseDepth);\n charStack.length = noteBaseDepth;\n }\n noteBaseDepth = 0;\n if (!terminated)\n note.closed = \"false\";\n note = undefined;\n };\n // Tables have no closing marker and no `closed` metadata: the table object is already in\n // place, so ending one just drops the open-row state — rows/cells never resume.\n const endTable = () => {\n table = undefined;\n tableRow = undefined;\n };\n /**\n * Open a cell in the current row and make it the content container: text, char spans, notes and\n * verses then flow into it through the ordinary `para`-based container logic. Shared by both\n * token kinds a cell marker can arrive as — a stylesheet that knows `\\th1`/`\\tc1` classifies\n * them as Character (usfm.sty does), one that does not delivers them as unknown paragraph\n * markers, and ParatextData derives a cell from the NAME either way.\n */\n const pushTableCell = (row, marker, cellMatch) => {\n closeCharStack();\n const [, alignInfix, spanStart, spanEnd] = cellMatch;\n // The cell keeps only the starting column in its marker (`thc3-4` → `thc3`); the span width\n // becomes `colspan`, a string of columns spanned (`thc3-4` → \"2\").\n const cell = {\n type: \"table:cell\",\n marker: spanEnd ? marker.slice(0, marker.indexOf(\"-\")) : marker,\n align: TABLE_CELL_ALIGN_BY_INFIX[alignInfix],\n content: [],\n };\n if (spanEnd)\n cell.colspan = String(Number(spanEnd) + 1 - Number(spanStart));\n getContent(row).push(cell);\n para = cell;\n };\n const closeSidebar = (terminated) => {\n if (!sidebar)\n return;\n // Only `\\esbe` terminates a sidebar explicitly; an implicit close (fragment end or a\n // chapter boundary) gets closed=\"false\", mirroring ParatextData's auto-close.\n if (!terminated)\n sidebar.closed = \"false\";\n sidebar = undefined;\n };\n /** End the open division. Nothing to record: a division has no closing bytes to be missing. */\n const closePeriph = () => {\n periph = undefined;\n };\n // ---- attribute-marker folding state (see ATTRIBUTE_MARKERS) ----\n // The most recent chapter/verse/note object, still \"receptive\": an adjacent attribute\n // marker folds onto it as an attribute. Any real content clears it.\n let attrTarget;\n // Whitespace-only text held while attrTarget is receptive: structural (dropped) if an\n // attribute marker follows; ordinary content (flushed) otherwise.\n let heldWhitespace = \"\";\n // An attribute-marker span currently being captured for folding. Aborts to a standalone\n // marker the moment its content turns out to be markup, exactly as ParatextData keeps a\n // `\\cat` with markup or a `\\cp` with markers as its own marker.\n let attrCapture;\n const flushHeldWhitespace = () => {\n if (heldWhitespace)\n pushContent(toUsjText(heldWhitespace));\n heldWhitespace = \"\";\n };\n const clearAttrTarget = (atBlockBoundary = false) => {\n // Line-end whitespace held while a SIDEBAR was receptive is structural: sidebar content\n // is block-level (paragraphs and tables), so the line break between `\\esb`/`\\cat` and\n // the first block never becomes text — ParatextData emits none there. Held whitespace\n // for the other targets (chapter/verse/note) flushes as content, EXCEPT its trailing\n // line break at a block boundary: ParatextData strips the space a line break leaves\n // behind when the next token is a paragraph/book/chapter (UsfmParser's Text case,\n // \"strip final space\"), so `\\c 1` ⏎ `\\ca 2\\ca*` ⏎ `\\p` puts no stray text at the root.\n // Before an INLINE token the space survives — a `\\cat` fold followed by `\\ft` keeps its\n // space inside the note.\n if (attrTarget?.type === \"sidebar\")\n heldWhitespace = \"\";\n else if (atBlockBoundary && heldWhitespace.endsWith(\"\\n\"))\n heldWhitespace = heldWhitespace.slice(0, -1);\n attrTarget = undefined;\n flushHeldWhitespace();\n };\n /** Abort a char-shaped capture: materialize the standalone open span (frame stays open —\n * nested markup and the eventual closer process normally). */\n const materializeCaptureAsChar = () => {\n if (!attrCapture)\n return;\n const object = { type: \"char\", marker: attrCapture.marker, content: [] };\n if (attrCapture.value)\n object.content = [toUsjText(attrCapture.value)];\n container().push(object);\n charStack.push({ object });\n attrCapture = undefined;\n };\n /** Start an ordinary paragraph block. Any non-row/cell paragraph-kind marker also ends an\n * open table — ParatextData never resumes a table across another block. */\n const startParagraph = (marker, initialText) => {\n atChapterRootScope = false;\n endTable();\n closeCharStack();\n closeNote(false);\n para = { type: \"para\", marker, content: [] };\n if (initialText)\n para.content = [toUsjText(initialText)];\n blockTarget().push(para);\n };\n /** Abort a para-shaped capture (`\\cp` with markup): materialize the standalone paragraph. */\n const materializeCaptureAsPara = () => {\n if (!attrCapture)\n return;\n startParagraph(attrCapture.marker, attrCapture.value);\n attrCapture = undefined;\n };\n // ---- peripheral-division capture state ----\n // The text of a `\\periph` marker line, collected between the marker and the next marker token.\n // `\\periph` has no closing bytes, so the next marker is the only thing that ends the line —\n // the same delimiter whether a USFM writer put the division on its own line or a copy laid the\n // whole construct out on one (a line break is ordinary whitespace to a tokenizer). That is what\n // lets a Tier-2 rebuild, which only ever re-tokenizes a single paragraph's bytes, reassemble a\n // whole division from one fragment.\n let periphCapture;\n /**\n * Close the captured marker line and open the division it describes: everything before the\n * line's first `|` is the division title (USX/USJ's `alt`), the rest an ordinary named-attribute\n * list.\n *\n * A list that does not parse is REFUSED, and so is one that spells the TITLE a second time\n * (`alt`, alongside a non-empty marker-line title): the two spellings are the same USJ property,\n * there is no reading that keeps both, and picking one would silently destroy bytes the author\n * can still see. `alt` on a TITLELESS line collides with nothing and is read normally. The editor\n * can never author the colliding shape — periph's display renders `alt` AS the marker-line title\n * (`unknownUsfm.utils.ts`), never as a pipe pair.\n *\n * Refusing the list does NOT refuse the division. `\\periph` opens a peripheral division for its\n * own sake — it has no closing marker and nothing else delimits it, so the division's extent is\n * a property of the marker, not of its attributes. Paratext 9 reads it the same way: a failed\n * `SetAttributes` leaves the marker-line text alone (`ParatextData/UsfmToken.cs`) and the USX\n * writer still opens `` for any `\\periph` token, with an empty `id`\n * (`ParatextData/UsxUsfmParserSink.cs`). Demoting the division to an ordinary paragraph would\n * re-parent every block it contained up to the root instead. The refused bytes survive as the\n * division's own title, where the author can still see and fix them, and they serialize back\n * byte-for-byte (`alt` renders as literal marker content, not a pipe pair).\n *\n * @param atBlockBoundary - Whether the token that ended the line starts a new block (or the\n * fragment ended). The line's trailing line break is structural there, the same rule the text\n * case applies: the line ends where the next block marker begins, and ParatextData emits no\n * content for it.\n */\n const finishPeriphLine = (atBlockBoundary) => {\n if (!periphCapture)\n return;\n let { value } = periphCapture;\n periphCapture = undefined;\n if (atBlockBoundary && value.endsWith(\"\\n\"))\n value = value.slice(0, -1);\n const pipeIndex = value.indexOf(\"|\");\n const parsed = pipeIndex >= 0 ? parseAttributeText(value.slice(pipeIndex + 1), PERIPH_MARKER) : undefined;\n const parsedTitle = pipeIndex >= 0 ? value.slice(0, pipeIndex) : value;\n const refused = pipeIndex >= 0 && (!parsed || (!!parsedTitle && !!parsed[PERIPH_TITLE_ATTRIBUTE]));\n const attributes = refused ? undefined : parsed;\n const title = refused ? value : parsedTitle;\n const opened = {\n type: \"periph\",\n ...(title ? { [PERIPH_TITLE_ATTRIBUTE]: toUsjText(title) } : {}),\n ...attributes,\n };\n opened.content = [];\n // Landed BEFORE `periph` is set, for the same reason the sidebar assembly does it: an already\n // open division would otherwise take the new one into its own content.\n blockTarget().push(opened);\n periph = opened;\n para = undefined;\n };\n // ---- figure capture state ----\n // A `\\fig …\\fig*` span being collected for faithful `figure` emission (ParatextData turns\n // the span into `{ type: \"figure\", … }`). Only a clean span folds: plain-text content with\n // `name=\"value\"` attributes and an explicit `\\fig*`. Anything else — nested markup, USFM\n // 2.0 positional attributes, no closer — degrades to exactly what the marker's own\n // classification produced before figure support (a char span or an unknown paragraph).\n let figCapture;\n /** Degrade an unfoldable figure span: a char frame (stays open — the eventual closer or\n * auto-close processes normally) or an unknown paragraph, matching pre-figure output. */\n const materializeFigCapture = () => {\n if (!figCapture)\n return;\n if (figCapture.shape === \"para\") {\n startParagraph(FIGURE_MARKER, figCapture.value);\n }\n else {\n const object = { type: \"char\", marker: FIGURE_MARKER, content: [] };\n if (figCapture.value)\n object.content = [toUsjText(figCapture.value)];\n container().push(object);\n charStack.push({ object });\n }\n figCapture = undefined;\n };\n const tokens = tokenize(fragment, options?.getMarker ?? getMarker, isNoteContext);\n for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {\n const token = tokens[tokenIndex];\n if (attrCapture) {\n if (token.kind === \"text\") {\n attrCapture.value += token.text;\n continue;\n }\n if (attrCapture.shape === \"char\" &&\n token.kind === \"end\" &&\n token.marker.replace(/^\\+/, \"\") === attrCapture.marker) {\n // The fold to an attribute requires NON-EMPTY content (PT9\n // `FindOtherVerseOrChapterNumber` folds only when `tokens[+2].Text != null`, and its\n // tokenizer never produces an empty text token). An EMPTY span (any spelling —\n // `\\va \\va*`, `\\va\\va*`, `\\va \\va*`) is NEVER an empty attribute: it stays a\n // first-class, explicitly-closed char element sitting after its target. The closer is\n // consumed here (no open frame is pushed), so the span is closed with no `closed`\n // metadata, exactly like any user-closed span.\n if (attrCapture.value.trim() === \"\") {\n container().push({ type: \"char\", marker: attrCapture.marker, content: [] });\n attrCapture = undefined;\n // The materialized element is REAL CONTENT, so the target stops being receptive — the\n // same rule a plain-text token applies through `clearAttrTarget` above. Without this, a\n // following attribute marker folded ACROSS the element now separating it from its target\n // (`\\v 11 \\va\\va*\\vp 11 vp\\vp*` put `pubnumber` on the verse while the `\\va` char trailed\n // behind it), and USJ cannot express that order: serializing back put the published\n // number FIRST, silently rewriting the document as `\\v 11 \\vp 11 vp\\vp*\\va \\va*`.\n // ParatextData folds NEITHER marker here — captured from `GetChapterUsx`, it emits\n // `11 vp` in document order.\n clearAttrTarget();\n continue;\n }\n // Explicit close with plain-text content: fold as the target's attribute, TRIMMED —\n // ParatextData trims every folded value (`FindOtherVerseOrChapterNumber` reads\n // `tokens[index + skip + 2].Text.Trim()`, and the note/sidebar category lookups do\n // `tokens[index + 2].Text.Trim()`), so `\\ca 2 \\ca*` yields altnumber \"2\". Any space\n // AFTER the closer is content, not structural — Paratext keeps it in the text\n // (`\\vp 11 vp\\vp* This…` → text starts with the space), per its\n // treat-space-after-attribute-markers-as-content behavior.\n Object.assign(attrCapture.target, {\n [attrCapture.attrName]: toUsjText(attrCapture.value.trim()),\n });\n const foldedMarker = attrCapture.marker;\n attrCapture = undefined;\n // The chapter path alone consumes ONE whitespace-only token after a successful fold,\n // unconditionally — not gated on a `\\cp` following — so a same-line space between\n // `\\ca*` and `\\cp` is structural and `\\cp` still folds, and the space before an\n // ordinary char span vanishes too. The verse path has no such skip: the identical\n // space between `\\va*` and `\\vp` BLOCKS the `\\vp` fold (the v12 rule). Both sides are\n // captured through ParatextData in paranext-core's\n // VerseAttributeFoldRoundTripCaptureTests (`FilledCaThenCp_BothFold`,\n // `SpaceAfterFoldedCa_IsConsumedEvenWithoutCp`,\n // `SpaceBetweenVaCloserAndVp_BlocksVpFold_SpaceIsContent`).\n if (foldedMarker === \"ca\") {\n const next = tokens[tokenIndex + 1];\n if (next?.kind === \"text\" && /^[\\s\\u200B]*$/.test(next.text))\n tokenIndex++;\n }\n continue;\n }\n if (attrCapture.shape === \"para\" && (token.kind === \"para\" || token.kind === \"chapter\")) {\n const cpValue = attrCapture.value.replace(/[\\s\\u200B]+$/, \"\");\n if (cpValue === \"\") {\n // Empty `\\cp` (paragraph-shaped, no end marker): PT9 never yields an empty pubnumber \\u2014\n // it stays a first-class (empty) para element. Materialize the standalone paragraph\n // with no content, then fall through to process the boundary token normally.\n //\n // Unlike the char-shaped empty branch above, this one deliberately does NOT clear\n // `attrTarget` (`startParagraph` leaves it alone), so the receptive window survives a\n // materialized empty `\\cp`. That is audited, not overlooked, on two counts.\n //\n // A CHAR-shaped attribute marker cannot reach the surviving window at all, so the\n // document-order rewrite the char branch closes has no para-shaped twin. This branch only\n // runs when the token that ENDS the capture is a para/chapter boundary; `\\ca` is\n // char-shaped, so it arrives while the capture is still open and falls to the\n // unfoldable-markup arm below, which clears `attrTarget` BEFORE materializing the\n // paragraph. `\\c 1` \\u23ce `\\cp ` \\u23ce `\\ca 2\\ca*` \\u23ce `\\p body` therefore leaves the chapter with\n // no altnumber and the `\\ca` an ordinary char span (pinned in usfmFragmentToUsj.test.ts).\n //\n // What DOES re-enter the window is a foldable PARA token, and `cp` is the only\n // `shape: \"para\"` attribute marker \\u2014 so that means a degenerate duplicate `\\cp` on one\n // chapter, which does still fold across the materialized empty para (probed). Closing\n // the window here would add a branch for a shape no document has.\n startParagraph(attrCapture.marker);\n attrCapture = undefined;\n }\n else {\n // The cp \"paragraph\" ended with plain-text content only: fold (trailing line\n // whitespace is structural).\n Object.assign(attrCapture.target, { [attrCapture.attrName]: toUsjText(cpValue) });\n attrCapture = undefined;\n }\n // fall through to process the boundary token normally\n }\n else {\n // Markup inside the span (or a mismatched closer): not foldable — materialize the\n // standalone marker, then reprocess this token normally. A trailing line break in the\n // captured value before a BLOCK boundary is structural, same as the text-case rule\n // (`\\ca 2 ca` ⏎ `\\cp` materializes content \"2 ca\", not \"2 ca \").\n attrTarget = undefined;\n if ((token.kind === \"para\" || token.kind === \"chapter\") && attrCapture.value.endsWith(\"\\n\"))\n attrCapture.value = attrCapture.value.slice(0, -1);\n if (attrCapture.shape === \"para\")\n materializeCaptureAsPara();\n else\n materializeCaptureAsChar();\n tokenIndex--;\n continue;\n }\n }\n if (periphCapture) {\n // The marker line runs to the next marker token. An optbreak rejoins as the literal `//`\n // the author typed, for the same reason the figure capture rejoins one: `//` inside an\n // attribute value (a URL, say) is plain value bytes to ParatextData, which strips the\n // attribute segment out at tokenize-time before its `//` pass ever runs.\n if (token.kind === \"text\" || token.kind === \"optbreak\") {\n periphCapture.value += token.kind === \"text\" ? token.text : \"//\";\n continue;\n }\n finishPeriphLine(token.kind === \"para\" || token.kind === \"chapter\");\n tokenIndex--;\n continue;\n }\n if (figCapture) {\n if (token.kind === \"text\" || token.kind === \"optbreak\") {\n // The tokenizer splits `//` into optbreak tokens spec-blind — including inside what\n // will become the `|attributes` segment, where ParatextData treats `//` as plain\n // value bytes (`UsfmToken.HandleAttributes` strips the segment out of the text run at\n // tokenize-time, before the parser's `//`→optbreak pass ever runs), so an attribute\n // value carrying a URL must reach `parseAttributeText` below byte-exact. Rejoining\n // every optbreak here (not just post-`|` ones) is safe: a clean figure's description\n // already folds to one flat string with no discretionary-break fidelity of its own\n // (see the `figure.content` assembly below and `materializeFigCapture`'s degrade\n // path), so a pre-`|` `//` landing back as literal text is no regression.\n //\n // This deliberately duplicates `extractAttributes`' optbreak→`//` byte mapping rather\n // than sharing a helper: that function rejoins an already-ASSEMBLED content array\n // post-hoc (strings and `{type:\"optbreak\"}` objects), while this path rejoins the token\n // STREAM as it arrives into a plain string accumulator — the two operate on different\n // shapes at different pipeline stages, and the shared knowledge is just the single\n // byte fact that an optbreak was a literal `//`.\n figCapture.value += token.kind === \"text\" ? token.text : \"//\";\n continue;\n }\n if (token.kind === \"end\" && token.marker.replace(/^\\+/, \"\") === FIGURE_MARKER) {\n const pipeIndex = figCapture.value.indexOf(\"|\");\n const attributes = pipeIndex >= 0\n ? parseAttributeText(figCapture.value.slice(pipeIndex + 1), FIGURE_MARKER)\n : undefined;\n if (attributes) {\n // Clean span: emit the faithful figure. USFM's `src` attribute is `file` in\n // USX/USJ (renamed in place to keep the author's attribute order); the pre-`|`\n // description is the figure's content, omitted when empty (as ParatextData does).\n const figureAttributes = {};\n for (const [name, value] of Object.entries(attributes))\n figureAttributes[name === \"src\" ? \"file\" : name] = value;\n const figure = {\n type: \"figure\",\n marker: FIGURE_MARKER,\n ...figureAttributes,\n };\n const description = figCapture.value.slice(0, pipeIndex);\n if (description)\n figure.content = [toUsjText(description)];\n pushContent(figure);\n figCapture = undefined;\n continue;\n }\n // No `name=\"value\"` attributes (USFM 2.0 positional syntax, or no `|` at all): fall\n // through to degrade — the reprocessed closer then closes the materialized char\n // frame (or lands unmatched in the materialized paragraph), exactly as before.\n }\n // Markup inside the span, a foreign closer, or unfoldable attributes: degrade, then\n // reprocess this token against the materialized span.\n materializeFigCapture();\n tokenIndex--;\n continue;\n }\n if (attrTarget) {\n if (token.kind === \"text\") {\n // Only LINE-BREAK whitespace between the target and its attribute marker is\n // structural (`\\ca 1 ca\\ca*` \\u23CE `\\cp 1 cp` still folds cp). A same-line space is\n // content per Paratext and BLOCKS the fold \\u2014 `\\va 12 va\\va* \\vp 12 vp\\vp*` keeps\n // altnumber but leaves vp a standalone marker with the space in the text.\n if (token.text.includes(\"\\n\") && /^[\\s\\u200B]*$/.test(token.text)) {\n heldWhitespace += token.text;\n continue;\n }\n clearAttrTarget();\n }\n else if (token.kind === \"charOpen\" || token.kind === \"para\") {\n const foldable = token.kind === \"para\" || !token.isNested ? attributeMarker(token.marker) : undefined;\n if (foldable && foldable.targetTypes.includes(attrTarget.type)) {\n // Whitespace between the target and its attribute marker is structural — dropped.\n heldWhitespace = \"\";\n attrCapture = {\n target: attrTarget,\n attrName: foldable.attrName,\n marker: token.marker,\n shape: foldable.shape,\n value: \"\",\n };\n // attrTarget stays receptive: a chapter takes BOTH \\ca and \\cp.\n continue;\n }\n clearAttrTarget(token.kind === \"para\");\n }\n else {\n clearAttrTarget(token.kind === \"chapter\");\n }\n }\n // ---- `\\fig` interception (marker-name driven, like ParatextData's parser figures) ----\n // `\\fig` reaches assembly as charOpen (a sheet that knows it as Character) or para\n // (unknown marker); BOTH fold to a `figure` when the span is clean. Note content keeps\n // the plain char behavior — this tokenizer does not build figures inside notes. Opening\n // a figure auto-closes open char spans exactly as its Character classification would\n // (UsfmParser.cs:247), so the success and degrade paths continue from the same stack.\n if (!note &&\n !isNoteContext &&\n ((token.kind === \"charOpen\" && !token.isNested && token.marker === FIGURE_MARKER) ||\n (token.kind === \"para\" && token.marker === FIGURE_MARKER))) {\n closeCharStack();\n figCapture = { shape: token.kind === \"charOpen\" ? \"char\" : \"para\", value: \"\" };\n continue;\n }\n switch (token.kind) {\n case \"text\": {\n let text = token.text;\n // A text run's trailing LINE BREAK before a block boundary (a paragraph-kind or\n // chapter token, or fragment end) is structural — the line ends where the next block\n // marker begins, and ParatextData emits no content there. A line break before an\n // INLINE token (char/note/verse/milestone: an ordinary line wrap) stays a content\n // space, as does any typed same-line space. Engine fragments carry no line breaks,\n // so only whole-file/direct-converter input reaches this path; `regularizeSpaces`\n // collapsed the run to `\"\\n\"`. Note interiors keep the pre-existing space (a note\n // terminated by a line-end block boundary is not corpus-attested).\n if (!note && text.endsWith(\"\\n\")) {\n const next = tokens[tokenIndex + 1];\n if (next === undefined || next.kind === \"para\" || next.kind === \"chapter\")\n text = text.slice(0, -1);\n }\n if (text)\n pushContent(toUsjText(text));\n break;\n }\n case \"para\": {\n // ---- table assembly ----\n // Row/cell markers reach assembly as para tokens (paragraph styles, or unknown to\n // the sheet). Table shapes never engage inside note content — a row/cell marker\n // there keeps its plain resolution, and ParatextData builds no tables there either.\n const tableEligible = !note && !isNoteContext;\n if (tableEligible && token.marker === TABLE_ROW_MARKER) {\n closeCharStack();\n // Consecutive `\\tr`s share one table; the first creates it.\n if (!table) {\n table = { type: \"table\", content: [] };\n blockTarget().push(table);\n }\n tableRow = { type: \"table:row\", marker: TABLE_ROW_MARKER, content: [] };\n getContent(table).push(tableRow);\n // Content before the first cell marker (degenerate) lands in the row itself.\n para = tableRow;\n atChapterRootScope = false;\n break;\n }\n if (tableEligible && tableRow) {\n const cellMatch = TABLE_CELL_MARKER_REGEX.exec(token.marker);\n // A name outside what ParatextData recognizes as a cell (see\n // isRecognizedTableCell) is an unknown marker that ENDS the table (and the next\n // `\\tr` starts a fresh one).\n if (cellMatch && isRecognizedTableCell(cellMatch)) {\n pushTableCell(tableRow, token.marker, cellMatch);\n break;\n }\n }\n // Any other paragraph-kind token (esb/esbe included) ends an open table; the token\n // itself then processes normally. A cell marker with NO open row is not table\n // content — it stays an unknown paragraph, exactly as ParatextData splits it out.\n endTable();\n // ---- sidebar assembly ----\n if (!isNoteContext && token.marker === SIDEBAR_MARKER) {\n closeCharStack();\n closeNote(false);\n // Sidebars never nest: an unterminated previous sidebar closes implicitly.\n closeSidebar(false);\n // Landed BEFORE `sidebar` is set: `blockTarget()` answers \"the open sidebar's content\"\n // whenever one is open, so assigning first would nest the new sidebar inside itself.\n const opened = {\n type: \"sidebar\",\n marker: SIDEBAR_MARKER,\n content: [],\n };\n blockTarget().push(opened);\n sidebar = opened;\n para = undefined;\n attrTarget = sidebar; // receptive to \\cat (directly after \\esb only)\n atChapterRootScope = false;\n break;\n }\n if (token.marker === SIDEBAR_END_MARKER && sidebar) {\n closeCharStack();\n closeNote(false);\n // `\\esbe` terminates the sidebar and is consumed — it emits nothing itself. With\n // no open sidebar it falls through to today's unknown-paragraph behavior.\n closeSidebar(true);\n para = undefined;\n break;\n }\n // ---- peripheral-division assembly ----\n if (!isNoteContext && token.marker === PERIPH_MARKER) {\n closeCharStack();\n closeNote(false);\n // A division ends every block structure open inside it, including a sidebar the author\n // never terminated; divisions themselves never nest.\n closeSidebar(false);\n closePeriph();\n periphCapture = { value: \"\" };\n para = undefined;\n atChapterRootScope = false;\n break;\n }\n startParagraph(token.marker);\n break;\n }\n case \"verse\": {\n // A verse closes an open note but — for USFM ≤3.0 — does NOT close open char styles: the\n // unclosed span continues across the verse and the verse milestone nests inside it (PT9\n // UsfmParser Verse case: `if (!RequiresPlusOnNestedStyles()) CloseCharStyles()`, and\n // RequiresPlusOnNestedStyles() is true for ≤3.0). This pipeline targets ≤3.0; when the\n // ParatextData dependency moves past 9.6 the close-at-verse must become version-switched\n // (guarded by the upgrade tripwire, alongside close-on-bare and `+` emission).\n closeNote(false);\n const verse = { type: \"verse\", marker: VERSE_MARKER, number: token.number };\n pushContent(verse);\n attrTarget = verse; // receptive to \\va/\\vp\n break;\n }\n case \"chapter\": {\n closeCharStack();\n closeNote(false);\n // A chapter boundary ends any open table and implicitly closes an open sidebar\n // (ParatextData auto-closes sidebars at the end of the chapter). A peripheral division\n // ends there too: chapters are book body, which is never division content.\n endTable();\n closeSidebar(false);\n closePeriph();\n para = undefined;\n const chapter = {\n type: \"chapter\",\n marker: CHAPTER_MARKER,\n number: token.number,\n };\n result.push(chapter);\n attrTarget = chapter; // receptive to \\ca/\\cp\n atChapterRootScope = true;\n break;\n }\n case \"note\": {\n // A note does NOT close open char spans: USX nests the note inside them and the\n // enclosing span continues after it (`\\wj a \\f …\\f* b\\wj*` → wj contains [text,\n // note, text]). Notes themselves never nest, so a previous open note closes first.\n closeNote(false);\n const target = container();\n note = { type: \"note\", marker: token.marker, caller: token.caller, content: [] };\n noteBaseDepth = charStack.length;\n target.push(note);\n attrTarget = note; // receptive to \\cat (right after the caller only)\n break;\n }\n case \"charOpen\": {\n // A cell marker inside an OPEN row is a cell, whichever token kind the stylesheet's\n // classification made it: usfm.sty declares `\\th1`/`\\tc1`/… as Character styles, so with a\n // real project sheet they arrive here rather than as paragraph tokens, and without this a\n // table copied out of the editor pastes back as loose char spans inside empty rows.\n // Guarded on an open row — only `\\tr` opens one — so a char span that merely shares a\n // cell marker's name outside a table keeps its ordinary resolution, and a NESTED span\n // (`\\+tc1`) is never a cell.\n if (!note && !isNoteContext && tableRow && !token.isNested) {\n const cellMatch = TABLE_CELL_MARKER_REGEX.exec(token.marker);\n if (cellMatch && isRecognizedTableCell(cellMatch)) {\n pushTableCell(tableRow, token.marker, cellMatch);\n break;\n }\n }\n // A new non-nested char marker auto-closes open char styles (PT9) — but never\n // across an open note's boundary: the frames enclosing the note stay open.\n if (!token.isNested) {\n const base = note ? noteBaseDepth : 0;\n markImplicitlyClosed(base);\n charStack.length = base;\n }\n const target = container();\n const object = { type: \"char\", marker: token.marker, content: [] };\n target.push(object);\n charStack.push({ object });\n break;\n }\n case \"end\": {\n const marker = token.marker.replace(/^\\+/, \"\");\n // While a note is open, a closer only matches frames opened INSIDE it — it must not\n // reach across the note boundary and close an enclosing span from within the note.\n const searchBase = note ? noteBaseDepth : 0;\n const frameIndex = charStack.findLastIndex((frame, index) => index >= searchBase && frame.object.marker === marker);\n if (frameIndex >= 0) {\n extractAttributes(charStack[frameIndex].object);\n // Nested spans above the explicitly-closed frame are closed IMPLICITLY by it.\n markImplicitlyClosed(frameIndex + 1);\n charStack.length = frameIndex;\n }\n else if (note && note.marker === marker) {\n // Explicit note close: chars opened inside the note close implicitly with it;\n // frames enclosing the note survive (closeNote truncates to the note boundary).\n closeNote(true);\n }\n else {\n // Unmatched closer: PT9 first pops every open char style above the note/para boundary\n // (UsfmParser End token: the while-loop closes each Char element on top of the stack\n // until it matches or hits a non-char element; with no match here they all close as\n // closed=\"false\"), THEN flags the stray closer (PT9 sink.Unmatched,\n // UsxUsfmParserSink.cs:262-266) — an unmatched element rendered as ImmutableUnmatchedNode\n // with the existing `.invalid` styling that serializes back to the same text. Closing\n // the open frames first keeps the following text in the paragraph/note instead of\n // swallowing it into a span PT9 has already terminated.\n markImplicitlyClosed(searchBase);\n charStack.length = searchBase;\n pushContent({ type: \"unmatched\", marker: `${token.marker}*` });\n }\n break;\n }\n case \"milestone\":\n pushContent({ type: \"ms\", marker: token.marker, ...token.attributes });\n break;\n case \"optbreak\":\n pushContent({ type: \"optbreak\" });\n break;\n }\n }\n // Fragment ended on a `\\periph` marker line: the division has its title and attributes but no\n // content, which is a complete division — a periph never had a closer to be missing.\n if (periphCapture)\n finishPeriphLine(true);\n // Fragment ended mid-figure: no `\\fig*` closer — degrade to the plain char/para span.\n if (figCapture)\n materializeFigCapture();\n if (attrCapture) {\n // Fragment ended mid-capture. A para-shaped capture (`\\cp 1 cp` at fragment end) folds —\n // the paragraph ended with plain-text content. A char-shaped capture never saw its\n // closer, so it stays a standalone (implicitly closed) span.\n if (attrCapture.shape === \"para\") {\n const cpValue = attrCapture.value.replace(/[\\s\\u200B]+$/, \"\");\n // Empty `\\cp` at fragment end stays a first-class empty para (never an empty pubnumber);\n // a non-empty one folds as the pubnumber.\n if (cpValue === \"\")\n startParagraph(attrCapture.marker);\n else\n Object.assign(attrCapture.target, { [attrCapture.attrName]: toUsjText(cpValue) });\n attrCapture = undefined;\n }\n else {\n // Trailing line break at the fragment-end boundary is structural (text-case rule).\n if (attrCapture.value.endsWith(\"\\n\"))\n attrCapture.value = attrCapture.value.slice(0, -1);\n materializeCaptureAsChar();\n }\n }\n closeCharStack();\n closeNote(false);\n // Fragment ended without `\\esbe`: the sidebar closes implicitly (closed=\"false\").\n closeSidebar(false);\n // Drop empty content arrays (USJ omits `content` for empty paras).\n const dropEmpty = (items) => {\n for (const item of items) {\n if (typeof item === \"string\")\n continue;\n if (item.content) {\n dropEmpty(item.content);\n if (item.content.length === 0)\n delete item.content;\n }\n }\n };\n dropEmpty(result);\n return result;\n}\n/**\n * On explicit close, split a trailing `|attributes` chunk off the span's trailing text run.\n *\n * The tokenizer splits `//` into optbreak tokens spec-blind, so an attribute segment whose\n * VALUE contains `//` (a URL in `link-href`, say) arrives here as interleaved strings and\n * optbreak objects. ParatextData never does that: `UsfmToken.HandleAttributes` strips the\n * attribute segment out of the text run at tokenize-time — splitting the run at its FIRST `|` —\n * and only the remaining content text ever reaches the `//`→optbreak pass (UsfmParser's Text\n * case). So the trailing run (the maximal contiguous string/optbreak suffix, which by\n * construction came from ONE original text run) is rejoined byte-exact — each optbreak was a\n * literal `//` — before attribute parsing, and only pre-pipe optbreaks survive as content.\n * When the segment does not parse, the content stays as-is, split optbreaks included: that too\n * is ParatextData (a failed `SetAttributes` leaves the run literal TEXT, whose post-pipe `//`\n * then genuinely becomes optional breaks).\n */\nfunction extractAttributes(object) {\n const content = object.content;\n if (!content || content.length === 0)\n return;\n // Trailing text run: the maximal contiguous suffix of strings and optbreak objects.\n let runStart = content.length;\n while (runStart > 0) {\n const item = content[runStart - 1];\n if (typeof item !== \"string\" && item.type !== \"optbreak\")\n break;\n runStart--;\n }\n // First pipe-carrying string within the run (PT9: `text.IndexOf('|')` on the whole run).\n const pipeItemIndex = content.findIndex((item, index) => index >= runStart && typeof item === \"string\" && item.includes(\"|\"));\n if (pipeItemIndex < 0)\n return;\n const rejoined = content\n .slice(pipeItemIndex)\n .map((item) => (typeof item === \"string\" ? item : \"//\"))\n .join(\"\");\n const pipeIndex = rejoined.indexOf(\"|\");\n const attributes = parseAttributeText(rejoined.slice(pipeIndex + 1), object.marker ?? \"\");\n if (!attributes)\n return;\n const text = rejoined.slice(0, pipeIndex);\n content.length = pipeItemIndex;\n if (text)\n content.push(text);\n Object.assign(object, attributes);\n}\n","import { createState } from \"lexical\";\n/** Should only be used with CharNodes. */\nexport const charIdState = createState(\"cid\", {\n parse: (v) => (typeof v === \"string\" ? v : undefined),\n});\n/** Can be used on any standard USJ node. */\nexport const segmentState = createState(\"segment\", {\n parse: (v) => (typeof v === \"string\" ? v : undefined),\n});\n/** Should be used with TextNodes. */\nexport const textTypeState = createState(\"textType\", {\n parse: (v) => (typeof v === \"string\" ? v : undefined),\n});\n/**\n * `textTypeState` value tagging the engine-owned NBSP separator between an editable marker glyph\n * and its content (the `[glyph, separator, ...content]` prefix layout). The runtime creator and\n * reader key on this constant — `$createMarkerTrailingSeparator` and\n * `$isMarkerTrailingSeparator` (node.utils.ts) — as does the forward adaptor building the\n * serialized twin for a paragraph's own prefix.\n *\n * It is NOT the only spelling in the codebase: four sites write the tag as a raw string, so\n * changing this value means changing them too. One is a CREATOR — the forward adaptor's table-cell\n * separator (usj-editor.adaptor.ts), whose sibling paragraph-prefix creator a few lines away does\n * use the constant. The other three are readers: the two collab paths that keep the separator's\n * text out of content ops (editor-delta.adaptor.ts and delta-common.utils.ts), and the settled-note\n * unwrap that reads the tag off a SERIALIZED node's `$.textType` (virtualSettle.utils.ts).\n */\nexport const MARKER_TRAILING_SPACE_TEXT_TYPE = \"marker-trailing-space\";\n","import { $applyNodeReplacement, $getState, $setState, createState, DecoratorNode, isHTMLElement, } from \"lexical\";\nexport const IMMUTABLE_TYPED_TEXT_VERSION = 1;\n/** The `textType` every USFM marker glyph carries, whichever way the view renders it. */\nconst MARKER_TEXT_TYPE = \"marker\";\n/**\n * Marks a marker glyph as one the view renders in the GUTTER — the fixed column beside the text\n * (`hasGutterParaMarkers`) — rather than inline among the words.\n *\n * The distinction cannot be read off the node's class or its `textType`: the gutter aid and\n * markerMode \"visible\"'s INLINE glyph are both an `ImmutableTypedTextNode` with\n * `textType: \"marker\"`. Nor can it be read off the view, because \"is this marker in the gutter?\" is\n * asked one node at a time — a document can carry gutter markers and inline glyphs at once (a\n * book's `\\id` line, for one). So the fact travels on the node that has it, set where the glyph is\n * built, and it is what makes gutter markers unclickable in\n * `ParaMarkerPrefixCursorGuardPlugin` (shared-react) while inline glyphs keep their caret.\n *\n * Set on the SERIALIZED twin by the USJ→editor adaptor's `createImmutableTypedText`\n * (usj-editor.adaptor.ts, platform), which builds JSON rather than live nodes — the same split the\n * `textType` state already has.\n */\nexport const gutterMarkerState = createState(\"isGutterMarker\", {\n parse: (value) => value === true,\n});\nexport class ImmutableTypedTextNode extends DecoratorNode {\n __textType;\n __text;\n constructor(textType = \"\", text = \"\", key) {\n super(key);\n this.__textType = textType;\n this.__text = text;\n }\n static getType() {\n return \"immutable-typed-text\";\n }\n static clone(node) {\n const { __textType, __text, __key } = node;\n return new ImmutableTypedTextNode(__textType, __text, __key);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isTypedTextElement(node))\n return null;\n return {\n conversion: $convertImmutableTypedTextElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createImmutableTypedTextNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setTextType(serializedNode.textType)\n .setTextContent(serializedNode.text);\n }\n setTextType(textType) {\n if (this.__textType === textType)\n return this;\n const self = this.getWritable();\n self.__textType = textType;\n return self;\n }\n getTextType() {\n const self = this.getLatest();\n return self.__textType;\n }\n setTextContent(text) {\n if (this.__text === text)\n return this;\n const self = this.getWritable();\n self.__text = text;\n return self;\n }\n getTextContent() {\n const self = this.getLatest();\n return self.__text;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.setAttribute(\"data-text-type\", this.__textType);\n dom.classList.add(this.__textType);\n // The glyph bytes are written into the element itself, NOT rendered through the decorator\n // portal — see `decorate` for why. The resulting DOM is identical either way (React rendered\n // the same string as this element's only text child), so nothing downstream changes shape.\n dom.textContent = this.__text;\n return dom;\n }\n updateDOM(prevNode, dom) {\n // Keep the rendered bytes in step with an in-place `setTextContent` (the JSON update path is\n // its only caller); the guard means an untouched node writes nothing. `__textType` is\n // deliberately NOT re-applied here — it is only ever set while building a node from JSON,\n // before this element exists, so the class list has never been able to go stale, and\n // re-deriving it would be a behavior change this fix does not need.\n if (prevNode.__text !== this.__text)\n dom.textContent = this.__text;\n // Returning false tells Lexical that this node does not need its\n // DOM element replacing with a new copy from createDOM.\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-text-type\", this.getTextType());\n }\n return { element };\n }\n /**\n * No decorator payload: the glyph bytes are rendered by {@link createDOM} instead.\n *\n * This node used to return its text here, so `@lexical/react`'s `useDecorators` painted the\n * bytes into this element through a React portal. That is unsound for a value with STABLE\n * IDENTITY. Lexical only notifies its decorator listener when the decorator value actually\n * changes — `reconcileDecorator` bails on `currentDecorators[key] === decorator` — and two equal\n * strings always compare equal. So whenever Lexical DESTROYS and RE-CREATES this node's element\n * while the node itself survives (`$createNode` runs for every child of a freshly created parent,\n * which is exactly what re-parenting a node does), the map never changed, no listener fired,\n * `useDecorators` never rebuilt its portal list, and the portal stayed pointed at the OLD,\n * detached element. The new element was left permanently EMPTY — the glyph vanished from the\n * screen while the node, the USJ, and the file on disk all still carried it, and only remounting\n * the editor brought it back.\n *\n * The marker-edit engine re-parents preserved nodes on every Tier-2 paragraph rebuild\n * (`$replaceSentinels`, tier2Rebuild.utils.ts, moves each preserved node into the rebuilt\n * paragraph), so an `\\optbreak`'s `//` token — a child of the preserved `UnknownNode` — blanked\n * out the first time anything else in its paragraph settled. Rendering from `createDOM` removes\n * the portal indirection entirely: the bytes travel with the element that carries them, so any\n * number of re-parents keeps them, and there is one less React portal per glyph.\n */\n decorate() {\n return null;\n }\n exportJSON() {\n return {\n // Spread first so this node's own properties win: super contributes the NodeState (e.g.\n // `gutterMarkerState`), which `updateFromJSON` reads back, so a glyph that round-trips\n // through JSON stays the same KIND of glyph.\n ...super.exportJSON(),\n type: this.getType(),\n textType: this.getTextType(),\n text: this.getTextContent(),\n version: IMMUTABLE_TYPED_TEXT_VERSION,\n };\n }\n // Mutation\n isKeyboardSelectable() {\n return false;\n }\n}\nfunction $convertImmutableTypedTextElement(element) {\n const textType = element.getAttribute(\"data-text-type\") ?? \"\";\n const text = element.textContent ?? \"\";\n const node = $createImmutableTypedTextNode(textType, text);\n return { node };\n}\nexport function $createImmutableTypedTextNode(textType, text) {\n return $applyNodeReplacement(new ImmutableTypedTextNode(textType, text));\n}\n/**\n * Creates a marker glyph that renders in the gutter — see {@link gutterMarkerState}.\n *\n * @param text - The glyph's bytes, e.g. `\\q1` plus its separator.\n * @returns the marker node, flagged as gutter-rendered.\n */\nexport function $createGutterMarkerNode(text) {\n return $setState($createImmutableTypedTextNode(MARKER_TEXT_TYPE, text), gutterMarkerState, true);\n}\n/**\n * Whether the node is a marker glyph the view renders in the gutter, which is never a place a\n * caret may come to rest — see {@link gutterMarkerState}.\n *\n * @param node - The node to check.\n * @returns `true` for a gutter-rendered marker glyph.\n */\nexport function $isGutterMarkerNode(node) {\n return $isImmutableTypedTextNode(node) && $getState(node, gutterMarkerState);\n}\nfunction isTypedTextElement(node) {\n // NOTE: `tagName` is upper-cased for HTML-namespace elements, so this comparison never matches\n // and the `span` conversion above is unreachable. Correcting the case ALONE is not the fix: this\n // predicate's only discriminator is the tag, so a case-insensitive compare makes this node claim\n // EVERY pasted `` at priority 1 — including the verse spans that\n // `StructureKeyboardPlugin`'s paste sanitizer expects to strip down to text. A real fix also has\n // to narrow the predicate (the `data-text-type` attribute `exportDOM` emits is the natural\n // discriminator) and decide what pasting a marker glyph should do, which is a behavior decision\n // rather than a typo fix.\n return node?.tagName === \"span\";\n}\nexport function $isImmutableTypedTextNode(node) {\n return node instanceof ImmutableTypedTextNode;\n}\nexport function isSerializedImmutableTypedTextNode(node) {\n return node?.type === ImmutableTypedTextNode.getType();\n}\n","/**\n * Adapted from https://github.com/facebook/lexical/blob/92c47217244f9d3c22a59728633fb41a10420724/packages/lexical-mark/src/MarkNode.ts\n * This adaption allows for different types of marks while still only requiring one mark to enclose\n * a selection.\n */\nimport { assertSafeKey } from \"@eten-tech-foundation/scripture-utilities\";\nimport { addClassNamesToElement, removeClassNamesFromElement } from \"@lexical/utils\";\nimport { $applyNodeReplacement, $isElementNode, $isRangeSelection, $isTextNode, ElementNode, } from \"lexical\";\n/** Reserved mark type for CommentPlugin. */\nexport const COMMENT_MARK_TYPE = \"internal-comment\";\nconst reservedTypes = [COMMENT_MARK_TYPE];\nconst NO_IDS = Object.freeze({});\nconst NO_ON_CLICKS = Object.freeze({});\nconst NO_ON_REMOVES = Object.freeze({});\nconst NO_ON_MOUSE_ENTERS = Object.freeze({});\nconst NO_ON_MOUSE_LEAVES = Object.freeze({});\nconst TYPED_MARK_VERSION = 1;\nconst typedOnClickRegistry = new Map();\nconst typedOnRemoveRegistry = new Map();\nconst typedOnMouseEnterRegistry = new Map();\nconst typedOnMouseLeaveRegistry = new Map();\nexport class TypedMarkNode extends ElementNode {\n __typedIDs;\n __typedOnClicks;\n __typedOnRemoves;\n __typedOnMouseEnters;\n __typedOnMouseLeaves;\n __domOnClickListener;\n __domOnMouseEnterListener;\n __domOnMouseLeaveListener;\n __suppressOnRemoveCallbacks;\n constructor(typedIds = NO_IDS, typedOnClicks, typedOnRemoves, typedOnMouseEnters, typedOnMouseLeaves, key) {\n super(key);\n this.__typedIDs = cloneTypedIDs(typedIds);\n this.__typedOnClicks = cloneTypedOnClicks(typedOnClicks);\n this.__typedOnRemoves = cloneTypedOnRemoves(typedOnRemoves);\n this.__typedOnMouseEnters = cloneTypedOnMouseEnters(typedOnMouseEnters);\n this.__typedOnMouseLeaves = cloneTypedOnMouseLeaves(typedOnMouseLeaves);\n this.pruneTypedOnClicks();\n this.pruneTypedOnRemoves();\n this.pruneTypedOnMouseEnters();\n this.pruneTypedOnMouseLeaves();\n this.syncTypedOnClicksToRegistry();\n this.syncTypedOnRemovesToRegistry();\n this.syncTypedOnMouseEntersToRegistry();\n this.syncTypedOnMouseLeavesToRegistry();\n }\n static getType() {\n return \"typed-mark\";\n }\n static clone(node) {\n const __typedIDs = cloneTypedIDs(node.__typedIDs);\n const __typedOnClicks = cloneTypedOnClicks(node.__typedOnClicks);\n const __typedOnRemoves = cloneTypedOnRemoves(node.__typedOnRemoves);\n const __typedOnMouseEnters = cloneTypedOnMouseEnters(node.__typedOnMouseEnters);\n const __typedOnMouseLeaves = cloneTypedOnMouseLeaves(node.__typedOnMouseLeaves);\n return new TypedMarkNode(__typedIDs, __typedOnClicks, __typedOnRemoves, __typedOnMouseEnters, __typedOnMouseLeaves, node.__key);\n }\n static isReservedType(type) {\n return reservedTypes.includes(type);\n }\n static importDOM() {\n return null;\n }\n static importJSON(serializedNode) {\n return $createTypedMarkNode().updateFromJSON(serializedNode);\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n typedIDs: this.getTypedIDs(),\n version: TYPED_MARK_VERSION,\n };\n }\n createDOM(config, editor) {\n const element = document.createElement(\"mark\");\n for (const [type, ids] of Object.entries(this.__typedIDs)) {\n addClassNamesToElement(element, getTypedClassName(config.theme.typedMark, type));\n if (ids.length > 1) {\n addClassNamesToElement(element, getTypedClassName(config.theme.typedMarkOverlap, type));\n }\n for (const id of ids) {\n addClassNamesToElement(element, getTypedClassName(\"annotationId\", id));\n }\n }\n const clickListener = this.getOrCreateDOMClickListener(editor);\n element.addEventListener(\"click\", clickListener);\n const mouseEnterListener = this.getOrCreateDOMMouseEnterListener(editor);\n element.addEventListener(\"mouseenter\", mouseEnterListener);\n const mouseLeaveListener = this.getOrCreateDOMMouseLeaveListener(editor);\n element.addEventListener(\"mouseleave\", mouseLeaveListener);\n return element;\n }\n updateDOM(prevNode, element, config) {\n const types = new Set([\n ...Object.keys(prevNode.__typedIDs ?? {}),\n ...Object.keys(this.__typedIDs ?? {}),\n ]);\n for (const type of types) {\n const prevIDs = prevNode.__typedIDs[type] ?? [];\n const nextIDs = this.__typedIDs[type] ?? [];\n const prevIDsCount = prevIDs.length;\n const nextIDsCount = nextIDs.length;\n const markTheme = getTypedClassName(config.theme.typedMark, type);\n const overlapTheme = getTypedClassName(config.theme.typedMarkOverlap, type);\n if (prevIDsCount !== nextIDsCount) {\n if (prevIDsCount === 0) {\n if (nextIDsCount === 1)\n addClassNamesToElement(element, markTheme);\n }\n else if (nextIDsCount === 0) {\n removeClassNamesFromElement(element, markTheme);\n }\n if (prevIDsCount === 1) {\n if (nextIDsCount === 2)\n addClassNamesToElement(element, overlapTheme);\n }\n else if (nextIDsCount === 1) {\n removeClassNamesFromElement(element, overlapTheme);\n }\n }\n const prevIDsSet = new Set(prevIDs);\n const nextIDsSet = new Set(nextIDs);\n for (const id of prevIDs) {\n if (!nextIDsSet.has(id)) {\n removeClassNamesFromElement(element, getTypedClassName(\"annotationId\", id));\n }\n }\n for (const id of nextIDs) {\n if (!prevIDsSet.has(id)) {\n addClassNamesToElement(element, getTypedClassName(\"annotationId\", id));\n }\n }\n }\n return false;\n }\n updateFromJSON(serializedNode) {\n return super.updateFromJSON(serializedNode).setTypedIDs(serializedNode.typedIDs);\n }\n hasID(type, id) {\n const typedIDs = this.getTypedIDs();\n const ids = typedIDs[type];\n if (!ids)\n return false;\n for (const existingId of ids) {\n if (id === existingId) {\n return true;\n }\n }\n return false;\n }\n getTypedIDs() {\n const self = this.getLatest();\n return $isTypedMarkNode(self) ? self.__typedIDs : {};\n }\n setTypedIDs(ids) {\n const self = this.getWritable();\n const previousIDs = cloneTypedIDs(self.__typedIDs);\n self.__typedIDs = cloneTypedIDs(ids);\n self.dispatchRemovedIDs(previousIDs, self.__typedIDs, \"removed\");\n self.pruneTypedOnClicks();\n self.pruneTypedOnRemoves();\n self.pruneTypedOnMouseEnters();\n self.pruneTypedOnMouseLeaves();\n self.syncTypedOnClicksToRegistry();\n self.syncTypedOnRemovesToRegistry();\n self.syncTypedOnMouseEntersToRegistry();\n self.syncTypedOnMouseLeavesToRegistry();\n const mergedNode = self.mergeWithAdjacentTypedMarks();\n if (mergedNode.hasNoIDsForEveryType() && mergedNode.getParent() !== null)\n $unwrapTypedMarkNode(mergedNode);\n return mergedNode;\n }\n setTypedOnClicks(typedOnClicks) {\n const self = this.getWritable();\n self.__typedOnClicks = cloneTypedOnClicks(typedOnClicks);\n self.pruneTypedOnClicks();\n self.syncTypedOnClicksToRegistry();\n return self;\n }\n getTypedOnClicks() {\n const self = this.getLatest();\n if (!$isTypedMarkNode(self))\n return {};\n const stored = typedOnClickRegistry.get(self.getKey());\n return stored ?? {};\n }\n setTypedOnRemoves(typedOnRemoves) {\n const self = this.getWritable();\n self.__typedOnRemoves = cloneTypedOnRemoves(typedOnRemoves);\n self.pruneTypedOnRemoves();\n self.syncTypedOnRemovesToRegistry();\n return self;\n }\n getTypedOnRemoves() {\n const self = this.getLatest();\n if (!$isTypedMarkNode(self))\n return {};\n const stored = typedOnRemoveRegistry.get(self.getKey());\n return stored ?? {};\n }\n setTypedOnMouseEnters(typedOnMouseEnters) {\n const self = this.getWritable();\n self.__typedOnMouseEnters = cloneTypedOnMouseEnters(typedOnMouseEnters);\n self.pruneTypedOnMouseEnters();\n self.syncTypedOnMouseEntersToRegistry();\n return self;\n }\n getTypedOnMouseEnters() {\n const self = this.getLatest();\n if (!$isTypedMarkNode(self))\n return {};\n const stored = typedOnMouseEnterRegistry.get(self.getKey());\n return stored ?? {};\n }\n setTypedOnMouseLeaves(typedOnMouseLeaves) {\n const self = this.getWritable();\n self.__typedOnMouseLeaves = cloneTypedOnMouseLeaves(typedOnMouseLeaves);\n self.pruneTypedOnMouseLeaves();\n self.syncTypedOnMouseLeavesToRegistry();\n return self;\n }\n getTypedOnMouseLeaves() {\n const self = this.getLatest();\n if (!$isTypedMarkNode(self))\n return {};\n const stored = typedOnMouseLeaveRegistry.get(self.getKey());\n return stored ?? {};\n }\n addID(type, id, onClick, onRemove, onMouseEnter, onMouseLeave) {\n const self = this.getWritable();\n if (!$isTypedMarkNode(self))\n return;\n assertSafeKey(type);\n assertSafeKey(id);\n let ids = self.__typedIDs[type];\n if (!ids) {\n ids = [];\n self.__typedIDs[type] = ids;\n }\n for (const existingId of ids) {\n if (id === existingId) {\n if (onClick)\n self.setOnClickFor(type, id, onClick);\n if (onRemove)\n self.setOnRemoveFor(type, id, onRemove);\n if (onMouseEnter)\n self.setOnMouseEnterFor(type, id, onMouseEnter);\n if (onMouseLeave)\n self.setOnMouseLeaveFor(type, id, onMouseLeave);\n return;\n }\n }\n ids.push(id);\n if (onClick)\n self.setOnClickFor(type, id, onClick);\n if (onRemove)\n self.setOnRemoveFor(type, id, onRemove);\n if (onMouseEnter)\n self.setOnMouseEnterFor(type, id, onMouseEnter);\n if (onMouseLeave)\n self.setOnMouseLeaveFor(type, id, onMouseLeave);\n }\n deleteID(type, id) {\n const self = this.getWritable();\n if (!$isTypedMarkNode(self))\n return;\n const ids = self.__typedIDs[type];\n if (!ids || ids.length === 0)\n return;\n for (let i = 0; i < ids.length; i++) {\n if (id === ids[i]) {\n ids.splice(i, 1);\n self.invokeOnRemove(type, id, \"removed\");\n break;\n }\n }\n self.removeOnClickFor(type, id);\n self.removeOnRemoveFor(type, id);\n self.removeOnMouseEnterFor(type, id);\n self.removeOnMouseLeaveFor(type, id);\n self.pruneTypedOnClicks();\n self.pruneTypedOnRemoves();\n self.pruneTypedOnMouseEnters();\n self.pruneTypedOnMouseLeaves();\n const mergedNode = self.mergeWithAdjacentTypedMarks();\n if (mergedNode.hasNoIDsForEveryType() && mergedNode.getParent() !== null)\n $unwrapTypedMarkNode(mergedNode);\n }\n hasNoIDsForEveryType() {\n return Object.values(this.getTypedIDs()).every((ids) => ids === undefined || ids.length === 0);\n }\n insertNewAfter(_selection, restoreSelection = true) {\n const node = $createTypedMarkNode(this.__typedIDs, this.getTypedOnClicks());\n this.insertAfter(node, restoreSelection);\n return node;\n }\n canInsertTextBefore() {\n return false;\n }\n canInsertTextAfter() {\n return false;\n }\n canBeEmpty() {\n return false;\n }\n isInline() {\n return true;\n }\n extractWithChild(_child, selection, destination) {\n if (!$isRangeSelection(selection) || destination === \"html\") {\n return false;\n }\n const anchor = selection.anchor;\n const focus = selection.focus;\n const anchorNode = anchor.getNode();\n const focusNode = focus.getNode();\n const isBackward = selection.isBackward();\n const selectionLength = isBackward\n ? anchor.offset - focus.offset\n : focus.offset - anchor.offset;\n return (this.isParentOf(anchorNode) &&\n this.isParentOf(focusNode) &&\n this.getTextContent().length === selectionLength);\n }\n excludeFromCopy(destination) {\n return destination !== \"clone\";\n }\n remove(preserveEmptyParent) {\n const self = this.getWritable();\n const typedIDs = this.getTypedIDs();\n if (self.__suppressOnRemoveCallbacks) {\n self.__suppressOnRemoveCallbacks = undefined;\n }\n else {\n self.dispatchOnRemoveForTypedIDs(typedIDs, \"destroyed\");\n }\n typedOnClickRegistry.delete(self.getKey());\n typedOnRemoveRegistry.delete(self.getKey());\n typedOnMouseEnterRegistry.delete(self.getKey());\n typedOnMouseLeaveRegistry.delete(self.getKey());\n self.__typedOnClicks = undefined;\n self.__typedOnRemoves = undefined;\n self.__typedOnMouseEnters = undefined;\n self.__typedOnMouseLeaves = undefined;\n super.remove.call(self, preserveEmptyParent);\n }\n getOrCreateDOMClickListener(editor) {\n if (!this.__domOnClickListener) {\n this.__domOnClickListener = (event) => {\n this.handleDOMClick(event, editor);\n };\n }\n return this.__domOnClickListener;\n }\n handleDOMClick(event, editor) {\n const typedOnClicks = typedOnClickRegistry.get(this.getKey());\n if (!typedOnClicks)\n return;\n const callbacks = [];\n for (const [type, callbacksById] of Object.entries(typedOnClicks)) {\n for (const [id, callback] of Object.entries(callbacksById)) {\n if (callback)\n callbacks.push([callback, type, id]);\n }\n }\n if (callbacks.length === 0)\n return;\n const textContent = editor.read(() => this.getTextContent());\n for (const [callback, type, id] of callbacks) {\n callback(event, type, id, textContent);\n }\n }\n getOrCreateDOMMouseEnterListener(editor) {\n if (!this.__domOnMouseEnterListener) {\n this.__domOnMouseEnterListener = (event) => {\n this.handleDOMMouseEnter(event, editor);\n };\n }\n return this.__domOnMouseEnterListener;\n }\n handleDOMMouseEnter(event, editor) {\n const typedOnMouseEnters = typedOnMouseEnterRegistry.get(this.getKey());\n if (!typedOnMouseEnters)\n return;\n const callbacks = [];\n for (const [type, callbacksById] of Object.entries(typedOnMouseEnters)) {\n for (const [id, callback] of Object.entries(callbacksById)) {\n if (callback)\n callbacks.push([callback, type, id]);\n }\n }\n if (callbacks.length === 0)\n return;\n const textContent = editor.read(() => this.getTextContent());\n for (const [callback, type, id] of callbacks) {\n callback(event, type, id, textContent);\n }\n }\n getOrCreateDOMMouseLeaveListener(editor) {\n if (!this.__domOnMouseLeaveListener) {\n this.__domOnMouseLeaveListener = (event) => {\n this.handleDOMMouseLeave(event, editor);\n };\n }\n return this.__domOnMouseLeaveListener;\n }\n handleDOMMouseLeave(event, editor) {\n const typedOnMouseLeaves = typedOnMouseLeaveRegistry.get(this.getKey());\n if (!typedOnMouseLeaves)\n return;\n const callbacks = [];\n for (const [type, callbacksById] of Object.entries(typedOnMouseLeaves)) {\n for (const [id, callback] of Object.entries(callbacksById)) {\n if (callback)\n callbacks.push([callback, type, id]);\n }\n }\n if (callbacks.length === 0)\n return;\n const textContent = editor.read(() => this.getTextContent());\n for (const [callback, type, id] of callbacks) {\n callback(event, type, id, textContent);\n }\n }\n ensureOnClickMapMutable() {\n if (this.__typedOnClicks === undefined || this.__typedOnClicks === NO_ON_CLICKS) {\n const existing = typedOnClickRegistry.get(this.getKey());\n this.__typedOnClicks = existing ?? {};\n }\n return this.__typedOnClicks;\n }\n syncTypedOnClicksToRegistry() {\n if (!this.__typedOnClicks || Object.keys(this.__typedOnClicks).length === 0) {\n typedOnClickRegistry.delete(this.getKey());\n if (this.__typedOnClicks && Object.keys(this.__typedOnClicks).length === 0) {\n this.__typedOnClicks = undefined;\n }\n return;\n }\n typedOnClickRegistry.set(this.getKey(), this.__typedOnClicks);\n }\n setOnClickFor(type, id, onClick) {\n assertSafeKey(type);\n assertSafeKey(id);\n const callbacks = this.ensureOnClickMapMutable();\n const typeCallbacks = callbacks[type] ?? (callbacks[type] = {});\n typeCallbacks[id] = onClick;\n this.syncTypedOnClicksToRegistry();\n }\n removeOnClickFor(type, id) {\n if (!this.__typedOnClicks)\n return;\n const typeCallbacks = this.__typedOnClicks[type];\n if (!typeCallbacks)\n return;\n const updatedTypeCallbacks = omitRecordKey(typeCallbacks, id);\n const hasRemainingCallbacks = Object.keys(updatedTypeCallbacks).length > 0;\n if (hasRemainingCallbacks) {\n this.__typedOnClicks = {\n ...this.__typedOnClicks,\n [type]: updatedTypeCallbacks,\n };\n }\n else {\n const remainingCallbacks = omitRecordKey(this.__typedOnClicks, type);\n this.__typedOnClicks =\n Object.keys(remainingCallbacks).length > 0 ? remainingCallbacks : undefined;\n }\n this.syncTypedOnClicksToRegistry();\n }\n pruneTypedOnClicks() {\n if (!this.__typedOnClicks || this.__typedOnClicks === NO_ON_CLICKS) {\n this.__typedOnClicks = undefined;\n this.syncTypedOnClicksToRegistry();\n return;\n }\n const nextTypedOnClicks = {};\n for (const [type, callbacks] of Object.entries(this.__typedOnClicks)) {\n const ids = this.__typedIDs[type];\n if (!ids || ids.length === 0) {\n continue;\n }\n const idSet = new Set(ids);\n const filteredCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n if (idSet.has(id)) {\n filteredCallbacks[id] = callback;\n }\n }\n if (Object.keys(filteredCallbacks).length > 0) {\n nextTypedOnClicks[type] = filteredCallbacks;\n }\n }\n this.__typedOnClicks =\n Object.keys(nextTypedOnClicks).length > 0 ? nextTypedOnClicks : undefined;\n this.syncTypedOnClicksToRegistry();\n }\n ensureOnRemoveMapMutable() {\n if (this.__typedOnRemoves === undefined || this.__typedOnRemoves === NO_ON_REMOVES) {\n const existing = typedOnRemoveRegistry.get(this.getKey());\n this.__typedOnRemoves = existing ?? {};\n }\n return this.__typedOnRemoves;\n }\n syncTypedOnRemovesToRegistry() {\n if (!this.__typedOnRemoves || Object.keys(this.__typedOnRemoves).length === 0) {\n typedOnRemoveRegistry.delete(this.getKey());\n if (this.__typedOnRemoves && Object.keys(this.__typedOnRemoves).length === 0) {\n this.__typedOnRemoves = undefined;\n }\n return;\n }\n typedOnRemoveRegistry.set(this.getKey(), this.__typedOnRemoves);\n }\n setOnRemoveFor(type, id, onRemove) {\n assertSafeKey(type);\n assertSafeKey(id);\n const callbacks = this.ensureOnRemoveMapMutable();\n const typeCallbacks = callbacks[type] ?? (callbacks[type] = {});\n typeCallbacks[id] = onRemove;\n this.syncTypedOnRemovesToRegistry();\n }\n removeOnRemoveFor(type, id) {\n if (!this.__typedOnRemoves)\n return;\n const typeCallbacks = this.__typedOnRemoves[type];\n if (!typeCallbacks)\n return;\n const updatedTypeCallbacks = omitRecordKey(typeCallbacks, id);\n const hasRemainingCallbacks = Object.keys(updatedTypeCallbacks).length > 0;\n if (hasRemainingCallbacks) {\n this.__typedOnRemoves = {\n ...this.__typedOnRemoves,\n [type]: updatedTypeCallbacks,\n };\n }\n else {\n const remainingCallbacks = omitRecordKey(this.__typedOnRemoves, type);\n this.__typedOnRemoves =\n Object.keys(remainingCallbacks).length > 0 ? remainingCallbacks : undefined;\n }\n this.syncTypedOnRemovesToRegistry();\n }\n pruneTypedOnRemoves() {\n if (!this.__typedOnRemoves || this.__typedOnRemoves === NO_ON_REMOVES) {\n this.__typedOnRemoves = undefined;\n this.syncTypedOnRemovesToRegistry();\n return;\n }\n const nextTypedOnRemoves = {};\n for (const [type, callbacks] of Object.entries(this.__typedOnRemoves)) {\n const ids = this.__typedIDs[type];\n if (!ids || ids.length === 0)\n continue;\n const idSet = new Set(ids);\n const filteredCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n if (idSet.has(id))\n filteredCallbacks[id] = callback;\n }\n if (Object.keys(filteredCallbacks).length > 0) {\n nextTypedOnRemoves[type] = filteredCallbacks;\n }\n }\n this.__typedOnRemoves =\n Object.keys(nextTypedOnRemoves).length > 0 ? nextTypedOnRemoves : undefined;\n this.syncTypedOnRemovesToRegistry();\n }\n ensureOnMouseEnterMapMutable() {\n if (this.__typedOnMouseEnters === undefined ||\n this.__typedOnMouseEnters === NO_ON_MOUSE_ENTERS) {\n const existing = typedOnMouseEnterRegistry.get(this.getKey());\n this.__typedOnMouseEnters = existing ?? {};\n }\n return this.__typedOnMouseEnters;\n }\n syncTypedOnMouseEntersToRegistry() {\n if (!this.__typedOnMouseEnters || Object.keys(this.__typedOnMouseEnters).length === 0) {\n typedOnMouseEnterRegistry.delete(this.getKey());\n if (this.__typedOnMouseEnters && Object.keys(this.__typedOnMouseEnters).length === 0) {\n this.__typedOnMouseEnters = undefined;\n }\n return;\n }\n typedOnMouseEnterRegistry.set(this.getKey(), this.__typedOnMouseEnters);\n }\n setOnMouseEnterFor(type, id, onMouseEnter) {\n assertSafeKey(type);\n assertSafeKey(id);\n const callbacks = this.ensureOnMouseEnterMapMutable();\n const typeCallbacks = callbacks[type] ?? (callbacks[type] = {});\n typeCallbacks[id] = onMouseEnter;\n this.syncTypedOnMouseEntersToRegistry();\n }\n removeOnMouseEnterFor(type, id) {\n if (!this.__typedOnMouseEnters)\n return;\n const typeCallbacks = this.__typedOnMouseEnters[type];\n if (!typeCallbacks)\n return;\n const updatedTypeCallbacks = omitRecordKey(typeCallbacks, id);\n const hasRemainingCallbacks = Object.keys(updatedTypeCallbacks).length > 0;\n if (hasRemainingCallbacks) {\n this.__typedOnMouseEnters = {\n ...this.__typedOnMouseEnters,\n [type]: updatedTypeCallbacks,\n };\n }\n else {\n const remainingCallbacks = omitRecordKey(this.__typedOnMouseEnters, type);\n this.__typedOnMouseEnters =\n Object.keys(remainingCallbacks).length > 0 ? remainingCallbacks : undefined;\n }\n this.syncTypedOnMouseEntersToRegistry();\n }\n pruneTypedOnMouseEnters() {\n if (!this.__typedOnMouseEnters || this.__typedOnMouseEnters === NO_ON_MOUSE_ENTERS) {\n this.__typedOnMouseEnters = undefined;\n this.syncTypedOnMouseEntersToRegistry();\n return;\n }\n const nextTypedOnMouseEnters = {};\n for (const [type, callbacks] of Object.entries(this.__typedOnMouseEnters)) {\n const ids = this.__typedIDs[type];\n if (!ids || ids.length === 0) {\n continue;\n }\n const idSet = new Set(ids);\n const filteredCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n if (idSet.has(id)) {\n filteredCallbacks[id] = callback;\n }\n }\n if (Object.keys(filteredCallbacks).length > 0) {\n nextTypedOnMouseEnters[type] = filteredCallbacks;\n }\n }\n this.__typedOnMouseEnters =\n Object.keys(nextTypedOnMouseEnters).length > 0 ? nextTypedOnMouseEnters : undefined;\n this.syncTypedOnMouseEntersToRegistry();\n }\n ensureOnMouseLeaveMapMutable() {\n if (this.__typedOnMouseLeaves === undefined ||\n this.__typedOnMouseLeaves === NO_ON_MOUSE_LEAVES) {\n const existing = typedOnMouseLeaveRegistry.get(this.getKey());\n this.__typedOnMouseLeaves = existing ?? {};\n }\n return this.__typedOnMouseLeaves;\n }\n syncTypedOnMouseLeavesToRegistry() {\n if (!this.__typedOnMouseLeaves || Object.keys(this.__typedOnMouseLeaves).length === 0) {\n typedOnMouseLeaveRegistry.delete(this.getKey());\n if (this.__typedOnMouseLeaves && Object.keys(this.__typedOnMouseLeaves).length === 0) {\n this.__typedOnMouseLeaves = undefined;\n }\n return;\n }\n typedOnMouseLeaveRegistry.set(this.getKey(), this.__typedOnMouseLeaves);\n }\n setOnMouseLeaveFor(type, id, onMouseLeave) {\n assertSafeKey(type);\n assertSafeKey(id);\n const callbacks = this.ensureOnMouseLeaveMapMutable();\n const typeCallbacks = callbacks[type] ?? (callbacks[type] = {});\n typeCallbacks[id] = onMouseLeave;\n this.syncTypedOnMouseLeavesToRegistry();\n }\n removeOnMouseLeaveFor(type, id) {\n if (!this.__typedOnMouseLeaves)\n return;\n const typeCallbacks = this.__typedOnMouseLeaves[type];\n if (!typeCallbacks)\n return;\n const updatedTypeCallbacks = omitRecordKey(typeCallbacks, id);\n const hasRemainingCallbacks = Object.keys(updatedTypeCallbacks).length > 0;\n if (hasRemainingCallbacks) {\n this.__typedOnMouseLeaves = {\n ...this.__typedOnMouseLeaves,\n [type]: updatedTypeCallbacks,\n };\n }\n else {\n const remainingCallbacks = omitRecordKey(this.__typedOnMouseLeaves, type);\n this.__typedOnMouseLeaves =\n Object.keys(remainingCallbacks).length > 0 ? remainingCallbacks : undefined;\n }\n this.syncTypedOnMouseLeavesToRegistry();\n }\n pruneTypedOnMouseLeaves() {\n if (!this.__typedOnMouseLeaves || this.__typedOnMouseLeaves === NO_ON_MOUSE_LEAVES) {\n this.__typedOnMouseLeaves = undefined;\n this.syncTypedOnMouseLeavesToRegistry();\n return;\n }\n const nextTypedOnMouseLeaves = {};\n for (const [type, callbacks] of Object.entries(this.__typedOnMouseLeaves)) {\n const ids = this.__typedIDs[type];\n if (!ids || ids.length === 0) {\n continue;\n }\n const idSet = new Set(ids);\n const filteredCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n if (idSet.has(id)) {\n filteredCallbacks[id] = callback;\n }\n }\n if (Object.keys(filteredCallbacks).length > 0) {\n nextTypedOnMouseLeaves[type] = filteredCallbacks;\n }\n }\n this.__typedOnMouseLeaves =\n Object.keys(nextTypedOnMouseLeaves).length > 0 ? nextTypedOnMouseLeaves : undefined;\n this.syncTypedOnMouseLeavesToRegistry();\n }\n invokeOnRemove(type, id, cause) {\n const callbacks = this.getTypedOnRemoves();\n const callback = callbacks[type]?.[id];\n if (!callback)\n return;\n callback(type, id, cause, this.getTextContent());\n this.removeOnRemoveFor(type, id);\n }\n dispatchRemovedIDs(previous, next, cause) {\n const removedPairs = collectRemovedTypeIdPairs(previous, next);\n if (removedPairs.length === 0)\n return;\n for (const [type, id] of removedPairs)\n this.invokeOnRemove(type, id, cause);\n }\n dispatchOnRemoveForTypedIDs(typedIDs, cause) {\n for (const [type, ids] of Object.entries(typedIDs)) {\n if (!ids)\n continue;\n for (const id of ids)\n this.invokeOnRemove(type, id, cause);\n }\n }\n mergeWithAdjacentTypedMarks() {\n if (this.hasNoIDsForEveryType())\n return this;\n let previousSibling = this.getPreviousSibling();\n while ($isTypedMarkNode(previousSibling) &&\n typedIDsAreEqual(previousSibling.getTypedIDs(), this.getTypedIDs())) {\n this.mergeWithPreviousTypedMark(previousSibling);\n previousSibling = this.getPreviousSibling();\n }\n let nextSibling = this.getNextSibling();\n while ($isTypedMarkNode(nextSibling) &&\n typedIDsAreEqual(this.getTypedIDs(), nextSibling.getTypedIDs())) {\n this.mergeWithNextTypedMark(nextSibling);\n nextSibling = this.getNextSibling();\n }\n return this;\n }\n mergeWithPreviousTypedMark(previous) {\n this.mergeOnClicksFrom(previous.getTypedOnClicks());\n this.mergeOnRemovesFrom(previous.getTypedOnRemoves());\n this.mergeOnMouseEntersFrom(previous.getTypedOnMouseEnters());\n this.mergeOnMouseLeavesFrom(previous.getTypedOnMouseLeaves());\n const previousChildren = previous.getChildren();\n if (previousChildren.length > 0) {\n this.splice(0, 0, previousChildren);\n }\n previous.getWritable().__suppressOnRemoveCallbacks = true;\n previous.remove();\n }\n mergeWithNextTypedMark(next) {\n this.mergeOnClicksFrom(next.getTypedOnClicks());\n this.mergeOnRemovesFrom(next.getTypedOnRemoves());\n this.mergeOnMouseEntersFrom(next.getTypedOnMouseEnters());\n this.mergeOnMouseLeavesFrom(next.getTypedOnMouseLeaves());\n const nextChildren = next.getChildren();\n if (nextChildren.length > 0) {\n this.append(...nextChildren);\n }\n next.getWritable().__suppressOnRemoveCallbacks = true;\n next.remove();\n }\n mergeOnClicksFrom(additional) {\n if (!additional || Object.keys(additional).length === 0)\n return;\n const merged = mergeTypedOnClickMaps(this.getTypedOnClicks(), additional);\n if (Object.keys(merged).length === 0)\n return;\n this.setTypedOnClicks(merged);\n }\n mergeOnRemovesFrom(additional) {\n if (!additional || Object.keys(additional).length === 0)\n return;\n const merged = mergeTypedOnRemoveMaps(this.getTypedOnRemoves(), additional);\n if (Object.keys(merged).length === 0)\n return;\n this.setTypedOnRemoves(merged);\n }\n mergeOnMouseEntersFrom(additional) {\n if (!additional || Object.keys(additional).length === 0)\n return;\n const merged = mergeTypedOnMouseEnterMaps(this.getTypedOnMouseEnters(), additional);\n if (Object.keys(merged).length === 0)\n return;\n this.setTypedOnMouseEnters(merged);\n }\n mergeOnMouseLeavesFrom(additional) {\n if (!additional || Object.keys(additional).length === 0)\n return;\n const merged = mergeTypedOnMouseLeaveMaps(this.getTypedOnMouseLeaves(), additional);\n if (Object.keys(merged).length === 0)\n return;\n this.setTypedOnMouseLeaves(merged);\n }\n}\nfunction cloneTypedIDs(typedIds = NO_IDS) {\n const clone = {};\n for (const [type, ids] of Object.entries(typedIds)) {\n assertSafeKey(type);\n if (!Array.isArray(ids)) {\n clone[type] = [];\n continue;\n }\n const clonedIds = [];\n for (const id of ids) {\n assertSafeKey(id);\n clonedIds.push(id);\n }\n clone[type] = clonedIds;\n }\n return clone;\n}\nfunction cloneTypedOnClicks(typedOnClicks) {\n if (!typedOnClicks || typedOnClicks === NO_ON_CLICKS)\n return undefined;\n const clone = {};\n for (const [type, callbacks] of Object.entries(typedOnClicks)) {\n assertSafeKey(type);\n const clonedCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n assertSafeKey(id);\n clonedCallbacks[id] = callback;\n }\n if (Object.keys(clonedCallbacks).length > 0)\n clone[type] = clonedCallbacks;\n }\n return Object.keys(clone).length > 0 ? clone : undefined;\n}\nfunction cloneTypedOnRemoves(typedOnRemoves) {\n if (!typedOnRemoves || typedOnRemoves === NO_ON_REMOVES)\n return undefined;\n const clone = {};\n for (const [type, callbacks] of Object.entries(typedOnRemoves)) {\n assertSafeKey(type);\n const clonedCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n assertSafeKey(id);\n clonedCallbacks[id] = callback;\n }\n if (Object.keys(clonedCallbacks).length > 0)\n clone[type] = clonedCallbacks;\n }\n return Object.keys(clone).length > 0 ? clone : undefined;\n}\nfunction cloneTypedOnMouseEnters(typedOnMouseEnters) {\n if (!typedOnMouseEnters || typedOnMouseEnters === NO_ON_MOUSE_ENTERS)\n return undefined;\n const clone = {};\n for (const [type, callbacks] of Object.entries(typedOnMouseEnters)) {\n assertSafeKey(type);\n const clonedCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n assertSafeKey(id);\n clonedCallbacks[id] = callback;\n }\n if (Object.keys(clonedCallbacks).length > 0)\n clone[type] = clonedCallbacks;\n }\n return Object.keys(clone).length > 0 ? clone : undefined;\n}\nfunction cloneTypedOnMouseLeaves(typedOnMouseLeaves) {\n if (!typedOnMouseLeaves || typedOnMouseLeaves === NO_ON_MOUSE_LEAVES)\n return undefined;\n const clone = {};\n for (const [type, callbacks] of Object.entries(typedOnMouseLeaves)) {\n assertSafeKey(type);\n const clonedCallbacks = {};\n for (const [id, callback] of Object.entries(callbacks)) {\n assertSafeKey(id);\n clonedCallbacks[id] = callback;\n }\n if (Object.keys(clonedCallbacks).length > 0)\n clone[type] = clonedCallbacks;\n }\n return Object.keys(clone).length > 0 ? clone : undefined;\n}\nfunction omitRecordKey(source, keyToOmit) {\n const result = {};\n for (const [currentKey, value] of Object.entries(source)) {\n if (currentKey !== keyToOmit) {\n result[currentKey] = value;\n }\n }\n return result;\n}\nfunction normalizeTypedIDs(source) {\n const normalized = {};\n for (const [type, ids] of Object.entries(source)) {\n if (!ids || ids.length === 0)\n continue;\n normalized[type] = [...ids].sort();\n }\n return normalized;\n}\nfunction collectRemovedTypeIdPairs(previous, next) {\n const removed = [];\n for (const [type, previousIds] of Object.entries(previous)) {\n const nextIds = new Set(next[type] ?? []);\n for (const id of previousIds ?? []) {\n if (!nextIds.has(id))\n removed.push([type, id]);\n }\n }\n return removed;\n}\nfunction typedIDsAreEqual(a, b) {\n const normalizedA = normalizeTypedIDs(a);\n const normalizedB = normalizeTypedIDs(b);\n const typesA = Object.keys(normalizedA).sort();\n const typesB = Object.keys(normalizedB).sort();\n if (typesA.length !== typesB.length)\n return false;\n for (let i = 0; i < typesA.length; i++) {\n const type = typesA[i];\n if (type !== typesB[i])\n return false;\n const idsA = normalizedA[type];\n const idsB = normalizedB[type];\n if (!idsA || !idsB || idsA.length !== idsB.length)\n return false;\n for (let j = 0; j < idsA.length; j++) {\n if (idsA[j] !== idsB[j])\n return false;\n }\n }\n return true;\n}\nfunction mergeTypedOnClickMaps(primary, secondary) {\n const merged = {};\n const types = new Set([...Object.keys(primary), ...Object.keys(secondary)]);\n for (const type of types) {\n const primaryCallbacks = primary[type] ?? {};\n const secondaryCallbacks = secondary[type] ?? {};\n const ids = new Set([...Object.keys(primaryCallbacks), ...Object.keys(secondaryCallbacks)]);\n const mergedCallbacks = {};\n for (const id of ids) {\n const callback = primaryCallbacks[id] ?? secondaryCallbacks[id];\n if (callback)\n mergedCallbacks[id] = callback;\n }\n if (Object.keys(mergedCallbacks).length > 0)\n merged[type] = mergedCallbacks;\n }\n return merged;\n}\nfunction mergeTypedOnRemoveMaps(primary, secondary) {\n const merged = {};\n const types = new Set([...Object.keys(primary), ...Object.keys(secondary)]);\n for (const type of types) {\n const primaryCallbacks = primary[type] ?? {};\n const secondaryCallbacks = secondary[type] ?? {};\n const ids = new Set([...Object.keys(primaryCallbacks), ...Object.keys(secondaryCallbacks)]);\n const mergedCallbacks = {};\n for (const id of ids) {\n const callback = primaryCallbacks[id] ?? secondaryCallbacks[id];\n if (callback)\n mergedCallbacks[id] = callback;\n }\n if (Object.keys(mergedCallbacks).length > 0)\n merged[type] = mergedCallbacks;\n }\n return merged;\n}\nfunction mergeTypedOnMouseEnterMaps(primary, secondary) {\n const merged = {};\n const types = new Set([...Object.keys(primary), ...Object.keys(secondary)]);\n for (const type of types) {\n const primaryCallbacks = primary[type] ?? {};\n const secondaryCallbacks = secondary[type] ?? {};\n const ids = new Set([...Object.keys(primaryCallbacks), ...Object.keys(secondaryCallbacks)]);\n const mergedCallbacks = {};\n for (const id of ids) {\n const callback = primaryCallbacks[id] ?? secondaryCallbacks[id];\n if (callback)\n mergedCallbacks[id] = callback;\n }\n if (Object.keys(mergedCallbacks).length > 0)\n merged[type] = mergedCallbacks;\n }\n return merged;\n}\nfunction mergeTypedOnMouseLeaveMaps(primary, secondary) {\n const merged = {};\n const types = new Set([...Object.keys(primary), ...Object.keys(secondary)]);\n for (const type of types) {\n const primaryCallbacks = primary[type] ?? {};\n const secondaryCallbacks = secondary[type] ?? {};\n const ids = new Set([...Object.keys(primaryCallbacks), ...Object.keys(secondaryCallbacks)]);\n const mergedCallbacks = {};\n for (const id of ids) {\n const callback = primaryCallbacks[id] ?? secondaryCallbacks[id];\n if (callback)\n mergedCallbacks[id] = callback;\n }\n if (Object.keys(mergedCallbacks).length > 0)\n merged[type] = mergedCallbacks;\n }\n return merged;\n}\nfunction getTypedClassName(className, type) {\n return `${className}-${type}`;\n}\n/**\n * Gets the external typed mark type.\n *\n * @remarks\n * This is used to ensure unique identification of external typed marks. Along with reserved types\n * prefaced with 'internal-' for internal typed marks @see {@link COMMENT_MARK_TYPE}.\n */\nexport function externalTypedMarkType(type) {\n return `external-${type}`;\n}\nexport function $createTypedMarkNode(typedIds, typedOnClicks, typedOnRemoves, typedOnMouseEnters, typedOnMouseLeaves) {\n return $applyNodeReplacement(new TypedMarkNode(typedIds, typedOnClicks, typedOnRemoves, typedOnMouseEnters, typedOnMouseLeaves));\n}\nexport function $isTypedMarkNode(node) {\n return node instanceof TypedMarkNode;\n}\nexport function isSerializedTypedMarkNode(node) {\n return node?.type === TypedMarkNode.getType();\n}\n// #region adapted from https://github.com/facebook/lexical/blob/92c47217244f9d3c22a59728633fb41a10420724/packages/lexical-mark/src/index.ts\nexport function $unwrapTypedMarkNode(node) {\n const children = node.getChildren();\n let target = null;\n for (const child of children) {\n if (target === null) {\n node.insertBefore(child);\n }\n else {\n target.insertAfter(child);\n }\n target = child;\n }\n node.remove();\n}\nexport function $wrapSelectionInTypedMarkNode(selection, type, id, onClick, onRemove, onMouseEnter, onMouseLeave) {\n const nodes = selection.getNodes();\n const anchorOffset = selection.anchor.offset;\n const focusOffset = selection.focus.offset;\n const nodesLength = nodes.length;\n const isBackward = selection.isBackward();\n const startOffset = isBackward ? focusOffset : anchorOffset;\n const endOffset = isBackward ? anchorOffset : focusOffset;\n let currentNodeParent;\n let lastCreatedMarkNode;\n // We only want wrap adjacent text nodes, line break nodes and inline element nodes. For decorator\n // nodes and block element nodes, we step out of their boundary and start again after, if there\n // are more nodes.\n for (let i = 0; i < nodesLength; i++) {\n const node = nodes[i];\n if ($isElementNode(lastCreatedMarkNode) && lastCreatedMarkNode.isParentOf(node)) {\n // If the current node is a child of the last created mark node, there is nothing to do here\n continue;\n }\n const isFirstNode = i === 0;\n const isLastNode = i === nodesLength - 1;\n let targetNode = null;\n if ($isTextNode(node)) {\n // Case 1: The node is a text node and we can split it\n const textContentSize = node.getTextContentSize();\n const startTextOffset = isFirstNode ? startOffset : 0;\n const endTextOffset = isLastNode ? endOffset : textContentSize;\n if (startTextOffset === 0 && endTextOffset === 0) {\n continue;\n }\n const splitNodes = node.splitText(startTextOffset, endTextOffset);\n targetNode =\n splitNodes.length > 1 &&\n (splitNodes.length === 3 ||\n (isFirstNode && !isLastNode) ||\n endTextOffset === textContentSize)\n ? splitNodes[1]\n : splitNodes[0];\n }\n else if ($isTypedMarkNode(node)) {\n // Case 2: the node is a mark node and we can ignore it as a target, moving on to its\n // children. Note that when we make a mark inside another mark, it may ultimately be un-nested\n // by a call to `registerNestedElementResolver` somewhere else in the\n // codebase.\n continue;\n }\n else if ($isElementNode(node) && node.isInline()) {\n // Case 3: inline element nodes can be added in their entirety to the new mark\n targetNode = node;\n }\n if (targetNode !== null) {\n // Now that we have a target node for wrapping with a mark, we can run through special cases.\n if (targetNode && targetNode.is(currentNodeParent)) {\n // The current node is a child of the target node to be wrapped, there is nothing to do\n // here.\n continue;\n }\n const parentNode = targetNode.getParent();\n if (parentNode == null || !parentNode.is(currentNodeParent)) {\n // If the parent node is not the current node's parent node, we can clear the last created\n // mark node.\n lastCreatedMarkNode = undefined;\n }\n currentNodeParent = parentNode;\n if (lastCreatedMarkNode === undefined) {\n lastCreatedMarkNode = $createTypedMarkNode();\n lastCreatedMarkNode.addID(type, id, onClick, onRemove, onMouseEnter, onMouseLeave);\n targetNode.insertBefore(lastCreatedMarkNode);\n }\n // Add the target node to be wrapped in the latest created mark node\n lastCreatedMarkNode.append(targetNode);\n }\n else {\n // If we don't have a target node to wrap we can clear our state and continue on with the next\n // node\n currentNodeParent = undefined;\n lastCreatedMarkNode = undefined;\n }\n }\n // Make selection collapsed at the end for comments.\n if (type === COMMENT_MARK_TYPE && $isElementNode(lastCreatedMarkNode)) {\n if (isBackward)\n lastCreatedMarkNode.selectStart();\n else\n lastCreatedMarkNode.selectEnd();\n }\n}\nexport function $getMarkIDs(node, type, offset) {\n let currentNode = node;\n while (currentNode !== null) {\n if ($isTypedMarkNode(currentNode)) {\n return currentNode.getTypedIDs()[type];\n }\n else if ($isTextNode(currentNode) && offset === currentNode.getTextContentSize()) {\n const nextSibling = currentNode.getNextSibling();\n if ($isTypedMarkNode(nextSibling)) {\n return nextSibling.getTypedIDs()[type];\n }\n }\n currentNode = currentNode.getParent();\n }\n return undefined;\n}\n// #endregion\n","import { $applyNodeReplacement, $getSelection, $isNodeSelection, ElementNode, } from \"lexical\";\n/** List of known properties of `MarkerObject` */\nexport const UNKNOWN_MARKER_OBJECT_PROPS = [\"type\", \"marker\", \"content\"];\nexport const UNKNOWN_TAG_NAME = \"unknown\";\nexport const UNKNOWN_VERSION = 1;\n/**\n * `UnknownNode` tags that render inline instead of as a subdued block container: a line-level box\n * in the middle of a sentence would be visibly wrong. These are the two corpus-proven\n * mid-paragraph constructs — both nest INSIDE a ``'s running text in the corpus fixtures\n * (\"optional line break (optbreak)\" and \"cross-reference ref target\"), and\n * `packages/utilities/src/converters/usj/converter-test.data.ts:2571,2581` shows both becoming\n * `UnknownNode`s (tags \"optbreak\" and \"ref\"):\n *\n * - `\\optbreak` — PT9 renders it as a literal `//` token mid-sentence; `createUnknown`\n * (usj-editor.adaptor.ts) renders that token as the node's own display child in editable\n * mode, via `unknownDisplayParts` (unknownUsfm.utils.ts).\n * - `\\ref` — a cross-reference target with real child text that must display inline; it carries\n * no USFM bytes of its own, so it gets no display children at all.\n *\n * Everything else (table/figure/sidebar/periph/...) stays block-level.\n */\nconst INLINE_UNKNOWN_TAGS = new Set([\"optbreak\", \"ref\"]);\nexport class UnknownNode extends ElementNode {\n __tag;\n __marker;\n __unknownAttributes;\n constructor(tag = \"\", marker, unknownAttributes, key) {\n super(key);\n this.__tag = tag;\n this.__marker = marker;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"unknown\";\n }\n static clone(node) {\n const { __tag, __marker, __unknownAttributes, __key } = node;\n return new UnknownNode(__tag, __marker, __unknownAttributes, __key);\n }\n static importDOM() {\n return {\n [UNKNOWN_TAG_NAME]: (node) => {\n if (!isUnknownElement(node))\n return null;\n return {\n conversion: $convertUnknownElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createUnknownNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setTag(serializedNode.tag)\n .setMarker(serializedNode.marker)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setTag(tag) {\n if (this.__tag === tag)\n return this;\n const self = this.getWritable();\n self.__tag = tag;\n return self;\n }\n getTag() {\n const self = this.getLatest();\n return self.__tag;\n }\n /**\n * Whether this unknown renders inline (optbreak, ref) rather than as a block box (figure,\n * sidebar, periph, ...). Inline unknowns sit within paragraph prose and carry SIGNIFICANT\n * surrounding whitespace — the spaces Paratext 9 preserves byte-for-byte around `//` — so\n * callers must not add or strip spaces next to them.\n */\n isInlineTag() {\n return INLINE_UNKNOWN_TAGS.has(this.getTag());\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(UNKNOWN_TAG_NAME);\n // data-tag records the UnknownNode's USJ type so importDOM's $convertUnknownElement can read it\n // back on a DOM round-trip. The inline-vs-block CSS treatment is driven by the class chosen from\n // INLINE_UNKNOWN_TAGS below, not by data-tag; optbreak's `//` token renders as a real child text\n // node (see createUnknown in usj-editor.adaptor.ts), not a CSS-generated label.\n dom.setAttribute(\"data-tag\", this.getTag());\n dom.setAttribute(\"data-marker\", this.getMarker() ?? \"\");\n dom.classList.add(this.isInlineTag() ? \"unknown-inline\" : \"unknown-block\");\n // Read-only whole-block: no inline display:none here (that hid the content in every view).\n // Visibility is CSS-mode-gated in usj-nodes.css (hidden by default, revealed as a subdued\n // block/token in standard view's .marker-editable scope). contentEditable=false stops the\n // browser from placing a native caret inside it, so caret navigation skips over the whole\n // node like any decorator node.\n dom.contentEditable = \"false\";\n return dom;\n }\n updateDOM(prevNode, dom) {\n // On a key-reused node whose tag/marker changed, sync the attributes and the inline-vs-block\n // class in place so createDOM's discriminators (data-tag/data-marker, unknown-inline vs\n // unknown-block) don't go stale. tag drives both data-tag and the class; marker drives\n // data-marker (empty string when absent, mirroring createDOM).\n if (prevNode.__tag !== this.__tag) {\n dom.setAttribute(\"data-tag\", this.__tag);\n const inline = this.isInlineTag();\n dom.classList.toggle(\"unknown-inline\", inline);\n dom.classList.toggle(\"unknown-block\", !inline);\n }\n if ((prevNode.__marker ?? \"\") !== (this.__marker ?? \"\"))\n dom.setAttribute(\"data-marker\", this.__marker ?? \"\");\n // Returning false keeps the existing DOM element (updated in place, never recreated — this\n // preserves contentEditable=false and the caret-skip behavior).\n return false;\n }\n exportDOM() {\n return { element: null };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n tag: this.getTag(),\n marker: this.getMarker(),\n unknownAttributes: this.getUnknownAttributes(),\n version: UNKNOWN_VERSION,\n };\n }\n // Mutation\n canBeEmpty() {\n return true;\n }\n isInline() {\n return true;\n }\n extractWithChild() {\n return false;\n }\n // A CHILD-BEARING `UnknownNode` of any kind stays IN the copy. This affects ONLY the\n // `application/x-lexical-editor` (lexical-JSON) flavor, for two independent reasons: an\n // editable-marker view's own `text/html` is not a DOM export at all (Standard view renders the\n // copy walker's USFM bytes — `usfmToClipboardHtml`, platform's whitespaceDisplay.plugin.utils.ts),\n // and where Lexical's exporter DOES run, `$appendNodesToHTML` (`@lexical/html`) computes this same\n // `excludeFromCopy('html')` value but returns early on `exportDOM()`'s unconditional\n // `{element: null}` BEFORE ever consulting it. `'clone'` is never passed by any Lexical-shipped\n // code path in the installed version, so an unconditional `destination !== \"clone\"` would\n // exclude every `UnknownNode` from the lexical-JSON flavor outright.\n //\n // Excluding a node does not drop it silently: `$appendNodesToJSON` HOISTS the excluded node's own\n // children into its parent's list in its place. Every kind's marker and attribute bytes are\n // content-free `ImmutableTypedTextNode` display decorators (`unknownDisplayParts` builds them\n // identically for all of them), so hoisting strands decorators that still `decorate()` their own\n // literal text as loose siblings with no owning wrapper: the pasted document RENDERS the\n // construct's full USFM bytes while its USJ has lost the node and every attribute on it — a\n // convincing display over missing data, which a save then persists with no error.\n //\n // The `getChildrenSize() > 0` guard excludes a node with no children of its OWN — of any kind,\n // and for whatever reason it has none — because that is exactly when `text/plain` also emits\n // nothing for it, and the two carriers must agree. Three shapes reach it, and only the first is\n // a husk: an optbreak whose `//` display child was deleted (`markerEditTier1.utils.ts`'s\n // husk-removal check recognizes it by that same zero-child shape); a content-less construct in\n // an editable-marker view, where the marker/attribute display bytes `createUnknown`\n // (`usj-editor.adaptor.ts`) prepends are themselves children, so only a kind with no display\n // bytes at all can be childless; and, in a HIDDEN-marker view, ANY content-less construct — a\n // caption-less `figure`, an empty `ref`, every optbreak — because `createUnknown` builds no\n // display children there at all. The last is prose-copy semantics rather than a husk: a\n // hidden-marker view copies what it shows, and it shows no bytes for a construct with no\n // content.\n //\n // The guard does NOT, on its own, cover a DIFFERENT carrier-agreement gap: a selection whose\n // ending boundary resolves to an ELEMENT-type point ON this node at offset 0 (touching the\n // wrapper without covering any of its content) still marks a CHILD-BEARING node \"selected\" under\n // Lexical's default `isSelected` (key-membership in `selection.getNodes()`, unaffected by this\n // node's own child count), so `$appendNodesToJSON` would serialize a CHILDLESS placeholder for a\n // node that has children, disagreeing with `text/plain` (`$selectionToUsfmText`, which walks that\n // same `getNodes()` list and correctly emits nothing for this boundary). The `isSelected`\n // override below closes that second gap at its actual source, since `excludeFromCopy` has no\n // visibility into which of a node's children a given selection will include — for every\n // construct whose display bytes lead, which is all of them but `ref`; see that override for the\n // one shape it deliberately does not close, and why.\n excludeFromCopy(destination) {\n return this.getChildrenSize() > 0 ? false : destination !== \"clone\";\n }\n // An `UnknownNode` is only meaningfully \"selected\" (and so only copy-included, per the\n // `excludeFromCopy` guard above) when at least one of its own children is — mirrors\n // `$selectionToUsfmText`'s copy walker so both clipboard carriers agree at the same selection\n // boundary, the same way Lexical's own base `isSelected` (`LexicalNode.prototype.isSelected`)\n // already special-cases an inline DECORATOR node sitting as a parent's last child at an\n // exactly-there boundary point, for the identical reason. Without this, a selection ending\n // exactly at this node's own start (an ElementNode touch-boundary that covers none of its\n // content) still counts the WRAPPER as selected via the default `ElementNode.isSelected`\n // (key-membership in `selection.getNodes()`), while no child is — producing a childless entry in\n // the `application/x-lexical-editor` copy with nothing corresponding to it in `text/plain`.\n //\n // Child MEMBERSHIP is the test, deliberately, and not the narrower \"does the selection cover a\n // child's CONTENT\". The two differ for exactly one shape, because that boundary reaches the\n // children two ways depending on what the first child IS. A construct whose display bytes lead\n // (a `figure`'s `\\fig ` glyph, an optbreak's `//`) starts with a DECORATOR: the element-type\n // point stays one, no child is in `getNodes()`, and both carriers agree. A construct with no\n // display bytes (a `ref`, whose container USFM never carried) starts with a real `TextNode`, and\n // Lexical normalizes that same point into a TEXT point at the child's offset 0 — the child is in\n // `getNodes()` contributing zero characters, so `text/plain` emits nothing for it while this\n // predicate still answers true and the construct rides along in the lexical flavor.\n //\n // Answering false there is WORSE, not better, and measurably so. Excluding the wrapper does not\n // drop it quietly: `$appendNodesToJSON` HOISTS its children in its place, and `createUnknown`\n // stamps `mode:\"token\"` on every text child, which `$sliceSelectedTextNodeContent` refuses to\n // slice — so the zero-width child keeps its full text, the emptied-text reset never fires, and\n // the copy ends up carrying the construct's CHARACTERS with the wrapper and its attributes\n // silently gone. That is the convincing-lie hazard this whole pair exists to prevent. Membership\n // keeps the construct whole, so the lexical flavor is a SUPERSET of `text/plain` at that one\n // boundary rather than a structural loss. That residual stands deliberately: closing it needs a\n // lever Lexical does not offer — `exportNodeToJSON` requires every ElementNode's `exportJSON()`\n // to return a `children` array, so a node cannot say \"drop me AND my children\".\n isSelected(selection) {\n const targetSelection = selection ?? $getSelection();\n if (!targetSelection)\n return false;\n // A `NodeSelection` marks a node selected by its OWN key, never by its children's, so the\n // child-membership test below cannot see one and the base predicate has to answer first.\n // Answering false there does not drop the construct quietly — `$appendNodesToJSON` HOISTS its\n // (also unselected) children in its place — so a node the user selected outright would copy as\n // nothing at all, the same convincing-lie hazard the range case above exists to prevent.\n if ($isNodeSelection(targetSelection) && super.isSelected(targetSelection))\n return true;\n // A caret selects nothing, though its one point puts the child it rests in into `getNodes()`.\n // (`NodeSelection.isCollapsed()` is always false, so this cannot shadow the branch above.)\n if (targetSelection.isCollapsed())\n return false;\n const selectedNodes = targetSelection.getNodes();\n return this.getChildren().some((child) => selectedNodes.some((node) => node.is(child)));\n }\n}\nfunction $convertUnknownElement(element) {\n const tag = element.getAttribute(\"data-tag\") ?? \"\";\n const marker = element.getAttribute(\"data-marker\") ?? \"\";\n const node = $createUnknownNode(tag, marker);\n return { node };\n}\nexport function $createUnknownNode(tag, marker, unknownAttributes) {\n return $applyNodeReplacement(new UnknownNode(tag, marker, unknownAttributes));\n}\nfunction isUnknownElement(node) {\n // `tagName` is upper-cased for HTML-namespace elements, so compare case-insensitively.\n return node?.tagName.toLowerCase() === UNKNOWN_TAG_NAME;\n}\nexport function $isUnknownNode(node) {\n return node instanceof UnknownNode;\n}\nexport function isSerializedUnknownNode(node) {\n return node?.type === UnknownNode.getType();\n}\n","/**\n * `AttributeRunNode` — the ONE sibling `ElementNode` that holds a leaf display owner's attribute\n * display run: a verse's `\\va`/`\\vp` value triplet, or a milestone's attribute value. Both owners\n * are leaves that cannot hold children of their own — a `VerseNode` is itself a `TextNode`, and a\n * `MilestoneNode` is a `DecoratorNode` — unlike a `CharNode`, whose attribute run lives INSIDE the\n * span as ordinary children. `AttributeRunNode` gives those two owners the same \"run lives inside\n * a container\" shape a char span already has, without changing either owner's own node type.\n *\n * Its children are the run's pieces — `MarkerNode` opening/closing (or self-closing) glyphs and,\n * in between, the attribute-tagged value `TextNode` (textType \"attribute\") — the same pieces that\n * used to ride as bare following siblings directly on the owner's parent. The wrapper itself\n * contributes no USFM bytes of its own: it is pure editor-owned structure, never part of a\n * conversion to USJ in its own right — only its children's bytes matter.\n *\n * Ownership is POSITION-derived, not stored: the wrapper directly follows the leaf it belongs to —\n * a verse's `\\va` wrapper follows the `VerseNode` itself, and its `\\vp` wrapper follows the `\\va`\n * wrapper (or the verse, when no `\\va` wrapper exists); a milestone's wrapper follows the\n * `MilestoneNode`. Nothing stores a back-reference — a caller locates a wrapper by walking from\n * its owner, the same sibling-walk shape the pre-wrapper display-run code already used.\n *\n * An `AttributeRunNode` with no children is a transient husk — every piece of its run was deleted,\n * leaving an empty wrapper with nothing left to display. It is not itself meaningful state: the\n * marker-edit engine's deletion driver removes empty wrappers as part of settling a deletion, the\n * same way a milestone with its run entirely gone is itself removed. `canBeEmpty()` reports `true`\n * so Lexical's own empty-element normalization does not race that driver by deleting the wrapper\n * on its own schedule.\n */\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\nexport const ATTRIBUTE_RUN_VERSION = 1;\n/** The class every `AttributeRunNode` carries, regardless of `runKind`. */\nexport const ATTRIBUTE_RUN_CLASS_NAME = \"attribute-run\";\n/** The `usfm_` DOM class a runKind's wrapper carries so the stylesheet styles the run\n * exactly like the standalone `char ` span it folds from/unfolds to — or `undefined` for\n * the kinds with no such stylesheet hook (see `createDOM`'s comment). */\nfunction runKindMarkerClass(runKind) {\n return runKind === \"va\" || runKind === \"vp\" || runKind === \"ca\" || runKind === \"cp\"\n ? `usfm_${runKind}`\n : undefined;\n}\nexport class AttributeRunNode extends ElementNode {\n __runKind;\n constructor(runKind, key) {\n super(key);\n this.__runKind = runKind;\n }\n static getType() {\n return \"attribute-run\";\n }\n static clone(node) {\n const { __runKind, __key } = node;\n return new AttributeRunNode(__runKind, __key);\n }\n static importJSON(serializedNode) {\n return $createAttributeRunNode(serializedNode.runKind).updateFromJSON(serializedNode);\n }\n // No HTML shape ever round-trips: `exportDOM` below contributes no wrapper element of its own\n // (a DocumentFragment leaves no markup behind), so there is nothing for a paste to hand back\n // for conversion.\n // Declared explicitly (rather than left unimplemented) so Lexical's dev-mode registration check\n // — which otherwise warns that a custom `exportDOM` needs a matching `importDOM` — recognizes the\n // omission as deliberate.\n static importDOM() {\n return null;\n }\n updateFromJSON(serializedNode) {\n return super.updateFromJSON(serializedNode).setRunKind(serializedNode.runKind);\n }\n setRunKind(runKind) {\n if (this.__runKind === runKind)\n return this;\n const self = this.getWritable();\n self.__runKind = runKind;\n return self;\n }\n getRunKind() {\n const self = this.getLatest();\n return self.__runKind;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.classList.add(ATTRIBUTE_RUN_CLASS_NAME);\n // va/vp/ca/cp carry a marker-specific class (matching a standalone CharNode's\n // `usfm_` class, per CharNode.test.ts's standalone-attribute-marker pin), so the\n // stylesheet styles the run and the standalone span IDENTICALLY — the run must not look\n // different just because the same bytes currently ride as an attribute. \"milestone\" gets\n // nothing extra — a milestone's marker varies per instance (ts-s, qt1-e, ...) and is not a\n // fixed stylesheet hook — and \"cat\" has no standalone stylesheet styling to match.\n const markerClass = runKindMarkerClass(this.__runKind);\n if (markerClass !== undefined)\n dom.classList.add(markerClass);\n return dom;\n }\n updateDOM(prevNode, dom) {\n // On a key-reused node whose runKind changed, sync the usfm_ class in place so\n // createDOM's discriminator doesn't go stale. `attribute-run` never changes (always present on\n // every runKind), so only the marker-class toggle needs syncing here.\n if (prevNode.__runKind !== this.__runKind) {\n const prevClass = runKindMarkerClass(prevNode.__runKind);\n if (prevClass !== undefined)\n dom.classList.remove(prevClass);\n const nextClass = runKindMarkerClass(this.__runKind);\n if (nextClass !== undefined)\n dom.classList.add(nextClass);\n }\n // Returning false keeps the existing DOM element (updated in place, never recreated — the\n // run-piece children reconcile independently).\n return false;\n }\n exportDOM() {\n // A DocumentFragment rather than null: @lexical/html's $appendNodesToHTML treats a null element\n // as \"skip this subtree\" and never walks the children, which would drop the run's glyphs AND its\n // value text (the \"2\" of `\\va 2\\va*`) from any DOM-export html while getTextContent() keeps them\n // on text/plain. That export is what the HIDDEN-marker views' copy still ships, and what any\n // consumer re-importing html through $generateNodesFromDOM reads; an editable-marker view's own\n // `text/html` is the copy walker's USFM bytes instead and never reaches this method. The\n // fragment exports the children while still contributing no wrapper markup of its own.\n return { element: document.createDocumentFragment() };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n runKind: this.getRunKind(),\n version: ATTRIBUTE_RUN_VERSION,\n };\n }\n // Mutation\n canBeEmpty() {\n return true;\n }\n isInline() {\n return true;\n }\n}\nexport function $createAttributeRunNode(runKind) {\n return $applyNodeReplacement(new AttributeRunNode(runKind));\n}\nexport function $isAttributeRunNode(node) {\n return node instanceof AttributeRunNode;\n}\nexport function isSerializedAttributeRunNode(node) {\n return node?.type === AttributeRunNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/doc/id.html */\nimport { isValidBookCode } from \"@eten-tech-foundation/scripture-utilities\";\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\nexport const BOOK_MARKER = \"id\";\nexport const BOOK_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const BOOK_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"code\",\n \"content\",\n];\nexport class BookNode extends ElementNode {\n __marker = BOOK_MARKER;\n __code;\n __unknownAttributes;\n constructor(code = \"\", unknownAttributes, key) {\n super(key);\n this.__code = code;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"book\";\n }\n static clone(node) {\n const { __code, __unknownAttributes, __key } = node;\n return new BookNode(__code, __unknownAttributes, __key);\n }\n static importJSON(serializedNode) {\n const { code } = serializedNode;\n return $createBookNode(code).updateFromJSON(serializedNode);\n }\n static isValidBookCode(code) {\n return isValidBookCode(code);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setCode(serializedNode.code)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setCode(code) {\n if (this.__code === code)\n return this;\n const self = this.getWritable();\n self.__code = code;\n return self;\n }\n /**\n * Get the book code (ID).\n * @returns the book code (ID).\n */\n getCode() {\n const self = this.getLatest();\n return self.__code;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n // The `.book` class is targeted by platform gutter-positioning rules in usj-nodes.css\n // alongside `.para` (paragraph-like layout). Changes here that affect the first-child marker\n // node, the data-code attribute, or the element tag may require corresponding CSS updates.\n const dom = document.createElement(\"p\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(this.__type, `usfm_${this.__marker}`);\n dom.setAttribute(\"data-code\", this.__code);\n return dom;\n }\n updateDOM() {\n // Returning false tells Lexical that this node does not need its\n // DOM element replacing with a new copy from createDOM.\n return false;\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n code: this.getCode(),\n unknownAttributes: this.getUnknownAttributes(),\n version: BOOK_VERSION,\n };\n }\n}\nexport function $createBookNode(code, unknownAttributes) {\n return $applyNodeReplacement(new BookNode(code, unknownAttributes));\n}\nexport function $isBookNode(node) {\n return node instanceof BookNode;\n}\nexport function isSerializedBookNode(node) {\n return node?.type === BookNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/cv/c.html */\nimport { CHAPTER_CLASS_NAME } from \"./node-constants.js\";\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\nexport const CHAPTER_MARKER = \"c\";\nexport const CHAPTER_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const CHAPTER_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"number\",\n \"sid\",\n \"altnumber\",\n \"pubnumber\",\n \"content\",\n];\nexport class ChapterNode extends ElementNode {\n __marker;\n __number;\n __sid;\n __altnumber;\n __pubnumber;\n __unknownAttributes;\n constructor(chapterNumber = \"\", sid, altnumber, pubnumber, unknownAttributes, key) {\n super(key);\n this.__marker = CHAPTER_MARKER;\n this.__number = chapterNumber;\n this.__sid = sid;\n this.__altnumber = altnumber;\n this.__pubnumber = pubnumber;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"chapter\";\n }\n static clone(node) {\n const { __number, __sid, __altnumber, __pubnumber, __unknownAttributes, __key } = node;\n return new ChapterNode(__number, __sid, __altnumber, __pubnumber, __unknownAttributes, __key);\n }\n static importJSON(serializedNode) {\n return $createChapterNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setNumber(serializedNode.number)\n .setSid(serializedNode.sid)\n .setAltnumber(serializedNode.altnumber)\n .setPubnumber(serializedNode.pubnumber)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setNumber(chapterNumber) {\n if (this.__number === chapterNumber)\n return this;\n const self = this.getWritable();\n self.__number = chapterNumber;\n return self;\n }\n getNumber() {\n const self = this.getLatest();\n return self.__number;\n }\n setSid(sid) {\n if (this.__sid === sid)\n return this;\n const self = this.getWritable();\n self.__sid = sid;\n return self;\n }\n getSid() {\n const self = this.getLatest();\n return self.__sid;\n }\n setAltnumber(altnumber) {\n if (this.__altnumber === altnumber)\n return this;\n const self = this.getWritable();\n self.__altnumber = altnumber;\n return self;\n }\n getAltnumber() {\n const self = this.getLatest();\n return self.__altnumber;\n }\n setPubnumber(pubnumber) {\n if (this.__pubnumber === pubnumber)\n return this;\n const self = this.getWritable();\n self.__pubnumber = pubnumber;\n return self;\n }\n getPubnumber() {\n const self = this.getLatest();\n return self.__pubnumber;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(\"p\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(CHAPTER_CLASS_NAME, `usfm_${this.__marker}`);\n dom.setAttribute(\"data-number\", this.__number);\n return dom;\n }\n updateDOM(prevNode, dom) {\n if (prevNode.__number !== this.__number)\n dom.setAttribute(\"data-number\", this.__number);\n // Returning false keeps the existing DOM element (children reconcile independently).\n return false;\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n number: this.getNumber(),\n sid: this.getSid(),\n altnumber: this.getAltnumber(),\n pubnumber: this.getPubnumber(),\n unknownAttributes: this.getUnknownAttributes(),\n version: CHAPTER_VERSION,\n };\n }\n}\nexport function $createChapterNode(chapterNumber, sid, altnumber, pubnumber, unknownAttributes) {\n return $applyNodeReplacement(new ChapterNode(chapterNumber, sid, altnumber, pubnumber, unknownAttributes));\n}\nexport function $isChapterNode(node) {\n return node instanceof ChapterNode;\n}\nexport function isSerializedChapterNode(node) {\n return node?.type === ChapterNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/char/index.html */\nimport { $applyNodeReplacement, ElementNode, isHTMLElement, } from \"lexical\";\n/** @see https://docs.usfm.bible/usfm/3.1/char/notes/footnote/index.html */\nconst VALID_CHAR_FOOTNOTE_MARKERS = [\n \"fr\",\n \"fq\",\n \"fqa\",\n \"fk\",\n \"ft\",\n \"fl\",\n \"fw\",\n \"fp\",\n \"fv\",\n \"fm\",\n \"fdc\", // Deprecated marker.\n];\n/** @see https://docs.usfm.bible/usfm/3.1/char/notes/crossref/index.html */\nconst VALID_CHAR_CROSS_REFERENCE_MARKERS = [\n \"xo\",\n \"xop\",\n \"xk\",\n \"xq\",\n \"xt\",\n \"xta\",\n \"xot\",\n \"xnt\",\n \"xdc\", // Deprecated marker.\n];\n/** @see https://docs.usfm.bible/usfm/3.1/char/index.html */\nconst VALID_CHAR_MARKERS = [\n // Chapter & Verse\n \"ca\",\n \"cp\",\n \"va\",\n \"vp\",\n // Text Features\n \"add\",\n \"bk\",\n \"dc\",\n \"em\",\n \"jmp\",\n \"k\",\n \"nd\",\n \"ord\",\n \"pn\",\n \"png\",\n \"qt\",\n \"rb\",\n \"rq\",\n // \"ref\", // This has its own tag and is not a Char\n \"sig\",\n \"sls\",\n \"tl\",\n \"w\",\n \"wa\",\n \"wg\",\n \"wh\",\n \"wj\",\n \"addpn\", // Deprecated marker.\n \"pro\", // Deprecated marker.\n // Text Formatting\n \"bd\",\n \"it\",\n \"bdit\",\n \"no\",\n \"sc\",\n \"sup\",\n // Introductions\n \"ior\",\n \"iqt\",\n // Poetry\n \"qac\",\n \"qs\",\n // Lists\n \"litl\",\n \"lik\",\n \"liv\",\n \"liv1\",\n \"liv2\",\n \"liv3\",\n \"liv4\",\n \"liv5\",\n ...VALID_CHAR_FOOTNOTE_MARKERS,\n ...VALID_CHAR_CROSS_REFERENCE_MARKERS,\n];\nexport const CHAR_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const CHAR_MARKER_OBJECT_PROPS = [\"type\", \"marker\", \"content\"];\nexport class CharNode extends ElementNode {\n __marker;\n __unknownAttributes;\n constructor(marker = \"\", unknownAttributes, key) {\n super(key);\n this.__marker = marker;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"char\";\n }\n static clone(node) {\n const { __marker, __unknownAttributes, __key } = node;\n return new CharNode(__marker, __unknownAttributes, __key);\n }\n static isValidMarker(marker, extraValidMarkers) {\n return (marker !== undefined &&\n (VALID_CHAR_MARKERS.includes(marker) || (extraValidMarkers?.includes(marker) ?? false)));\n }\n static isValidFootnoteMarker(marker) {\n return marker !== undefined && VALID_CHAR_FOOTNOTE_MARKERS.includes(marker);\n }\n static isValidCrossReferenceMarker(marker) {\n return marker !== undefined && VALID_CHAR_CROSS_REFERENCE_MARKERS.includes(marker);\n }\n /**\n * Whether a character marker belongs to the note-content families - footnote or cross-reference.\n *\n * These markers only ever occur inside a `NoteNode`, and unlike every other character marker they\n * are written without a closing marker. Callers branch on this for one reason or the other, so the\n * predicate names the family rather than either consequence; each call site documents which\n * consequence it cares about.\n *\n * @param marker - The character marker to check.\n * @returns `true` if the marker is a footnote or cross-reference marker, `false` otherwise.\n */\n static isNoteContentMarker(marker) {\n return CharNode.isValidFootnoteMarker(marker) || CharNode.isValidCrossReferenceMarker(marker);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isCharElement(node))\n return null;\n return {\n conversion: $convertCharElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createCharNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM(config) {\n const dom = document.createElement(\"span\");\n applyMarkerToDom(dom, this.__marker, config);\n dom.classList.add(this.__type);\n return dom;\n }\n updateDOM(prevNode, dom, config) {\n // Returning false tells Lexical the element can be reused — but reuse means createDOM does not\n // run again, so a marker change has to be written onto the existing element by hand: the\n // data-marker, the title (gated by showCharMarkerTitles, same as createDOM), and the usfm_*\n // class.\n //\n // Scope: this span's own attributes and classes, nothing else. The synthesized marker children\n // that markerMode \"editable\"/\"visible\" add are not touched here - $setCharNodeMarker\n // (node.utils.ts) is what retargets those, and callers changing a marker on a rendered node\n // should go through it. The collaboration path in delta-apply-update.utils.ts still calls\n // setMarker directly, so it gets this attribute refresh but not the child retargeting.\n //\n // No super.updateDOM() call, unlike ParaNode.updateDOM: ParaNode extends ParagraphNode, which\n // implements it. CharNode extends ElementNode, which does not, so super would reach\n // LexicalNode's base method and throw.\n if (prevNode.__marker !== this.__marker) {\n dom.classList.remove(`usfm_${prevNode.__marker}`);\n // The same writes createDOM makes, so the two paths cannot drift: a reused element ends up\n // indistinguishable from a freshly created one, title gating included.\n applyMarkerToDom(dom, this.__marker, config);\n }\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(this.getType(), `usfm_${this.getMarker()}`);\n }\n return { element };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n unknownAttributes: this.getUnknownAttributes(),\n version: CHAR_VERSION,\n };\n }\n // Mutation\n insertNewAfter(_selection, restoreSelection) {\n // The continuation span keeps the implicit-close convention when this span has it: splitting\n // an unclosed (closed=\"false\") span yields two unclosed spans — the same structural-state rule\n // as the marker-edit split paths ($splitCharNodeAt, $liftOutOfChar). Other unknownAttributes\n // are deliberately NOT copied (duplicating them would double the `|name=\"value\"` bytes on\n // serialization).\n const isUnclosed = this.getUnknownAttributes()?.closed === \"false\";\n const newElement = $createCharNode(this.getMarker(), isUnclosed ? { closed: \"false\" } : undefined);\n newElement.setDirection(this.getDirection());\n newElement.setFormat(this.getFormatType());\n newElement.setStyle(this.getTextStyle());\n this.insertAfter(newElement, restoreSelection);\n return newElement;\n }\n canBeEmpty() {\n return false;\n }\n isInline() {\n return true;\n }\n}\n/**\n * Write a marker onto a `CharNode`'s rendered span.\n *\n * Shared by `createDOM` and `updateDOM` so the created and the reused element can't drift. Only the\n * `usfm_*` class is added, never removed — `updateDOM` removes the previous marker's class itself,\n * and `createDOM` has no previous marker to remove.\n *\n * @param dom - The span to write to.\n * @param marker - The character marker to apply.\n * @param config - The editor config, read for the `showCharMarkerTitles` theme flag.\n */\nfunction applyMarkerToDom(dom, marker, config) {\n dom.setAttribute(\"data-marker\", marker);\n // Consumers can suppress the per-char marker tooltip via\n // `ViewOptions.showCharMarkerTitles = false` - useful when the marker name shouldn't\n // surface as a browser tooltip on every char span. Default (undefined or true) preserves\n // the marker hint for consumers that want it while authoring USFM.\n if (config.theme?.showCharMarkerTitles !== false) {\n dom.setAttribute(\"title\", marker);\n }\n else {\n // Clear any title a previous render left behind; on a reused element the gate can flip.\n dom.removeAttribute(\"title\");\n }\n dom.classList.add(`usfm_${marker}`);\n}\nfunction $convertCharElement(element) {\n const marker = element.getAttribute(\"data-marker\") ?? \"f\";\n const node = $createCharNode(marker);\n return { node };\n}\nexport function $createCharNode(marker, unknownAttributes) {\n return $applyNodeReplacement(new CharNode(marker, unknownAttributes));\n}\nfunction isCharElement(node) {\n if (!node)\n return false;\n const marker = node.getAttribute(\"data-marker\") ?? \"\";\n return CharNode.isValidMarker(marker) && node.classList.contains(CharNode.getType());\n}\nexport function $isCharNode(node) {\n return node instanceof CharNode;\n}\nexport function isSerializedCharNode(node) {\n return node?.type === CharNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/cv/c.html */\nimport { $applyNodeReplacement, DecoratorNode, isHTMLElement, } from \"lexical\";\nimport { CHAPTER_CLASS_NAME } from \"./node-constants.js\";\nimport { getVisibleOpenMarkerText } from \"./node.utils.js\";\nexport const IMMUTABLE_CHAPTER_VERSION = 1;\nconst CHAPTER_MARKER = \"c\";\nconst IMMUTABLE_CHAPTER_TAG_NAME = \"span\";\nexport class ImmutableChapterNode extends DecoratorNode {\n __marker;\n __number;\n __showMarker;\n __sid;\n __altnumber;\n __pubnumber;\n __unknownAttributes;\n constructor(chapterNumber = \"\", showMarker = false, sid, altnumber, pubnumber, unknownAttributes, key) {\n super(key);\n this.__marker = CHAPTER_MARKER;\n this.__number = chapterNumber;\n this.__showMarker = showMarker;\n this.__sid = sid;\n this.__altnumber = altnumber;\n this.__pubnumber = pubnumber;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"immutable-chapter\";\n }\n static clone(node) {\n const { __number, __showMarker, __sid, __altnumber, __pubnumber, __unknownAttributes, __key } = node;\n return new ImmutableChapterNode(__number, __showMarker, __sid, __altnumber, __pubnumber, __unknownAttributes, __key);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isImmutableChapterElement(node))\n return null;\n return {\n conversion: $convertImmutableChapterElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createImmutableChapterNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setNumber(serializedNode.number)\n .setShowMarker(serializedNode.showMarker)\n .setSid(serializedNode.sid)\n .setAltnumber(serializedNode.altnumber)\n .setPubnumber(serializedNode.pubnumber)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setNumber(chapterNumber) {\n if (this.__number === chapterNumber)\n return this;\n const self = this.getWritable();\n self.__number = chapterNumber;\n return self;\n }\n getNumber() {\n const self = this.getLatest();\n return self.__number;\n }\n setShowMarker(showMarker = false) {\n if (this.__showMarker === showMarker)\n return this;\n const self = this.getWritable();\n self.__showMarker = showMarker;\n return self;\n }\n getShowMarker() {\n const self = this.getLatest();\n return self.__showMarker;\n }\n setSid(sid) {\n if (this.__sid === sid)\n return this;\n const self = this.getWritable();\n self.__sid = sid;\n return self;\n }\n getSid() {\n const self = this.getLatest();\n return self.__sid;\n }\n setAltnumber(altnumber) {\n if (this.__altnumber === altnumber)\n return this;\n const self = this.getWritable();\n self.__altnumber = altnumber;\n return self;\n }\n getAltnumber() {\n const self = this.getLatest();\n return self.__altnumber;\n }\n setPubnumber(pubnumber) {\n if (this.__pubnumber === pubnumber)\n return this;\n const self = this.getWritable();\n self.__pubnumber = pubnumber;\n return self;\n }\n getPubnumber() {\n const self = this.getLatest();\n return self.__pubnumber;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(IMMUTABLE_CHAPTER_TAG_NAME);\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(CHAPTER_CLASS_NAME, `usfm_${this.__marker}`);\n if (this.__showMarker)\n dom.classList.add(\"marker\");\n dom.setAttribute(\"data-number\", this.__number);\n return dom;\n }\n updateDOM() {\n // Returning false tells Lexical that this node does not need its\n // DOM element replacing with a new copy from createDOM.\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(CHAPTER_CLASS_NAME, `usfm_${this.getMarker()}`);\n element.setAttribute(\"data-number\", this.getNumber());\n }\n return { element };\n }\n /**\n * VISIBLE bytes with STABLE IDENTITY, which is only safe because of where this node lives.\n *\n * `@lexical/react` paints a decorator payload into the node's element through a portal, and\n * Lexical only rebuilds that portal when the payload CHANGES (`reconcileDecorator` bails on\n * `currentDecorators[key] === decorator`, and equal strings always compare equal). So any node\n * whose element is destroyed and re-created while the node itself survives — which is precisely\n * what re-parenting does, since Lexical builds fresh elements for every child of a newly created\n * parent — keeps a portal bound to the OLD, detached element and renders permanently EMPTY.\n *\n * A chapter escapes that only by POSITION: it is a root-level block, and the marker engine's\n * Tier-2 rebuild re-parents preserved nodes only within paragraph-kind blocks, so nothing ever\n * moves a chapter. That invariant is not enforced anywhere. Moving chapter nodes into any\n * rebuilt/re-created container would blank this glyph on screen while the node, the USJ, and the\n * file all stayed correct — a silent rendering loss with no error. If that day comes, render\n * these bytes from `createDOM` instead, the way `ImmutableTypedTextNode` does.\n */\n decorate() {\n return this.getShowMarker()\n ? getVisibleOpenMarkerText(this.getMarker(), this.getNumber())\n : this.getNumber();\n }\n exportJSON() {\n return {\n type: this.getType(),\n marker: this.getMarker(),\n number: this.getNumber(),\n showMarker: this.getShowMarker(),\n sid: this.getSid(),\n altnumber: this.getAltnumber(),\n pubnumber: this.getPubnumber(),\n unknownAttributes: this.getUnknownAttributes(),\n version: IMMUTABLE_CHAPTER_VERSION,\n };\n }\n // Mutation\n isInline() {\n return false;\n }\n isKeyboardSelectable() {\n return false;\n }\n}\nfunction $convertImmutableChapterElement(element) {\n const chapterNumber = element.getAttribute(\"data-number\") ?? \"0\";\n const node = $createImmutableChapterNode(chapterNumber);\n return { node };\n}\nexport function $createImmutableChapterNode(chapterNumber, showMarker, sid, altnumber, pubnumber, unknownAttributes) {\n return $applyNodeReplacement(new ImmutableChapterNode(chapterNumber, showMarker, sid, altnumber, pubnumber, unknownAttributes));\n}\nexport function isImmutableChapterElement(element) {\n if (!element)\n return false;\n return (element.classList.contains(CHAPTER_CLASS_NAME) &&\n element.tagName.toLowerCase() === IMMUTABLE_CHAPTER_TAG_NAME);\n}\nexport function $isImmutableChapterNode(node) {\n return node instanceof ImmutableChapterNode;\n}\nexport function isSerializedImmutableChapterNode(node) {\n return node?.type === ImmutableChapterNode.getType();\n}\n","/** Conforms with USJ v3.1 and adapted from @see https://docs.usfm.bible/usfm/3.1/para/index.html */\nimport { $applyNodeReplacement, ParagraphNode, } from \"lexical\";\nimport { PARA_MARKER_DEFAULT } from \"./node-constants.js\";\nexport const IMPLIED_PARA_VERSION = 1;\nexport class ImpliedParaNode extends ParagraphNode {\n static getType() {\n return \"implied-para\";\n }\n static clone(node) {\n return new ImpliedParaNode(node.__key);\n }\n static importJSON(serializedNode) {\n return $createImpliedParaNode().updateFromJSON(serializedNode);\n }\n getMarker() {\n return PARA_MARKER_DEFAULT;\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n version: IMPLIED_PARA_VERSION,\n };\n }\n // Mutation\n insertNewAfter(rangeSelection, restoreSelection) {\n const newElement = $createImpliedParaNode();\n newElement.setTextFormat(rangeSelection.format);\n newElement.setTextStyle(rangeSelection.style);\n newElement.setDirection(this.getDirection());\n newElement.setFormat(this.getFormatType());\n newElement.setStyle(this.getTextStyle());\n this.insertAfter(newElement, restoreSelection);\n return newElement;\n }\n}\nexport function $createImpliedParaNode() {\n return $applyNodeReplacement(new ImpliedParaNode());\n}\nexport function $isImpliedParaNode(node) {\n return node instanceof ImpliedParaNode;\n}\nexport function isSerializedImpliedParaNode(node) {\n return node?.type === ImpliedParaNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/para/index.html */\nimport { PARA_MARKER_DEFAULT } from \"./node-constants.js\";\nimport { $applyNodeReplacement, ParagraphNode, isHTMLElement, } from \"lexical\";\n/** @see https://docs.usfm.bible/usfm/3.1/para/index.html */\nconst VALID_PARA_MARKERS = [\n // Identification\n \"ide\",\n \"sts\",\n \"rem\",\n \"h\",\n \"toc1\",\n \"toc2\",\n \"toc3\",\n \"toca1\",\n \"toca2\",\n \"toca3\",\n // Introductions\n \"imt\",\n \"imt1\",\n \"imt2\",\n \"imt3\",\n \"imt4\",\n \"is\",\n \"is1\",\n \"is2\",\n \"ip\",\n \"ipi\",\n \"im\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"ib\",\n \"iot\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"iex\",\n \"imte\",\n \"imte1\",\n \"imte2\",\n \"ie\",\n // Titles and Headings\n \"mt\",\n \"mt1\",\n \"mt2\",\n \"mt3\",\n \"mt4\",\n \"mte\",\n \"mte1\",\n \"mte2\",\n \"cl\",\n \"cd\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"ms3\",\n \"mr\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"sr\",\n \"r\",\n \"d\",\n \"sp\",\n \"sd\",\n \"sd1\",\n \"sd2\",\n \"sd3\",\n \"sd4\",\n // Body Paragraphs\n PARA_MARKER_DEFAULT,\n \"m\",\n \"po\",\n \"cls\",\n \"pr\",\n \"pc\",\n \"pm\",\n \"pmo\",\n \"pmc\",\n \"pmr\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"mi\",\n \"lit\",\n \"nb\",\n \"ph\", // Deprecated marker.\n \"ph1\", // Deprecated marker.\n \"ph2\", // Deprecated marker.\n \"ph3\", // Deprecated marker.\n // Poetry\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qr\",\n \"qc\",\n \"qa\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"qd\",\n \"b\",\n // Lists\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n // Breaks - see https://docs.usfm.bible/usfm/3.1/char/breaks/pb.html\n \"pb\",\n];\nexport const PARA_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const PARA_MARKER_OBJECT_PROPS = [\"type\", \"marker\", \"content\"];\nexport class ParaNode extends ParagraphNode {\n __marker;\n __unknownAttributes;\n constructor(marker = PARA_MARKER_DEFAULT, unknownAttributes, key) {\n super(key);\n this.__marker = marker;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"para\";\n }\n static clone(node) {\n const { __marker, __unknownAttributes, __key } = node;\n return new ParaNode(__marker, __unknownAttributes, __key);\n }\n static isValidMarker(marker, extraValidMarkers) {\n return (marker !== undefined &&\n (VALID_PARA_MARKERS.includes(marker) ||\n (extraValidMarkers?.includes(marker) ?? false)));\n }\n static importDOM() {\n return {\n p: () => ({\n conversion: $convertParaElement,\n priority: 1,\n }),\n };\n }\n static importJSON(serializedNode) {\n return $createParaNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n // Define the DOM element here\n const dom = document.createElement(\"p\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(this.__type, `usfm_${this.__marker}`);\n return dom;\n }\n updateDOM(prevNode, dom, config) {\n const recreate = super.updateDOM(prevNode, dom, config);\n if (!recreate && prevNode.__marker !== this.__marker) {\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.remove(`usfm_${prevNode.__marker}`);\n dom.classList.add(`usfm_${this.__marker}`);\n }\n return recreate;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(this.getType(), `usfm_${this.getMarker()}`);\n }\n return { element };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n unknownAttributes: this.getUnknownAttributes(),\n version: PARA_VERSION,\n };\n }\n // Mutation\n insertNewAfter(rangeSelection, restoreSelection) {\n const newElement = $createParaNode(this.getMarker());\n newElement.setTextFormat(rangeSelection.format);\n newElement.setTextStyle(rangeSelection.style);\n newElement.setDirection(this.getDirection());\n newElement.setFormat(this.getFormatType());\n newElement.setStyle(this.getTextStyle());\n this.insertAfter(newElement, restoreSelection);\n return newElement;\n }\n}\nfunction $convertParaElement(element) {\n const marker = element.getAttribute(\"data-marker\") ?? undefined;\n const node = $createParaNode(marker);\n if (element.style) {\n node.setFormat(element.style.textAlign);\n const indent = parseInt(element.style.textIndent, 10) / 20;\n if (indent > 0) {\n node.setIndent(indent);\n }\n }\n return { node };\n}\nexport function $createParaNode(marker, unknownAttributes) {\n return $applyNodeReplacement(new ParaNode(marker, unknownAttributes));\n}\nexport function $isParaNode(node) {\n return node instanceof ParaNode;\n}\nexport function isSerializedParaNode(node) {\n return node?.type === ParaNode.getType();\n}\n","/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/cv/v.html */\nimport { VERSE_CLASS_NAME } from \"./node-constants.js\";\nimport { $applyNodeReplacement, TextNode, } from \"lexical\";\nexport const VERSE_MARKER = \"v\";\nexport const VERSE_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const VERSE_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"number\",\n \"sid\",\n \"altnumber\",\n \"pubnumber\",\n \"content\",\n];\nexport class VerseNode extends TextNode {\n __marker;\n __number;\n __sid;\n __altnumber;\n __pubnumber;\n __unknownAttributes;\n constructor(verseNumber = \"\", text, sid, altnumber, pubnumber, unknownAttributes, key) {\n super(text ?? verseNumber, key);\n this.__marker = VERSE_MARKER;\n this.__number = verseNumber;\n this.__sid = sid;\n this.__altnumber = altnumber;\n this.__pubnumber = pubnumber;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"verse\";\n }\n static clone(node) {\n const { __number, __text, __sid, __altnumber, __pubnumber, __unknownAttributes, __key } = node;\n return new VerseNode(__number, __text, __sid, __altnumber, __pubnumber, __unknownAttributes, __key);\n }\n static importJSON(serializedNode) {\n return $createVerseNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setNumber(serializedNode.number)\n .setSid(serializedNode.sid)\n .setAltnumber(serializedNode.altnumber)\n .setPubnumber(serializedNode.pubnumber)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setNumber(verseNumber) {\n if (this.__number === verseNumber)\n return this;\n const self = this.getWritable();\n self.__number = verseNumber;\n return self;\n }\n getNumber() {\n const self = this.getLatest();\n return self.__number;\n }\n setSid(sid) {\n if (this.__sid === sid)\n return this;\n const self = this.getWritable();\n self.__sid = sid;\n return self;\n }\n getSid() {\n const self = this.getLatest();\n return self.__sid;\n }\n setAltnumber(altnumber) {\n if (this.__altnumber === altnumber)\n return this;\n const self = this.getWritable();\n self.__altnumber = altnumber;\n return self;\n }\n getAltnumber() {\n const self = this.getLatest();\n return self.__altnumber;\n }\n setPubnumber(pubnumber) {\n if (this.__pubnumber === pubnumber)\n return this;\n const self = this.getWritable();\n self.__pubnumber = pubnumber;\n return self;\n }\n getPubnumber() {\n const self = this.getLatest();\n return self.__pubnumber;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM(config) {\n const dom = super.createDOM(config);\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(VERSE_CLASS_NAME, `usfm_${this.__marker}`);\n dom.setAttribute(\"data-number\", this.__number);\n return dom;\n }\n updateDOM(prevNode, dom, config) {\n const recreate = super.updateDOM(prevNode, dom, config);\n if (!recreate && prevNode.__number !== this.__number)\n dom.setAttribute(\"data-number\", this.__number);\n return recreate;\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n number: this.getNumber(),\n sid: this.getSid(),\n altnumber: this.getAltnumber(),\n pubnumber: this.getPubnumber(),\n unknownAttributes: this.getUnknownAttributes(),\n version: VERSE_VERSION,\n };\n }\n}\nexport function $createVerseNode(verseNumber, text, sid, altnumber, pubnumber, unknownAttributes) {\n return $applyNodeReplacement(new VerseNode(verseNumber, text, sid, altnumber, pubnumber, unknownAttributes));\n}\nexport function $isVerseNode(node) {\n return node instanceof VerseNode;\n}\nexport function isSerializedVerseNode(node) {\n return node?.type === VerseNode.getType();\n}\n","export const ZERO_WIDTH_SPACE = \"\\u200B\";\nexport const CARET = \"\\u2038\";\nexport const CURSOR_PLACEHOLDER_CHAR = ZERO_WIDTH_SPACE;\nexport var CursorMovementDirection;\n(function (CursorMovementDirection) {\n CursorMovementDirection[\"LEFT\"] = \"left\";\n CursorMovementDirection[\"RIGHT\"] = \"right\";\n})(CursorMovementDirection || (CursorMovementDirection = {}));\nexport var CharSelectionOffset;\n(function (CharSelectionOffset) {\n CharSelectionOffset[CharSelectionOffset[\"BEFORE\"] = 0] = \"BEFORE\";\n CharSelectionOffset[CharSelectionOffset[\"AFTER\"] = 1] = \"AFTER\";\n})(CharSelectionOffset || (CharSelectionOffset = {}));\n","import { $createTextNode, $isElementNode, $isRootNode, $isTextNode, } from \"lexical\";\nimport { CURSOR_PLACEHOLDER_CHAR } from \"./constants.js\";\nimport { CursorPosition } from \"./CursorSelectionContext.js\";\nexport function $createCursorPlaceholderNode() {\n return $createTextNode(CURSOR_PLACEHOLDER_CHAR);\n}\nexport function $insertCursorPlaceholder(node, position, restoreSelection = false) {\n const cursorPlaceholderNode = $createCursorPlaceholderNode();\n if (position === CursorPosition.Start)\n node.insertBefore(cursorPlaceholderNode, restoreSelection);\n else\n node.insertAfter(cursorPlaceholderNode, restoreSelection);\n return cursorPlaceholderNode;\n}\nexport function $removeCursorPlaceholder(node) {\n const textContent = node.getTextContent();\n node.setTextContent(textContent.replaceAll(CURSOR_PLACEHOLDER_CHAR, \"\"));\n}\n/**\n * Whether `text` is a bare cursor host: non-empty but made up entirely of placeholder characters.\n * A zero-width space embedded in real text (e.g. a Thai/Khmer line break) is NOT placeholder-only,\n * so it is preserved. State consumers (serializers, OT positions, content indexes) use this to\n * treat a transient host as if it were not there.\n *\n * @param text - The text content to test.\n * @returns `true` when `text` is non-empty and every character is a cursor placeholder.\n */\nexport function isCursorPlaceholderOnly(text) {\n // Fast path: the common case (no placeholder at all) short-circuits before any allocation, so\n // this stays cheap when called per text node in serialization hot paths.\n return (text.length > 0 &&\n text.includes(CURSOR_PLACEHOLDER_CHAR) &&\n text.replaceAll(CURSOR_PLACEHOLDER_CHAR, \"\") === \"\");\n}\n/**\n * Whether `node` is a text node holding only cursor placeholder(s) — see {@link isCursorPlaceholderOnly}.\n *\n * @param node - The node to test; may be `null`/`undefined`.\n * @returns `true` when `node` is a `TextNode` whose content is entirely cursor placeholders.\n */\nexport function $isCursorPlaceholderOnlyText(node) {\n return $isTextNode(node) && isCursorPlaceholderOnly(node.getTextContent());\n}\nexport function $getValidAncestor(node, cursor, canHavePlaceholder) {\n const ancestor = node.getParent();\n if (!ancestor || !$isElementNode(ancestor) || !ancestor.isInline()) {\n return { ancestor: null, ancestorSibling: null };\n }\n const ancestorParent = ancestor.getParent();\n if (!ancestorParent || $isRootNode(ancestorParent)) {\n return { ancestor: null, ancestorSibling: null };\n }\n const parentCanHavePlaceholder = canHavePlaceholder(ancestorParent);\n const canHavePlaceholderAsSibling = cursor.isMovingRight\n ? ancestor.canInsertTextAfter()\n : ancestor.canInsertTextBefore();\n const canInsert = parentCanHavePlaceholder && canHavePlaceholderAsSibling;\n const ancestorSibling = cursor.isMovingRight\n ? ancestor.getNextSibling()\n : ancestor.getPreviousSibling();\n if (!ancestorSibling && !canInsert) {\n return $getValidAncestor(ancestor, cursor, canHavePlaceholder);\n }\n if ($isTextNode(ancestorSibling) && !canInsert) {\n return { ancestor: null, ancestorSibling: null };\n }\n return { ancestor, ancestorSibling };\n}\nexport function $findDescendantEligibleForPlaceholder(node, cursor, canHavePlaceholder) {\n if (canHavePlaceholder(node)) {\n return node;\n }\n if ($isElementNode(node)) {\n const child = cursor.isMovingRight ? node.getFirstChild() : node.getLastChild();\n if (child) {\n return $findDescendantEligibleForPlaceholder(child, cursor, canHavePlaceholder);\n }\n }\n return null;\n}\n","/** Utility functions for editor nodes */\nimport { MARKER_OBJECT_PROPS } from \"@eten-tech-foundation/scripture-utilities\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport { $createTextNode, $getCommonAncestor, $getSelection, $getState, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, $setState, NODE_STATE_KEY, TextNode, } from \"lexical\";\nimport { charIdState, MARKER_TRAILING_SPACE_TEXT_TYPE, textTypeState, } from \"../collab/delta.state.js\";\nimport { $isImmutableTypedTextNode, isSerializedImmutableTypedTextNode, } from \"../features/ImmutableTypedTextNode.js\";\nimport { $isMarkerNode, isSerializedMarkerNode } from \"../features/MarkerNode.js\";\nimport { $isTypedMarkNode } from \"../features/TypedMarkNode.js\";\nimport { $isUnknownNode } from \"../features/UnknownNode.js\";\nimport { $isAttributeRunNode } from \"./AttributeRunNode.js\";\nimport { $isBookNode } from \"./BookNode.js\";\nimport { $isChapterNode, isSerializedChapterNode, } from \"./ChapterNode.js\";\nimport { $isCharNode, CharNode, isSerializedCharNode } from \"./CharNode.js\";\nimport { $isImmutableChapterNode, isSerializedImmutableChapterNode, } from \"./ImmutableChapterNode.js\";\nimport { $isImpliedParaNode, isSerializedImpliedParaNode, } from \"./ImpliedParaNode.js\";\nimport { $isMilestoneNode } from \"./MilestoneNode.js\";\nimport { $isNoteNode } from \"./NoteNode.js\";\nimport { $isParaNode, isSerializedParaNode } from \"./ParaNode.js\";\nimport { $isVerseNode } from \"./VerseNode.js\";\nimport { EMPTY_CHAR_PLACEHOLDER_TEXT, NBSP } from \"./node-constants.js\";\nimport { isCursorPlaceholderOnly } from \"../../plugins/CursorHandler/index.js\";\n/** RegEx to test for a string only containing digits. */\nconst ONLY_DIGITS_TEST = /^\\d+$/;\n/**\n * Check if the marker is valid and numbered.\n * @param marker - Marker to check.\n * @param numberedMarkers - List of valid numbered markers ('#' removed).\n * @returns true if the marker is a valid numbered marker, false otherwise.\n */\nexport function isValidNumberedMarker(marker, numberedMarkers) {\n if (!marker)\n return false;\n // Starts with a valid numbered marker.\n const numberedMarker = numberedMarkers.find((markerNumbered) => marker.startsWith(markerNumbered));\n if (!numberedMarker)\n return false;\n // Ends with a number.\n const maybeNumber = marker.slice(numberedMarker.length);\n return ONLY_DIGITS_TEST.test(maybeNumber);\n}\n/**\n * Checks if the given node is a SerializedChapterNode or SerializedImmutableChapterNode.\n * @param node - The serialized node to check.\n * @returns `true` if the node is a SerializedChapterNode or SerializedImmutableChapterNode, `false` otherwise.\n */\nexport function isSomeSerializedChapterNode(node) {\n return isSerializedChapterNode(node) || isSerializedImmutableChapterNode(node);\n}\n/**\n * Checks if the given node is a ChapterNode or ImmutableChapterNode.\n * @param node - The node to check.\n * @returns `true` if the node is a ChapterNode or ImmutableChapterNode, `false` otherwise.\n */\nexport function $isSomeChapterNode(node) {\n return $isChapterNode(node) || $isImmutableChapterNode(node);\n}\n/**\n * Finds the chapter node with the given chapter number amongst the nodes.\n * @param nodes - Nodes to look in.\n * @param chapterNum - Chapter number to look for.\n * @returns the chapter node if found, `undefined` otherwise.\n */\nexport function $findChapter(nodes, chapterNum) {\n return nodes.find((node) => $isSomeChapterNode(node) && node.getNumber() === chapterNum.toString());\n}\n/**\n * Finds the next chapter.\n * @param nodes - Nodes to look in.\n * @param isCurrentChapterAtFirstNode - If `true` ignore the first node.\n * @returns the next chapter node if found, `undefined` otherwise.\n */\nexport function $findNextChapter(nodes, isCurrentChapterAtFirstNode = false) {\n return nodes.find((node, index) => (!isCurrentChapterAtFirstNode || index > 0) && $isSomeChapterNode(node));\n}\n/**\n * Finds the nearest previous node by checking the node's previous sibling, then walking up\n * through ancestors and checking their previous siblings. Stops at root.\n * @param node - Node to start from.\n * @returns the nearest previous node, or `undefined` if none exists.\n */\nexport function $findNearestPreviousNode(node) {\n let current = node;\n while (current && current.getParent() !== null) {\n const prev = current.getPreviousSibling();\n if (prev)\n return prev;\n current = current.getParent();\n }\n return undefined;\n}\n/**\n * Find the chapter that this node is in.\n * @param node - Node to find the chapter it's in.\n * @returns the chapter node if found, `undefined` otherwise.\n */\nexport function $findThisChapter(node) {\n if (!node)\n return undefined;\n // is this node a chapter\n if ($isSomeChapterNode(node))\n return node;\n // is the chapter a previous top level sibling\n let previousSibling = node.getTopLevelElement()?.getPreviousSibling();\n while (previousSibling && !$isSomeChapterNode(previousSibling)) {\n previousSibling = previousSibling.getPreviousSibling();\n }\n if (previousSibling && $isSomeChapterNode(previousSibling))\n return previousSibling;\n return undefined;\n}\n/**\n * Traverses up the node tree from startNode (itself included) to find the first ancestor NoteNode.\n * A named convenience over `$findMatchingParent` — the one shared ancestor-walk.\n * @param startNode - The node to start the upward search from.\n * @returns The first ancestor NoteNode found, or `undefined` if none exists before the root.\n */\nexport function $findFirstAncestorNoteNode(startNode) {\n return $findMatchingParent(startNode, $isNoteNode) ?? undefined;\n}\n/**\n * Checks if the node has a `getMarker` method. Excludes React nodes - consider using\n * `$isReactNodeWithMarker` instead.\n * @param node - LexicalNode to check.\n * @returns `true` if the node has a `getMarker` method, `false` otherwise.\n */\nexport function $isNodeWithMarker(node) {\n return ($isBookNode(node) ||\n $isChapterNode(node) ||\n $isCharNode(node) ||\n $isImmutableChapterNode(node) ||\n $isImpliedParaNode(node) ||\n $isMilestoneNode(node) ||\n $isParaNode(node) ||\n $isNoteNode(node) ||\n $isVerseNode(node) ||\n $isUnknownNode(node)\n // ImmutableUnmatchedNode & MarkerNode also have the `getMarker` method but they left out for\n // now until we know we need them.\n );\n}\n/**\n * Get the next node in the document tree.\n * @param selection - The current selection to get the next node from.\n * @returns The next node or null if there is no next node.\n */\nexport function $getNextNode(selection) {\n if (selection.anchor.type === \"element\") {\n const anchorNode = selection.anchor.getNode();\n const offset = selection.anchor.offset;\n if (offset < anchorNode.getChildrenSize())\n return anchorNode.getChildAtIndex(offset);\n }\n const anchorNode = selection.anchor.getNode();\n return anchorNode.getNextSibling() ?? anchorNode.getParent()?.getNextSibling() ?? null;\n}\n/**\n * Get the previous node in the document tree.\n * @param selection - The current selection to get the previous node from.\n * @returns The previous node or null if there is no previous node.\n */\nexport function $getPreviousNode(selection) {\n const offset = selection.anchor.offset;\n if (selection.anchor.type === \"element\" && offset > 0) {\n const anchorNode = selection.anchor.getNode();\n return anchorNode.getChildAtIndex(offset - 1);\n }\n const anchorNode = selection.anchor.getNode();\n return anchorNode.getPreviousSibling() ?? anchorNode.getParent()?.getPreviousSibling() ?? null;\n}\n/**\n * Type guard to check if a node is para-like. Para-like nodes have an OT length of 1 that is\n * counted on its close (rather than its open).\n */\nexport function $isParaLikeNode(node) {\n return $isSomeParaNode(node) || $isBookNode(node);\n}\n/**\n * Checks if the given node is a ParaNode or ImpliedParaNode.\n * @param node - The node to check.\n * @returns `true` if the node is a ParaNode or ImpliedParaNode, `false` otherwise.\n */\nexport function $isSomeParaNode(node) {\n return $isParaNode(node) || $isImpliedParaNode(node);\n}\n/**\n * Checks if the given serialized node is a SerializedParaNode or SerializedImpliedParaNode.\n * @param node - The serialized node to check.\n * @returns `true` if the node is a SerializedParaNode or SerializedImpliedParaNode, `false`\n * otherwise.\n */\nexport function isSomeSerializedParaNode(node) {\n return isSerializedParaNode(node) || isSerializedImpliedParaNode(node);\n}\n/**\n * Check if a node is a descendant of a potential ancestor node.\n *\n * Deliberately NOT delegated to `$findMatchingParent`: this walk excludes the starting node (a\n * node is not its own descendant) and must be able to match the RootNode's key, which\n * `$findMatchingParent` never tests.\n *\n * @param node - The node to check.\n * @param ancestorKey - The key of the potential ancestor node.\n * @returns `true` if the node is a descendant of the ancestor, `false` otherwise.\n */\nexport function $isDescendantOf(node, ancestorKey) {\n let parent = node.getParent();\n while (parent) {\n if (parent.getKey() === ancestorKey)\n return true;\n parent = parent.getParent();\n }\n return false;\n}\n/**\n * Check if the given char attributes are the same as the ones in the CharNode.\n * @param charAttributes - The char attributes to compare.\n * @param charNode - The character node to compare against.\n * @returns `true` if the attributes are the same, `false` otherwise.\n */\nexport function $hasSameCharAttributes(charAttributes, charNode) {\n const charNodeCid = $getState(charNode, charIdState);\n const bothHaveCid = !!(charAttributes.cid && charNodeCid);\n const bothHaveNoCid = !charAttributes.cid && !charNodeCid;\n return (charAttributes.style === charNode.getMarker() &&\n (bothHaveNoCid || (bothHaveCid && charAttributes.cid === charNodeCid)));\n}\n/**\n * Find a common ancestor of a and b and return the common ancestor,\n * or undefined if there is no common ancestor between the two nodes.\n *\n * This function is compatible with the deprecated `LexicalNode.getCommonAncestor` function but\n * uses the new (as of Lexical v0.26.0) NodeCaret APIs.\n *\n * @param a A LexicalNode\n * @param b A LexicalNode\n * @returns The common ancestor between the two nodes or undefined if they have no common ancestor\n */\nexport function $getCommonAncestorCompatible(a, b) {\n const a1 = $isElementNode(a) ? a : a.getParent();\n const b1 = $isElementNode(b) ? b : b.getParent();\n const result = a1 && b1 ? $getCommonAncestor(a1, b1) : undefined;\n return result ? result.commonAncestor : undefined;\n}\n/**\n * Moves the selection to the end of the current range, accounting for backward selections.\n * @param selection - The range selection to move to the end.\n */\nexport function $moveSelectionToEnd(selection) {\n const startEndPoints = selection.getStartEndPoints();\n if (!startEndPoints)\n return undefined;\n const [start, end] = startEndPoints;\n const actualEnd = selection.isBackward() ? start : end;\n selection.focus.set(actualEnd.key, actualEnd.offset, actualEnd.type);\n selection.anchor.set(actualEnd.key, actualEnd.offset, actualEnd.type);\n}\n/**\n * Checks if the given node is a SerializedTextNode.\n * @param node - The node to check.\n * @returns `true` if the node is a SerializedTextNode, `false` otherwise.\n */\nexport function isSerializedTextNode(node) {\n return node?.type === TextNode.getType();\n}\n/**\n * Remove the given node and all the nodes after.\n * @param nodes - Nodes to prune.\n * @param pruneNode - Node to prune and all nodes after.\n */\nexport function removeNodeAndAfter(nodes, pruneNode) {\n if (!pruneNode)\n return;\n const pruneNodeIndex = nodes.findIndex((node) => node === pruneNode);\n // prune node and after\n if (pruneNodeIndex)\n nodes.length = pruneNodeIndex;\n}\n/**\n * Removes all the nodes that proceed the given node.\n * @param nodes - Nodes to prune.\n * @param firstNode - Node to prune before.\n * @returns the nodes from the node and after.\n */\nexport function removeNodesBeforeNode(nodes, firstNode) {\n if (!firstNode)\n return nodes;\n const firstNodeIndex = firstNode.getIndexWithinParent();\n return nodes.splice(firstNodeIndex + 1, nodes.length - firstNodeIndex - 1);\n}\n/**\n * Gets the opening marker text.\n * @param marker - The USFM marker.\n * @param nested - Whether the span nests inside another char span. A nested span's marker carries\n * the `+` prefix (`\\+w`) — ParatextData's writer rule and PT9's on-screen display for USFM ≤3.0,\n * where `+` is what makes a bare char marker nest instead of closing the enclosing span. The\n * glyph must show it so a re-tokenization of the visible text reproduces the same nesting.\n * @returns the opening marker text.\n */\nexport function openingMarkerText(marker, nested = false) {\n return `\\\\${nested ? \"+\" : \"\"}${marker}`;\n}\n/**\n * Gets the closing marker text.\n * @param marker - The USFM marker.\n * @param nested - Whether the span nests inside another char span (see {@link openingMarkerText}).\n * @returns the closing marker text.\n */\nexport function closingMarkerText(marker, nested = false) {\n return `\\\\${nested ? \"+\" : \"\"}${marker}*`;\n}\n/**\n * Parse number from marker text.\n * @param marker - Chapter or verse marker.\n * @param text - Text to parse.\n * @param number - Default number to use if none is found.\n * @returns the parsed number or the default value as a string.\n */\nexport function parseNumberFromMarkerText(marker, text, number) {\n const openMarkerText = openingMarkerText(marker);\n if (text?.startsWith(openMarkerText)) {\n // Skip the NBSP/space separator inserted by `getVisibleOpenMarkerText`.\n const rest = text.slice(openMarkerText.length).replace(/^[\\s ]+/, \"\");\n // The number is the whole WORD, valid or not — the same scan Paratext 9's GetNextWord applies\n // and the same one Tier 1 uses to keep the glyph and the node in step\n // (`leadingAttributeGlyphRegexes`). Anything narrower drops displayed bytes on the way to the\n // file: a bridge the user is still typing (`5-`), a typo (`5--`, `5*`), a half-typed segment\n // (`5-Da`) are all on screen and in the node's own number, so a grammar that recognized only\n // well-formed numbers would save something the editor is not showing.\n //\n // The word still ends where the leading-attribute rule says it does, which is what keeps this\n // from swallowing content: whitespace ends it (`\\v 7 5` is verse 7 plus body text `5`) and so\n // does a backslash (`\\v 2\\ Da` is verse 2 plus the literal), because both are the tokenizer's\n // own name-scan terminators.\n const match = /^([^ \\u00A0\\\\]+)/.exec(rest);\n if (match)\n number = match[1];\n }\n return number;\n}\n/**\n * Gets the open marker text with the marker visible.\n * @param marker - Verse marker.\n * @param content - Content such as chapter or verse number.\n * @returns the marker text with the open marker visible.\n */\nexport function getVisibleOpenMarkerText(marker, content) {\n let text = openingMarkerText(marker);\n if (content)\n text += `${NBSP}${content}`;\n text += \" \";\n return text;\n}\n/** The `textType` NodeState of a serialized node, if any — the serialize-only mirror of the live\n * `$getState(node, textTypeState)`. */\nfunction serializedTextType(node) {\n const state = node[NODE_STATE_KEY];\n if (state && typeof state === \"object\" && \"textType\" in state) {\n const textType = state.textType;\n if (typeof textType === \"string\")\n return textType;\n }\n return undefined;\n}\n/**\n * Recursively extracts text content from a serialized Lexical node and its descendants.\n * Excludes marker nodes (both MarkerNode and ImmutableTypedTextNode with type \"marker\").\n * @param node - The serialized node to process.\n * @returns The concatenated text content.\n */\n// Keep this function in sync with `$getTextContentExcludingMarkers`. The two are deliberately\n// parallel rather than merged: this one walks SERIALIZED nodes (plain objects, `children` arrays),\n// the other walks LIVE nodes (Lexical accessors inside a read) — a shared core would need a\n// node-accessor abstraction that costs more than the ~20 duplicated lines it would save.\nfunction extractTextFromNode(node) {\n // Skip marker nodes - they're structural/formatting elements, not content\n if (isSerializedMarkerNode(node))\n return \"\";\n if (isSerializedImmutableTypedTextNode(node) && node.textType === \"marker\")\n return \"\";\n // The attribute display run (textType \"attribute\") is engine-owned presentation, not content —\n // exclude its bytes (`|gloss`) from note-preview text.\n if (isSerializedTextNode(node) && serializedTextType(node) === \"attribute\")\n return \"\";\n if (isSerializedTextNode(node) && node.text !== NBSP)\n return node.text;\n if (isSerializedCharNode(node)) {\n // If it's an ElementNode, process its children recursively and join their text\n // We join with '' here because spacing is usually handled by spaces within TextNodes\n // or potentially by joining results from the top-level nodes with spaces later.\n return node.children.map((child) => extractTextFromNode(child)).join(\"\");\n }\n // Ignore other node types (e.g., LineBreakNode, custom nodes without text/children)\n return \"\";\n}\n/**\n * Gets the preview text from an array of serialized Lexical nodes,\n * handling nested elements like the modified CharNode.\n * @param childNodes - Child nodes (e.g., from a NoteNode or ParagraphNode).\n * @returns The preview text.\n */\nexport function getPreviewTextFromSerializedNodes(childNodes) {\n const previewText = childNodes\n .map((node) => extractTextFromNode(node))\n .filter((text) => text.length > 0)\n .join(\" \")\n .trim();\n return previewText;\n}\n/**\n * Get editable note caller text.\n * @param noteCaller - Note caller.\n * @returns caller text.\n */\nexport function getEditableCallerText(noteCaller) {\n return \" \" + noteCaller + NBSP;\n}\n/**\n * Gets the preview text for a note caller.\n * Excludes marker nodes from the text content.\n * @param childNodes - Child nodes of the NoteNode.\n * @returns the preview text.\n */\nexport function $getNoteCallerPreviewText(childNodes) {\n const parts = [];\n for (const node of childNodes) {\n if (!$isCharNode(node))\n continue;\n const textContent = $getTextContentExcludingMarkers(node);\n if (textContent === EMPTY_CHAR_PLACEHOLDER_TEXT)\n continue;\n if (textContent.length > 0)\n parts.push(textContent);\n }\n return parts.join(\" \").trim();\n}\n/**\n * Recursively gets text content from a node, excluding marker nodes.\n * @param node - The node to extract text from.\n * @returns The text content without markers.\n */\n// Keep this function in sync with `extractTextFromNode`.\nfunction $getTextContentExcludingMarkers(node) {\n // Skip marker nodes\n if ($isMarkerNode(node))\n return \"\";\n if ($isVisibleMarkerNode(node))\n return \"\";\n // The attribute display run (textType \"attribute\") is engine-owned presentation, not content.\n if ($isTextNode(node) && $getState(node, textTypeState) === \"attribute\")\n return \"\";\n // For text nodes, return the text\n if ($isTextNode(node))\n return node.getTextContent();\n // For element nodes, recursively process children\n if ($isElementNode(node)) {\n return node\n .getChildren()\n .map((child) => $getTextContentExcludingMarkers(child))\n .join(\"\");\n }\n return \"\";\n}\n/**\n * Checks whether a node is a visible marker node.\n *\n * Visible marker nodes are immutable typed text nodes whose text type is \"marker\".\n *\n * @param node - The node to check.\n * @returns `true` if the node is an ImmutableTypedTextNode with text type \"marker\".\n */\nexport function $isVisibleMarkerNode(node) {\n return $isImmutableTypedTextNode(node) && node.getTextType() === \"marker\";\n}\n/**\n * True for either flavor of synthesized marker node: a `MarkerNode` (markerMode \"editable\") or an\n * `ImmutableTypedTextNode` with `textType: \"marker\"` (markerMode \"visible\" or gutter views).\n * These are the two node shapes used to render a USFM marker as visible content — a paragraph's\n * marker (e.g. `\\p`, `\\s2`, `\\q1`) as the first child of its `ParaNode`, or a character marker's\n * opening and closing markers inside its `CharNode`.\n *\n * @param node - The node to check.\n * @returns `true` if the node is a `MarkerNode` or a visible marker node.\n */\nexport function $isSynthesizedMarkerNode(node) {\n return $isMarkerNode(node) || $isVisibleMarkerNode(node);\n}\n/**\n * Set a `CharNode`'s marker, keeping its content and identity.\n *\n * Use this rather than `CharNode.setMarker` whenever an existing `CharNode`'s marker changes in a\n * rendered editor: it pairs the `setMarker` with the retargeting of the node's synthesized marker\n * children, in the one order that works.\n *\n * Coalescing with an identically-marked adjacent sibling is deliberately not handled here:\n * `$charNodeTransform` (`shared-react`'s `CharNodePlugin.tsx`) already does it, and\n * `CharNodePlugin.test.tsx` proves it for exactly this call. Don't hand-roll it, and don't fight it.\n *\n * The merge is not deferred to a later update: Lexical runs node transforms to fixpoint inside the\n * *same* `editor.update()`, before reconciliation and before any update listener fires, and the node\n * this touches is dirty and therefore transformed. So no committed `EditorState` is ever observable\n * with the un-coalesced pair in it. That matters wherever a caller copies an existing `CharNode`'s\n * cid onto a new sibling and leans on this merge to reunite them — the duplicate cid has no window\n * in which anything can read it.\n *\n * Callers must pre-validate `marker`: passing a footnote or cross-reference marker (e.g. `\"ft\"`,\n * `\"xt\"`) removes the node's closing marker child rather than rewriting it, because\n * `addClosingMarker` never emits one for those families. That is a deliberate contract rather than a\n * guard - see `$retargetSynthesizedMarkers` - and no current caller reaches it, since\n * `EditorRef.replaceCharacterMarker` rejects those markers up front.\n *\n * @param charNode - The `CharNode` to change.\n * @param marker - The character marker to change to. Must not be a footnote or cross-reference\n * marker; see above.\n */\nexport function $setCharNodeMarker(charNode, marker) {\n // Before setMarker, not after. $retargetSynthesizedMarkers's ImmutableTypedTextNode branch\n // (markerMode \"visible\") matches a child's text against the *old* marker's opening/closing form,\n // read via charNode.getMarker() — reversed, that read would already return the new marker, and\n // the stale child would silently go unmatched. Its MarkerNode branch (markerMode \"editable\")\n // doesn't care about this order: MarkerNode.setMarker recomputes text from the new marker it's\n // given plus its own stored __markerSyntax, not from anything read off charNode.\n // Guarded by \"matches marker children against the old marker, not the new one\" in\n // node-utils.test.ts - reverse these two calls and it fails.\n $retargetSynthesizedMarkers(charNode, marker);\n charNode.setMarker(marker);\n}\n/**\n * Point a `CharNode`'s synthesized marker children at a new marker.\n *\n * Under `markerMode: \"editable\"` those children are `MarkerNode`s and under `\"visible\"` they are\n * `ImmutableTypedTextNode`s with `textType: \"marker\"`; both are produced by the USJ editor\n * adaptor's `addOpeningMarker` / `addClosingMarker` and neither is touched by `CharNode.setMarker`,\n * so without this every marker change in those modes leaves the old marker's text on screen.\n *\n * Retargets rather than strips: stripping would leave the changed span looking unmarked, which\n * reads worse than stale. The one exception is the closing marker child when `toMarker` is a\n * note-content marker: `addClosingMarker` never emits a closing marker for those families, so the\n * child is removed rather than rewritten to a form the adaptor would never produce (e.g. `\\ft*`).\n * That is a contract of the function, not a live path — `EditorRef.replaceCharacterMarker` rejects\n * those markers up front.\n *\n * The old marker is read from the node itself rather than taken from a caller-supplied \"from\"\n * marker, which callers may not know when the innermost marker was targeted.\n *\n * A child whose text is neither the opening nor the closing form of the old marker — in either the\n * plain or the nested (`\\\\+nd`) spelling — is left verbatim rather than rewritten by guesswork. This applies to both synthesized child flavors: a\n * `MarkerNode` is a `TextNode` in default (non-token) mode, so a selection anchored inside its\n * visible text can `splitText` it into a fragment whose `__marker` is stale but whose text no\n * longer matches — rewriting that verbatim would re-expand it to the wrong marker.\n *\n * @param charNode - The `CharNode` whose marker is about to change.\n * @param toMarker - The character marker to change to.\n */\nfunction $retargetSynthesizedMarkers(charNode, toMarker) {\n const fromMarker = charNode.getMarker();\n // Both spellings: a nested span's glyphs carry the `+` (`\\\\+nd`), and matching only the plain\n // form left them unmatched, so a marker change inside another span rewrote the node while its\n // glyphs — and the bytes they serialize to — kept the old marker.\n const openingText = openingMarkerText(fromMarker);\n const nestedOpeningText = openingMarkerText(fromMarker, true);\n const closingText = closingMarkerText(fromMarker);\n const nestedClosingText = closingMarkerText(fromMarker, true);\n // Note-content markers are written without a closing marker, so a closing child is removed rather\n // than retargeted to a form `addClosingMarker` would never emit.\n const dropsClosingMarker = CharNode.isNoteContentMarker(toMarker);\n charNode.getChildren().forEach((child) => {\n // Gate on the node type first: only a synthesized marker child is ever a candidate for\n // rewriting or removal here, regardless of what its text happens to contain.\n if (!$isSynthesizedMarkerNode(child))\n return;\n const text = child.getTextContent();\n const isOpening = text === openingText || text === nestedOpeningText;\n const isClosing = !isOpening && (text === closingText || text === nestedClosingText);\n if (!isOpening && !isClosing)\n return;\n if (isClosing && dropsClosingMarker) {\n child.remove();\n return;\n }\n // MarkerNode.setMarker recomputes the node's text for us, nesting included — it re-derives\n // from its own stored nesting rather than from anything read here.\n if ($isMarkerNode(child))\n child.setMarker(toMarker);\n else if ($isVisibleMarkerNode(child)) {\n // A visible glyph stores no nesting, so carry over the nesting its text already showed.\n const nested = text.startsWith(openingMarkerText(\"\", true));\n child.setTextContent(isOpening ? openingMarkerText(toMarker, nested) : closingMarkerText(toMarker, nested));\n }\n });\n}\n/**\n * Remove all known properties of the `markerObject`.\n * @param markerObject - Scripture marker and its contents.\n * @param markerObjectProps - List of known properties to remove. Defaults to `MARKER_OBJECT_PROPS`.\n * @returns all the unknown properties or `undefined` if all are known.\n */\nexport function getUnknownAttributes(markerObject, markerObjectProps = MARKER_OBJECT_PROPS) {\n const attributes = { ...markerObject };\n markerObjectProps.forEach((property) => {\n Reflect.deleteProperty(attributes, property);\n });\n return Object.keys(attributes).length === 0 ? undefined : attributes;\n}\n/**\n * Retrieves the lowercase tag name of the DOM element associated with a LexicalNode.\n * @param node - The LexicalNode for which to find the corresponding DOM element's tag name.\n * @param editor - The LexicalEditor instance used to access the DOM.\n * @returns The lowercase tag name of the DOM element if found, or `undefined` if no corresponding\n * DOM element exists.\n * @deprecated Not used anymore.\n */\nexport function getNodeElementTagName(node, editor) {\n const domElement = editor.getElementByKey(node.getKey());\n return domElement ? domElement.tagName.toLowerCase() : undefined;\n}\n/**\n * Removes properties with undefined values from an object.\n *\n * @param obj - The object to remove undefined properties from.\n * @returns A new object with the same type as the input, but with undefined properties removed.\n *\n * @example\n * const input = { a: 1, b: undefined, c: 'hello' };\n * const result = removeUndefinedProperties(input);\n * // result: { a: 1, c: 'hello' }\n *\n * @remarks\n * This function creates a new object and does not modify the original input object.\n */\nexport function removeUndefinedProperties(obj) {\n return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));\n}\n/**\n * Returns true when the error is Lexical's getNodes() throw (selection on DecoratorNode).\n * Message-based; may break if Lexical changes error text. Prefer pre-checking anchor\n * node type before calling getSelectionStartNode.\n */\nexport function isSelectionStartNodeExpectedError(err) {\n const message = err instanceof Error ? err.message : String(err);\n return (message.includes(\"$caretFromPoint\") &&\n (message.includes(\"does not inherit from ElementNode\") ||\n message.includes(\"does not inherit from TextNode\")));\n}\n/**\n * Get the start node of the selection.\n * For range selections, avoids throws from `getNodes()` when the anchor is on a node type that\n * does not match the anchor type (e.g. DecoratorNode with an element selection), by returning the\n * anchor node or applying `isSelectionStartNodeExpectedError` fallback.\n * @param selection - The selection to get the start node from.\n * @returns The start node of the selection or `undefined` if no selection is provided.\n */\nexport function getSelectionStartNode(selection) {\n if (!$isRangeSelection(selection)) {\n return getSelectionStartNodeInner(selection);\n }\n const anchorNode = selection.anchor.getNode();\n const isAnchorTypeMismatch = anchorNode &&\n ((selection.anchor.type === \"element\" && !$isElementNode(anchorNode)) ||\n (selection.anchor.type === \"text\" && !$isTextNode(anchorNode)));\n if (isAnchorTypeMismatch) {\n return anchorNode ?? undefined;\n }\n try {\n const node = getSelectionStartNodeInner(selection);\n return node ?? anchorNode ?? undefined;\n }\n catch (err) {\n if (isSelectionStartNodeExpectedError(err)) {\n return anchorNode ?? undefined;\n }\n throw err;\n }\n}\n/**\n * Get the next verse number or segment.\n *\n * A verse range increments the end of the range (even if the range includes segments), and a verse\n * segment increments the segment character. This is intentional to simplify the UX.\n * @param verseNum - The current verse number.\n * @param verse - The current verse string, which can be a single verse, a range, or a segment.\n * @returns The next verse number or segment as a string.\n */\nexport function getNextVerse(verseNum, verse) {\n if (!verse)\n return (verseNum + 1).toString();\n const verseParts = verse.split(\"-\");\n if (verseParts.length === 2)\n return parseInt(verseParts[1])\n ? `${parseInt(verseParts[1]) + 1}`\n : `${parseInt(verseParts[0]) + 1}`;\n // Don't increment beyond 'z' or 'Z'.\n const verseSegment = RegExp(/^(\\d+)([a-yA-Y]{1,3})$/).exec(verse);\n if (!verseSegment)\n return (parseInt(verse) + 1).toString();\n const nextSegmentChar = String.fromCharCode(verseSegment[2].charCodeAt(0) + 1);\n return `${verseSegment[1]}${nextSegmentChar}`;\n}\n/**\n * Determines if the verse number is in the given verse range. Verse segments are accounted for.\n * @param verseNum - The current verse number.\n * @param verseRange - The verse range including segments.\n * @returns `true` if the verse number is in the range, `false` otherwise.\n * @example\n * verseRange \"1-2\" - verseNum 1 and 2 are `true`\n * verseRange \"1a-2b\" - verseNum 1 and 2 are `true`\n * verseRange \"1-3\" - verseNum 1, 2, and 3 are `true`\n */\nexport function isVerseInRange(verseNum, verseRange) {\n if (!verseRange)\n return false;\n const verseNumParts = verseRange.split(\"-\").map((v) => parseInt(v));\n if (verseNumParts.length < 1 || verseNumParts.length > 2 || verseNumParts[0] > verseNumParts[1])\n throw new Error(\"isVerseInRange: invalid range\");\n if (verseNumParts.length === 1)\n return verseNum === verseNumParts[0];\n if (verseNumParts.length === 2 && isNaN(verseNumParts[1]))\n return verseNum >= verseNumParts[0];\n if (verseNumParts.length === 2 && isNaN(verseNumParts[0]))\n return verseNum <= verseNumParts[1];\n return verseNum >= verseNumParts[0] && verseNum <= verseNumParts[1];\n}\n/**\n * Checks if the given verse range is a range (i.e. contains a dash).\n * @param verseRange - The verse range to check.\n * @returns `true` if the verse range is a range, `false` otherwise.\n */\nexport function isVerseRange(verseRange) {\n return !!verseRange && verseRange.includes(\"-\");\n}\n/**\n * Parses a (possibly combined/partial) verse marker into its numeric bounds.\n *\n * Unlike {@link isVerseInRange}, this never throws: a marker that isn't numeric yields `NaN` bounds\n * for the caller to reject. Verse numbers come from imported USFM and are not guaranteed to be\n * well-formed.\n *\n * @param verseRange - The verse marker, e.g. `\"5\"`, `\"14-15\"`, `\"3a\"`.\n * @returns the first and last verse numbers the marker covers.\n * @example\n * \"5\" - `{ start: 5, end: 5 }`\n * \"14-15\" - `{ start: 14, end: 15 }`\n * \"1-3a\" - `{ start: 1, end: 3 }`\n * \"3a\" - `{ start: 3, end: 3 }`\n * \"abc\" - `{ start: NaN, end: NaN }`\n */\nexport function parseVerseRange(verseRange) {\n const parts = verseRange.split(\"-\");\n const start = parseInt(parts[0], 10);\n const end = parts.length > 1 ? parseInt(parts[parts.length - 1], 10) : start;\n return { start, end };\n}\nfunction getSelectionStartNodeInner(selection) {\n if (!selection)\n return undefined;\n const nodes = selection.getNodes();\n if (nodes.length > 0) {\n return selection.isBackward() ? nodes[nodes.length - 1] : nodes[0];\n }\n return undefined;\n}\n/**\n * Checks whether a node is presentation-only and therefore not part of USJ content:\n * line breaks, marker scaffolding (editable and visible), the `AttributeRunNode` wrapper that\n * carries a verse's, milestone's, chapter's or note's attribute display run,\n * marker-trailing-space or attribute text (as a plain TextNode or as an opaque block's folded\n * ImmutableTypedTextNode display run, e.g. an UnknownNode's `\\cat` byte display), and empty or\n * NBSP-only spacer text (which the editor→USJ conversion drops as well; ideally the USJ→editor\n * conversion would create such spacers as presentation-typed text nodes instead — follow-up work).\n * @param node - The node to check.\n * @returns `true` if the node must be skipped when computing USJ content indexes.\n */\nexport function $shouldIgnoreNodeForContentIndexes(node) {\n if (!node)\n return false;\n if ($isLineBreakNode(node))\n return true;\n if ($isMarkerNode(node))\n return true;\n if ($isVisibleMarkerNode(node))\n return true;\n // The owner's attributes are what the exporter reads; the wrapper and everything in it is the\n // display of them, skipped wholesale by the editor→USJ conversion as well.\n if ($isAttributeRunNode(node))\n return true;\n // ImmutableTypedTextNode's \"attribute\" flavor (an opaque block's folded attribute-byte display\n // run, e.g. an UnknownNode's `\\cat ...\\cat*`) is a DecoratorNode, not a TextNode, so it never\n // reaches the $isTextNode branch below — mirror the \"marker\" flavor handled above by\n // $isVisibleMarkerNode.\n if ($isImmutableTypedTextNode(node) && node.getTextType() === \"attribute\")\n return true;\n if ($isTextNode(node)) {\n const textType = $getState(node, textTypeState);\n if (textType === MARKER_TRAILING_SPACE_TEXT_TYPE || textType === \"attribute\")\n return true;\n const text = node.getTextContent();\n // \"\" / NBSP are presentation-only; a bare cursor host (EmptyVerseCaretGuardPlugin) likewise\n // carries no content, so it must not shift annotation content indexes while it rests.\n if (text === \"\" || text === NBSP || isCursorPlaceholderOnly(text))\n return true;\n }\n return false;\n}\n/**\n * Creates the engine-owned NBSP separator that sits between an editable marker glyph and its\n * content (the `[glyph, separator, ...content]` prefix layout). Token mode so typing at the\n * separator's boundary can never insert INTO it — Lexical routes boundary insertions into a new\n * plain sibling TextNode instead. Without token mode, a fresh empty paragraph (whose caret\n * fallback is the separator's end) absorbed typed text into this node (`asdf`), which the\n * serializer — matching the separator by exact-NBSP text — then leaked into USJ content (`\\p\n * ~asdf` in USFM, and a non-convergent PDP echo loop in the host). The forward adaptor builds the\n * SERIALIZED twin of this node with the same {@link MARKER_TRAILING_SPACE_TEXT_TYPE} tag and\n * token mode.\n *\n * Mutating factory (creates a node): call inside `editor.update()`.\n */\nexport function $createMarkerTrailingSeparator() {\n const separator = $createTextNode(NBSP);\n $setState(separator, textTypeState, MARKER_TRAILING_SPACE_TEXT_TYPE);\n separator.setMode(\"token\");\n return separator;\n}\n/**\n * Prepends the structural NBSP that leads an editable char span's text content (after the opening\n * glyph), if not already present. The prefix separates the glyph from the content so caret\n * placement and Tier-2 fragment building can address the content boundary; the reverse adaptor\n * strips it on serialization. String-building code paths (the forward adaptor,\n * `$createNoteContentChar`, the delta materializer) prepend the same `NBSP` when constructing\n * content text.\n *\n * Mutating: call inside `editor.update()`.\n */\nexport function $withCharContentNbspPrefix(node) {\n const text = node.getTextContent();\n if (!text.startsWith(NBSP))\n node.setTextContent(NBSP + text);\n}\n/**\n * Whether `node` is the engine-owned marker-trailing NBSP separator (see\n * {@link $createMarkerTrailingSeparator}). Read-only: call inside\n * `editor.getEditorState().read(...)` or an update.\n *\n * Deliberately NOT a `node is TextNode` type predicate: a false result must not narrow the node\n * away from `TextNode` (an untagged plain text node also returns false), which a type predicate's\n * false branch would wrongly do.\n */\nexport function $isMarkerTrailingSeparator(node) {\n return $isTextNode(node) && $getState(node, textTypeState) === MARKER_TRAILING_SPACE_TEXT_TYPE;\n}\n/**\n * Whether `element`'s para-prefix separator is MISSING while the collapsed caret sits at its\n * site — on the prefix glyph, on the element itself (an element point), or at the very start of\n * the node after the glyph. This is where the caret lands when the user deletes the separator,\n * and it is the para-prefix twin of the char opener's caret-boundary rule\n * (markerSeparators.utils.ts): while it holds, healing the byte back would be healing against a\n * user edit, so the heal and the settle both defer to caret departure. One definition, used by\n * both the deletion transform's grace and the departure settle's re-pend, so the two can never\n * disagree about what \"at the site\" means. Read-only: call inside\n * `editor.getEditorState().read(...)` or an update.\n */\nexport function $paraPrefixSeparatorCaretHeld(element) {\n const glyph = element.getFirstChild();\n if (!$isSynthesizedMarkerNode(glyph) || glyph === null)\n return false;\n if ($isMarkerTrailingSeparator(glyph.getNextSibling()))\n return false;\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n if (anchorNode.is(glyph) || anchorNode.is(element))\n return true;\n const next = glyph.getNextSibling();\n return next !== null && anchorNode.is(next) && selection.anchor.offset === 0;\n}\n/**\n * Maps a parent element's Lexical children to its logical USJ content items — the items the\n * editor→USJ conversion would export: presentation-only nodes skipped, TypedMarkNodes\n * transparent (children spliced in, recursively), contiguous text coalesced into single items.\n *\n * Known exclusion: comment-type TypedMarkNodes are treated as transparent like every other\n * mark, even though the exporter still serializes them as milestone items. That milestone\n * serialization is deprecated and pending removal, so the model intentionally ignores it.\n * @param parent - The parent element node.\n * @returns the logical content items in document order.\n */\nexport function $getLogicalContentItems(parent) {\n const items = [];\n let run;\n const flushRun = () => {\n if (run) {\n items.push({ type: \"text\", segments: run.segments, length: run.length });\n run = undefined;\n }\n };\n const visit = (node) => {\n if ($shouldIgnoreNodeForContentIndexes(node))\n return;\n if ($isTypedMarkNode(node)) {\n // Recursion is defense-in-depth: nested marks only exist transiently before the\n // AnnotationPlugin's nested-element resolver flattens them into siblings.\n node.getChildren().forEach(visit);\n return;\n }\n // Only plain TextNodes (exact \"text\" type) join a coalesced run, mirroring the exporter.\n // TextNode subclasses (e.g. VerseNode) fall through to become standalone items.\n if ($isTextNode(node) && node.getType() === TextNode.getType()) {\n run ??= { segments: [], length: 0 };\n run.segments.push({ node, start: run.length });\n run.length += node.getTextContentSize();\n return;\n }\n flushRun();\n items.push({ type: \"element\", node });\n };\n parent.getChildren().forEach(visit);\n flushRun();\n return items;\n}\n/**\n * Gets the nearest ancestor that is not a TypedMarkNode — the element that owns the node's\n * logical content index (annotation wrappers are transparent in USJ).\n * @param node - The node to get the logical parent of.\n * @returns the logical parent element, or `null` at the root.\n */\nexport function $getLogicalParent(node) {\n let parent = node.getParent();\n while (parent && $isTypedMarkNode(parent))\n parent = parent.getParent();\n return parent;\n}\n/**\n * Gets the logical content index of the item containing the child (the child may be nested\n * inside TypedMarkNodes under the parent).\n * @param parent - The logical parent element.\n * @param child - The node to find.\n * @returns the logical index, or -1 if the child is presentation-only or not found.\n */\nexport function $getLogicalIndexOfChild(parent, child) {\n return $getLogicalContentItems(parent).findIndex((item) => item.type === \"element\"\n ? item.node.is(child)\n : item.segments.some((segment) => segment.node.is(child)));\n}\n/**\n * Converts a (TextNode, local offset) point to logical USJ text coordinates: the index of the\n * coalesced text item within the logical parent and the cumulative offset within it.\n * @param textNode - The Lexical text node.\n * @param offset - The offset within the text node.\n * @returns the logical parent, item index, and cumulative offset, or `undefined` if the text\n * node is not part of any logical text item (e.g. presentation-only text).\n */\nexport function $getLogicalTextLocation(textNode, offset) {\n const parent = $getLogicalParent(textNode);\n if (!parent)\n return undefined;\n const items = $getLogicalContentItems(parent);\n for (let index = 0; index < items.length; index++) {\n const item = items[index];\n if (item.type !== \"text\")\n continue;\n const segment = item.segments.find((segment) => segment.node.is(textNode));\n if (segment)\n return { parent, index, offset: segment.start + offset };\n }\n return undefined;\n}\n/**\n * Finds the TextNode and local offset at a cumulative offset within a logical text item.\n * At internal segment boundaries the next segment's start is preferred, so a point at an\n * annotation edge targets the start of the following content rather than the end of the\n * previous piece.\n * @param item - The logical text item.\n * @param offset - The cumulative offset within the item.\n * @returns the text node and local offset, or `undefined` when out of range.\n */\nexport function $getTextNodeAtLogicalOffset(item, offset) {\n if (offset < 0 || offset > item.length)\n return undefined;\n for (const segment of item.segments) {\n const segmentLength = segment.node.getTextContentSize();\n if (offset >= segment.start && offset < segment.start + segmentLength)\n return [segment.node, offset - segment.start];\n }\n // offset === item.length: end of the last segment.\n const lastSegment = item.segments[item.segments.length - 1];\n if (!lastSegment)\n return undefined;\n return [lastSegment.node, offset - lastSegment.start];\n}\n/**\n * Converts an element point (parent + child index) to a logical point. Boundaries that fall\n * inside a coalesced text item (e.g. at an annotation edge) become text points; boundaries\n * between logical items become index points.\n * @param parent - The parent element node of the element point.\n * @param elementOffset - The child index of the element point.\n * @returns the logical point.\n */\nexport function $getLogicalPointFromElementPoint(parent, elementOffset) {\n const items = $getLogicalContentItems(parent);\n const child = parent.getChildAtIndex(elementOffset);\n if (!child)\n return { type: \"index\", index: items.length };\n // Boundary before a presentation-only node: use the boundary before the next content child.\n if ($shouldIgnoreNodeForContentIndexes(child))\n return $getLogicalPointFromElementPoint(parent, elementOffset + 1);\n for (let index = 0; index < items.length; index++) {\n const item = items[index];\n if (item.type === \"element\") {\n if (item.node.is(child) || $isDescendantOf(item.node, child.getKey()))\n return { type: \"index\", index };\n continue;\n }\n for (const segment of item.segments) {\n if (segment.node.is(child) || $isDescendantOf(segment.node, child.getKey())) {\n return segment.start === 0\n ? { type: \"index\", index }\n : { type: \"text\", index, offset: segment.start };\n }\n }\n }\n return { type: \"index\", index: items.length };\n}\n/**\n * Converts a logical boundary index to the earliest element child offset at that boundary\n * (the inverse of `$getLogicalPointFromElementPoint` for index points).\n * @param parent - The parent element node.\n * @param logicalIndex - The logical boundary index (0 = before the first item).\n * @returns the element child offset.\n */\nexport function $getElementOffsetFromLogicalIndex(parent, logicalIndex) {\n if (logicalIndex <= 0)\n return 0;\n const items = $getLogicalContentItems(parent);\n if (items.length === 0 || logicalIndex > items.length)\n return parent.getChildrenSize();\n const previousItem = items[logicalIndex - 1];\n const lastNode = previousItem.type === \"element\"\n ? previousItem.node\n : previousItem.segments[previousItem.segments.length - 1]?.node;\n const topLevelChild = lastNode ? $findChildOfParent(parent, lastNode) : undefined;\n return topLevelChild ? topLevelChild.getIndexWithinParent() + 1 : parent.getChildrenSize();\n}\n/** Walks up from a descendant to the direct child of the given parent. */\nfunction $findChildOfParent(parent, descendant) {\n let current = descendant;\n while (current) {\n const currentParent = current.getParent();\n if (currentParent?.is(parent))\n return current;\n current = currentParent;\n }\n return undefined;\n}\n","/** Marker node used when displaying USFM */\nimport { closingMarkerText, openingMarkerText } from \"../usj/node.utils.js\";\nimport { $applyNodeReplacement, TextNode, } from \"lexical\";\nexport const MARKER_VERSION = 1;\nexport class MarkerNode extends TextNode {\n __marker;\n __markerSyntax;\n __nested;\n // `key` stays in Lexical's own third-parameter slot (`TextNode(text, key)`), with `nested`\n // appended after it: a node's key is the last argument every Lexical node constructor takes, and\n // slotting a new field ahead of it would silently reinterpret an existing 3-argument call's\n // `NodeKey` as this flag.\n constructor(marker = \"\", markerSyntax = \"opening\", key, nested = false) {\n super(getMarkerText(marker, markerSyntax, nested), key);\n this.__marker = marker;\n this.__markerSyntax = markerSyntax;\n this.__nested = nested;\n }\n static getType() {\n return \"marker\";\n }\n static clone(node) {\n return new MarkerNode(node.__marker, node.__markerSyntax, node.__key, node.__nested);\n }\n static importJSON(serializedNode) {\n return $createMarkerNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n const { marker, markerSyntax = \"opening\", nested = false } = serializedNode;\n const self = super.updateFromJSON({\n ...serializedNode,\n // An EMPTY serialized text is the \"build canonical bytes\" sentinel — the adaptor's\n // createMarker serializes glyphs with `text: \"\"` and relies on the import deriving them.\n // Any non-empty text is the glyph's actual displayed bytes and is kept verbatim.\n text: serializedNode.text || getMarkerText(marker, markerSyntax, nested),\n });\n // Assigned directly rather than via setMarker/setMarkerSyntax/setNested, which rewrite the\n // just-applied text to canonical and lose a serialized mid-edit divergence: a glyph whose\n // `*` the user deleted must round-trip through parseEditorState or clipboard\n // deserialization still divergent — rewriting it here heals against a user edit, and\n // $isCanonicalMarkerNode then wrongly reports the glyph at rest. Same treatment as\n // ImmutableUnmatchedNode.updateFromJSON.\n const writable = self.getWritable();\n writable.__marker = marker;\n writable.__markerSyntax = markerSyntax;\n writable.__nested = nested;\n return writable;\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n self.__text = getMarkerText(marker, self.__markerSyntax, self.__nested);\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setMarkerSyntax(markerSyntax) {\n if (this.__markerSyntax === markerSyntax)\n return this;\n const self = this.getWritable();\n self.__markerSyntax = markerSyntax;\n self.__text = getMarkerText(self.__marker, markerSyntax, self.__nested);\n return self;\n }\n getMarkerSyntax() {\n const self = this.getLatest();\n return self.__markerSyntax;\n }\n setNested(nested) {\n if (this.__nested === nested)\n return this;\n const self = this.getWritable();\n self.__nested = nested;\n self.__text = getMarkerText(self.__marker, self.__markerSyntax, nested);\n return self;\n }\n getNested() {\n const self = this.getLatest();\n return self.__nested;\n }\n createDOM(config) {\n const dom = super.createDOM(config);\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(this.__markerSyntax);\n return dom;\n }\n updateDOM(prevNode, dom, config) {\n // TextNode implements updateDOM, so unlike CharNode.updateDOM this can - and must - defer to\n // super: its return value decides whether Lexical reuses the element or rebuilds it via\n // createDOM. On reuse, createDOM does not run again, so the marker-derived attribute and class\n // it set have to be rewritten here by hand. setMarker/setMarkerSyntax already reconcile the\n // visible text; without this the presentational data-marker and syntax class go stale, which is\n // the same gap CharNode.updateDOM closes for the char span.\n const isRecreated = super.updateDOM(prevNode, dom, config);\n if (prevNode.__marker !== this.__marker)\n dom.setAttribute(\"data-marker\", this.__marker);\n if (prevNode.__markerSyntax !== this.__markerSyntax) {\n dom.classList.remove(prevNode.__markerSyntax);\n dom.classList.add(this.__markerSyntax);\n }\n return isRecreated;\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n text: this.getTextContent(),\n marker: this.getMarker(),\n markerSyntax: this.getMarkerSyntax(),\n // Only serialize the flag for genuinely nested glyphs; absence means non-nested, so\n // existing states (and the overwhelmingly common non-nested markers) stay unchanged.\n ...(this.getNested() ? { nested: true } : {}),\n version: MARKER_VERSION,\n };\n }\n}\nexport function $createMarkerNode(marker, markerSyntax, nested) {\n return $applyNodeReplacement(new MarkerNode(marker, markerSyntax, undefined, nested));\n}\nexport function $isMarkerNode(node) {\n return node instanceof MarkerNode;\n}\nexport function isSerializedMarkerNode(node) {\n return node?.type === MarkerNode.getType();\n}\n/**\n * Whether `node`'s RENDERED bytes still spell the glyph its own state describes — the single\n * definition of \"this glyph is at rest\", as opposed to mid-edit (pend-shaped).\n *\n * A glyph's text is a cache of (marker, syntax, nested) that only the setters below rewrite, so a\n * user edit to the displayed characters — deleting the `*` from `\\va*`, backspacing into `\\qt-s` —\n * leaves the node's state fully intact while the bytes on screen say something else. Anything that\n * classifies a glyph must ask this, not just the state: a glyph whose bytes have drifted is\n * mid-edit, and treating it as canonical is how a run comes to be canonical to one subsystem and\n * pending to another at the same time. That standing disagreement is the defect shape this\n * predicate exists to make impossible, so it belongs next to the `__text` writer it mirrors, not\n * beside any one of its callers — the marker transform, the pending-marker resolve, the historic\n * re-pend scan, the read-only settle, and the display-run piece scanners all key on it. A nested\n * span's `\\+w*` is canonical FOR A NESTED GLYPH; the `+` comes from the node's own stored nesting,\n * so a rest-state nested glyph is never mistaken for a damaged one.\n *\n * Read-only: call inside `editor.getEditorState().read(...)` or an update.\n */\nexport function $isCanonicalMarkerNode(node) {\n return (node.getTextContent() ===\n getMarkerText(node.getMarker(), node.getMarkerSyntax(), node.getNested()));\n}\n/**\n * Rewrite `node`'s rendered bytes to the canonical spelling of its own (marker, syntax, nested)\n * state — the HEAL arm for machine drift on a glyph's displayed characters. The one writer of the\n * canonical form lives in this module ({@link getMarkerText}); exposing the restore here keeps a\n * healer from re-deriving the spelling and drifting from it. Callers own the provenance decision\n * (invariants: machine drift heals, a user edit pends) — this function only writes the bytes.\n *\n * Mutating: call inside `editor.update()` (dispatched from the marker-edit engine's `MarkerNode`\n * transform when a non-user divergence is detected).\n */\nexport function $restoreCanonicalMarkerText(node) {\n node.setTextContent(getMarkerText(node.getMarker(), node.getMarkerSyntax(), node.getNested()));\n}\n/**\n * The single writer of a glyph's `__text` — the ONLY place the `+` becomes literal characters.\n * `marker` is always clean (`\"w\"`); `nested` contributes the `+` (`\\+w`). Called from the\n * constructor and every setter, so `__text` always reflects (marker, syntax, nested) — keeping\n * the cached text honest is therefore exactly the job of keeping `nested` honest, which\n * `$syncNestedGlyphs` (nestedGlyphs.utils.ts) does from tree position.\n */\nfunction getMarkerText(marker, markerSyntax, nested = false) {\n // The self-closing form is a milestone terminator (`\\*`); milestones never nest inside a char\n // span, so the `+` prefix does not apply to it.\n if (markerSyntax === \"closing\")\n return closingMarkerText(marker, nested);\n if (markerSyntax === \"selfClosing\")\n return closingMarkerText(\"\");\n return openingMarkerText(marker, nested);\n}\n","/**\n * Attribute display runs: the single place that owns HOW a node's USFM attribute bytes\n * (`|lemma=\"grace\" strong=\"G5485\"`, `|gloss`) are rendered as engine-owned display text and kept\n * in sync. Sibling of nestedGlyphs.utils.ts (glyph `+`) and markerSeparators.utils.ts (opener\n * separators), following the same owning-module shape.\n *\n * ## The representations (who owns what)\n *\n * - **Node state is the truth.** Char-span attributes live in `CharNode.__unknownAttributes`;\n * milestone attributes in `MilestoneNode` props + `__unknownAttributes`; a verse's `\\va`/`\\vp`\n * values in `VerseNode.__altnumber`/`__pubnumber`. The display run is a derived cache, never a\n * second store.\n * - **The display run** is a TextNode tagged textType \"attribute\" holding the canonical PT9 byte\n * form produced by {@link canonicalAttributeText}: a lone default attribute collapses to\n * `|value`; anything else is `|name=\"value\" …` (double quotes, single spaces, insertion\n * order). `closed` is derived metadata, never displayed. Char runs are bare `|…` directly\n * before the closing glyph (PT9's shape; an NBSP prefix would flatten to a space and leak\n * into span content on a Tier-2 rebuild). Milestone runs keep the NBSP+`|` prefix — that NBSP\n * flattens to the space genuinely in the file (`\\qt-s |sid=\"…\"\\*`). A verse's `\\va`/`\\vp`\n * values aren't `name=\"value\"` attribute bytes at all — PT9 displays them as their own\n * `MarkerNode` open + NBSP-prefixed value TextNode + `MarkerNode` close triplet, riding as the\n * verse's FOLLOWING SIBLINGS (a `VerseNode` is itself a TextNode, not a container). A\n * milestone's run is shaped the same way — opening `MarkerNode` + optional NBSP-prefixed\n * attribute TextNode + self-closing `MarkerNode` `\\*`, riding as the `MilestoneNode`'s\n * FOLLOWING SIBLINGS (a `MilestoneNode` is a `DecoratorNode`, so it cannot hold children\n * either) — except the glyphs themselves are unconditional: a milestone always shows its\n * opening/self-closing pair, with only the middle attribute text coming and going.\n * - **Excluded from data paths**: textType \"attribute\" text never enters OT content ops or the\n * editor→USJ conversion; the Tier-2 fragment is the one place it DOES flow, so edited bytes\n * re-tokenize back into node state (extractAttributes / scanMilestone).\n *\n * ## Keeping the cache honest\n *\n * Builders construct the run (usj-editor.adaptor's `createChar`/`addAttributes`/\n * `addVerseAttributes`; transforms do not run on `setEditorState`). A char span's own run, a\n * verse's `\\va`/`\\vp` runs, and a milestone's run all follow the identical contract through the\n * shared `$syncDisplayRun` driver (displayRunSync.utils.ts), parameterized by each kind's own\n * descriptor rather than defined in this module — re-deriving the run whenever its owner is\n * dirtied, healing remote collab updates (the collab materializer's `$createMilestone` builds a\n * BARE `MilestoneNode` with no run at all — delta-apply-update.utils.ts) and structure surgery.\n * While the collapsed caret sits inside the run the sync leaves it alone (mid-edit grace); the\n * marker-edit engine settles it on caret departure by pending the edited run into its Tier-2\n * completion path — the displayed bytes re-tokenize back into node state (last-write-wins,\n * uniformly across chars, verses, and milestones), and a milestone whose run was deleted OUTRIGHT\n * (the shared `$runEntirelyAbsent` check, displayRunSync.utils.ts, parameterized by the milestone\n * descriptor) is itself removed, since the run is its entire byte representation. `MilestoneNode`\n * is the SAME type in every mode — unlike the char/verse EDITABLE node types, which never appear\n * outside editable mode — so its sync is registered only in `MarkerEditPlugin.tsx`, which is itself\n * markerMode-\"editable\"-gated, to keep visible/hidden mode's `ImmutableTypedTextNode`-based\n * milestone runs (built by the adaptor, never edited) untouched.\n */\nimport { $isCanonicalMarkerNode, $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { textTypeState } from \"../collab/delta.state.js\";\nimport { $isAttributeRunNode } from \"./AttributeRunNode.js\";\nimport { $isCharNode } from \"./CharNode.js\";\nimport { MS_NON_ATTRIBUTE_PROPS } from \"./MilestoneNode.js\";\nimport { getEditableCallerText, openingMarkerText } from \"./node.utils.js\";\nimport { $isVerseNode } from \"./VerseNode.js\";\nimport { $getState, $isTextNode } from \"lexical\";\n/** USJ artifacts that are not USFM attribute bytes and must never display. */\nexport const ATTRIBUTE_EXCLUDED_KEYS = new Set([\"closed\"]);\n/**\n * The canonical PT9 byte form of an attribute set, including the leading `|` — or `\"\"` when\n * nothing displays. A lone attribute that IS the marker's default collapses to the bare value\n * (`|gloss`); everything else is explicit `name=\"value\"` pairs, double-quoted, single-spaced,\n * insertion order. Values are kept byte-exact (ParatextData treats trailing space as value).\n */\nexport function canonicalAttributeText(attributes, defaultAttributeName) {\n const entries = Object.entries(attributes).filter(([name, value]) => value !== undefined && !ATTRIBUTE_EXCLUDED_KEYS.has(name));\n if (entries.length === 0)\n return \"\";\n // The bare-value collapse requires a NON-EMPTY value: a lone `|` is a byte sequence the\n // tokenizer refuses outright (`parseAttributeText`, PT9 parity), so collapsing an empty\n // default value displayed a run whose settle re-read it as plain content — the attribute\n // name vanished from the file and a stray `|` landed in the scripture text. The explicit\n // `name=\"\"` form keeps the name on screen, and a refused re-tokenize then degrades to\n // visible literal bytes instead of silently corrupting.\n if (entries.length === 1 && entries[0][0] === defaultAttributeName && entries[0][1] !== \"\")\n return `|${entries[0][1]}`;\n return `|${entries.map(([name, value]) => `${name}=\"${value}\"`).join(\" \")}`;\n}\n/**\n * Re-keys `attributes` into `attributeOrder`, appending any name the order does not mention after\n * the ones it does, in their existing order. Both kinds of mismatch are expected rather than\n * exceptional, so neither is an error: the settle re-derives a milestone's attributes from its\n * DISPLAYED BYTES, so an edit can drop a name the order still lists or add one it never knew\n * about, and the surviving names must keep their authored positions either way.\n *\n * Membership is tested by presence, not against `undefined`, so a genuinely empty value (`sid=\"\"`)\n * keeps its authored slot rather than being silently relocated to the end. `Object.hasOwn` rather\n * than `in`: USFM attribute names are unconstrained, so an attribute literally named `toString` or\n * `constructor` reads as already-present on a plain object and would be dropped from the output —\n * deleting the author's bytes — while `in attributes` would copy a native function into the bag.\n */\nexport function orderedAttributes(attributes, attributeOrder) {\n if (!attributeOrder || attributeOrder.length === 0)\n return attributes;\n const ordered = {};\n attributeOrder.forEach((name) => {\n if (Object.hasOwn(attributes, name))\n ordered[name] = attributes[name];\n });\n Object.entries(attributes).forEach(([name, value]) => {\n if (!Object.hasOwn(ordered, name))\n ordered[name] = value;\n });\n return ordered;\n}\n/**\n * The order `markerObject` authored its attributes in — or `undefined` when that is already the\n * CANONICAL order, which is `sid`, then `eid`, then everything else in the order it appeared.\n *\n * Returning `undefined` for the canonical case is what keeps this change invisible to every\n * document that does not need it: `MilestoneNode.attributeOrder` then stays absent, so canonically\n * ordered milestones serialize byte-identically to before and no stored state has to be migrated.\n *\n * Which names take part is defined by {@link MS_NON_ATTRIBUTE_PROPS}: `type`, `marker`, and\n * `content` are never attribute bytes; `sid` and `eid` are ordinary keys of the USJ marker object\n * that render into the same `|…` run as `who`, so they order against the rest with no special case.\n */\nexport function milestoneAttributeOrder(markerObject) {\n const authored = Object.keys(markerObject).filter((name) => !MS_NON_ATTRIBUTE_PROPS.includes(name));\n const canonical = [\n ...authored.filter((name) => name === \"sid\"),\n ...authored.filter((name) => name === \"eid\"),\n ...authored.filter((name) => name !== \"sid\" && name !== \"eid\"),\n ];\n return authored.every((name, index) => name === canonical[index]) ? undefined : authored;\n}\n/**\n * The attribute object a milestone's canonical display text is derived from: `sid`/`eid` folded\n * in first (their real USJ-object positions), then whatever else the marker carries (chiefly\n * `who`). Shared by usj-editor.adaptor's `addAttributes` (building the run from a `MarkerObject`)\n * and the milestone descriptor's `expectedPieces` (displayRun/displayRunRegistry.ts, healing it\n * from a live `MilestoneNode`'s fields through the shared `$syncDisplayRun` driver) so the two\n * sites — one USJ-shaped, one node-shaped — can never drift on WHICH fields make up a milestone's\n * displayed attributes.\n *\n * `attributeOrder` overrides that sid-first default with the order the document actually authored\n * ({@link milestoneAttributeOrder}), which Paratext 9 preserves and the USJ-to-USFM writer emits\n * verbatim — so folding in a fixed order would rewrite bytes in the file. It is `undefined` for\n * every milestone whose source was already canonical, which is the overwhelming majority.\n */\nexport function milestoneAttributes(sid, eid, unknownAttributes, attributeOrder) {\n return orderedAttributes(\n // Presence, not truthiness: an authored `sid=\"\"` is a byte the document holds, and folding it\n // out here deletes it from the displayed run — which a settle then re-derives node state from,\n // so the empty value would be gone from the file. Matches `orderedAttributes`' own `in` test\n // directly above, which exists for exactly this reason.\n {\n ...(sid !== undefined && { sid }),\n ...(eid !== undefined && { eid }),\n ...unknownAttributes,\n }, attributeOrder);\n}\n/**\n * `char`'s own closing glyph among its direct children, if any — the display run's insertion\n * anchor, and the tree signal for whether a run may exist at all. A span whose closing glyph is\n * skipped (a `closed=\"false\"` span — the state that makes footnote/cross-ref content chars and any\n * other genuinely-unclosed span render closer-less) or simply absent never renders one: `createChar`\n * (usj-editor.adaptor) never builds a run there, so the sync must not fabricate one either —\n * deriving the rule from tree shape rather than viewOptions also keeps the sync a no-op outside\n * editable mode, where char spans carry no MarkerNode glyphs at all.\n */\nexport function $charClosingGlyph(char) {\n return char\n .getChildren()\n .find((child) => $isMarkerNode(child) &&\n child.getMarkerSyntax() === \"closing\" &&\n child.getMarker() === char.getMarker());\n}\n/**\n * True when `char` carries attribute bytes that Tier-2 re-tokenization can never recover: real\n * (non-`closed`) attributes with NEITHER a closing glyph ({@link $charClosingGlyph}) NOR an\n * existing display run ({@link $charAttributeDisplayNode}) anywhere among its children. EITHER\n * anchor alone is enough to recover: a live display run carries the bytes into the fragment\n * regardless of the closer (a closer edit — deleted, damaged — re-tokenizes and settles, possibly\n * degrading to literal content, exactly like any other char content); a live closing glyph gives\n * `extractAttributes` a well-defined close event even if the run itself was just deleted (settles\n * to no attributes). Only when BOTH are absent — a `closed=\"false\"` span skips the glyph AND never\n * gets a run built for it — does an attribute such as `link-href` on an unclosed span have no\n * visible representation anywhere in the tree for the Tier-2 fragment builder to pick up. (An\n * explicitly-closed `\\xt`, by contrast, renders its closing glyph, so its attribute run IS built\n * and the span is recoverable.) A span with unrecoverable attributes must stay a Tier-2 sentinel\n * (preserve-or-refuse, tier2Rebuild.utils.ts): recursing into it would silently drop the attribute.\n */\nexport function $hasUnrecoverableAttributes(char) {\n const attributes = char.getUnknownAttributes();\n if (!attributes)\n return false;\n const hasRealAttributes = Object.keys(attributes).some((name) => name !== \"closed\");\n if (!hasRealAttributes)\n return false;\n return $charClosingGlyph(char) === undefined && $charAttributeDisplayNode(char) === undefined;\n}\n/**\n * `char`'s direct-child display run — the TextNode tagged textType \"attribute\" — or `undefined`\n * if none exists.\n */\nexport function $charAttributeDisplayNode(char) {\n return char\n .getChildren()\n .find((child) => $isTextNode(child) && $getState(child, textTypeState) === \"attribute\");\n}\n/**\n * A verse attribute marker's run pieces — opener `MarkerNode` (matching `marker`), value TextNode\n * (textType \"attribute\"), closer `MarkerNode` — scanned tolerantly in their fixed order starting\n * immediately after `after`, with EACH piece individually optional. Mirrors\n * {@link $milestoneAttributeRunPieces}: a mid-edit tree can be missing any subset — deleting just\n * the value leaves opener + closer debris — and the tolerant scan lets callers recognize and grace\n * that partial state and repair only the genuinely missing pieces around whatever survives, never\n * duplicating a leftover. The old all-or-nothing model returned \"no run at all\" for a\n * value-deleted run and re-derived a whole new opener/value/closer over the surviving debris on the\n * next sync (the value-deletion resurrect/duplicate bug).\n *\n * When `after`'s immediately following sibling is an `AttributeRunNode` whose `runKind` matches\n * `marker`, the SAME tolerant scan runs over the wrapper's CHILDREN instead of `after`'s siblings\n * — a wrapper's children are the run's pieces in the identical fixed order, so redirecting the\n * cursor's starting point is the only change needed. The adaptor always builds this shape now; the\n * sync still heals whichever shape — loose siblings (a pre-flip editor state, an undo stack, or a\n * collab-materialized bare owner) or an existing wrapper — is actually in the tree.\n *\n * A glyph is a piece only while its RENDERED BYTES still spell what its state describes\n * ({@link $isCanonicalMarkerNode}). Editing a glyph's characters leaves its marker and syntax\n * untouched, so a state-only scan reports a damaged `\\va` (the `*` deleted) as a perfectly good\n * closer: the run reads canonical here while the marker engine holds the same glyph pending,\n * and that standing disagreement is what silently suppressed the run's mid-edit caret grace —\n * `$runDiverges` saw nothing wrong, so the settle re-tokenized the whole paragraph out from\n * under the caret two keystrokes into the edit. Byte-damaged glyphs are therefore reported\n * ABSENT, which is the truth the rest of the pipeline already knows how to handle: the run\n * diverges, the caret graces it, and departure settles it.\n *\n * Exported (mirrors {@link $milestoneAttributeRunPieces}) so `markerEditTier1.utils.ts`'s\n * deletion-settle path (`packages/platform`) can locate a verse's wrapper(s) directly to detect\n * and clean up an emptied husk.\n */\nexport function $verseAttributeRunPieces(after, marker) {\n return $attributeMarkerRunPieces(after.getNextSibling(), marker);\n}\n/** The separator-run alphabet — plain space and NBSP, the whitespace class the marker-edit\n * engine's separator runs are built from. */\nconst TYPED_SPACING_ONLY_REGEX = /^[ \\u00A0]+$/;\n/**\n * Whether a display-run OPENING glyph's bytes are canonical, or canonical plus trailing TYPED\n * SPACING. A space typed at the end of a run's opening glyph is the same gesture as one typed\n * beside its value — the typed-space-stays rule: the writer emits structural whitespace itself,\n * so the byte never reaches the file. Reporting the glyph byte-damaged instead routed the\n * keystroke into a settle that discarded the space while the caret advanced past the run — a\n * keystroke accepted and then discarded (\"no silent no-ops\"). Openers only: a closer's `*` is\n * its final byte, so trailing bytes there genuinely respell it. Only for glyphs the CALLER knows\n * are run pieces — a char or para opener's spacing story belongs to its separator machinery\n * (markerSeparators.utils.ts), never to this license.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nexport function $isCanonicalRunOpenerGlyph(node) {\n if ($isCanonicalMarkerNode(node))\n return true;\n if (node.getMarkerSyntax() !== \"opening\")\n return false;\n const canonical = openingMarkerText(node.getMarker(), node.getNested());\n const text = node.getTextContent();\n return text.startsWith(canonical) && TYPED_SPACING_ONLY_REGEX.test(text.slice(canonical.length));\n}\n/**\n * The shared tolerant scan behind {@link $verseAttributeRunPieces} and\n * {@link $noteCategoryRunPieces}: an attribute MARKER's run pieces — opener `MarkerNode`\n * (matching `marker`), value TextNode (textType \"attribute\"), closer `MarkerNode` — read in their\n * fixed order starting at `cursor`, each piece individually optional, descending into an\n * `AttributeRunNode` wrapper whose `runKind` matches `marker` when one sits at `cursor`. The two\n * public entry points differ only in where the run RIDES (a verse's following siblings vs a\n * note's children after the caller), which is entirely captured by the starting cursor.\n */\nfunction $attributeMarkerRunPieces(cursor, marker) {\n let opener;\n let value;\n let closer;\n let wrapper;\n if ($isAttributeRunNode(cursor) && cursor.getRunKind() === marker) {\n wrapper = cursor;\n cursor = cursor.getFirstChild();\n }\n if ($isMarkerNode(cursor) &&\n cursor.getMarkerSyntax() === \"opening\" &&\n cursor.getMarker() === marker &&\n // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the\n // opener is at rest, not byte damage.\n $isCanonicalRunOpenerGlyph(cursor)) {\n opener = cursor;\n cursor = cursor.getNextSibling();\n }\n // A MarkerNode is itself a TextNode subclass, so the attribute-state check (never set on a\n // glyph) is what keeps a closer from being misread as the value.\n if ($isTextNode(cursor) && $getState(cursor, textTypeState) === \"attribute\") {\n value = cursor;\n cursor = cursor.getNextSibling();\n }\n if ($isMarkerNode(cursor) &&\n cursor.getMarkerSyntax() === \"closing\" &&\n cursor.getMarker() === marker &&\n $isCanonicalMarkerNode(cursor))\n closer = cursor;\n return { opener, value, closer, wrapper };\n}\n/**\n * The TextNode carrying a note's EDITABLE caller (` + ` with an NBSP tail —\n * `getEditableCallerText`), skipping any leading opening glyph(s) — the anchor a note's `\\cat`\n * run scans from and is inserted after. `undefined` outside the expanded editable shape: a\n * collapsed note renders its caller as a DecoratorNode and deliberately shows no category run,\n * and visible/hidden modes build no editable caller at all. Deriving the anchor from tree shape\n * (rather than viewOptions) keeps the cat sync a structural no-op in every mode that never\n * builds the run, the same rule {@link $charClosingGlyph} applies for a char span's run.\n */\nexport function $noteEditableCallerNode(note) {\n const children = note.getChildren();\n let index = 0;\n while (index < children.length) {\n const child = children[index];\n if (!$isMarkerNode(child) || child.getMarkerSyntax() !== \"opening\")\n break;\n index++;\n }\n const caller = children[index];\n if ($isTextNode(caller) && caller.getTextContent() === getEditableCallerText(note.getCaller()))\n return caller;\n return undefined;\n}\n/**\n * A note's `\\cat` category display run — the same opener/value/closer triplet shape a verse's\n * `\\va`/`\\vp` runs take ({@link $verseAttributeRunPieces}), riding as the note's CHILDREN\n * directly after the editable caller (a `NoteNode` is an ElementNode, so unlike the leaf owners\n * its run needs no sibling position). Empty pieces when the note has no editable caller anchor —\n * the collapsed and non-editable shapes, which never carry a run.\n */\nexport function $noteCategoryRunPieces(note) {\n const caller = $noteEditableCallerNode(note);\n if (!caller)\n return {};\n return $attributeMarkerRunPieces(caller.getNextSibling(), \"cat\");\n}\n/**\n * The plain TextNode carrying an editable chapter's `\\c N` glyph — its first child — the anchor\n * a chapter's `\\ca` run scans from and is inserted after. Accepts the node while it remains a\n * plain (non-glyph, non-attribute) TextNode even when its BYTES are mid-edit — unlike the note's\n * caller, the chapter glyph is itself editable display text, so an exact-text requirement would\n * dissolve the anchor on the first keystroke of a number rename. `undefined` when the glyph text\n * was deleted outright or the chapter is not the editable element shape.\n */\nexport function $chapterGlyphTextNode(chapter) {\n const first = chapter.getFirstChild();\n if (!$isTextNode(first) || $isMarkerNode(first))\n return undefined;\n if ($getState(first, textTypeState) === \"attribute\")\n return undefined;\n return first;\n}\n/**\n * A chapter's `\\ca` alternate-number display run — the identical triplet shape and child\n * position as a note's `\\cat` run ({@link $noteCategoryRunPieces}): an editable `ChapterNode` is\n * an ElementNode, and the run rides directly after its `\\c N` glyph text, where the file puts\n * the span (`\\c 1 \\ca 2\\ca*`). Empty pieces when the glyph anchor is gone.\n */\nexport function $chapterAltnumberRunPieces(chapter) {\n const glyph = $chapterGlyphTextNode(chapter);\n if (!glyph)\n return {};\n return $attributeMarkerRunPieces(glyph.getNextSibling(), \"ca\");\n}\n/** The child a chapter's `\\cp` run is anchored after: `\\ca`'s wrapper (or, while caret-grace\n * defers the wrap, its loose closer), else the `\\c N` glyph text — the chapter twin of\n * `$verseRunAnchor`'s `\\vp` arm. Shared by the scanner and the writer so the two can never\n * disagree about where the run belongs. `undefined` when the glyph anchor is gone. */\nexport function $chapterCpAnchor(chapter) {\n const glyph = $chapterGlyphTextNode(chapter);\n if (!glyph)\n return undefined;\n const ca = $attributeMarkerRunPieces(glyph.getNextSibling(), \"ca\");\n return ca.wrapper ?? ca.closer ?? glyph;\n}\n/**\n * A chapter's `\\cp` published-number display run — opener glyph + NBSP-prefixed value, with NO\n * closing glyph: `\\cp`'s span closes implicitly at the next block boundary in the file, so its\n * displayed run is bounded by its wrapper alone. Rides directly after the `\\ca` run (or the\n * `\\c N` glyph when there is none) — document order `ca` before `cp`, the order ParatextData\n * preserves on disk. Empty pieces when the glyph anchor is gone.\n */\nexport function $chapterPubnumberRunPieces(chapter) {\n const anchor = $chapterCpAnchor(chapter);\n if (!anchor)\n return {};\n return $attributeMarkerRunPieces(anchor.getNextSibling(), \"cp\");\n}\n/**\n * The VerseNode whose `\\va`/`\\vp` SOURCE span `node` is content of, or `undefined`. A settled\n * empty run leaves a standalone `char va`/`char vp` span in the verse's run position (displayed\n * `\\va \\va*`); a value typed into it is an ordinary content edit that no textType tag marks, so\n * the pend decision must key on the SITE — content of a va/vp span whose sibling chain reaches\n * back to a verse over run pieces only — for departure's re-tokenize to fold the bytes onto the\n * verse (the tokenizer's attrCapture). A va/vp span NOT in a verse's run position re-tokenizes\n * to itself (fixed point) and settles nothing — pending it is harmless. A preceding run piece may\n * be loose (a bare `MarkerNode`/attribute `TextNode`) or a whole `AttributeRunNode` wrapper\n * crossed in one step — the adaptor always builds a wanted run wrapped now, so a `\\vp` span\n * sitting behind a WRAPPED `\\va` run (the only shape an altnumber-bearing verse can have\n * post-migration) must still walk past it to reach the verse. Sibling walk to `$ownerOfRunPiece`'s\n * verse descriptors (displayRunRegistry.ts): those start from a run PIECE — including an opening\n * glyph, the shape MarkerEditPlugin.tsx's MarkerNode transform re-drives its sync/pend from — and\n * walk back to find the owning verse; this one starts from a SOURCE SPAN's content text and walks\n * back to find the owning verse for the pend decision. Both classify the same run-piece shapes\n * over the same sibling chain and must keep agreeing on what counts as one.\n */\nexport function $verseOfAttributeSourceText(node) {\n const span = node.getParent();\n if (!$isCharNode(span))\n return undefined;\n const marker = span.getMarker();\n if (marker !== \"va\" && marker !== \"vp\")\n return undefined;\n for (let prev = span.getPreviousSibling(); prev; prev = prev.getPreviousSibling()) {\n if ($isVerseNode(prev))\n return prev;\n const isRunPiece = ($isMarkerNode(prev) && (prev.getMarker() === \"va\" || prev.getMarker() === \"vp\")) ||\n ($isTextNode(prev) && $getState(prev, textTypeState) === \"attribute\") ||\n ($isCharNode(prev) && (prev.getMarker() === \"va\" || prev.getMarker() === \"vp\")) ||\n $isAttributeRunNode(prev);\n if (!isRunPiece)\n return undefined;\n }\n return undefined;\n}\n/**\n * A milestone's display-run pieces, scanned tolerantly in their fixed order — opening\n * `MarkerNode` (matching `milestone`'s own marker), attribute TextNode (textType \"attribute\"),\n * self-closing `MarkerNode` — with EACH piece individually optional: a bare collab-materialized\n * milestone has none of them, and a mid-edit tree can be missing any subset (only the opening\n * deleted leaves attribute + closer debris; only the closer deleted leaves opening + attribute).\n * The tolerant scan lets callers repair only the genuinely missing/stale pieces around whatever\n * survives — never duplicating a leftover — and lets the shared `$runEntirelyAbsent` check\n * (displayRunSync.utils.ts, parameterized by the milestone descriptor) distinguish \"every byte of\n * the run deleted\" from a partial mangle. Exported as the single definition of \"a milestone's run\"\n * — the Tier-2 rebuild's `$milestoneDisplayRun` delegates to it so the sync and the rebuild can\n * never disagree about which siblings make up the run.\n *\n * When `milestone`'s immediately following sibling is an `AttributeRunNode` whose `runKind` is\n * `\"milestone\"`, the SAME tolerant scan runs over the wrapper's CHILDREN instead of `milestone`'s\n * siblings — a wrapper's children are the run's pieces in the identical fixed order, so\n * redirecting the cursor's starting point is the only change needed. The adaptor always builds\n * this shape now; the sync still heals whichever shape — loose siblings (a pre-flip editor state,\n * an undo stack, or a collab-materialized bare milestone) or an existing wrapper — is actually in\n * the tree.\n *\n * As in {@link $verseAttributeRunPieces}, a glyph counts as a piece only while its rendered bytes\n * still spell what its state describes ({@link $isCanonicalMarkerNode}) — a byte-damaged glyph is\n * reported absent so the run diverges and the caret can grace the mid-edit shape.\n */\nexport function $milestoneAttributeRunPieces(milestone) {\n let opening;\n let attribute;\n let closing;\n let wrapper;\n let cursor = milestone.getNextSibling();\n if ($isAttributeRunNode(cursor) && cursor.getRunKind() === \"milestone\") {\n wrapper = cursor;\n cursor = cursor.getFirstChild();\n }\n if ($isMarkerNode(cursor) &&\n cursor.getMarkerSyntax() === \"opening\" &&\n cursor.getMarker() === milestone.getMarker() &&\n // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the\n // opener is at rest, not byte damage.\n $isCanonicalRunOpenerGlyph(cursor)) {\n opening = cursor;\n cursor = cursor.getNextSibling();\n }\n if ($isTextNode(cursor) && $getState(cursor, textTypeState) === \"attribute\") {\n attribute = cursor;\n cursor = cursor.getNextSibling();\n }\n if ($isMarkerNode(cursor) &&\n cursor.getMarkerSyntax() === \"selfClosing\" &&\n $isCanonicalMarkerNode(cursor))\n closing = cursor;\n return { opening, attribute, closing, wrapper };\n}\n","/**\n * Nested inline-marker glyphs: the single place that owns HOW the `+` prefix (`\\+w …\\+w*`) is\n * represented and kept in sync.\n *\n * ## The representations (who owns what)\n *\n * - **Tree containment is the truth.** A char span is nested iff its parent is another CharNode\n * ({@link $isNestedCharNode}) — exactly USJ/USX's model, where nesting is element containment\n * and no `+` exists in any marker name.\n * - **Markers are always CLEAN** (`\"w\"`, never `\"+w\"`), in CharNode state, in USJ, and in the\n * OT/collab delta (which conveys nesting by char-ARRAY position, outermost-first; see\n * `$buildCharItem` in editor-delta.adaptor and `$createNestedChars` in delta-apply-update —\n * both in `shared-react`'s collab plugin).\n * - **Only the rendered glyph text carries the `+`** — USFM's serialization of nesting, and PT9's\n * on-screen display for USFM ≤3.0. `MarkerNode` caches it: Lexical renders a TextNode's stored\n * `__text` (there is no computed-text hook, and nothing re-runs when an ANCESTOR moves), so the\n * `+` must be baked into `__text`. `MarkerNode.__nested` is the flag that `getMarkerText`\n * derives it from — see MarkerNode.ts, which points back here.\n *\n * ## Keeping the cache honest\n *\n * Because `__text` is a cache of tree-derived state, every path that BUILDS glyphs sets `nested`\n * at construction (the USJ load adaptor's `createChar`, `$splitCharNodeAt`, the marker-apply\n * paths, the collab materializer) — construction-time correctness matters because transforms do\n * not run on `setEditorState` (initial load / restored states render straight from serialized\n * `__text`). {@link $syncNestedGlyphs} is the safety net for everything AFTER load: registered as\n * a CharNode transform (CharNodePlugin in `shared-react`), it re-derives each glyph's `nested`\n * from tree position whenever a span is dirtied, so structure surgery that forgets to refresh\n * glyphs (a move, an unwrap, a merge) self-heals instead of leaving a stale `+` that Tier-2\n * re-tokenization would misread (`\\+w` with nothing open parses as an unknown marker; a missing\n * `+` flattens the nesting via close-on-bare).\n */\nimport { $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { $isCharNode } from \"./CharNode.js\";\nimport { $getLogicalParent } from \"./node.utils.js\";\n/** Whether `char` is a nested char span — its LOGICAL parent is another char span. Read through\n * `$getLogicalParent`, never the raw parent: an annotation `TypedMarkNode` wrapped around the\n * span is presentation-transparent in USJ, and a raw-parent read made a genuinely nested span\n * read as un-nested — the sync then stripped its glyphs' `+`, and the bare inner marker\n * re-tokenized as closing the outer span. The one derivation rule for the glyph `+`; every\n * representation above follows from this. */\nexport function $isNestedCharNode(char) {\n return $isCharNode($getLogicalParent(char));\n}\n/**\n * Which char span a glyph that is a DIRECT child of `char` describes:\n *\n * - `char` itself (`glyph.getMarker() === char.getMarker()`) — the span's own opener/closer;\n * nested iff `char` is nested.\n * - A nested CHILD span with that marker (the collab-flattened shape, where an inner span's\n * glyphs sit as siblings around the inner CharNode) — nested by construction.\n * - Neither — e.g. a milestone's display run (`\\qt-s` … `\\*`) rendered inside the span. Not a\n * char glyph at all: milestones never take the `+`, so it is left untouched.\n *\n * Exported as the shared \"is this a char-span glyph, and of which span?\" classifier — the\n * display-separator sync (markerSeparators.utils.ts) uses the same distinction to know which\n * opening glyphs take a separator.\n *\n * @returns the nested value the glyph must carry, or `undefined` to leave the glyph alone.\n */\nexport function $charGlyphNestedValue(glyph, char) {\n if (glyph.getMarkerSyntax() === \"selfClosing\")\n return undefined;\n const marker = glyph.getMarker();\n if (marker === char.getMarker())\n return $isNestedCharNode(char);\n const describesNestedChild = char\n .getChildren()\n .some((child) => $isCharNode(child) && child.getMarker() === marker);\n return describesNestedChild ? true : undefined;\n}\n/**\n * Re-derive the `nested` flag (and thereby the `+` in the glyph text) of `char`'s direct marker\n * glyphs from tree position. Idempotent — `setNested` writes only on change, so a second pass is\n * a no-op and the registering transform converges.\n *\n * @param char - The char span whose glyphs to sync. Must be called inside `editor.update()`.\n */\nexport function $syncNestedGlyphs(char) {\n // An earlier transform in the same pass may have merged/removed the span (adjacent-span\n // combining); a detached span has no tree position to derive from.\n if (!char.isAttached())\n return;\n char.getChildren().forEach((child) => {\n if (!$isMarkerNode(child))\n return;\n const nested = $charGlyphNestedValue(child, char);\n if (nested !== undefined)\n child.setNested(nested);\n });\n}\n","/**\n * Editable-mode display separators after opening char glyphs: the single place that owns HOW the\n * space the user sees after `\\nd` is represented and kept in sync. Sibling of\n * nestedGlyphs.utils.ts, which owns the glyph `+` the same way.\n *\n * ## The convention\n *\n * In editable marker mode an opening char glyph (`\\nd`) is followed on screen by a separator —\n * the space PT9 shows and the serializer writes after the marker. That separator is\n * PRESENTATION-ONLY state: the USFM writer emits the space after an opening marker structurally\n * and the tokenizer consumes it, so no separator ever lives in USJ content or in the saved bytes\n * as data. In the editor it is an NBSP (never a plain space, so it cannot word-wrap away from its\n * glyph), stored as:\n *\n * - a prefix of the following text (`\\nd` + `⍽one`) when the glyph is directly followed by plain\n * text — the shape `createChar` (usj-editor.adaptor), `$splitCharNodeAt`, and the marker-apply\n * paths build, and the reverse adaptor strips on save;\n * - a standalone NBSP text node when the glyph is directly followed by an element (a nested char\n * span, note, milestone, or verse: `\\nd` + `⍽` + `\\+wj …`) — NBSP-only text nodes are\n * presentation-only by convention (`$shouldIgnoreNodeForContentIndexes`) and dropped by the\n * editor→USJ conversion.\n *\n * ## Keeping it in sync\n *\n * Builders construct the separator (transforms do not run on `setEditorState`, so loaded states\n * must render correctly as-is), and {@link $syncOpenerSeparators} — registered as a CharNode\n * transform in CharNodePlugin — re-derives it whenever a span is dirtied, healing paths that\n * restructure spans without rebuilding them through an adaptor. Deleting the separator is\n * semantically a no-op (the writer emits the space regardless), so healing it back is display\n * canonicalization, exactly like Tier-2's rebuild produces — but deleting must still be\n * ALLOWED: while the collapsed caret sits at the deletion point the sync leaves the gap alone\n * (mid-edit grace), and the marker-edit engine settles it back on caret departure by pending\n * spans reported by {@link $hasCaretHeldSeparatorGap} into its Tier-2 completion path.\n *\n * Only char-span glyphs take a separator — a milestone's display run inside a span is left\n * alone — so which glyphs qualify is decided by the same classifier the nested-`+` sync uses\n * ({@link $charGlyphNestedValue}).\n */\nimport { $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { textTypeState } from \"../collab/delta.state.js\";\nimport { $charGlyphNestedValue } from \"./nestedGlyphs.utils.js\";\nimport { NBSP } from \"./node-constants.js\";\nimport { $createTextNode, $getSelection, $getState, $isRangeSelection, $isTextNode, TextNode, } from \"lexical\";\n/**\n * Whether `node` is text that may carry a char-span separator NBSP as its own PREFIX: exactly a\n * plain `TextNode` — subclasses (`VerseNode`, `ImmutableUnmatchedNode`, `MarkerNode`) render their\n * own marker bytes, and splicing an NBSP into those rewrites a glyph — and not an attribute\n * display run (textType \"attribute\"), whose `|…` bytes are engine-owned canonical output that an\n * NBSP prefix would corrupt. THE one predicate for every site that splices a separator into\n * leading text ({@link $openerSeparatorGap} here, plus the continuation/absorb span builders in\n * charGlyphs.utils.ts and charStack.utils.ts), so the rule cannot drift between them; anything\n * else takes a standalone NBSP spacer instead.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nexport function $isSeparatorPrefixHostText(node) {\n return ($isTextNode(node) &&\n node.getType() === TextNode.getType() &&\n $getState(node, textTypeState) !== \"attribute\");\n}\n/**\n * Where a separator is missing after `opener` (a direct child of `char`):\n *\n * - `\"prefix\"` — the glyph is followed by plain text that lacks the NBSP prefix;\n * - `\"spacer\"` — the glyph is followed by an element (or, in the collab-flattened shape, a nested\n * span's opening glyph) with no standalone NBSP spacer between them;\n * - `undefined` — no separator is owed: the glyph is not a char-span glyph (a milestone's display\n * run), has nothing after it, sits directly before a non-nested glyph, or its separator exists.\n */\nfunction $openerSeparatorGap(opener, char) {\n if (opener.getMarkerSyntax() !== \"opening\")\n return undefined;\n // Only char-span glyphs take a separator (not a milestone's display run).\n if ($charGlyphNestedValue(opener, char) === undefined)\n return undefined;\n const next = opener.getNextSibling();\n if (next === null)\n return undefined;\n if ($isMarkerNode(next)) {\n // Opening glyph directly before another glyph: in the collab-flattened shape that next glyph\n // opens a nested span (`\\add\\+wj …`) and the separator goes between them. Any other adjacent\n // glyph (the span's own closer on a degenerate empty span) takes none.\n return $charGlyphNestedValue(next, char) === true ? \"spacer\" : undefined;\n }\n // Plain text directly after the glyph carries the separator as its prefix — see\n // $isSeparatorPrefixHostText for what qualifies and why.\n if ($isSeparatorPrefixHostText(next))\n return next.getTextContent().startsWith(NBSP) ? undefined : \"prefix\";\n // Element content (nested char span, note, milestone, verse), TextNode subclasses, and\n // attribute-run text: standalone NBSP spacer.\n return \"spacer\";\n}\n/**\n * The displayed bytes immediately after `char`'s first missing-separator site, or `undefined`\n * when every opening glyph's separator is present (or none is owed). This is the input the\n * tokenize-identity predicate (`separatorRemovalTokenizesIdentically`, the fragment tokenizer's\n * sibling) needs to decide whether the missing byte may be healed in place or the bytes now mean\n * something new and must re-tokenize. Read-only: call inside `editor.getEditorState().read(...)`\n * or an update.\n */\nexport function $openerSeparatorGapFollowingBytes(char) {\n if (!char.isAttached())\n return undefined;\n for (const child of char.getChildren()) {\n if (!$isMarkerNode(child))\n continue;\n if ($openerSeparatorGap(child, char) === undefined)\n continue;\n return child.getNextSibling()?.getTextContent() ?? \"\";\n }\n return undefined;\n}\n/**\n * Whether the collapsed caret sits at `opener`'s separator site — on the glyph itself, on the\n * span (an element point), or at the very start of the node after the glyph. This is where the\n * caret lands when the user deletes the separator, and deleting must always be allowed: while\n * the caret stays here the sync leaves the gap alone (mid-edit grace), and the marker-edit\n * engine settles the span back to canonical on caret departure (it pends spans reported by\n * {@link $hasCaretHeldSeparatorGap} and routes them to a Tier-2 rebuild, the same completion\n * path as a pending marker literal).\n */\nfunction $isCaretAtOpenerBoundary(opener, char) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n if (anchorNode.is(opener) || anchorNode.is(char))\n return true;\n const next = opener.getNextSibling();\n return next !== null && anchorNode.is(next) && selection.anchor.offset === 0;\n}\n/**\n * Ensure every opening char glyph among `char`'s direct children is followed by its display\n * separator — except one whose separator site holds the collapsed caret (see\n * {@link $isCaretAtOpenerBoundary}). Idempotent — a healed span passes untouched, so the\n * registering transform converges.\n *\n * @param char - The char span whose separators to sync. Must be called inside `editor.update()`.\n */\nexport function $syncOpenerSeparators(char) {\n // An earlier transform in the same pass may have merged/removed the span.\n if (!char.isAttached())\n return;\n char.getChildren().forEach((child) => {\n if (!$isMarkerNode(child))\n return;\n const gap = $openerSeparatorGap(child, char);\n if (gap === undefined)\n return;\n if ($isCaretAtOpenerBoundary(child, char))\n return;\n if (gap === \"prefix\") {\n const next = child.getNextSibling();\n if ($isTextNode(next))\n next.setTextContent(NBSP + next.getTextContent());\n }\n else {\n child.insertAfter($createTextNode(NBSP));\n }\n });\n}\n/**\n * True when `char` has a separator gap the sync is deliberately leaving alone because the caret\n * sits at it (a just-deleted separator). The marker-edit engine pends such spans so caret\n * departure settles them back to canonical via Tier-2.\n */\nexport function $hasCaretHeldSeparatorGap(char) {\n if (!char.isAttached())\n return false;\n return char\n .getChildren()\n .some((child) => $isMarkerNode(child) &&\n $openerSeparatorGap(child, char) !== undefined &&\n $isCaretAtOpenerBoundary(child, char));\n}\n","/**\n * Unknown-node USFM byte rendering: the single place that owns HOW an `UnknownNode`'s opening\n * marker, attribute, and closing marker bytes are computed for read-only display. Sibling of\n * attributeDisplay.utils.ts (char/verse attribute display runs), following the same owning-module\n * shape, but for a different tree: `UnknownNode`'s kinds (figure, table/table:row/table:cell,\n * sidebar, periph, ref, optbreak) carry no USFM byte representation anywhere in the tree —\n * markers and attributes are simply invisible today. The renderer that flanks a node's existing\n * content children with the bytes this module computes lives elsewhere; this module is the pure\n * byte-computation function it calls.\n *\n * ## Why these bytes carry no round-trip obligation\n *\n * Every kind here stays a Tier-2 sentinel — unlike char spans and verses (attributeDisplay.utils.ts),\n * which grew live display runs that re-tokenize on edit. These bytes only need to be CORRECT\n * USFM, not byte-identical to whatever produced the node; nothing here ever re-tokenizes back\n * into node state, so there is no cache to keep honest and no sync to register.\n *\n * ## Per-kind shapes\n *\n * All four parts are OPAQUE bytes for the consumer to concatenate around the node's content,\n * never to parse. The `attributes` part carries only bytes that belong BETWEEN the opening and\n * the content (sidebar's `\\cat …\\cat*` marker run, periph's marker-line pipe pairs; `\"\"` for\n * every other kind). USFM pipe attributes on span-shaped kinds (the generic default, figure)\n * come AFTER content, directly before the closer, so those kinds populate `closingAttributes`\n * instead — kept as its own part (rather than folded into `closing`) so the consumer can style\n * the `|…` run dimmer than the closer glyph, matching PT9's attribute-run display. Concatenating\n * `closingAttributes + closing` reproduces the exact same bytes a single `closing` string used\n * to carry.\n *\n * - **Generic default** (any kind without a special case below): `\\{marker} ` opening, a\n * `closingAttributes` of {@link canonicalAttributeText} named pairs — always explicit\n * `name=\"value\"`, never the default-attribute collapse, because unknown kinds carry no\n * StyleInfo default to collapse against — and a `closing` of `\\{marker}*` (the char-span shape:\n * `\\zzz content|foo=\"bar\"\\zzz*`).\n * - **optbreak** — PT9 renders `\\optbreak` as the literal token `//`, not a marker: the opening\n * IS the bare text `//`, with no attributes and no closing glyph.\n * - **figure** — USFM 3.0 puts the caption first (`\\fig caption|src=\"…\"\\fig*`), so the attribute\n * bytes go in `closingAttributes`, ahead of the `closing` glyph. USX/USJ's `file` attribute is\n * USFM's `src` (the tokenizer performs the same rename in the other direction;\n * usfmFragmentToUsj.ts); rendering reverses it back so the bytes match what a `\\fig …\\fig*`\n * span actually carries in the file.\n * - **table / table:row / table:cell** — tables have no USFM pipe-attribute syntax at all; a\n * cell's `align`/`colspan` are ENCODED in the marker name itself (`\\thc3-4`), never rendered\n * as attribute bytes. The container contributes no bytes of its own; a row opens with `\\tr `;\n * a cell opens with its own marker, re-encoding a `colspan` back into the span suffix the\n * tokenizer trimmed off (marker `thc3` + colspan `2` → `\\thc3-4 `; see\n * {@link tableCellMarkerWithSpan}); neither ever closes.\n * - **sidebar** — `\\esb`/`\\esbe` bracket the block. A `category` attribute is not a pipe\n * attribute at all but its own char-shaped marker directly after `\\esb`\n * (`\\esb \\cat Missions\\cat*`), mirroring how the tokenizer folds a `\\cat` onto a receptive\n * sidebar (usfmFragmentToUsj.ts).\n * - **periph** — `alt` is a text-content attribute: its value renders as literal marker content\n * right after `\\periph` (`\\periph Title`) rather than a pipe attribute; any remaining\n * attributes (e.g. `id`) are ordinary named pairs. `periph` is an open-ended division marker,\n * so it never closes.\n * - **ref** — a generated wrapper around cross-reference target text; USJ invented this\n * container, USFM never carried it, so it contributes no bytes at all — only its child text\n * renders, as-is.\n *\n * ## Unterminated constructs render no closer\n *\n * Cutting across every kind above: a construct the source never terminated (`closed=\"false\"`) has\n * no closing bytes in the file, so it displays none. Char spans already work this way — see\n * `$charClosingGlyph` (attributeDisplay.utils.ts) — and the rule is the same one for the same\n * reason: a closer shown for bytes the document does not contain is a byte the user can neither\n * edit away nor save.\n */\nimport { canonicalAttributeText } from \"../usj/attributeDisplay.utils.js\";\n/** The USJ attribute name a figure's file reference is stored under — renamed from USFM's `src`\n * by the tokenizer (usfmFragmentToUsj.ts) on the way in; rendering reverses it back. */\nconst FIGURE_FILE_ATTRIBUTE = \"file\";\nconst FIGURE_SRC_ATTRIBUTE = \"src\";\n/** The cell attribute holding the span COUNT the tokenizer derived from the marker's trimmed\n * span suffix; re-encoded back into the opening marker rather than rendered as a pipe pair. */\nconst TABLE_CELL_COLSPAN_ATTRIBUTE = \"colspan\";\n/** The sidebar attribute that renders as its own `\\cat …\\cat*` marker rather than a pipe pair. */\nconst SIDEBAR_CATEGORY_ATTRIBUTE = \"category\";\n/** The periph attribute that renders as literal marker content rather than a pipe pair. */\nconst PERIPH_TEXT_CONTENT_ATTRIBUTE = \"alt\";\n/** The USJ metadata flagging a construct the source never terminated. */\nconst IMPLICITLY_CLOSED_ATTRIBUTE = \"closed\";\nconst IMPLICITLY_CLOSED_VALUE = \"false\";\n/**\n * Whether the source terminated this construct with its own closing bytes. An implicitly closed\n * one (`closed=\"false\"` — the tokenizer's mark for a sidebar the fragment or chapter boundary\n * auto-closed, and USX's same flag read back) has no closing bytes in the file, so it must display\n * none: char spans already follow exactly this rule (`$charClosingGlyph`,\n * attributeDisplay.utils.ts — `createChar` builds no closing glyph for such a span and the display\n * sync must not fabricate one). A closer shown for bytes the document does not contain is one the\n * user can neither edit away nor save.\n */\nfunction isExplicitlyClosed(attributes) {\n return attributes[IMPLICITLY_CLOSED_ATTRIBUTE] !== IMPLICITLY_CLOSED_VALUE;\n}\n/** `attributes` with `file` renamed to `src` in place — the USX/USJ naming reversed back to the\n * byte name a figure's file attribute is actually written with in USFM. Key order is preserved\n * (a `Object.entries` map, not a delete-then-add) so a renamed `file` renders wherever it\n * originally sat among the figure's other attributes. */\nfunction renameFigureFileToSrc(attributes) {\n return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [\n name === FIGURE_FILE_ATTRIBUTE ? FIGURE_SRC_ATTRIBUTE : name,\n value,\n ]));\n}\n/**\n * The cell's opening marker with its span suffix re-encoded from `colspan`. Exported because a\n * cell's display bytes are built by `ImmutableTableCellNode`'s own adaptor path (the kind stopped\n * being an `UnknownNode`), and both builders must spell a spanning cell the same way. The tokenizer\n * (usfmFragmentToUsj.ts, table-cell assembly) splits a spanning cell marker apart on the way in —\n * `\\thc3-4` becomes marker `thc3` (span suffix trimmed off after the start column) plus colspan\n * `\"2\"` (the COUNT of columns spanned, end − start + 1) — so rendering the stored marker bare\n * would silently narrow the cell to a single column. Re-encoding reverses the split: end column =\n * start + count − 1. A `colspan` that is not a real span (absent, non-numeric, or below 2) or a\n * marker with no trailing start column to count from yields the bare marker rather than a garbage\n * suffix.\n */\nexport function tableCellMarkerWithSpan(marker, colspan) {\n if (marker === undefined)\n return undefined;\n const spanCount = Number(colspan);\n if (!Number.isInteger(spanCount) || spanCount < 2)\n return marker;\n // The trailing start column is found by a manual backward scan rather than `/(\\d+)$/`: an\n // end-anchored greedy digit run makes the regex engine retry the match from every position of a\n // long digit run (quadratic on adversarial markers like \"000…0X\"), while the scan is linear.\n // Byte-identical to the regex for every input — a marker that is ALL digits contributes the\n // whole string as its start column, exactly as `(\\d+)$` would match it.\n let digitsStart = marker.length;\n while (digitsStart > 0) {\n const charCode = marker.charCodeAt(digitsStart - 1);\n if (charCode < 0x30 || charCode > 0x39)\n break; // outside '0'..'9'\n digitsStart -= 1;\n }\n if (digitsStart === marker.length)\n return marker;\n return `${marker}-${Number(marker.slice(digitsStart)) + spanCount - 1}`;\n}\n/**\n * The USFM byte strings to render around an `UnknownNode`'s existing content children —\n * `opening` and `attributes` before the content, `closingAttributes` then `closing` after it (see\n * {@link UnknownDisplayParts} for what each part carries per kind) — computed purely from the\n * node's stored `tag` (USJ `type`), `marker`, and `unknownAttributes`. Pure function; read-only\n * display only (see module doc for why these bytes carry no round-trip obligation).\n *\n * @param tag - The `UnknownNode`'s USJ type (e.g. \"figure\", \"table:row\", \"optbreak\").\n * @param marker - The node's stored USFM marker, when the USJ shape carries one.\n * @param unknownAttributes - The node's stored attribute bag, when the USJ shape carries one.\n */\nexport function unknownDisplayParts(tag, marker, unknownAttributes) {\n const attributes = unknownAttributes ?? {};\n const closed = isExplicitlyClosed(attributes);\n switch (tag) {\n case \"optbreak\":\n return { opening: \"//\", attributes: \"\", closingAttributes: \"\", closing: \"\" };\n case \"ref\":\n case \"table\":\n // Generated wrapper (ref) and bare container (table): neither carries USFM bytes of its\n // own — a table's bytes live entirely on its table:row/table:cell children.\n return { opening: \"\", attributes: \"\", closingAttributes: \"\", closing: \"\" };\n case \"table:row\":\n return { opening: `\\\\${marker} `, attributes: \"\", closingAttributes: \"\", closing: \"\" };\n case \"table:cell\":\n // The marker name itself IS the cell's shape (`\\tc1`, `\\thc3-4`, …): `align` is already\n // encoded in it, and a `colspan` re-encodes into its span suffix (see\n // tableCellMarkerWithSpan) — neither ever renders as pipe-attribute bytes, which USFM\n // tables do not have.\n return {\n opening: `\\\\${tableCellMarkerWithSpan(marker, attributes[TABLE_CELL_COLSPAN_ATTRIBUTE])} `,\n attributes: \"\",\n closingAttributes: \"\",\n closing: \"\",\n };\n case \"figure\":\n // USFM 3.0 figures put the caption FIRST (`\\fig caption|src=\"…\"\\fig*`), so the attribute\n // bytes go in closingAttributes, ahead of the closing glyph — rendering them between the\n // opening and the caption would strand the caption after the attribute list, which is\n // invalid USFM. Kept as its own part (rather than folded into `closing`) so the caller can\n // style the `|…` run dimmer than the `\\fig*` glyph, matching PT9's attribute-run display.\n return {\n opening: `\\\\${marker} `,\n attributes: \"\",\n closingAttributes: canonicalAttributeText(renameFigureFileToSrc(attributes), undefined),\n closing: closed ? `\\\\${marker}*` : \"\",\n };\n case \"sidebar\": {\n const { [SIDEBAR_CATEGORY_ATTRIBUTE]: category, ...rest } = attributes;\n const categoryBytes = category === undefined ? \"\" : ` \\\\cat ${category}\\\\cat*`;\n return {\n opening: \"\\\\esb\",\n attributes: categoryBytes + canonicalAttributeText(rest, undefined),\n closingAttributes: \"\",\n closing: closed ? \"\\\\esbe\" : \"\",\n };\n }\n case \"periph\": {\n const { [PERIPH_TEXT_CONTENT_ATTRIBUTE]: alt, ...rest } = attributes;\n return {\n opening: `\\\\periph ${alt ?? \"\"}`,\n attributes: canonicalAttributeText(rest, undefined),\n closingAttributes: \"\",\n closing: \"\",\n };\n }\n default:\n // Char-span shape is the natural default for an attributed unknown span: content first,\n // pipe attributes directly before the closer (`\\zzz content|foo=\"bar\"\\zzz*`) — kept as its\n // own closingAttributes part (rather than folded into `closing`) so the caller can style\n // the `|…` run dimmer than the `\\zzz*` glyph, matching PT9's attribute-run display.\n return {\n opening: `\\\\${marker} `,\n attributes: \"\",\n closingAttributes: canonicalAttributeText(attributes, undefined),\n closing: closed ? `\\\\${marker}*` : \"\",\n };\n }\n}\n","/**\n * The display-run registry: one {@link DisplayRunDescriptor} per engine-owned display kind.\n *\n * Assembled HERE rather than in `nodes/usj` because a descriptor's byte derivation needs the\n * converters (`defaultMarkerAttribute`, `milestoneDefaultAttribute`) and `nodes/usj` must not\n * import from `converters/usfm`, which already imports FROM `nodes/usj`. This module sits above\n * both, so it can hold the assembly without a cycle — the same layering `plugins/PerfOperations`\n * uses. The drivers that CONSUME descriptors take one as a parameter and stay in `nodes/usj`.\n *\n * Each descriptor's `ownerOf` implements the ONE owner walk for its kind, keyed on marker\n * identity: only pieces of that same kind's run may sit between a candidate piece and its owner,\n * so a foreign glyph or unrelated content ends the walk with no owner. `$ownerOfRunPiece`\n * (displayRunOwner.utils.ts) is the single classifier that consults every descriptor in order.\n */\nimport { $chapterAltnumberRunPieces, $chapterCpAnchor, $chapterGlyphTextNode, $chapterPubnumberRunPieces, $charAttributeDisplayNode, $charClosingGlyph, $milestoneAttributeRunPieces, $noteCategoryRunPieces, $noteEditableCallerNode, $verseAttributeRunPieces, canonicalAttributeText, milestoneAttributes, } from \"../nodes/usj/attributeDisplay.utils.js\";\nimport { $isAttributeRunNode } from \"../nodes/usj/AttributeRunNode.js\";\nimport { $isChapterNode } from \"../nodes/usj/ChapterNode.js\";\nimport { $isCharNode } from \"../nodes/usj/CharNode.js\";\nimport { $isNoteNode } from \"../nodes/usj/NoteNode.js\";\nimport { $hasCaretHeldSeparatorGap } from \"../nodes/usj/markerSeparators.utils.js\";\nimport { $isMilestoneNode } from \"../nodes/usj/MilestoneNode.js\";\nimport { NBSP } from \"../nodes/usj/node-constants.js\";\nimport { $isVerseNode } from \"../nodes/usj/VerseNode.js\";\nimport { textTypeState } from \"../nodes/collab/delta.state.js\";\nimport { $isImmutableTypedTextNode } from \"../nodes/features/ImmutableTypedTextNode.js\";\nimport { $isMarkerNode } from \"../nodes/features/MarkerNode.js\";\nimport { $isUnknownNode } from \"../nodes/features/UnknownNode.js\";\nimport { unknownDisplayParts } from \"../nodes/features/unknownUsfm.utils.js\";\nimport { defaultMarkerAttribute, milestoneDefaultAttribute, } from \"../converters/usfm/usfmFragmentToUsj.js\";\nimport { $getSelection, $getState, $isElementNode, $isRangeSelection, $isTextNode, } from \"lexical\";\n/** No run wanted and no value — the answer for an owner whose state carries nothing to display,\n * and the safe answer when a descriptor is handed a node of the wrong type. */\nconst NO_RUN = { wantsRun: false, valueText: undefined };\n/** No pieces found — the answer when a descriptor is handed a node of the wrong type. */\nconst NO_PIECES = {};\n/** The sibling a verse's run for `marker` is anchored after: the verse itself for `\\va`, and\n * `\\va`'s wrapper (or, while caret-grace defers the wrap, its loose closer) for `\\vp`. Shared by\n * the scanner and the writer so the two can never disagree about where a run belongs. */\nfunction $verseRunAnchor(verse, marker) {\n if (marker === \"va\")\n return verse;\n const va = $verseAttributeRunPieces(verse, \"va\");\n return va.wrapper ?? va.closer ?? verse;\n}\n/** The caret arm a run graces when NO piece survives: the run's insertion point is the end of\n * its anchor or the very start of the anchor's next sibling, where a range deletion collapses\n * the caret. An ELEMENT anchor (a chapter `\\cp` run's anchor is the `\\ca` run's WRAPPER) has no\n * text end of its own — the collapse point is the end of its last descendant text piece (the\n * `\\ca` closer glyph), so that arm is checked too. The shared reporter already graces the\n * wrapper's subtree and the live value node. */\nfunction $verseFlankGrace(anchor) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n if (anchorNode.is(anchor) && selection.anchor.offset === anchor.getTextContentSize())\n return true;\n if ($isElementNode(anchor)) {\n const last = anchor.getLastDescendant();\n if (last !== null &&\n anchorNode.is(last) &&\n selection.anchor.offset === last.getTextContentSize())\n return true;\n }\n const next = anchor.getNextSibling();\n return next !== null && anchorNode.is(next) && selection.anchor.offset === 0;\n}\n/** The caret arm shared by verse and milestone runs when only the VALUE was deleted beside a\n * surviving opening glyph: the end of the opening glyph's own text, or the trailing glyph. */\nfunction $glyphDebrisGrace(pieces) {\n const { opener, closer } = pieces;\n if (!opener)\n return false;\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n const atOpenerEnd = anchorNode.is(opener) && selection.anchor.offset === opener.getTextContentSize();\n if (closer)\n return atOpenerEnd || anchorNode.is(closer);\n return atOpenerEnd;\n}\n/** Whether `node` is a piece of a verse's `\\va`/`\\vp` run — a whole wrapper (crossed in one step,\n * so a `\\vp` piece's walk passes its own `\\va` wrapper), a `va`/`vp` glyph riding loose, or a\n * loose attribute-tagged value. Loose shapes are transient (an undo stack, a collab-materialized\n * bare verse, a mid-edit tree with one marker wrapped and the other not) but real for a commit. */\nfunction $isVerseRunPiece(node) {\n if ($isAttributeRunNode(node))\n return node.getRunKind() === \"va\" || node.getRunKind() === \"vp\";\n if ($isMarkerNode(node))\n return node.getMarker() === \"va\" || node.getMarker() === \"vp\";\n return $isTextNode(node) && $getState(node, textTypeState) === \"attribute\";\n}\n/** The `va`/`vp` marker a loose piece belongs to: its own marker for a glyph, and for a value the\n * marker of the glyph immediately before it — the run pieces' fixed order puts a value's own opener\n * exactly one step back, even in the previous state where that opener is also being destroyed.\n *\n * The value arm requires the candidate to BE a run piece (an attribute-tagged TextNode), not merely\n * to sit behind one, mirroring the milestone side's `$isMilestoneRunPiece` rule. Position alone is\n * not enough: the ordinary verse text that follows a settled `\\va …\\va*` run also has that run's\n * closing glyph as its previous sibling, and claiming it would pend the verse — and run a settle\n * plus a whole-paragraph rebuild — for a deletion in unrelated content. */\nfunction $loosePieceMarker(node) {\n if ($isMarkerNode(node)) {\n const marker = node.getMarker();\n return marker === \"va\" || marker === \"vp\" ? marker : undefined;\n }\n if (!$isTextNode(node) || $getState(node, textTypeState) !== \"attribute\")\n return undefined;\n const previous = node.getPreviousSibling();\n if (!$isMarkerNode(previous))\n return undefined;\n const marker = previous.getMarker();\n return marker === \"va\" || marker === \"vp\" ? marker : undefined;\n}\n/** Walk back from `start` over `marker`'s own run pieces to the VerseNode the run rides on. */\nfunction $verseOfRunChain(start) {\n for (let previous = start.getPreviousSibling(); previous; previous = previous.getPreviousSibling()) {\n if ($isVerseNode(previous))\n return previous;\n if (!$isVerseRunPiece(previous))\n return undefined;\n }\n return undefined;\n}\nfunction verseDescriptor(marker) {\n return {\n kind: marker,\n ownerPredicate: (node) => $isVerseNode(node),\n ownerOf: (node) => {\n // A wrapper of this marker is its own walk start; a piece INSIDE one is only positioned\n // relative to its siblings within the wrapper, so the walk starts from the wrapper instead.\n if ($isAttributeRunNode(node))\n return node.getRunKind() === marker ? $verseOfRunChain(node) : undefined;\n const parent = node.getParent();\n if ($isAttributeRunNode(parent))\n return parent.getRunKind() === marker ? $verseOfRunChain(parent) : undefined;\n return $loosePieceMarker(node) === marker ? $verseOfRunChain(node) : undefined;\n },\n expectedPieces: (owner) => {\n if (!$isVerseNode(owner))\n return NO_RUN;\n const value = marker === \"va\" ? owner.getAltnumber() : owner.getPubnumber();\n if (value === undefined)\n return NO_RUN;\n return { wantsRun: true, valueText: NBSP + value };\n },\n scanPieces: (owner) => $isVerseNode(owner)\n ? $verseAttributeRunPieces($verseRunAnchor(owner, marker), marker)\n : NO_PIECES,\n graceSite: (owner, pieces) => {\n if (!$isVerseNode(owner))\n return false;\n if (!pieces.opener && !pieces.closer)\n return $verseFlankGrace($verseRunAnchor(owner, marker));\n return $glyphDebrisGrace(pieces);\n },\n settleScope: \"owner\",\n deletionPolicy: \"retokenize\",\n byteFormat: {\n writer: \"wrapper\",\n runKind: marker,\n glyphs: \"with-value\",\n glyphMarker: () => marker,\n closerSyntax: \"closing\",\n insertRunAfter: (owner) => ($isVerseNode(owner) ? $verseRunAnchor(owner, marker) : undefined),\n },\n };\n}\nconst separatorDescriptor = {\n kind: \"separator\",\n // The NBSP a char span shows after its opening glyph. Its \"deletion\" is a TEXT mutation (an NBSP\n // prefix edit), not node destruction, so it has no owner walk and no destruction pend — its\n // caret-grace path is what settles it, exactly as before joining the registry.\n ownerPredicate: (node) => $isCharNode(node),\n ownerOf: () => undefined,\n expectedPieces: () => NO_RUN,\n scanPieces: () => NO_PIECES,\n graceSite: (owner) => $isCharNode(owner) && $hasCaretHeldSeparatorGap(owner),\n settleScope: \"owner\",\n deletionPolicy: \"retokenize\",\n byteFormat: { writer: \"kind-owned\", glyphs: \"none\" },\n};\nconst charDescriptor = {\n kind: \"char\",\n ownerPredicate: (node) => $isCharNode(node),\n ownerOf: (node) => {\n // A char span's run is a direct TextNode child, never wrapped and never a glyph.\n if (!$isTextNode(node) || $getState(node, textTypeState) !== \"attribute\")\n return undefined;\n const parent = node.getParent();\n return $isCharNode(parent) ? parent : undefined;\n },\n expectedPieces: (owner) => {\n if (!$isCharNode(owner))\n return NO_RUN;\n // A span with no closing glyph never carries a run regardless of its attributes: the adaptor\n // never builds one there, so the sync must not fabricate one either.\n if ($charClosingGlyph(owner) === undefined)\n return NO_RUN;\n const text = canonicalAttributeText(owner.getUnknownAttributes() ?? {}, defaultMarkerAttribute(owner.getMarker()));\n return text === \"\" ? NO_RUN : { wantsRun: true, valueText: text };\n },\n scanPieces: (owner) => $isCharNode(owner) ? { value: $charAttributeDisplayNode(owner) } : NO_PIECES,\n graceSite: (owner, pieces) => {\n // The insertion-point arms for a run that is missing: the closing glyph the run would be\n // inserted before, or the text-end of the content immediately preceding that glyph.\n if (!$isCharNode(owner) || pieces.value)\n return false;\n const closingGlyph = $charClosingGlyph(owner);\n if (!closingGlyph)\n return false;\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n if (anchorNode.is(closingGlyph))\n return true;\n const lastContent = closingGlyph.getPreviousSibling();\n return (lastContent !== null &&\n anchorNode.is(lastContent) &&\n selection.anchor.offset === lastContent.getTextContentSize());\n },\n settleScope: \"owner\",\n deletionPolicy: \"retokenize\",\n byteFormat: {\n writer: \"owner-children\",\n glyphs: \"none\",\n insertRunBefore: (owner) => ($isCharNode(owner) ? $charClosingGlyph(owner) : undefined),\n },\n};\n/** Whether `node` is a loose piece of a note's `\\cat` run — a `cat` glyph, or an attribute-tagged\n * value whose own opener (one step back, the run pieces' fixed order) is a `cat` glyph. The value\n * arm requires the candidate to BE a run piece, mirroring `$loosePieceMarker`'s rule for verses:\n * position alone must not claim ordinary note content that happens to follow a settled run. */\nfunction $isLooseCatPiece(node) {\n if ($isMarkerNode(node))\n return node.getMarker() === \"cat\";\n if (!$isTextNode(node) || $getState(node, textTypeState) !== \"attribute\")\n return false;\n const previous = node.getPreviousSibling();\n return $isMarkerNode(previous) && previous.getMarker() === \"cat\";\n}\n/** Walk back from `start` over the cat run's own pieces to the editable caller anchor, and from\n * there to the NoteNode the run rides in. A piece's parent must BE the note (loose pieces are the\n * note's direct children); crossing anything that is not a cat piece ends the walk with no owner. */\nfunction $noteOfCatChain(start) {\n const parent = start.getParent();\n if (!$isNoteNode(parent))\n return undefined;\n const anchor = $noteEditableCallerNode(parent);\n if (!anchor)\n return undefined;\n for (let previous = start.getPreviousSibling(); previous; previous = previous.getPreviousSibling()) {\n if (previous.is(anchor))\n return parent;\n if (!$isLooseCatPiece(previous))\n return undefined;\n }\n return undefined;\n}\nconst catDescriptor = {\n kind: \"cat\",\n ownerPredicate: (node) => $isNoteNode(node),\n ownerOf: (node) => {\n // A note's run rides as its CHILDREN (a NoteNode is an ElementNode), so the wrapper's parent\n // IS the owner — no sibling chain to walk for the wrapped shape. Loose pieces walk back to\n // the caller anchor exactly like a verse's loose pieces walk back to their verse.\n if ($isAttributeRunNode(node))\n return node.getRunKind() === \"cat\" && $isNoteNode(node.getParent())\n ? (node.getParent() ?? undefined)\n : undefined;\n const parent = node.getParent();\n if ($isAttributeRunNode(parent))\n return parent.getRunKind() === \"cat\" && $isNoteNode(parent.getParent())\n ? (parent.getParent() ?? undefined)\n : undefined;\n return $isLooseCatPiece(node) ? $noteOfCatChain(node) : undefined;\n },\n expectedPieces: (owner) => {\n if (!$isNoteNode(owner))\n return NO_RUN;\n // Collapsed notes deliberately do not display the category (their content is not inline\n // display text at all), and only the expanded editable shape carries the caller anchor the\n // run is positioned by.\n if (owner.getIsCollapsed() !== false)\n return NO_RUN;\n const category = owner.getCategory();\n if (category === undefined)\n return NO_RUN;\n return { wantsRun: true, valueText: NBSP + category };\n },\n scanPieces: (owner) => ($isNoteNode(owner) ? $noteCategoryRunPieces(owner) : NO_PIECES),\n graceSite: (owner, pieces) => {\n if (!$isNoteNode(owner))\n return false;\n if (!pieces.opener && !pieces.closer) {\n const anchor = $noteEditableCallerNode(owner);\n return anchor !== undefined && $verseFlankGrace(anchor);\n }\n return $glyphDebrisGrace(pieces);\n },\n settleScope: \"owner\",\n deletionPolicy: \"retokenize\",\n byteFormat: {\n writer: \"wrapper\",\n runKind: \"cat\",\n glyphs: \"with-value\",\n glyphMarker: () => \"cat\",\n closerSyntax: \"closing\",\n insertRunAfter: (owner) => ($isNoteNode(owner) ? $noteEditableCallerNode(owner) : undefined),\n },\n};\n/** Whether `node` is a piece of a chapter's `\\ca`/`\\cp` runs — a whole wrapper of either kind\n * (crossed in one step, so a loose `\\cp` piece's walk passes the `\\ca` wrapper), a `ca`/`cp`\n * glyph riding loose, or a loose attribute-tagged value — the chapter twin of\n * `$isVerseRunPiece`. */\nfunction $isChapterRunPiece(node) {\n if ($isAttributeRunNode(node))\n return node.getRunKind() === \"ca\" || node.getRunKind() === \"cp\";\n if ($isMarkerNode(node))\n return node.getMarker() === \"ca\" || node.getMarker() === \"cp\";\n return $isTextNode(node) && $getState(node, textTypeState) === \"attribute\";\n}\n/** The `ca`/`cp` marker a loose chapter piece belongs to — the chapter twin of\n * `$loosePieceMarker`, with the same value-arm rule (the candidate must BE a run piece whose own\n * opener sits one step back; position alone must not claim unrelated content). */\nfunction $chapterLoosePieceMarker(node) {\n if ($isMarkerNode(node)) {\n const marker = node.getMarker();\n return marker === \"ca\" || marker === \"cp\" ? marker : undefined;\n }\n if (!$isTextNode(node) || $getState(node, textTypeState) !== \"attribute\")\n return undefined;\n const previous = node.getPreviousSibling();\n if (!$isMarkerNode(previous))\n return undefined;\n const marker = previous.getMarker();\n return marker === \"ca\" || marker === \"cp\" ? marker : undefined;\n}\n/** Walk back from `start` over the chapter runs' own pieces (of EITHER kind — a `\\cp` piece's\n * walk crosses the whole `\\ca` wrapper in one step) to the chapter's `\\c N` glyph anchor, and\n * from there to the ChapterNode the runs ride in — the chapter twin of `$verseOfRunChain`. */\nfunction $chapterOfRunChain(start) {\n const parent = start.getParent();\n if (!$isChapterNode(parent))\n return undefined;\n const anchor = $chapterGlyphTextNode(parent);\n if (!anchor)\n return undefined;\n for (let previous = start.getPreviousSibling(); previous; previous = previous.getPreviousSibling()) {\n if (previous.is(anchor))\n return parent;\n if (!$isChapterRunPiece(previous))\n return undefined;\n }\n return undefined;\n}\n/** One chapter attribute-marker descriptor — `\"ca\"` (altnumber; opener + value + closer) or\n * `\"cp\"` (pubnumber; opener + value, NO closer: the span closes implicitly at the next block\n * boundary in the file, so its wrapper alone bounds the value). The runs ride as the editable\n * chapter's CHILDREN in document order — glyph text, then `\\ca`'s wrapper, then `\\cp`'s — the\n * same-line byte position the chapter fragment re-tokenizes and ParatextData's own chapter-path\n * folding accepts. */\nfunction chapterDescriptor(marker) {\n const $anchor = (chapter) => !$isChapterNode(chapter)\n ? undefined\n : marker === \"ca\"\n ? $chapterGlyphTextNode(chapter)\n : $chapterCpAnchor(chapter);\n return {\n kind: marker,\n ownerPredicate: (node) => $isChapterNode(node),\n ownerOf: (node) => {\n if ($isAttributeRunNode(node))\n return node.getRunKind() === marker && $isChapterNode(node.getParent())\n ? (node.getParent() ?? undefined)\n : undefined;\n const parent = node.getParent();\n if ($isAttributeRunNode(parent))\n return parent.getRunKind() === marker && $isChapterNode(parent.getParent())\n ? (parent.getParent() ?? undefined)\n : undefined;\n return $chapterLoosePieceMarker(node) === marker ? $chapterOfRunChain(node) : undefined;\n },\n expectedPieces: (owner) => {\n if (!$isChapterNode(owner))\n return NO_RUN;\n const value = marker === \"ca\" ? owner.getAltnumber() : owner.getPubnumber();\n if (value === undefined)\n return NO_RUN;\n return { wantsRun: true, valueText: NBSP + value };\n },\n scanPieces: (owner) => !$isChapterNode(owner)\n ? NO_PIECES\n : marker === \"ca\"\n ? $chapterAltnumberRunPieces(owner)\n : $chapterPubnumberRunPieces(owner),\n graceSite: (owner, pieces) => {\n if (!$isChapterNode(owner))\n return false;\n if (!pieces.opener && !pieces.closer) {\n const anchor = $anchor(owner);\n return anchor !== undefined && $verseFlankGrace(anchor);\n }\n return $glyphDebrisGrace(pieces);\n },\n settleScope: \"owner\",\n deletionPolicy: \"retokenize\",\n byteFormat: {\n writer: \"wrapper\",\n runKind: marker,\n glyphs: \"with-value\",\n glyphMarker: () => marker,\n closerSyntax: marker === \"ca\" ? \"closing\" : \"none\",\n insertRunAfter: $anchor,\n },\n };\n}\n/** Whether `node` is a loose piece of a milestone's run — an opening glyph, a self-closing glyph,\n * or an attribute-tagged value. A milestone's opening glyph carries the milestone's OWN marker,\n * which the chain walk re-checks against the candidate owner. */\nfunction $isMilestoneRunPiece(node) {\n if ($isMarkerNode(node)) {\n const syntax = node.getMarkerSyntax();\n return syntax === \"selfClosing\" || syntax === \"opening\";\n }\n return $isTextNode(node) && $getState(node, textTypeState) === \"attribute\";\n}\n/** Walk back from a LOOSE milestone run piece over the run's other loose pieces to the milestone,\n * requiring a matching marker on any opening glyph crossed. */\nfunction $milestoneOfLooseChain(start) {\n for (let previous = start.getPreviousSibling(); previous; previous = previous.getPreviousSibling()) {\n if ($isMilestoneNode(previous)) {\n const opening = $isMarkerNode(start) && start.getMarkerSyntax() === \"opening\" ? start : undefined;\n return !opening || opening.getMarker() === previous.getMarker() ? previous : undefined;\n }\n if (!$isMilestoneRunPiece(previous))\n return undefined;\n }\n return undefined;\n}\nconst milestoneDescriptor = {\n kind: \"milestone\",\n ownerPredicate: (node) => $isMilestoneNode(node),\n ownerOf: (node) => {\n const start = $isAttributeRunNode(node)\n ? node.getRunKind() === \"milestone\"\n ? node\n : undefined\n : $isAttributeRunNode(node.getParent())\n ? node.getParent()\n : $isMilestoneRunPiece(node)\n ? node\n : undefined;\n if (!start)\n return undefined;\n if ($isAttributeRunNode(start) && start.getRunKind() !== \"milestone\")\n return undefined;\n const previous = start.getPreviousSibling();\n // A milestone's run is a SINGLE wrapper (or one contiguous loose group) directly following its\n // milestone — there is no second marker to cross, unlike a verse's `\\va`/`\\vp` pair. A WRAPPER\n // requires direct adjacency to the milestone (the builder always creates/heals it immediately\n // after — never behind intervening debris), so it gets no chain walk of its own; a LOOSE piece\n // delegates entirely to $milestoneOfLooseChain, which both walks the chain AND re-checks marker\n // identity against any opening glyph it crosses — the check the retired sibling walk lacked, so\n // a foreign opening glyph (any marker) adjacent to a milestone must not classify it as owner.\n if ($isAttributeRunNode(start))\n return $isMilestoneNode(previous) ? previous : undefined;\n return $milestoneOfLooseChain(start);\n },\n expectedPieces: (owner) => {\n if (!$isMilestoneNode(owner))\n return NO_RUN;\n const attributes = milestoneAttributes(owner.getSid(), owner.getEid(), owner.getUnknownAttributes(), owner.getAttributeOrder());\n const text = canonicalAttributeText(attributes, milestoneDefaultAttribute(owner.getMarker()));\n // The glyph pair is unconditional: a milestone always displays `\\qt-s …\\*`, so the run is\n // wanted even when no attribute text rides between the glyphs.\n return { wantsRun: true, valueText: text === \"\" ? undefined : NBSP + text };\n },\n scanPieces: (owner) => {\n if (!$isMilestoneNode(owner))\n return NO_PIECES;\n // $milestoneAttributeRunPieces names its fields opening/attribute/closing (its own long-lived\n // vocabulary, shared with the Tier-2 rebuild); ScannedRun's fields are opener/value/closer —\n // the descriptor registry's cross-kind vocabulary. Translate rather than rename either side.\n //\n // This translation is NOT enforced by the compiler: MilestoneRunPieces's fields\n // (opening/attribute/closing/wrapper) and ScannedRun's fields (opener/value/closer/wrapper)\n // are ALL optional on both sides, so returning $milestoneAttributeRunPieces(owner) directly —\n // the untranslated shape — type-checks with zero errors (verified directly: `tsc --build`\n // reports nothing). Excess-property checking only fires on fresh object literals, not on a\n // value flowing through a function call, and an all-optional target type has no required\n // field whose absence would fail assignability either. The wrong shape compiles clean and\n // then reads as permanently empty at runtime (opener/value/closer come back undefined\n // forever) — displayRunRegistry.test.ts's scanPieces suite is what actually catches this.\n const { opening, attribute, closing, wrapper } = $milestoneAttributeRunPieces(owner);\n return { opener: opening, value: attribute, closer: closing, wrapper };\n },\n graceSite: (owner, pieces) => {\n if (!$isMilestoneNode(owner))\n return false;\n if (!pieces.opener && !pieces.closer) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n const previous = owner.getPreviousSibling();\n if (previous !== null &&\n anchorNode.is(previous) &&\n selection.anchor.offset === previous.getTextContentSize())\n return true;\n const next = owner.getNextSibling();\n return next !== null && anchorNode.is(next) && selection.anchor.offset === 0;\n }\n return $glyphDebrisGrace(pieces);\n },\n settleScope: \"owner\",\n deletionPolicy: \"remove-owner\",\n byteFormat: {\n writer: \"wrapper\",\n runKind: \"milestone\",\n glyphs: \"unconditional\",\n glyphMarker: (owner) => ($isMilestoneNode(owner) ? owner.getMarker() : \"\"),\n closerSyntax: \"selfClosing\",\n insertRunAfter: (owner) => owner,\n },\n};\n/** The optbreak's entire byte representation — the literal `//` token — derived from the ONE\n * renderer of unknown-kind bytes so the registry can never drift from what is actually drawn. */\nconst optbreakDisplayText = unknownDisplayParts(\"optbreak\", undefined, undefined).opening;\nconst optbreakDescriptor = {\n kind: \"optbreak\",\n ownerPredicate: (node) => $isUnknownNode(node) && node.getTag() === \"optbreak\",\n ownerOf: (node) => {\n // The adaptor renders the `//` token as an ImmutableTypedTextNode (a read-only DecoratorNode),\n // but an edited optbreak can hold a plain TextNode instead, so both are recognized.\n const parent = node.getParent();\n if (!$isUnknownNode(parent) || parent.getTag() !== \"optbreak\")\n return undefined;\n return $isTextNode(node) || $isImmutableTypedTextNode(node) ? parent : undefined;\n },\n // `valueText` is the RENDERED BYTES the kind owes — so `$runDiverges`'s value-byte comparison\n // classifies the scanned token by what it actually spells: a canonical `//` is at rest, a\n // byte-damaged or deleted token diverges. With `valueText: undefined` (the pre-audit shape)\n // both answers were backwards: a canonical token's text never equalled `undefined`, so a\n // CANONICAL optbreak read as diverged while a GUTTED one read as at rest. Nothing ever WRITES\n // from this (the `\"read-only\"` writer returns before any sync write), so the value is purely\n // classificatory.\n expectedPieces: () => ({ wantsRun: true, valueText: optbreakDisplayText }),\n scanPieces: (owner) => $isUnknownNode(owner) ? { value: owner.getFirstChild() ?? undefined } : NO_PIECES,\n graceSite: () => false,\n settleScope: \"owner\",\n deletionPolicy: \"remove-owner\",\n byteFormat: { writer: \"read-only\", glyphs: \"none\" },\n};\nconst opaqueUnknownDescriptor = {\n kind: \"opaqueUnknown\",\n // Scope is every UnknownNode kind EXCEPT optbreak — `ownerPredicate` excludes it explicitly, so\n // `optbreakDescriptor` above is the sole owner of that kind. A non-optbreak UnknownNode is a\n // permanent Tier-2 sentinel whose bytes are read-only rendering, never re-tokenized: it owns no\n // display run, but is recognized so the settle reports it handled and the caller never routes one\n // through a rebuild that would bail. (A pended optbreak that does NOT match `optbreakDescriptor`'s\n // `remove-owner` shape — i.e. isn't entirely absent — falls through unhandled by either\n // descriptor instead; harmlessly inert, since `$settleScopeForNode` refuses every `UnknownNode`\n // outright, so the caller's `$requestTier2ForNode` fallback always bails on it too.)\n ownerPredicate: (node) => $isUnknownNode(node) && node.getTag() !== \"optbreak\",\n ownerOf: () => undefined,\n expectedPieces: () => NO_RUN,\n scanPieces: () => NO_PIECES,\n graceSite: () => false,\n settleScope: \"owner\",\n deletionPolicy: \"none\",\n byteFormat: { writer: \"read-only\", glyphs: \"none\" },\n};\nconst nestedGlyphDescriptor = {\n kind: \"nestedGlyph\",\n // The `+` on a nested span's glyphs. Purely tree-derived and rewritten in place by its own sync;\n // there is no state a user edit can leave half-finished, so it owes no pend or deletion duty.\n ownerPredicate: (node) => $isCharNode(node),\n ownerOf: () => undefined,\n expectedPieces: () => NO_RUN,\n scanPieces: () => NO_PIECES,\n graceSite: () => false,\n settleScope: \"none\",\n deletionPolicy: \"none\",\n byteFormat: { writer: \"kind-owned\", glyphs: \"none\" },\n};\n/** Every registered kind, in the order the pend/settle driver consults them. THREE descriptors\n * declare `ownerPredicate: $isCharNode` — `separator` (the NBSP gap after an opening glyph), `char`\n * (the span's own `|…` attribute run), and `nestedGlyph` (the `+` on a nested span's glyphs) — so a\n * `CharNode` matches all three. `separator` is listed before `char`, so its grace is checked first,\n * preserving the order the per-kind arms ran in. `cat`, `ca`, and `cp` are listed before `milestone` so their\n * loose glyphs are claimed by their own descriptors first — the milestone loose-piece test\n * accepts ANY opening glyph and only rejects it deeper in its chain walk. `nestedGlyph` never acts in the\n * settle loops at all: its `settleScope` is `\"none\"`, so those loops skip it outright — its `+` is\n * purely tree-derived and rewritten in place by its own sync, with no state a user edit can leave\n * half-finished. */\nexport const displayRunDescriptors = [\n separatorDescriptor,\n charDescriptor,\n verseDescriptor(\"va\"),\n verseDescriptor(\"vp\"),\n catDescriptor,\n chapterDescriptor(\"ca\"),\n chapterDescriptor(\"cp\"),\n milestoneDescriptor,\n optbreakDescriptor,\n opaqueUnknownDescriptor,\n nestedGlyphDescriptor,\n];\nconst byKind = new Map(displayRunDescriptors.map((descriptor) => [descriptor.kind, descriptor]));\n/** The descriptor for `kind`. Throws for an unregistered kind — a kind is only nameable once its\n * descriptor exists, so a miss is a wiring bug, never a runtime condition to handle. */\nexport function displayRunDescriptor(kind) {\n const descriptor = byKind.get(kind);\n if (!descriptor)\n throw new Error(`No display-run descriptor registered for kind \"${kind}\"`);\n return descriptor;\n}\n","/**\n * The ONE walk from a display-run piece back to its owner, for every kind.\n *\n * A piece can be a live node (an edit inside a run dirties the piece or its wrapper, never the\n * leaf owner, whose own transform would then not fire) or a node read from the PREVIOUS editor\n * state (a destroyed piece, which still has its tree position there). The walk is identical in\n * both cases — it reads only tree position — so one function serves the live re-sync path and the\n * destruction-pend path alike.\n *\n * The chain classification is keyed on MARKER IDENTITY, not on \"is this a glyph\": only pieces of\n * the same kind's run may sit between a piece and its owner. Anything else — a char span's own\n * opener riding beside a verse, a note glyph — ends the walk with no owner, so a deletion in\n * unrelated content can never pend a nearby verse or milestone.\n */\nimport { displayRunDescriptors } from \"./displayRunRegistry.js\";\n/**\n * The owner whose run `piece` belongs to, and that run's kind — or `undefined` when `piece` is not\n * part of any registered display run. Descriptors are consulted in registry order and the first\n * match wins; each kind's `ownerOf` recognizes only its own pieces, so at most one can match.\n */\nexport function $ownerOfRunPiece(piece) {\n for (const descriptor of displayRunDescriptors) {\n const owner = descriptor.ownerOf(piece);\n if (owner)\n return { owner, kind: descriptor.kind };\n }\n return undefined;\n}\n/**\n * True when `node` is a piece — a run glyph or an attribute value — of a display run whose kind\n * implements an owner walk (currently `va`/`vp`, `milestone`, `char`, and `optbreak`; see each\n * descriptor's `ownerOf` in displayRunRegistry.ts). A kind with no owner walk (`separator`,\n * `opaqueUnknown`, `nestedGlyph` — their `ownerOf` always returns `undefined`) never reports true\n * here, regardless of what `node` is. Engine-owned presentation, never content, for the kinds it\n * does cover: it must not enter OT content ops or the editor→USJ conversion.\n *\n * Keyed on the piece's KIND (via {@link $ownerOfRunPiece}) rather than on tree shape, so both the\n * wrapped shape the adaptor builds and the loose shape a mid-edit commit, an undo stack, or a\n * collab-materialized bare owner can leave behind are recognized by the same rule — WHEN the\n * piece is directly adjacent to (or one wrapper-hop from) its owner, which is what each `ownerOf`\n * walk requires. It is therefore NOT a full ancestor check: a wrapped piece separated from its\n * owner by an intervening node, or nested more than one level below its wrapper, reports false\n * here even though it is still presentation — callers that also need that shape covered pair this\n * with an ancestry check (see `editor-delta.adaptor.ts`'s glyph gate).\n */\nexport function $isDisplayRunPiece(node) {\n return $ownerOfRunPiece(node) !== undefined;\n}\n","import { INVALID_CLASS_NAME } from \"../usj/node-constants.js\";\nimport { $applyNodeReplacement, TextNode, } from \"lexical\";\nexport const UNMATCHED_TAG_NAME = \"unmatched\";\nexport const IMMUTABLE_UNMATCHED_VERSION = 2;\n/** The displayed bytes for an unmatched marker: `marker` keeps its own trailing `*` (`\"nd*\"`,\n * or `\"*\"` for a bare stray closer), so the glyph is just a backslash prefix away. */\nexport function unmatchedGlyphText(marker) {\n return `\\\\${marker}`;\n}\n/**\n * A marker with no counterpart to pair with — an unmatched closer (`\\nd*` with no open span, PT9\n * `sink.Unmatched`) or a stray `\\*`. Ordinary editable TEXT, not a decorator: under Invariant I\n * the flagged bytes are document bytes that happen to re-tokenize to nothing yet, so they must\n * remain caret-addressable and must flow through a Tier-2 rebuild as bytes — which is exactly how\n * an unmatched closer RE-matches when the document later supplies its opener (the tokenizer's own\n * frame matching consumes it). The `marker` field mirrors the node's rest-state bytes the same\n * way `MarkerNode`'s does: a user edit diverges the text from the state, the divergence pends,\n * and the settle re-tokenizes the displayed bytes.\n *\n * Despite the historical name, only the DEFAULT text mode is immutable-ish: \"token\", which makes\n * the node atomic (caret steps over it whole, deletion removes it whole, no in-place typing) —\n * the right behavior for view modes with no marker-edit engine to settle an in-place edit\n * (visible/hidden marker modes, and collab-materialized nodes). The editable-marker adaptor\n * serializes these nodes with mode \"normal\" so Standard view can edit the bytes in place.\n */\nexport class ImmutableUnmatchedNode extends TextNode {\n __marker;\n constructor(marker = \"\", key) {\n super(unmatchedGlyphText(marker), key);\n this.__marker = marker;\n // Direct assignment, not setMode: the constructor owns the instance, and the writable-node\n // machinery is not available until the node is attached to an update.\n this.__mode = 1; // \"token\"\n }\n static getType() {\n return \"unmatched\";\n }\n static clone(node) {\n const { __marker, __key } = node;\n // Text, format, style, mode, and detail are copied by afterCloneFrom (TextNode), so a\n // mid-edit divergence between the bytes and __marker survives cloning.\n return new ImmutableUnmatchedNode(__marker, __key);\n }\n static importDOM() {\n return {\n [UNMATCHED_TAG_NAME]: (node) => {\n if (!isUnmatchedElement(node))\n return null;\n return {\n conversion: $convertImmutableUnmatchedElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createImmutableUnmatchedNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n // Version-1 serializations (and the collab materializer's embed data) carry only `marker` —\n // none of the TextNode fields — so every text field defaults at runtime even though the\n // serialized type declares them required: bytes derive from the marker, and the mode\n // defaults to the constructor's atomic \"token\".\n const marker = serializedNode.marker ?? \"\";\n const self = super.updateFromJSON({\n ...serializedNode,\n detail: serializedNode.detail ?? 0,\n format: serializedNode.format ?? 0,\n mode: serializedNode.mode ?? \"token\",\n style: serializedNode.style ?? \"\",\n text: serializedNode.text ?? unmatchedGlyphText(marker),\n });\n // Assigned directly rather than via setMarker, which would rewrite the just-applied text to\n // canonical and lose a serialized mid-edit divergence.\n const writable = self.getWritable();\n writable.__marker = marker;\n return writable;\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n self.__text = unmatchedGlyphText(marker);\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n createDOM(config) {\n const dom = super.createDOM(config);\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(INVALID_CLASS_NAME);\n dom.title = unmatchedTitle(this.__marker);\n return dom;\n }\n updateDOM(prevNode, dom, config) {\n // TextNode reconciles the visible text; on element reuse createDOM does not run again, so the\n // marker-derived attribute and title are rewritten by hand — same gap MarkerNode.updateDOM\n // closes for glyphs.\n const isRecreated = super.updateDOM(prevNode, dom, config);\n if (prevNode.__marker !== this.__marker) {\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.title = unmatchedTitle(this.__marker);\n }\n return isRecreated;\n }\n exportDOM() {\n // The dedicated element round-trips through importDOM above, so an HTML copy of the flagged\n // bytes pastes back as the same construct.\n const element = document.createElement(UNMATCHED_TAG_NAME);\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(INVALID_CLASS_NAME);\n element.textContent = this.getTextContent();\n return { element };\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: this.getType(),\n marker: this.getMarker(),\n version: IMMUTABLE_UNMATCHED_VERSION,\n };\n }\n canInsertTextBefore() {\n // Typing at either edge belongs to the surrounding content, not to the flagged bytes — the\n // same reason a completed closer keeps appended typing outside the glyph.\n return false;\n }\n canInsertTextAfter() {\n return false;\n }\n}\n/** Whether the node's rendered bytes still spell its own state's glyph — at rest, as opposed to\n * mid-edit (pend-shaped). The unmatched counterpart of `$isCanonicalMarkerNode`.\n * Read-only: call inside `editor.getEditorState().read(...)` or an update. */\nexport function $isCanonicalUnmatchedNode(node) {\n return node.getTextContent() === unmatchedGlyphText(node.getMarker());\n}\nfunction unmatchedTitle(marker) {\n return marker.endsWith(\"*\")\n ? `This closing marker has no matching opening marker!`\n : `This opening marker has no matching closing marker!`;\n}\nfunction $convertImmutableUnmatchedElement(element) {\n const marker = element.getAttribute(\"data-marker\") ?? \"\";\n const node = $createImmutableUnmatchedNode(marker);\n return { node };\n}\nexport function $createImmutableUnmatchedNode(marker) {\n return $applyNodeReplacement(new ImmutableUnmatchedNode(marker));\n}\nfunction isUnmatchedElement(node) {\n // `tagName` is upper-cased for HTML-namespace elements, so compare case-insensitively.\n return node?.tagName.toLowerCase() === UNMATCHED_TAG_NAME;\n}\nexport function $isImmutableUnmatchedNode(node) {\n return node instanceof ImmutableUnmatchedNode;\n}\nexport function isSerializedImmutableUnmatchedNode(node) {\n return node?.type === ImmutableUnmatchedNode.getType();\n}\n","/** Conforms with USJ v3.1 tables @see https://docs.usfm.bible/usfm/3.1/para/table.html */\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\n/** USJ marker type this node renders; the forward adaptor matches USJ input against it. */\nexport const TABLE_TYPE = \"table\";\nexport const IMMUTABLE_TABLE_TYPE = \"immutable-table\";\nexport const IMMUTABLE_TABLE_VERSION = 1;\n/** List of known properties of `MarkerObject` */\nexport const TABLE_MARKER_OBJECT_PROPS = [\"type\", \"marker\", \"content\"];\nexport class ImmutableTableNode extends ElementNode {\n __unknownAttributes;\n constructor(unknownAttributes, key) {\n super(key);\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return IMMUTABLE_TABLE_TYPE;\n }\n static clone(node) {\n return new ImmutableTableNode(node.__unknownAttributes, node.__key);\n }\n static importJSON(serializedNode) {\n return $createImmutableTableNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n return this.getLatest().__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(\"table\");\n dom.classList.add(\"table\");\n // Read-only whole-block: the same treatment tables had while they were `UnknownNode`s, and the\n // one `UnknownNode` still gives the other Tier-3 kinds (figures, sidebars, periph) — visible\n // and byte-preserved, but not editable. contentEditable=false stops the browser from placing a\n // native caret inside, so caret navigation skips the table like a decorator node. Set on the\n // container so it covers every row and cell. Editing tables is not supported yet; whether and\n // how they become editable is still open. Set as an ATTRIBUTE rather than via the\n // `contentEditable` IDL property (which `UnknownNode` uses): the two are equivalent in a\n // browser, but jsdom does not reflect the property back to the attribute, so only this form is\n // assertable in tests.\n dom.setAttribute(\"contenteditable\", \"false\");\n return dom;\n }\n updateDOM() {\n return false;\n }\n exportJSON() {\n const unknownAttributes = this.getUnknownAttributes();\n return {\n ...super.exportJSON(),\n type: IMMUTABLE_TABLE_TYPE,\n ...(unknownAttributes !== undefined && { unknownAttributes }),\n version: IMMUTABLE_TABLE_VERSION,\n };\n }\n // Shadow root: isolate selection so content doesn't merge across the table boundary.\n isShadowRoot() {\n return true;\n }\n}\nexport function $createImmutableTableNode(unknownAttributes) {\n return $applyNodeReplacement(new ImmutableTableNode(unknownAttributes));\n}\nexport function $isImmutableTableNode(node) {\n return node instanceof ImmutableTableNode;\n}\nexport function isSerializedImmutableTableNode(node) {\n return node?.type === IMMUTABLE_TABLE_TYPE;\n}\n","/** Conforms with USJ v3.1 table rows @see https://docs.usfm.bible/usfm/3.1/para/table.html */\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\n/** USJ marker type this node renders; the forward adaptor matches USJ input against it. */\nexport const TABLE_ROW_TYPE = \"table:row\";\nexport const IMMUTABLE_TABLE_ROW_TYPE = \"immutable-table-row\";\nexport const IMMUTABLE_TABLE_ROW_VERSION = 1;\nexport const TABLE_ROW_DEFAULT_MARKER = \"tr\";\n/** List of known properties of `MarkerObject` */\nexport const TABLE_ROW_MARKER_OBJECT_PROPS = [\"type\", \"marker\", \"content\"];\nexport class ImmutableTableRowNode extends ElementNode {\n __marker;\n __unknownAttributes;\n constructor(marker = TABLE_ROW_DEFAULT_MARKER, unknownAttributes, key) {\n super(key);\n this.__marker = marker;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return IMMUTABLE_TABLE_ROW_TYPE;\n }\n static clone(node) {\n return new ImmutableTableRowNode(node.__marker, node.__unknownAttributes, node.__key);\n }\n static importJSON(serializedNode) {\n return $createImmutableTableRowNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker ?? TABLE_ROW_DEFAULT_MARKER)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n return this.getLatest().__marker;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n return this.getLatest().__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(\"tr\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(\"table-row\", `usfm_${this.__marker}`);\n // A first-line indent is meaningful for a `\\tr` rendered as a BLOCK of text and meaningless\n // inside a real table, where it only drags content left. It reaches two places here: the cells\n // (reset by `.table-cell` in usj-nodes.css) and the row's own marker glyph, which — being the\n // only non-cell content of a — the browser wraps in an ANONYMOUS table cell. That box has\n // no class to target, so a negative indent pulls the glyph clear outside the table.\n //\n // Inline rather than a stylesheet rule because a stylesheet rule cannot reliably win: a project\n // StyleInfo that gives `tr` a firstLineIndent emits `.editor-input.usfm .usfm_tr { text-indent }`\n // (generateUsjCss.ts), injected after the static sheet and at a specificity any reasonable\n // static selector ties at best. Paratext 9 resolves it the same way, stamping\n // `style=\"TEXT-INDENT: 0in\"` on every `` it emits. Cells need nothing of their own: with the\n // row at zero they inherit zero.\n dom.style.textIndent = \"0\";\n return dom;\n }\n updateDOM(prevNode) {\n return prevNode.__marker !== this.__marker;\n }\n exportJSON() {\n const unknownAttributes = this.getUnknownAttributes();\n return {\n ...super.exportJSON(),\n type: IMMUTABLE_TABLE_ROW_TYPE,\n marker: this.getMarker(),\n ...(unknownAttributes !== undefined && { unknownAttributes }),\n version: IMMUTABLE_TABLE_ROW_VERSION,\n };\n }\n}\nexport function $createImmutableTableRowNode(marker, unknownAttributes) {\n return $applyNodeReplacement(new ImmutableTableRowNode(marker, unknownAttributes));\n}\nexport function $isImmutableTableRowNode(node) {\n return node instanceof ImmutableTableRowNode;\n}\nexport function isSerializedImmutableTableRowNode(node) {\n return node?.type === IMMUTABLE_TABLE_ROW_TYPE;\n}\n","/** Conforms with USJ v3.1 table cells @see https://docs.usfm.bible/usfm/3.1/para/table.html */\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\n/** USJ marker type this node renders; the forward adaptor matches USJ input against it. */\nexport const TABLE_CELL_TYPE = \"table:cell\";\nexport const IMMUTABLE_TABLE_CELL_TYPE = \"immutable-table-cell\";\nexport const IMMUTABLE_TABLE_CELL_VERSION = 1;\nexport const TABLE_CELL_DEFAULT_MARKER = \"tc1\";\n/** List of known properties of a table cell `MarkerObject` */\nexport const TABLE_CELL_MARKER_OBJECT_PROPS = [\n \"type\",\n \"marker\",\n \"align\",\n \"colspan\",\n \"content\",\n];\n// USJ `align` is logical (start/center/end), which CSS `text-align` supports natively and mirrors\n// correctly under `dir` (RTL). Pass it straight through, ignoring unrecognized values, rather than\n// mapping to the physical left/right — that would flip alignment in RTL scripts.\nfunction toLogicalTextAlign(align) {\n return align === \"start\" || align === \"center\" || align === \"end\" ? align : undefined;\n}\nexport class ImmutableTableCellNode extends ElementNode {\n __marker;\n __align;\n __colspan;\n __unknownAttributes;\n constructor(marker = TABLE_CELL_DEFAULT_MARKER, align, colspan, unknownAttributes, key) {\n super(key);\n this.__marker = marker;\n this.__align = align;\n this.__colspan = colspan;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return IMMUTABLE_TABLE_CELL_TYPE;\n }\n static clone(node) {\n const { __marker, __align, __colspan, __unknownAttributes, __key } = node;\n return new ImmutableTableCellNode(__marker, __align, __colspan, __unknownAttributes, __key);\n }\n static importJSON(serializedNode) {\n return $createImmutableTableCellNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker ?? TABLE_CELL_DEFAULT_MARKER)\n .setAlign(serializedNode.align)\n .setColspan(serializedNode.colspan)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n return this.getLatest().__marker;\n }\n setAlign(align) {\n if (this.__align === align)\n return this;\n const self = this.getWritable();\n self.__align = align;\n return self;\n }\n getAlign() {\n return this.getLatest().__align;\n }\n setColspan(colspan) {\n if (this.__colspan === colspan)\n return this;\n const self = this.getWritable();\n self.__colspan = colspan;\n return self;\n }\n getColspan() {\n return this.getLatest().__colspan;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n return this.getLatest().__unknownAttributes;\n }\n createDOM() {\n const isHeader = this.__marker.startsWith(\"th\");\n const dom = document.createElement(isHeader ? \"th\" : \"td\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(\"table-cell\", `usfm_${this.__marker}`);\n const textAlign = toLogicalTextAlign(this.__align);\n if (textAlign)\n dom.style.textAlign = textAlign;\n if (this.__colspan)\n dom.setAttribute(\"colspan\", this.__colspan);\n return dom;\n }\n updateDOM(prevNode) {\n // Recreate the DOM element when marker, align, or colspan changes.\n return (prevNode.__marker !== this.__marker ||\n prevNode.__align !== this.__align ||\n prevNode.__colspan !== this.__colspan);\n }\n exportJSON() {\n const align = this.getAlign();\n const colspan = this.getColspan();\n const unknownAttributes = this.getUnknownAttributes();\n return {\n ...super.exportJSON(),\n type: IMMUTABLE_TABLE_CELL_TYPE,\n marker: this.getMarker(),\n ...(align !== undefined && { align }),\n ...(colspan !== undefined && { colspan }),\n ...(unknownAttributes !== undefined && { unknownAttributes }),\n version: IMMUTABLE_TABLE_CELL_VERSION,\n };\n }\n}\nexport function $createImmutableTableCellNode(marker, align, colspan, unknownAttributes) {\n return $applyNodeReplacement(new ImmutableTableCellNode(marker, align, colspan, unknownAttributes));\n}\nexport function $isImmutableTableCellNode(node) {\n return node instanceof ImmutableTableCellNode;\n}\nexport function isSerializedImmutableTableCellNode(node) {\n return node?.type === IMMUTABLE_TABLE_CELL_TYPE;\n}\n","/**\n * Caret positions at a content boundary: the single place that owns HOW a boundary between an\n * element's children becomes a Lexical caret position, and WHICH node renders the caret there. A\n * \"boundary\" is the location just before a parent's child at some index — the location the child\n * count itself names being the end of the parent's content.\n *\n * Every path that parks a caret at the start of some content answers this same question, and each\n * one used to answer it by hand: the para-marker prefix cursor guard, the marker-edit prefix\n * injection, verse-start placement, and the `\\fp` visual-line hop. The empty-verse caret guard asks\n * the other half of it — whether a boundary has a host at all — because that is exactly the state it\n * exists to repair.\n *\n * ## The convention\n *\n * - **A boundary is hosted by the text node that FOLLOWS it**, and a text point at that node's\n * offset 0 is the caret position — not the end of whatever text precedes the boundary, and not the\n * element point, even when a text node sits on both sides. The reason is what the caller is doing:\n * it has chosen a boundary because the content AFTER it is where the user's next keystroke\n * belongs, and at these boundaries what precedes is structure — a paragraph's marker glyph, its\n * token-mode separator, a verse marker — that typed text must never merge into.\n * - **The element point is the fallback, and it renders no caret of its own.** Where the following\n * child is a decorator (an immutable verse or chapter number, an immutable marker glyph) or where\n * there is no child at all, the boundary can only be expressed as an element point, and the\n * browser draws nothing at it. That is not a failure of this module — it is the condition\n * `EmptyVerseCaretGuardPlugin` detects with {@link $caretHostAtBoundary} and repairs by\n * materializing a text node to host the caret.\n * - **Hosting is a question about the TREE, not about the view.** A `MarkerNode` is a `TextNode`\n * subclass, so an editable-mode marker glyph hosts a caret exactly as content text does. That is\n * why one implementation serves every marker mode: it never asks which nodes are visible, only\n * which node can carry a text point.\n *\n * ## What this module does NOT own\n *\n * WHICH boundary is the legal one. That rule is genuinely different in each caller, and deliberately\n * different per marker mode, so it is stated at each site rather than merged here:\n *\n * - `$guardCursorAtParaStart` (shared-react) rules that a click may not come to rest before or\n * inside a paragraph's structural prefix, and advances FORWARD past it. It runs in every marker\n * mode — it is what stops a click in the hanging-indent gutter of `\\li`/`\\ili`/poetry from landing\n * before the marker — and its prefix scan therefore enumerates both marker flavors.\n * - `ArrowNavigationPlugin`'s visible-stop canonicalizer picks, among the tree positions sharing one\n * screen location, the outermost and earliest: the end of the nearest PRECEDING visible text — the\n * opposite preference to this module's, and correct there, because what precedes an arrow landing\n * is rendered content the caret just walked over rather than structure. It is also scoped to\n * markerMode \"editable\", where the display runs and glyph text that stack those positions exist at\n * all. Merging the two preferences would be a behavior change in both directions.\n * - `$selectParaContentStart` (platform's marker-edit paths) knows its paragraphs have the fixed\n * editable-mode `[glyph, separator, …content]` shape and names boundary 2 outright, where\n * `$advancePastParaPrefixes` scans for its boundary. Same convention, different way of finding the\n * index — which is precisely the part that is not shareable.\n *\n * Nor the mirror question, the caret position at the END of a node: `$placeCaretAtEnd`\n * (shared-react's structureKeyboard.utils.ts) owns that for the delete/merge paths, and it descends\n * into the node rather than resting at a boundary between children.\n */\nimport { $isTextNode } from \"lexical\";\n/**\n * The text node hosting a caret at the boundary before `parent`'s child at `index`, or `undefined`\n * when nothing there can carry a text point — a decorator child, or a boundary past the last child.\n *\n * Deliberately forward-looking: text preceding the boundary occupies the same screen location at its\n * own end, but it is not what this answers. See the module's convention.\n *\n * @param parent - The element whose children the boundary lies between.\n * @param index - The boundary: the index of the child that follows it.\n */\nexport function $caretHostAtBoundary(parent, index) {\n const child = parent.getChildAtIndex(index);\n return $isTextNode(child) ? child : undefined;\n}\n/**\n * Collapse the caret to the boundary before `parent`'s child at `index`: offset 0 of the text node\n * hosting it, or the element point when nothing hosts it.\n *\n * @param parent - The element whose children the boundary lies between.\n * @param index - The boundary: the index of the child that follows it.\n */\nexport function $placeCaretAtBoundary(parent, index) {\n const host = $caretHostAtBoundary(parent, index);\n if (host)\n host.select(0, 0);\n else\n parent.select(index, index);\n}\n","/**\n * Char-span glyph shape: the single place that owns WHETHER a char span carries an opening and a\n * closing glyph, and HOW a fresh span's pair is built. Sibling of nestedGlyphs.utils.ts (the `+` a\n * nested span's glyphs carry), markerSeparators.utils.ts (the NBSP between an opening glyph and its\n * content), and attributeDisplay.utils.ts (the `|name=\"value\"` run) — between them those three own\n * everything about a rendered char glyph EXCEPT whether there is one at all, which is this module.\n *\n * ## The conventions\n *\n * - **Glyphs are markerMode \"editable\" presentation.** A `MarkerNode` exists only there — \"visible\"\n * mode renders `ImmutableTypedTextNode` byte runs instead and \"hidden\" mode renders no glyph at\n * all — so a builder that fabricates one unconditionally puts literal `\\ft ` text (and its\n * separator NBSP) into the content of a document that displays no glyphs, where it is visible in\n * the span's text content and wrong on serialization. Callers that can run outside editable mode\n * pass `renderGlyphs` false. It is a plain boolean rather than `ViewOptions` because that type\n * lives in `shared-react`, which this package must not depend on.\n * - **Closer display keys on the span's STATE, never on its marker family**\n * ({@link $charOwesClosingGlyph}): a span renders a closing glyph iff it does NOT carry\n * `closed=\"false\"`. ParatextData stamps that flag on every genuinely-unclosed span — chiefly\n * footnote/cross-reference content chars (`\\fr`, `\\ft`, `\\xo`, `\\xt`), which the next bare marker\n * closes — so those render closer-less, while an explicitly-closed `\\xt` keeps its `\\xt*`. The\n * same state decides whether a missing closer is deletion damage (`$charNodeDeletionTransform`)\n * and whether an attribute run may exist at all (attributeDisplay.utils.ts).\n * - **A span CONTINUING another reproduces that span's shape**, not the convention's default\n * ({@link $buildContinuationCharSpan}): the PT9 close-and-reopen (`$liftOutOfChar`) and the\n * Ctrl+Space-style split (`$splitCharNodeAt`) both build a right-hand span keeping the source's\n * marker, nesting, and `closed` state, and take its closing glyph from the source's actual\n * CHILDREN ({@link $charHasClosingGlyph}) rather than from that state — the tree is what says\n * which glyphs this document renders, so a mode with no glyphs reopens with none.\n *\n * ## What this module does NOT own\n *\n * Only the shape of a span BUILT here. Keeping an existing span's glyphs honest afterwards belongs\n * to the syncs and the marker-edit engine: `$syncNestedGlyphs` re-derives the `+`,\n * `$syncOpenerSeparators` heals the separator, `$charNodeDeletionTransform` reads a missing\n * opener/closer as deletion damage, and the Tier-1 rename engine retargets a live pair. Nor are\n * these the only glyph MATERIALIZATIONS: the load adaptor's `createChar`/`addOpeningMarker`/\n * `addClosingMarker` build the same shape as SERIALIZED nodes, the collab materializer builds it\n * for \"visible\" mode as `ImmutableTypedTextNode`s, and `CharNode`'s `applyMarkerToDom` writes the\n * marker onto the rendered DOM. Each is a different layer with its own node kinds; this module is\n * the live-`MarkerNode` one, and states the conventions the others follow.\n */\nimport { $createMarkerNode, $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { $isSeparatorPrefixHostText } from \"./markerSeparators.utils.js\";\nimport { $isNestedCharNode } from \"./nestedGlyphs.utils.js\";\nimport { NBSP } from \"./node-constants.js\";\n/**\n * Whether `char` owes a closing glyph — the implicit-close convention read from the span's own\n * state: everything closes except a span explicitly marked `closed=\"false\"`. This is the rule for\n * whether a closer SHOULD exist; {@link $charHasClosingGlyph} is the separate question of whether\n * one actually does.\n */\nexport function $charOwesClosingGlyph(char) {\n return char.getUnknownAttributes()?.closed !== \"false\";\n}\n/**\n * Whether `char` currently renders a closing glyph — a direct-child `MarkerNode` with closing\n * syntax. Outside markerMode \"editable\" a span has no glyph children at all, so this is also the\n * tree's answer to \"does this document display glyphs for this span\".\n *\n * Deliberately marker-AGNOSTIC, unlike `$charClosingGlyph` (attributeDisplay.utils.ts), which\n * requires the glyph to name `char`'s own marker because it must anchor `char`'s own attribute run\n * and the collab-flattened shape parks a nested child span's glyphs among the same children. Here\n * the question is only whether the span was built with a closer to reproduce.\n */\nexport function $charHasClosingGlyph(char) {\n return char\n .getChildren()\n .some((child) => $isMarkerNode(child) && child.getMarkerSyntax() === \"closing\");\n}\n/**\n * The unknown attributes a span continuing `char` must be created with: `closed=\"false\"` when\n * `char` carries the implicit-close convention, otherwise none.\n *\n * `closed` is structural state, not attribute bytes — an implicitly-closed span splits into TWO\n * implicitly-closed spans, and without the flag the continuation's (correct) missing closer reads\n * as deletion damage and gets routed through Tier 2. The DISPLAY attributes (`|name=\"value\"`) are\n * deliberately not copied: they stay on the left half only, since duplicating them would double\n * those bytes on serialization.\n */\nexport function $continuationCharAttributes(char) {\n return $charOwesClosingGlyph(char) ? undefined : { closed: \"false\" };\n}\n/**\n * Fill `span` — a freshly created, not-yet-inserted char span continuing `source` — with `content`\n * and the glyphs that content is owed. The caller creates `span` (with `source`'s marker and\n * {@link $continuationCharAttributes}) and inserts it; this decides its shape:\n *\n * - an opening glyph, first, only when `renderGlyphs` — the document has to display glyphs at all;\n * - the separator NBSP on `content`'s leading text, only when an opening glyph was emitted, since\n * the NBSP is the separator BETWEEN the glyph and the content it opens (markerSeparators.utils.ts);\n * - a closing glyph, last, only when `source` itself renders one ({@link $charHasClosingGlyph});\n * - the `+` on both glyphs when `source` is nested ({@link $isNestedCharNode}) — the continuation\n * becomes `source`'s sibling, so it inherits `source`'s nesting.\n *\n * @param span - The continuation span to fill. Must be empty; must be called inside `editor.update()`.\n * @param source - The span being continued, read for marker, nesting, and closing-glyph shape.\n * @param content - The nodes moving into `span`, in order. May be empty.\n * @param renderGlyphs - Whether this document displays char glyphs (markerMode \"editable\").\n */\nexport function $buildContinuationCharSpan(span, source, content, renderGlyphs) {\n const marker = source.getMarker();\n const nested = $isNestedCharNode(source);\n const hasCloser = $charHasClosingGlyph(source);\n if (renderGlyphs) {\n span.append($createMarkerNode(marker, \"opening\", nested));\n // Plain-text-first content carries the separator as its prefix — the same host predicate the\n // separator sync uses ($isSeparatorPrefixHostText): TextNode SUBCLASSES (VerseNode,\n // ImmutableUnmatchedNode, MarkerNode) render their own marker bytes and attribute runs are\n // engine-owned canonical output, so splicing an NBSP into either rewrites a glyph or corrupts\n // the run. Everything else takes a standalone NBSP spacer, which `$syncOpenerSeparators` adds\n // when the span is next dirtied.\n const [firstContent] = content;\n if ($isSeparatorPrefixHostText(firstContent) && !firstContent.getTextContent().startsWith(NBSP))\n firstContent.setTextContent(NBSP + firstContent.getTextContent());\n }\n span.append(...content);\n if (hasCloser)\n span.append($createMarkerNode(marker, \"closing\", nested));\n}\n","/**\n * The character-style STACK: closing every char span open at a point and reopening the ones that\n * still have content after it. Sibling of charGlyphs.utils.ts, which owns the shape of each span\n * this module builds.\n *\n * ## Why one primitive\n *\n * Paratext 9 has no bespoke algorithm for this. Its `StyleApplicator` closes the open character\n * styles before the point and reopens the still-active ones after it, and every operation that\n * interrupts styled text — applying a non-nesting style, an unformatted space, a footnote-paragraph\n * break, a paragraph split — is the same close-and-reopen with a different thing placed in the gap.\n * Reimplemented per caller, that operation came out right in one place and wrong in four.\n *\n * ## Ordering falls out of the loop, not out of ordering code\n *\n * {@link $liftOutOfChar} closes the ONE span holding the node and reopens a continuation after it.\n * {@link $liftOutOfCharStack} iterates that outwards. Each iteration's continuation lands in the\n * next iteration's \"content after the node\" set, so the closers emerge innermost-to-outermost and\n * the openers outermost-to-innermost with no explicit ordering step:\n *\n * ```\n * \\wj \\+nd thing\\+nd*\\wj* -> \\wj \\+nd thi\\+nd*\\wj* \\wj \\+nd ng\\+nd*\\wj*\n * ```\n *\n * PT9 gets the reopen order WRONG for nested styles — its reopen loop walks the style list\n * innermost-first and emits index 0 without the `+`, so a two-deep stack comes back with the\n * markers swapped. That path has no PT9 test coverage. We port the intent, not the code.\n */\nimport { $createMarkerNode, $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { $buildContinuationCharSpan, $charOwesClosingGlyph, $continuationCharAttributes, } from \"./charGlyphs.utils.js\";\nimport { $createCharNode, $isCharNode } from \"./CharNode.js\";\nimport { $isSeparatorPrefixHostText } from \"./markerSeparators.utils.js\";\nimport { $isNestedCharNode } from \"./nestedGlyphs.utils.js\";\nimport { canonicalAttributeText } from \"./attributeDisplay.utils.js\";\nimport { textTypeState } from \"../collab/delta.state.js\";\nimport { defaultMarkerAttribute } from \"../../converters/usfm/usfmFragmentToUsj.js\";\nimport { NBSP } from \"./node-constants.js\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport { $createTextNode, $getState, $isElementNode, $isTextNode } from \"lexical\";\n/**\n * The innermost char span a point sits in: the nearest `CharNode` at or above `node`, or\n * `undefined` when the point is not inside one.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nexport function $innermostCharAncestor(node) {\n return $findMatchingParent(node, $isCharNode) ?? undefined;\n}\n/**\n * Where a lifted node comes to rest: the nearest non-char ancestor of `node` — the note or\n * paragraph a bare marker would land in. Returns `node`'s own parent when `node` is not inside a\n * char span at all.\n *\n * Deliberately NOT delegated to `$findMatchingParent`: for a root-level char span the resting\n * place is the RootNode itself, which `$findMatchingParent` never yields (it stops before testing\n * the root).\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nexport function $charStackContainer(node) {\n let parent = node.getParent();\n while ($isCharNode(parent))\n parent = parent.getParent();\n return parent;\n}\n/**\n * Whether `char` holds no CONTENT — only its own glyphs and the structural NBSP separator those\n * glyphs own (markerSeparators.utils.ts). Such a span serializes to `\\nd \\nd*`: a marker pair\n * around nothing, which is not what the user asked for when a close-and-reopen happened to land at\n * a run's edge. A genuine space is content and does NOT make a span empty — only the NBSP does.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nexport function $isCharContentEmpty(char) {\n // The display run is a rendering of the span's attribute state, not content — a span holding\n // nothing but its own `|name=\"value\"` bytes has had its content taken away and is empty. Only a\n // span that closes explicitly can own a run at all ({@link $ownsDisplayRun}); inside an\n // implicitly-closed one, `attribute`-tagged text is ordinary content.\n const ownsDisplayRun = $ownsDisplayRun(char);\n return char\n .getChildren()\n .every((child) => $isMarkerNode(child) ||\n (ownsDisplayRun && $getState(child, textTypeState) === \"attribute\") ||\n ($isTextNode(child) && child.getTextContent().replaceAll(NBSP, \"\") === \"\"));\n}\n/**\n * Whether `char` can own a display run at all. The run is anchored to the closing glyph, and a\n * `closed=\"false\"` span renders neither (attributeDisplay.utils.ts), so inside one — note content,\n * chiefly — `attribute`-tagged text is not a run but ordinary content.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nfunction $ownsDisplayRun(char) {\n return $charOwesClosingGlyph(char);\n}\n/**\n * Remove `char`, leaving its attribute bytes behind as plain text after `contentEnd` when it has\n * any. Paratext 9 keeps an unwrapped span's attributes as literal bytes, and the span is the only\n * thing that carried them — dropping it silently would delete `|lemma=\"grace\"` from the file.\n * Routed through {@link canonicalAttributeText}, the same one serializer `$unwrapCharNode` uses, so\n * the two paths cannot spell the same attributes differently: a lone default attribute collapses to\n * `|value`, anything else stays `|name=\"value\" …`.\n *\n * Mutating: call inside `editor.update()`.\n */\nfunction $removeCharKeepingAttributeBytes(char, contentEnd) {\n const attributes = char.getUnknownAttributes();\n const bytes = attributes\n ? canonicalAttributeText(attributes, defaultMarkerAttribute(char.getMarker()))\n : \"\";\n if (bytes !== \"\")\n contentEnd.insertAfter($createTextNode(bytes));\n char.remove();\n}\n/**\n * Leave `char` EXPLICITLY closed: drop the `closed=\"false\"` convention it was carrying and, where\n * this document renders glyphs, give it the closing glyph that state now owes. Already-explicit\n * spans are untouched.\n *\n * The state is the load-bearing half — it is what serializes — and the glyph follows it, the same\n * pairing `$charOwesClosingGlyph` reads everywhere else. Leaving one without the other makes the\n * marker-edit engine read the mismatch as deletion damage.\n *\n * Mutating: call inside `editor.update()`.\n */\nfunction $closeCharExplicitly(char, renderGlyphs) {\n if ($charOwesClosingGlyph(char))\n return;\n const attributes = char.getUnknownAttributes();\n if (attributes) {\n const rest = { ...attributes };\n delete rest.closed;\n char.setUnknownAttributes(Object.keys(rest).length > 0 ? rest : undefined);\n }\n if (renderGlyphs)\n char.append($createMarkerNode(char.getMarker(), \"closing\", $isNestedCharNode(char)));\n}\n/**\n * Whether writing `node`'s own opening marker is ITSELF how `char` ends, so there is nothing to\n * close and nothing to reopen.\n *\n * True when both spans close implicitly — the `closed=\"false\"` convention ParatextData stamps on\n * note-content and cross-reference markers (`\\ft`, `\\fq`, `\\fr`, `\\fp`, `\\xt`, …), which are\n * terminated by the next bare marker rather than by an end marker. Putting `\\fq` at a caret inside\n * `\\ft` therefore emits no `\\ft*` and no reopened `\\ft`: the remainder of `\\ft`'s content simply\n * belongs to `\\fq` now.\n *\n * False whenever either side closes explicitly. An explicitly-closed `char` (`\\wj`) must emit its\n * `\\wj*` and reopen afterwards, because the new marker cannot terminate it; and an\n * explicitly-closed `node` (`\\add`) ends at its own `\\add*`, so `char`'s remaining content is not\n * part of it and does need reopening.\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nfunction $endsImplicitly(node, char) {\n return $isCharNode(node) && !$charOwesClosingGlyph(node) && !$charOwesClosingGlyph(char);\n}\n/**\n * Move `content` into `span`, which is a freshly built, still content-less marker taking over the\n * remainder of the span it just ended ({@link $endsImplicitly}). Any placeholder the builder gave\n * `span` is dropped so the moved nodes become its real content, and a leading text run takes the\n * structural separator the opening glyph owes it (markerSeparators.utils.ts) — the same shape\n * {@link $buildContinuationCharSpan} gives a reopened clone.\n *\n * Mutating: call inside `editor.update()`.\n */\nfunction $absorbIntoCharSpan(span, content, renderGlyphs) {\n if ($isCharContentEmpty(span))\n span.getChildren().forEach((child) => {\n if (!$isMarkerNode(child))\n child.remove();\n });\n // Only PLAIN leading text takes the separator as its prefix — the same host predicate the\n // separator sync uses ($isSeparatorPrefixHostText): TextNode subclasses (VerseNode,\n // ImmutableUnmatchedNode) render their own marker bytes and attribute runs are engine-owned\n // canonical output, so splicing an NBSP into either rewrites a glyph or corrupts the run.\n // Everything else gets its standalone spacer from `$syncOpenerSeparators` on the next dirty.\n const [first] = content;\n if (renderGlyphs && $isSeparatorPrefixHostText(first) && !first.getTextContent().startsWith(NBSP))\n first.setTextContent(NBSP + first.getTextContent());\n span.append(...content);\n}\n/**\n * Lift `node` OUT of the char span `char` to `char`'s parent, splitting `char` around it: content\n * before `node` stays in `char` (its \"before\" half), content after `node` moves to a fresh reopened\n * clone inserted after `node`, and `node` itself becomes a sibling of `char`. A half left with no\n * content ({@link $isCharContentEmpty}) is dropped rather than serialized as an empty marker pair.\n * The reopened clone keeps `char`'s marker, closer convention, and nesting (its glyphs carry the\n * `+` when `char` was itself nested).\n *\n * The one shape with no reopened clone is a `node` that ends `char` implicitly\n * ({@link $endsImplicitly}): there the content after `node` moves INTO it instead.\n *\n * An implicitly-closed `char` is left EXPLICITLY closed when `options.closeImplicitSpans` says the\n * gap cannot terminate it — see {@link CharStackLiftOptions}, which also documents `renderGlyphs`.\n *\n * Mutating: call inside `editor.update()`.\n */\nexport function $liftOutOfChar(node, char, options) {\n const { renderGlyphs, closeImplicitSpans = false } = options;\n // Content strictly after `node`. Two kinds of child stay behind rather than move: `char`'s own\n // closing glyph, which terminates the \"before\" half (the clone gets a fresh one of its own), and\n // its display run — the `|name=\"value\"` bytes are a rendering of `char`'s OWN attribute state,\n // which stays on `char` (`$continuationCharAttributes` deliberately does not copy it, since\n // duplicating it would double those bytes on serialization). Carrying the bytes to the clone\n // left the attributes displayed on a span that does not have them and missing from the span that\n // does — the same content-versus-presentation split `$unwrapCharNode` makes.\n //\n // Only a span that closes EXPLICITLY can own a display run ({@link $ownsDisplayRun}). So inside\n // an implicitly-closed one — note content, chiefly — `attribute`-tagged text is not a run and is\n // ordinary content that must ride along with everything else after the caret.\n const ownsDisplayRun = $ownsDisplayRun(char);\n const after = [];\n for (let sibling = node.getNextSibling(); sibling;) {\n const next = sibling.getNextSibling();\n const isCloser = $isMarkerNode(sibling) && sibling.getMarkerSyntax() === \"closing\";\n const isDisplayRun = ownsDisplayRun && $getState(sibling, textTypeState) === \"attribute\";\n if (!isCloser && !isDisplayRun)\n after.push(sibling);\n sibling = next;\n }\n const endsImplicitly = $endsImplicitly(node, char);\n char.insertAfter(node); // node leaves char, becomes its next sibling\n // Where whatever came out of `char` ends, so a dropped span's attribute bytes land after its\n // former content rather than in front of it.\n let contentEnd = node;\n if (after.length > 0) {\n if (endsImplicitly) {\n $absorbIntoCharSpan(node, after, renderGlyphs);\n }\n else {\n const right = $createCharNode(char.getMarker(), $continuationCharAttributes(char));\n $buildContinuationCharSpan(right, char, after, renderGlyphs);\n node.insertAfter(right);\n if ($isCharContentEmpty(right))\n right.remove();\n else\n contentEnd = right;\n }\n }\n // The gap cannot terminate `char` on its own, so an implicitly-closed one has to be closed for\n // real: `\\ft` runs to the next note marker or `\\f*`, and anything merely placed after its\n // content would re-read as more of that content. Done AFTER the continuation is built, which\n // takes its own closing glyph from `char`'s children — the continuation reopens implicitly, as\n // the span it continues did.\n if (closeImplicitSpans && !endsImplicitly)\n $closeCharExplicitly(char, renderGlyphs);\n if ($isCharContentEmpty(char))\n $removeCharKeepingAttributeBytes(char, contentEnd);\n}\n/**\n * Lift `node` out of EVERY char span enclosing it, closing each on the way out and reopening the\n * ones with content after it — the whole close-and-reopen. `node` comes to rest in the nearest\n * non-char container ({@link $charStackContainer}), carrying with it the content of any span it\n * ended implicitly on the way ({@link $endsImplicitly}).\n *\n * Callers that place something in the gap (an unformatted space, a new non-nesting char span, a\n * paragraph-split marker) insert it at the caret first and lift THAT; the caret point they want\n * afterwards differs per caller, so this function deliberately does not move the selection.\n *\n * @param node - The node to lift. Must already be attached at the point the gap belongs.\n * @param options - How the gap is treated; see {@link CharStackLiftOptions}.\n *\n * Mutating: call inside `editor.update()`.\n */\nexport function $liftOutOfCharStack(node, options) {\n let parent = node.getParent();\n while ($isCharNode(parent)) {\n $liftOutOfChar(node, parent, options);\n parent = node.getParent();\n }\n}\n/**\n * Put the caret at the START of `node`'s content — immediately after the structural separator the\n * opening glyph owns, and descending through nested spans to the innermost one. This is where\n * typing belongs after a close-and-reopen: the user interrupted the text at that point, so the\n * next keystroke continues the reopened run rather than landing outside it. Offset 0 would sit\n * BEFORE the separator, splicing typed text between the glyph and the space it owns.\n *\n * Mutating (moves the selection): call inside `editor.update()`.\n */\nexport function $selectCharContentStart(node) {\n if ($isTextNode(node) && !$isMarkerNode(node)) {\n const offset = node.getTextContent().startsWith(NBSP) ? 1 : 0;\n node.select(offset, offset);\n return;\n }\n if ($isElementNode(node)) {\n const first = node.getChildren().find((child) => !$isMarkerNode(child));\n if (first) {\n $selectCharContentStart(first);\n return;\n }\n node.selectEnd(); // defensive: glyphs only, no content to sit in front of\n }\n}\n","/**\n * Editor-scoped registry of display-run OWNER keys the marker-edit engine currently holds\n * pending. The engine (MarkerEditPlugin, platform) registers its live pending set here so the\n * self-healing display syncs — which live in shared/shared-react and cannot import the engine —\n * can leave a pended owner's run alone instead of resurrecting a deletion the engine has not\n * settled yet. Keyed per editor (main editor and footnote popover each register their own set).\n */\nimport { $getEditor } from \"lexical\";\nconst pendedOwnersByEditor = new WeakMap();\nexport function registerPendedDisplayOwners(editor, pendedKeys) {\n pendedOwnersByEditor.set(editor, pendedKeys);\n return () => {\n if (pendedOwnersByEditor.get(editor) === pendedKeys)\n pendedOwnersByEditor.delete(editor);\n };\n}\n/**\n * The live pending-owner set registered for `editor`, or `undefined` when no marker-edit engine is\n * mounted on it (a non-editable marker mode, or an editor that has torn down). The SAME mutable Set\n * the engine holds, not a snapshot — a reader that keeps the reference sees later pends — so\n * callers must treat it as read-only. Takes the editor explicitly rather than reading `$getEditor()`\n * so it can be called from outside a read/update (the editor-facing `getUsj()` path decides whether\n * to enter a read at all based on whether anything is pending).\n */\nexport function getPendedDisplayOwners(editor) {\n return pendedOwnersByEditor.get(editor);\n}\n/** Whether `node`'s key is pended in the active editor. Call inside a read/update. */\nexport function $isDisplayOwnerPended(node) {\n return pendedOwnersByEditor.get($getEditor())?.has(node.getKey()) ?? false;\n}\n/**\n * Lets the shared self-healing display-run sync (`$syncDisplayRun`, displayRunSync.utils.ts)\n * report that it just found an owner's run destroyed by something other than itself, so the\n * marker-edit engine settles it on caret departure instead of the sync resurrecting it. Writes\n * directly into the SAME mutable Set `registerPendedDisplayOwners` was given, rather than\n * routing the report back through one of the engine's own node transforms: which plugin's\n * transform runs first on a shared dirty node depends on mount order (the sync and the engine\n * are registered by separate, independently ordered plugins), so a report that only took effect\n * via a later engine-side transform would still lose the race whenever the sync happens to run\n * first. A direct write has no such ordering dependency. Call inside a read/update. No-op if no\n * engine is currently registered for the active editor.\n */\nexport function $reportDestroyedDisplayOwner(node) {\n pendedOwnersByEditor.get($getEditor())?.add(node.getKey());\n}\n","/**\n * The shared display-run drivers: ONE self-healing sync transform and ONE caret-held reporter,\n * both parameterized by a {@link DisplayRunDescriptor}. Every engine-owned display kind runs\n * through these, so the four duties (construct, self-heal-with-grace, pend-on-edit/delete,\n * settle-on-departure) cannot diverge per kind.\n *\n * Descriptor INSTANCES live one layer up (displayRun/displayRunRegistry.ts) because they need the\n * converters; taking a descriptor as a parameter keeps these drivers importable from anywhere in\n * `nodes/usj`.\n */\nimport { $createAttributeRunNode } from \"./AttributeRunNode.js\";\nimport { DELTA_CHANGE_TAG } from \"./node-constants.js\";\nimport { $isDescendantOf } from \"./node.utils.js\";\nimport { $isDisplayOwnerPended, $reportDestroyedDisplayOwner, } from \"./pendedDisplayOwners.utils.js\";\nimport { $createMarkerNode } from \"../features/MarkerNode.js\";\nimport { textTypeState } from \"../collab/delta.state.js\";\nimport { $createTextNode, $getEditor, $getNodeByKey, $getSelection, $hasUpdateTag, $isRangeSelection, $isTextNode, $setState, } from \"lexical\";\n/** Whether any piece of the run is currently in the tree. */\nfunction runHasPieces(pieces) {\n return Boolean(pieces.opener || pieces.value || pieces.closer || pieces.wrapper);\n}\n/**\n * Whether any of the run's BYTE pieces — opener, value, closer — is currently in the tree. A\n * surviving `AttributeRunNode` wrapper alone does not count: an emptied wrapper is the transient\n * husk `$clearRun` deliberately leaves behind, not displayed bytes. Destruction detection reads\n * THIS predicate, so deleting a wrapped run's visible bytes reports the owner even when the\n * deletion left the husk standing ({@link runHasPieces} would see the husk and call the run\n * alive, and the sync would then resurrect bytes the user just deleted).\n */\nfunction runHasByteContent(pieces) {\n return Boolean(pieces.opener || pieces.value || pieces.closer);\n}\n/**\n * Whether a display kind's canonical value bytes OPEN with the structural whitespace separator\n * that stands between a marker and its value (`\\va` + NBSP + `2`, `\\cat` + NBSP + `People`).\n * Kinds whose value carries no such separator — a char span's `|…` attribute bytes, an optbreak's\n * `//` token — are outside the licence {@link valueDiverges} grants and compare byte-for-byte.\n */\nfunction opensWithSeparator(text) {\n return /^\\s/.test(text);\n}\n/**\n * Whether the run's DISPLAYED value bytes diverge from the ones the owner's own state calls for.\n *\n * Whitespace FLANKING the value is not a divergence for a kind whose canonical bytes carry a\n * separator ({@link opensWithSeparator}). Whitespace between an attribute marker and its value is\n * structural: the writer emits exactly one separator space whatever the screen shows, so those\n * bytes never reach the document — the same licence a trailing space at the end of a paragraph\n * already has. Counting them as divergence made every such run canonicalize a space the user had\n * just typed with the caret sitting right after it: while the caret held the site the run showed\n * `\\va 2 \\va*`, and on departure the settle re-tokenized it back to `\\va 2\\va*`. That is a\n * keystroke accepted and then discarded, which \"no silent no-ops\" forbids.\n *\n * Two neighbouring edits stay divergences, because neither is whitespace the writer would supply:\n * deleting the separator outright (the value no longer opens with whitespace at all), and\n * whitespace typed INSIDE the value, which genuinely respells it (`12` → `1 2`) and must settle\n * onto owner state like any other value edit.\n */\nfunction valueDiverges(actual, expected) {\n if (actual === expected)\n return false;\n if (actual === undefined || expected === undefined)\n return true;\n if (!opensWithSeparator(expected) || !opensWithSeparator(actual))\n return true;\n const value = expected.trim();\n return value === \"\" || actual.trim() !== value;\n}\n/**\n * Whether `pieces` diverge from `expected`.\n *\n * A run that should not exist diverges the moment any piece survives. A run that should exist\n * diverges when its value's bytes differ ({@link valueDiverges}, which excuses the structural\n * whitespace flanking a separator-bearing value), when either glyph of a glyph-bearing kind is\n * missing, or — for a wrapper-written kind — when the pieces are still riding LOOSE: the wrap\n * migration is itself a divergence to heal, and treating it as one here is what lets the caret\n * grace it and the settle finish it, instead of the migration being deferred forever with nothing\n * pending it.\n */\nexport function $runDiverges(descriptor, pieces, expected) {\n if (!expected.wantsRun)\n return runHasPieces(pieces);\n if (valueDiverges(pieces.value?.getTextContent(), expected.valueText))\n return true;\n // A closer-less kind (`closerSyntax: \"none\"` — a chapter's `\\cp`) owes only its opener; every\n // other glyph-bearing kind owes both glyphs.\n if (descriptor.byteFormat.glyphs !== \"none\") {\n if (!pieces.opener)\n return true;\n if (descriptor.byteFormat.closerSyntax !== \"none\" && !pieces.closer)\n return true;\n }\n return descriptor.byteFormat.writer === \"wrapper\" && pieces.wrapper === undefined;\n}\n/**\n * True when `owner`'s run diverges from `$runDiverges` for EXACTLY the wrap-migration reason: the\n * value's bytes already match, both glyphs of a glyph-bearing kind are present, and the ONLY thing\n * missing is the wrapper itself. This is the one slice of `$runDiverges` the marker-edit engine's\n * departure settle may finish by calling `$syncDisplayRun` directly, instead of routing to a Tier-2\n * re-tokenize: every OTHER divergence (a missing/stale value, a missing glyph) means the DISPLAYED\n * bytes have genuinely drifted from `owner`'s own state — deleted or edited content — and only\n * re-tokenizing (which reads the displayed bytes back into state) can settle that without\n * resurrecting what the user just changed. A kind whose `byteFormat.writer` is not `\"wrapper\"`\n * (nothing to migrate) always returns `false`.\n */\nexport function $runNeedsOnlyWrapMigration(descriptor, owner) {\n if (descriptor.byteFormat.writer !== \"wrapper\")\n return false;\n const expected = descriptor.expectedPieces(owner);\n if (!expected.wantsRun)\n return false;\n const pieces = descriptor.scanPieces(owner);\n if (valueDiverges(pieces.value?.getTextContent(), expected.valueText))\n return false;\n if (descriptor.byteFormat.glyphs !== \"none\") {\n if (!pieces.opener)\n return false;\n if (descriptor.byteFormat.closerSyntax !== \"none\" && !pieces.closer)\n return false;\n }\n return pieces.wrapper === undefined;\n}\n/** True when NO piece of `owner`'s run remains — the run was deleted outright, as opposed to a\n * partial mangle that still leaves debris to repair around. */\nexport function $runEntirelyAbsent(descriptor, owner) {\n return !runHasByteContent(descriptor.scanPieces(owner));\n}\n/**\n * True when `owner`'s run diverges from what its state calls for but the collapsed caret holds the\n * run's SITE, so the sync must leave it alone and the marker-edit engine settle it on departure.\n *\n * Two arms are shared by every WRITER-DRIVEN kind: the caret anywhere inside the run's wrapper\n * subtree (an element point can land on the wrapper itself, which no piece-specific arm\n * recognizes), and the caret inside a live value node. Everything else is the descriptor's own\n * `graceSite` — the insertion-point and glyph-debris anchors that differ by tree shape.\n *\n * A `\"kind-owned\"` writer (the separator, the nested glyph) skips both shared arms and `$runDiverges`\n * entirely: its `expectedPieces`/`scanPieces` are deliberately empty, so the shared divergence rule\n * would never see anything to grace. Its `graceSite` is authoritative on its own instead.\n */\nexport function $caretHoldsRunSite(descriptor, owner) {\n if (!owner.isAttached())\n return false;\n // A kind-owned writer keeps its own divergence rule (a separator's missing NBSP is not a run\n // piece at all), so its graceSite is authoritative on its own.\n if (descriptor.byteFormat.writer === \"kind-owned\")\n return descriptor.graceSite(owner, {});\n const expected = descriptor.expectedPieces(owner);\n const pieces = descriptor.scanPieces(owner);\n if (!$runDiverges(descriptor, pieces, expected))\n return false;\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n const { wrapper, value } = pieces;\n if (wrapper && (anchorNode.is(wrapper) || $isDescendantOf(anchorNode, wrapper.getKey())))\n return true;\n // A live value node is the mid-edit case: the caret is IN the bytes, so nothing else matters.\n if (value)\n return anchorNode.is(value);\n return descriptor.graceSite(owner, pieces);\n}\n/**\n * Whether `owner`'s run was destroyed by something other than this sync since the last committed\n * state. Gated on `wantsRun` so a call can never react to its own heal-removal: the writer below\n * only removes pieces when the run is NOT wanted, the opposite of this condition.\n *\n * Detecting the destruction from the last-committed state, inside the sync's own decision path,\n * keeps the result independent of which plugin's transforms happen to run first on a shared dirty\n * node — mount order varies across hosts. A remote collab apply is excluded: it clears owner state\n * directly, so the run is already unwanted before this sync next runs.\n */\nfunction $runDestroyedSinceLastCommit(descriptor, owner, expected, pieces) {\n if (!expected.wantsRun)\n return false;\n // Byte pieces only: a deletion that empties the wrapper but leaves the husk standing is still\n // a destroyed run — see runHasByteContent.\n if (runHasByteContent(pieces))\n return false;\n if ($hasUpdateTag(DELTA_CHANGE_TAG))\n return false;\n return $getEditor()\n .getEditorState()\n .read(() => {\n const previous = $getNodeByKey(owner.getKey());\n if (!previous || !descriptor.ownerPredicate(previous))\n return false;\n return runHasByteContent(descriptor.scanPieces(previous));\n });\n}\n/** Remove every surviving piece — the \"no run wanted\" path. An emptied wrapper is left in place:\n * it is a transient husk the marker-edit engine's settle removes, so the removal and the owner's\n * own deletion policy stay in one place. */\nfunction $clearRun(pieces) {\n pieces.opener?.remove();\n pieces.value?.remove();\n pieces.closer?.remove();\n}\nfunction $createValueNode(text) {\n const value = $createTextNode(text);\n $setState(value, textTypeState, \"attribute\");\n return value;\n}\n/** Ensure the run's wrapper exists, healing any loose survivors forward into a freshly created one\n * inserted where the run belongs. This is the one migration path from loose to wrapped. */\nfunction $ensureWrapper(descriptor, owner, pieces) {\n if (pieces.wrapper)\n return pieces.wrapper;\n const { runKind, insertRunAfter } = descriptor.byteFormat;\n const anchor = insertRunAfter?.(owner);\n if (!runKind || !anchor)\n return undefined;\n const created = $createAttributeRunNode(runKind);\n anchor.insertAfter(created);\n if (pieces.opener)\n created.append(pieces.opener);\n if (pieces.value)\n created.append(pieces.value);\n if (pieces.closer)\n created.append(pieces.closer);\n return created;\n}\n/** Build or repair the run AROUND whatever pieces survive, in their fixed order. A found piece\n * already sits in its correct position (the scan reads them in order), so a missing one is\n * inserted into its gap and a leftover is reused in place, never duplicated. */\nfunction $writeRun(descriptor, owner, pieces, expected) {\n const { writer, glyphs, glyphMarker, closerSyntax, insertRunBefore } = descriptor.byteFormat;\n if (writer === \"owner-children\") {\n const anchor = insertRunBefore?.(owner);\n if (!anchor || expected.valueText === undefined)\n return;\n if ($isTextNode(pieces.value))\n pieces.value.setTextContent(expected.valueText);\n else\n anchor.insertBefore($createValueNode(expected.valueText));\n return;\n }\n const wrapper = $ensureWrapper(descriptor, owner, pieces);\n if (!wrapper || glyphs === \"none\" || !glyphMarker || !closerSyntax)\n return;\n const opener = pieces.opener ??\n (() => {\n const created = $createMarkerNode(glyphMarker(owner), \"opening\");\n const first = wrapper.getFirstChild();\n if (first)\n first.insertBefore(created);\n else\n wrapper.append(created);\n return created;\n })();\n let value = pieces.value;\n if (expected.valueText === undefined) {\n value?.remove();\n value = undefined;\n }\n else if ($isTextNode(value)) {\n // The same licence the divergence rule grants: a value the user only padded with structural\n // whitespace is left exactly as typed, even when a missing glyph is what brought the writer here.\n if (valueDiverges(value.getTextContent(), expected.valueText))\n value.setTextContent(expected.valueText);\n }\n else {\n value = $createValueNode(expected.valueText);\n opener.insertAfter(value);\n }\n // A closer-less kind never builds a trailing glyph; its wrapper bounds the value instead.\n if (closerSyntax !== \"none\" && !pieces.closer)\n (value ?? opener).insertAfter($createMarkerNode(closerSyntax === \"selfClosing\" ? \"\" : glyphMarker(owner), closerSyntax));\n}\n/**\n * Heal `owner`'s display run to what its own state calls for: insert a missing run, rewrite a\n * stale one, migrate a loose one into its wrapper, or remove one that is no longer wanted —\n * except while the engine holds the owner pended, while the run was just destroyed by something\n * else (reported so the engine settles it), or while the caret holds the run's site. Idempotent —\n * writes only on change, so the registering transform converges.\n *\n * Kinds whose pieces the driver does not write (`\"kind-owned\"` and `\"read-only\"` byte formats)\n * return immediately: they join the registry for their pend/settle duties only.\n *\n * @param descriptor - The kind's descriptor.\n * @param owner - The owner whose run to sync. Must be called inside `editor.update()`.\n */\nexport function $syncDisplayRun(descriptor, owner) {\n const { writer } = descriptor.byteFormat;\n if (writer === \"kind-owned\" || writer === \"read-only\")\n return;\n // An earlier transform in the same pass may have merged or removed the owner; a detached node\n // has no tree position to derive from.\n if (!owner.isAttached())\n return;\n const expected = descriptor.expectedPieces(owner);\n const pieces = descriptor.scanPieces(owner);\n if (!$runDiverges(descriptor, pieces, expected))\n return;\n if ($isDisplayOwnerPended(owner))\n return;\n if ($runDestroyedSinceLastCommit(descriptor, owner, expected, pieces)) {\n $reportDestroyedDisplayOwner(owner);\n return;\n }\n if ($caretHoldsRunSite(descriptor, owner))\n return;\n if (!expected.wantsRun) {\n $clearRun(pieces);\n return;\n }\n $writeRun(descriptor, owner, pieces, expected);\n}\n/**\n * Sync `owner`'s run for `descriptor`, then pend `owner` while the caret holds the run's site so\n * caret departure settles it. The pairing every registration home needs: the sync leaves a\n * caret-held divergence alone, and without the matching pend nothing would ever settle it — the\n * run would silently resurrect from the owner's still-set state on the next unrelated dirtying.\n *\n * @param descriptor - The kind's descriptor.\n * @param owner - The owner whose run to sync. Must be called inside `editor.update()`.\n * @param pendingKeys - The marker-edit engine's live pending set.\n */\nexport function $syncAndPendDisplayRun(descriptor, owner, pendingKeys) {\n $syncDisplayRun(descriptor, owner);\n if (owner.isAttached() && $caretHoldsRunSite(descriptor, owner))\n pendingKeys.add(owner.getKey());\n}\n","/**\n * Glyph text and document positions: the single place that owns WHICH rendered bytes are display\n * rather than document, and HOW a caret or selection point that names those bytes is re-expressed\n * so an edit never operates on them.\n *\n * ## The property, not a list of node types\n *\n * A glyph text node is a `TextNode` whose rendered text is a PICTURE OF ITS OWN STATE — the\n * document owns none of those bytes. A `MarkerNode`'s `__text` is a cache of (marker, syntax,\n * nested) that only its setters write ({@link $isCanonicalMarkerNode} exists because that cache can\n * drift). A `VerseNode`'s `__text` is a rendering of its `__number`. An attribute display run's\n * text is a rendering of the char span's attributes, and the marker-trailing separator is a\n * rendering of the `[glyph, separator, …content]` layout itself. Membership is decided by that\n * property, and {@link $isGlyphTextNode} is the one place it is decided — a kind wired in\n * separately somewhere else is the recurring defect this module exists to end.\n *\n * ## Two spellings of one place, and why each gesture picks a different one\n *\n * A glyph's bytes are visible, so the caret really can sit between two of them, and the arrow keys\n * really do walk through them one character at a time. Both of a glyph's ENDS, though, name a place\n * on screen that a neighbouring node also names, and nothing the user does can choose between the\n * two spellings. So an edit must pick the spelling under which the glyph is NOT one of its\n * operands, and which spelling that is depends on what the edit does with a point:\n *\n * - **A collapsed caret is an insertion point**, and it names no node as an operand. Its illegal\n * region is a glyph's INTERIOR: a node inserted between two display bytes cuts the picture in\n * half and hands the right-hand half to the document as content — a verse number arriving in the\n * file as text, a closing glyph's tail stranded as literal paragraph bytes. An interior point\n * therefore resolves to the glyph's TRAILING end, the end a caret that has already crossed the\n * glyph's first byte is heading for. Both ends are ordinary positions and are left exactly as\n * they are.\n * - **A range endpoint drags its own node into the range.** Its illegal region is the mirror image:\n * a glyph's ENDS, where the endpoint takes the WHOLE glyph node as a wrap target even when it\n * selects none of its bytes. A glyph is its span's identity, so moving one into another span\n * deletes the span it came from — the marker vanishes from the file while its glyph is still on\n * screen. An endpoint at an end is therefore re-spelled onto the neighbouring node, INWARD, so\n * the glyph falls outside the range. An endpoint strictly inside a glyph is left alone: the user\n * can see exactly which bytes are highlighted, and re-tokenizing them is Invariant I working as\n * intended rather than a divergence.\n *\n * Stated once: express the point so the glyph is not an operand. The two arms differ in spelling\n * because insertion and range membership differ in what naming a node means, not because the\n * exclusion rule differs.\n *\n * ## Agreeing with arrow traversal\n *\n * `ArrowNavigationPlugin` walks glyph text one character at a time, so its idea of where a glyph\n * begins and ends is the same as this module's: the interior is inside, the two ends are the\n * boundary positions, and a caret at an end has crossed the glyph whole. Nothing here calls a\n * position outside a glyph that a press treats as inside it, or the reverse. The spelling agrees\n * too — the trailing end resolved to above is written as (glyph, its length), which is exactly the\n * spelling that plugin's canonicalizer prefers for that screen location (the end of the nearest\n * preceding visible text) over the neighbouring node's offset 0. The one thing this module does\n * NOT do is move the caret for traversal's sake: it re-expresses the point an EDIT acts at.\n *\n * ## What this module does NOT own\n *\n * - WHICH boundary an edit should prefer once the point is legal — `caretBoundaries.utils.ts` owns\n * the content-boundary convention, and `ArrowNavigationPlugin` owns the traversal one.\n * - Which nodes are skipped when counting USJ content indexes\n * (`$shouldIgnoreNodeForContentIndexes`, node.utils.ts). That question is broader: it also skips\n * empty and NBSP-only text and line breaks, which are legal insertion hosts and must stay so.\n * - Whether a glyph's bytes are at rest or mid-edit ({@link $isCanonicalMarkerNode}), and what to do\n * about it — the marker-edit engine owns healing and settling.\n */\nimport { $isImmutableUnmatchedNode } from \"../features/ImmutableUnmatchedNode.js\";\nimport { $isCanonicalMarkerNode, $isMarkerNode } from \"../features/MarkerNode.js\";\nimport { MARKER_TRAILING_SPACE_TEXT_TYPE, textTypeState } from \"../collab/delta.state.js\";\nimport { $isCharNode } from \"./CharNode.js\";\nimport { $isVerseNode } from \"./VerseNode.js\";\nimport { $getSelection, $getState, $isRangeSelection, $isTextNode, } from \"lexical\";\n/**\n * Whether `node`'s rendered text is engine-owned DISPLAY bytes — a picture of the node's own state\n * — rather than document content. See the module doc for why this is one property and not four\n * unrelated node types; anything new whose text is derived from state belongs here, and nowhere\n * else.\n *\n * Read-only: call inside `editor.getEditorState().read(...)` or an update.\n */\nexport function $isGlyphTextNode(node) {\n if (!$isTextNode(node))\n return false;\n // A marker glyph (`\\add`, `\\add*`, a paragraph's `\\p` prefix) and an editable verse marker\n // (`\\v` + NBSP + number + space) both render their own state as text.\n if ($isMarkerNode(node) || $isVerseNode(node))\n return true;\n // An unmatched-closer glyph (`\\f*` with no opener) is likewise a picture of its own\n // `__marker` state. It is a TextNode subclass, so without this arm the caret is never\n // normalized out of it and an insert can split the glyph (`\\f` + inserted note + a stranded\n // `*`).\n if ($isImmutableUnmatchedNode(node))\n return true;\n // The attribute display run (`|gloss=\"x\"`) renders the span's attributes; the marker-trailing\n // separator renders the prefix layout. Both are tagged rather than typed, so they are read off\n // the tag the engine writes when it builds them.\n const textType = $getState(node, textTypeState);\n return textType === \"attribute\" || textType === MARKER_TRAILING_SPACE_TEXT_TYPE;\n}\n/**\n * Whether a caret point (`node`, `offset`) sits INSIDE marker glyph text — as opposed to at the\n * TRAILING EDGE of a char span's canonical closing glyph, which is genuinely AFTER the span:\n * arrow traversal and clicks park the caret there at the end of a paragraph whose last child is\n * an inline span, and Enter there is a paragraph action (open the Enter menu), not a marker\n * edit. A NON-canonical (pended, mid-edit) closer keeps its trailing edge \"inside\": the caret is\n * there because the user is editing the glyph byte-by-byte, and Enter must keep settling that\n * edit instead of splitting. An OPENING glyph's trailing edge stays \"inside\" too — it is the\n * span's interior (the separator/content follows it). Deliberately scoped to CHAR-parented\n * closers: a display-run wrapper's closer (`\\va*`, `\\cat*`, a milestone's `\\*`) keeps today's\n * swallow — a split at that caret would land inside the `AttributeRunNode`, a path with no\n * close-and-reopen story yet.\n *\n * This asks whether the caret is in a marker the user may be EDITING, which is why an opening\n * glyph's trailing edge counts. It is not the same question as whether the point is a legal\n * document POSITION ({@link $normalizeSelectionOutOfGlyphText}), where both of a glyph's ends are\n * ordinary places to stand. Keeping the two in one module is deliberate: they read the same bytes\n * and the difference between them is the thing that is easy to get wrong.\n *\n * Read-only: call inside `editor.getEditorState().read(...)` or an update.\n */\nexport function $isPointInMarkerGlyphText(node, offset) {\n if (!$isMarkerNode(node))\n return false;\n return !(offset === node.getTextContentSize() &&\n node.getMarkerSyntax() !== \"opening\" &&\n $isCanonicalMarkerNode(node) &&\n $isCharNode(node.getParent()));\n}\n/**\n * Whether a range selection's caret end sits inside marker glyph text — the guard\n * `MarkerEditPlugin` and `UsjNodesMenuPlugin` use to swallow Enter presses inside a marker. Read\n * from the FOCUS point (the live cursor end, correct even for a backward range — the project's\n * standing rule for \"the node the caret is in\"); for the common collapsed caret the two points\n * coincide. The trailing edge of a canonical closer does NOT count (see\n * {@link $isPointInMarkerGlyphText}).\n * Read-only: call inside `editor.getEditorState().read(...)` or an update.\n */\nexport function $isSelectionInMarkerNode() {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return false;\n return $isPointInMarkerGlyphText(selection.focus.getNode(), selection.focus.offset);\n}\n/** The glyph text node a point is expressed against, if it is expressed against one. */\nfunction $glyphAtPoint(point) {\n if (point.type !== \"text\")\n return undefined;\n const node = point.getNode();\n return $isTextNode(node) && $isGlyphTextNode(node) ? node : undefined;\n}\n/** The glyph node a point sits strictly between two display bytes of, if it does. */\nfunction $glyphAroundPoint(point) {\n const node = $glyphAtPoint(point);\n if (!node)\n return undefined;\n return point.offset > 0 && point.offset < node.getTextContentSize() ? node : undefined;\n}\n/** The glyph node a point names one of the ENDS of, if it does. */\nfunction $glyphAtPointEnd(point) {\n const node = $glyphAtPoint(point);\n if (!node)\n return undefined;\n return point.offset === 0 || point.offset === node.getTextContentSize() ? node : undefined;\n}\nfunction snapshotPoint(point) {\n return { key: point.key, offset: point.offset, type: point.type };\n}\nfunction restorePoint(point, snapshot) {\n point.set(snapshot.key, snapshot.offset, snapshot.type);\n}\n/**\n * The position a range endpoint naming a glyph's end should move to, so the glyph stops being part\n * of the range — or `undefined` when it should not move.\n *\n * Walks siblings in `direction`, because glyphs sit next to each other (a paragraph's prefix is a\n * glyph followed by its separator) and one step would only move the problem along. The walk is\n * ALL-OR-NOTHING: it must reach a text node that is not itself a glyph, or the endpoint stays put.\n * Landing anywhere else would trade one glyph for another, and the next thing along is often an\n * element — a char span — where stepping in would change which span the range starts in. Deciding\n * span membership is not this module's call.\n */\nfunction $landingPastGlyphs(point, direction) {\n let cursor = $glyphAtPointEnd(point);\n while (cursor) {\n const sibling = direction === \"next\" ? cursor.getNextSibling() : cursor.getPreviousSibling();\n if (!$isTextNode(sibling))\n return undefined;\n if (!$isGlyphTextNode(sibling))\n return { node: sibling, offset: direction === \"next\" ? 0 : sibling.getTextContentSize() };\n cursor = sibling;\n }\n return undefined;\n}\n/** Apply {@link $landingPastGlyphs} to `point`, reporting whether it moved. */\nfunction $stepPointPastGlyphs(point, direction) {\n const landing = $landingPastGlyphs(point, direction);\n if (!landing)\n return false;\n point.set(landing.node.getKey(), landing.offset, \"text\");\n return true;\n}\n/**\n * Re-express `selection` so no glyph is an operand of the edit about to run, and report whether\n * anything moved. The one exclusion point: every gesture that places a node at a caret or acts on a\n * selected range routes through this instead of testing for its own favourite glyph shape.\n *\n * A COLLAPSED selection is an insertion point: a point in a glyph's interior resolves to that\n * glyph's trailing end, and the ends are left alone. A RANGE has its two endpoints stepped inward\n * past any glyph they name an end of, and interior endpoints are left alone. The module doc carries\n * the reasoning for the asymmetry — it is one rule about operands, not two policies.\n *\n * Both range steps are all-or-nothing (see {@link $landingPastGlyphs}), and a trim that would\n * invert or collapse the selection is abandoned whole — that happens when both endpoints name the\n * same glyph, as a selection of exactly one glyph does. Leaving the range as the user made it beats\n * guessing at a position this module cannot justify.\n *\n * Mutating: call inside `editor.update()`, before the edit reads the selection.\n */\nexport function $normalizeSelectionOutOfGlyphText(selection) {\n if (selection.isCollapsed()) {\n const glyph = $glyphAroundPoint(selection.anchor);\n if (!glyph)\n return false;\n const trailingEnd = glyph.getTextContentSize();\n selection.anchor.set(glyph.getKey(), trailingEnd, \"text\");\n selection.focus.set(glyph.getKey(), trailingEnd, \"text\");\n return true;\n }\n const wasBackward = selection.isBackward();\n const start = wasBackward ? selection.focus : selection.anchor;\n const end = wasBackward ? selection.anchor : selection.focus;\n const before = [snapshotPoint(start), snapshotPoint(end)];\n const movedStart = $stepPointPastGlyphs(start, \"next\");\n const movedEnd = $stepPointPastGlyphs(end, \"previous\");\n if (!movedStart && !movedEnd)\n return false;\n if (selection.isCollapsed() || selection.isBackward() !== wasBackward) {\n restorePoint(start, before[0]);\n restorePoint(end, before[1]);\n return false;\n }\n return true;\n}\n","/**\n * A block-level container for one verse, used by the block verse view (`ViewOptions.verseLayout`).\n *\n * A verse is normally an inline milestone marker with no element around its text, so there is no\n * per-verse box for a layout to position. This node is that box: it holds the verse's paragraphs -\n * plural, so poetry keeps its `q1`/`q2` line structure - while still occupying a single row.\n *\n * Read-only. It is produced by the USJ-to-editor adaptor and has no editing surface; it is not\n * round-trippable back to USJ, because a source paragraph spanning several verses is split across\n * their blocks.\n */\nimport { $applyNodeReplacement, ElementNode, } from \"lexical\";\nimport { parseVerseRange } from \"./node.utils.js\";\nexport const VERSE_BLOCK_TYPE = \"verse-block\";\nexport const VERSE_BLOCK_VERSION = 1;\nexport const VERSE_BLOCK_CLASS_NAME = \"verse-block\";\nexport class VerseBlockNode extends ElementNode {\n /** The verse marker verbatim. Authoritative: the range is derived from it, never stored. */\n __number;\n constructor(verseNumber = \"\", key) {\n super(key);\n this.__number = verseNumber;\n }\n static getType() {\n return VERSE_BLOCK_TYPE;\n }\n static clone(node) {\n return new VerseBlockNode(node.__number, node.__key);\n }\n static importJSON(serializedNode) {\n return $createVerseBlockNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super.updateFromJSON(serializedNode).setNumber(serializedNode.number);\n }\n setNumber(verseNumber) {\n if (this.__number === verseNumber)\n return this;\n const self = this.getWritable();\n self.__number = verseNumber;\n return self;\n }\n getNumber() {\n return this.getLatest().__number;\n }\n /** The first and last verse numbers this block covers. A bridge covers more than one. */\n getRange() {\n return parseVerseRange(this.getNumber());\n }\n createDOM() {\n const dom = document.createElement(\"div\");\n dom.classList.add(VERSE_BLOCK_CLASS_NAME);\n setVerseAttributes(dom, this.__number);\n return dom;\n }\n updateDOM(prevNode, dom) {\n if (prevNode.__number !== this.__number)\n setVerseAttributes(dom, this.__number);\n return false;\n }\n // No `exportDOM`/`importDOM`: Lexical's default export already emits `createDOM`'s element, so\n // the HTML flavor of a copied passage carries these wrappers, which an ordinary paste unwraps\n // because there is no import counterpart. Block verse is read-only, so pasting one back in is\n // not a supported flow.\n /**\n * Keeps the wrapper out of the `application/x-lexical-editor` clipboard payload; its paragraphs\n * travel in its place.\n *\n * Every platform editor shares one Lexical namespace, so a payload copied from a block verse\n * editor is accepted when pasted into an ordinary one - which registers no `VerseBlockNode`, so\n * `$parseSerializedNode` would throw on the unknown type and `onError` would rethrow and tear the\n * editor down. `TypedMarkNode` and `UnknownNode` exclude themselves the same way; Lexical's\n * clipboard only ever asks with `\"html\"`.\n */\n excludeFromCopy(destination) {\n return destination !== \"clone\";\n }\n exportJSON() {\n return {\n ...super.exportJSON(),\n type: VERSE_BLOCK_TYPE,\n number: this.getNumber(),\n version: VERSE_BLOCK_VERSION,\n };\n }\n canBeEmpty() {\n return false;\n }\n}\n/**\n * Attributes a layout consumes to place the block on a row; `data-verse-start`/`-end` let a bridge\n * span rows. Deliberately not `data-number`, which the inner verse marker span already uses.\n */\nfunction setVerseAttributes(dom, verseNumber) {\n const { start, end } = parseVerseRange(verseNumber);\n // Imported USFM can carry a reversed bridge like `3-1`, which would hand a layout a negative\n // span. Publish the range only when it describes one, and remove rather than skip the\n // attributes otherwise, so a stale range from a previous number cannot be read as this verse's.\n const isPublishable = !isNaN(start) && !isNaN(end) && start <= end;\n dom.setAttribute(\"data-verse-number\", verseNumber);\n setOrRemoveAttribute(dom, \"data-verse-start\", isPublishable ? start : NaN);\n setOrRemoveAttribute(dom, \"data-verse-end\", isPublishable ? end : NaN);\n}\nfunction setOrRemoveAttribute(dom, name, value) {\n if (isNaN(value))\n dom.removeAttribute(name);\n else\n dom.setAttribute(name, value.toString());\n}\nexport function $createVerseBlockNode(verseNumber) {\n return $applyNodeReplacement(new VerseBlockNode(verseNumber));\n}\nexport function $isVerseBlockNode(node) {\n return node instanceof VerseBlockNode;\n}\nexport function isSerializedVerseBlockNode(node) {\n return node?.type === VERSE_BLOCK_TYPE;\n}\n","import { ImmutableTypedTextNode } from \"../features/ImmutableTypedTextNode.js\";\nimport { ImmutableUnmatchedNode } from \"../features/ImmutableUnmatchedNode.js\";\nimport { MarkerNode } from \"../features/MarkerNode.js\";\nimport { UnknownNode } from \"../features/UnknownNode.js\";\nimport { AttributeRunNode } from \"./AttributeRunNode.js\";\nimport { BookNode } from \"./BookNode.js\";\nimport { ChapterNode } from \"./ChapterNode.js\";\nimport { CharNode } from \"./CharNode.js\";\nimport { ImmutableChapterNode } from \"./ImmutableChapterNode.js\";\nimport { ImmutableTableNode } from \"./ImmutableTableNode.js\";\nimport { ImmutableTableRowNode } from \"./ImmutableTableRowNode.js\";\nimport { ImmutableTableCellNode } from \"./ImmutableTableCellNode.js\";\nimport { $createImpliedParaNode, ImpliedParaNode } from \"./ImpliedParaNode.js\";\nimport { MilestoneNode } from \"./MilestoneNode.js\";\nimport { NoteNode } from \"./NoteNode.js\";\nimport { ParaNode } from \"./ParaNode.js\";\nimport { VerseNode } from \"./VerseNode.js\";\nimport { ParagraphNode } from \"lexical\";\nexport * from \"./attributeDisplay.utils.js\";\nexport * from \"./AttributeRunNode.js\";\nexport * from \"./BookNode.js\";\nexport * from \"./caretBoundaries.utils.js\";\nexport * from \"./ChapterNode.js\";\nexport * from \"./charGlyphs.utils.js\";\nexport * from \"./CharNode.js\";\nexport * from \"./charStack.utils.js\";\nexport * from \"./displayRunDescriptor.js\";\nexport * from \"./displayRunSync.utils.js\";\nexport * from \"./glyphPositions.utils.js\";\nexport * from \"./ImmutableChapterNode.js\";\nexport * from \"./ImmutableTableNode.js\";\nexport * from \"./ImmutableTableRowNode.js\";\nexport * from \"./ImmutableTableCellNode.js\";\nexport * from \"./ImpliedParaNode.js\";\nexport * from \"./markerSeparators.utils.js\";\nexport * from \"./MilestoneNode.js\";\nexport * from \"./nestedGlyphs.utils.js\";\nexport * from \"./node-constants.js\";\nexport * from \"./node.utils.js\";\nexport * from \"./NoteNode.js\";\nexport * from \"./ParaNode.js\";\nexport * from \"./pendedDisplayOwners.utils.js\";\nexport * from \"./VerseBlockNode.js\";\nexport * from \"./VerseNode.js\";\n// `VerseBlockNode` is deliberately absent below: it is registered only by editors using the block\n// verse layout, so every other editor's node registry is unchanged. See `usjBlockVerseNodes`.\n// Safe because the node excludes itself from the clipboard payload, so a passage copied out of a\n// block verse editor cannot reach an editor that has no class for it.\nexport const usjBaseNodes = [\n BookNode,\n ImmutableChapterNode,\n ChapterNode,\n VerseNode,\n CharNode,\n NoteNode,\n MilestoneNode,\n MarkerNode,\n UnknownNode,\n ImmutableTypedTextNode,\n ImmutableUnmatchedNode,\n ParaNode,\n ImpliedParaNode,\n ImmutableTableNode,\n ImmutableTableRowNode,\n ImmutableTableCellNode,\n // The forward adaptor (usj-editor.adaptor.ts, platform) serializes editable-mode verse/milestone\n // display runs as AttributeRunNode wrappers, and this package's own self-healing sync\n // (displayRunSync.utils.ts's shared $syncDisplayRun driver, parameterized by each kind's own\n // descriptor) constructs one whenever it heals a run forward from a loose or missing shape —\n // every USJ-shaped editor needs the class registered, not only shared-react's (a non-react host,\n // e.g. packages/scribe's NoteEditor, builds its editor straight from usjBaseNodes with no\n // react-specific node list).\n AttributeRunNode,\n {\n replace: ParagraphNode,\n with: () => $createImpliedParaNode(),\n withKlass: ImpliedParaNode,\n },\n];\n","/** Generated file using `nx generate markers-data` with 'tools/usfm-markers/src/generators/markers-data/data/usfm.sty' */\n/**\n * Default project `StyleInfo` derived from the bundled `usfm.sty` stylesheet.\n *\n * @public\n */\nexport const defaultStyleInfo = {\n markers: {\n id: {\n marker: \"id\",\n styleType: \"paragraph\",\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\", \"book\"],\n description: \"File identification information (BOOKID, FILENAME, EDITOR, MODIFICATION DATE)\",\n fontSize: 12,\n },\n usfm: {\n marker: \"usfm\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"File markup version information\",\n fontSize: 12,\n },\n ide: {\n marker: \"ide\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"File encoding information\",\n fontSize: 12,\n },\n h: {\n marker: \"h\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Running header text for a book (basic)\",\n fontSize: 12,\n },\n h1: {\n marker: \"h1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Running header text\",\n fontSize: 12,\n },\n h2: {\n marker: \"h2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Running header text, left side of page\",\n fontSize: 12,\n },\n h3: {\n marker: \"h3\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Running header text, right side of page\",\n fontSize: 12,\n },\n toc1: {\n marker: \"toc1\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\", \"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Long table of contents text\",\n fontSize: 12,\n bold: true,\n italic: true,\n color: \"#004000\",\n },\n toc2: {\n marker: \"toc2\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\", \"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Short table of contents text\",\n fontSize: 12,\n italic: true,\n color: \"#004000\",\n },\n toc3: {\n marker: \"toc3\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\", \"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Book Abbreviation\",\n fontSize: 12,\n bold: true,\n italic: true,\n color: \"#800000\",\n },\n toca1: {\n marker: \"toca1\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Alternative language long table of contents text\",\n fontSize: 10,\n italic: true,\n color: \"#808080\",\n },\n toca2: {\n marker: \"toca2\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Alternative language short table of contents text\",\n fontSize: 10,\n italic: true,\n color: \"#808080\",\n },\n toca3: {\n marker: \"toca3\",\n styleType: \"paragraph\",\n occursUnder: [\"h\", \"h1\", \"h2\", \"h3\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Alternative language book Abbreviation\",\n fontSize: 10,\n italic: true,\n color: \"#808080\",\n },\n rem: {\n marker: \"rem\",\n styleType: \"paragraph\",\n occursUnder: [\"id\", \"ide\", \"c\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"Comments and remarks\",\n fontSize: 12,\n color: \"#0000FF\",\n },\n sts: {\n marker: \"sts\",\n styleType: \"paragraph\",\n occursUnder: [\"id\", \"ide\", \"c\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"Status of this file\",\n fontSize: 12,\n color: \"#0000FF\",\n },\n restore: {\n marker: \"restore\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 99,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"Project restore information\",\n fontSize: 12,\n color: \"#0000FF\",\n },\n imt: {\n marker: \"imt\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 5,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction major title, level 1 (if single level) (basic)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n imt1: {\n marker: \"imt1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 5,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction major title, level 1 (if multiple levels)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n imt2: {\n marker: \"imt2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 5,\n textType: \"other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Introduction major title, level 2\",\n fontSize: 13,\n italic: true,\n justification: \"center\",\n spaceBefore: 6,\n spaceAfter: 3,\n },\n imt3: {\n marker: \"imt3\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 5,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"Introduction major title, level 3\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceBefore: 2,\n spaceAfter: 2,\n },\n imt4: {\n marker: \"imt4\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 5,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"Introduction major title, level 4 (usually within parenthesis)\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n spaceBefore: 2,\n spaceAfter: 2,\n },\n imte: {\n marker: \"imte\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 7,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction major title at introduction end, level 1 (if single level)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n imte1: {\n marker: \"imte1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 7,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction major title at introduction end, level 1 (if multiple levels)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n imte2: {\n marker: \"imte2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 7,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Introduction major title at introduction end, level 2\",\n fontSize: 16,\n italic: true,\n justification: \"center\",\n spaceAfter: 2,\n },\n is: {\n marker: \"is\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction section heading, level 1 (if single level) (basic)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n is1: {\n marker: \"is1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction section heading, level 1 (if multiple levels)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n is2: {\n marker: \"is2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Introduction section heading, level 2\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n iot: {\n marker: \"iot\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction outline title (basic)\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n io: {\n marker: \"io\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction outline text, level 1 (if single level)\",\n fontSize: 12,\n leftMargin: 0.5,\n },\n io1: {\n marker: \"io1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Introduction outline text, level 1 (if multiple levels) (basic)\",\n fontSize: 12,\n leftMargin: 0.5,\n },\n io2: {\n marker: \"io2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Introduction outline text, level 2\",\n fontSize: 12,\n leftMargin: 0.75,\n },\n io3: {\n marker: \"io3\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"Introduction outline text, level 3\",\n fontSize: 12,\n leftMargin: 1,\n },\n io4: {\n marker: \"io4\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"Introduction outline text, level 4\",\n fontSize: 12,\n leftMargin: 1.25,\n },\n ior: {\n marker: \"ior\",\n styleType: \"character\",\n endMarker: \"ior*\",\n occursUnder: [\"id\", \"io\", \"io1\", \"io2\", \"io3\", \"io4\", \"NEST\"],\n textType: \"Other\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Introduction references range for outline entry; for marking references separately\",\n fontSize: 12,\n },\n ip: {\n marker: \"ip\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph (basic)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n },\n im: {\n marker: \"im\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph, with no first line indent (may occur after poetry)\",\n fontSize: 12,\n },\n ipi: {\n marker: \"ipi\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph, indented, with first line indent\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n imi: {\n marker: \"imi\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph text, indented, with no first line indent\",\n fontSize: 12,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n ili: {\n marker: \"ili\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A list entry, level 1 (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.5,\n },\n ili1: {\n marker: \"ili1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A list entry, level 1 (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.5,\n },\n ili2: {\n marker: \"ili2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"A list entry, level 2\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.75,\n },\n ipq: {\n marker: \"ipq\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph, quote from the body text\",\n fontSize: 12,\n italic: true,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n imq: {\n marker: \"imq\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph, quote from the body text, with no first line indent\",\n fontSize: 12,\n italic: true,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n ipr: {\n marker: \"ipr\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction prose paragraph, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n ib: {\n marker: \"ib\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Introduction blank line\",\n fontSize: 10,\n },\n iq: {\n marker: \"iq\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_1\"],\n description: \"Introduction poetry text, level 1 (if single level)\",\n fontSize: 12,\n italic: true,\n firstLineIndent: -0.75,\n leftMargin: 1,\n },\n iq1: {\n marker: \"iq1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_1\"],\n description: \"Introduction poetry text, level 1 (if multiple levels)\",\n fontSize: 12,\n italic: true,\n firstLineIndent: -0.75,\n leftMargin: 1,\n },\n iq2: {\n marker: \"iq2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_2\"],\n description: \"Introduction poetry text, level 2\",\n fontSize: 12,\n italic: true,\n firstLineIndent: -0.5,\n leftMargin: 1,\n },\n iq3: {\n marker: \"iq3\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_3\"],\n description: \"Introduction poetry text, level 3\",\n fontSize: 12,\n italic: true,\n firstLineIndent: -0.25,\n leftMargin: 1,\n },\n iex: {\n marker: \"iex\",\n styleType: \"paragraph\",\n occursUnder: [\"id\", \"c\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction explanatory or bridge text (e.g. explanation of missing book in Short Old Testament)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n spaceBefore: 4,\n spaceAfter: 4,\n },\n iqt: {\n marker: \"iqt\",\n styleType: \"character\",\n endMarker: \"iqt*\",\n occursUnder: [\n \"imt\",\n \"imt1\",\n \"imt2\",\n \"imt3\",\n \"imt4\",\n \"ib\",\n \"ie\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"im\",\n \"imi\",\n \"imq\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"iot\",\n \"ip\",\n \"ipi\",\n \"ipq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"is\",\n \"is1\",\n \"is2\",\n \"imte\",\n \"imte1\",\n \"imte2\",\n \"iex\",\n ],\n textType: \"Other\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For quoted scripture text appearing in the introduction\",\n fontSize: 12,\n italic: true,\n },\n ie: {\n marker: \"ie\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 6,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Introduction ending marker\",\n fontSize: 10,\n },\n c: {\n marker: \"c\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 8,\n textType: \"ChapterNumber\",\n textProperties: [\"chapter\"],\n description: \"Chapter number (necessary for normal Paratext operation)\",\n fontSize: 18,\n bold: true,\n spaceBefore: 8,\n spaceAfter: 4,\n },\n ca: {\n marker: \"ca\",\n styleType: \"character\",\n endMarker: \"ca*\",\n occursUnder: [\"c\"],\n textType: \"Other\",\n description: \"Second (alternate) chapter number (for coding dual versification; useful for places where different traditions of chapter breaks need to be supported in the same translation)\",\n fontSize: 16,\n italic: true,\n color: \"#228B22\",\n },\n cp: {\n marker: \"cp\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Other\",\n textProperties: [\"paragraph\"],\n description: \"Published chapter number (chapter string that should appear in the published text)\",\n fontSize: 18,\n bold: true,\n color: \"#0000FF\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n cl: {\n marker: \"cl\",\n styleType: \"paragraph\",\n occursUnder: [\"id\", \"c\", \"ms\", \"ms1\", \"ms2\", \"ms3\", \"mr\"],\n textType: \"Other\",\n textProperties: [\"paragraph\"],\n description: 'Chapter label used for translations that add a word such as \"Chapter\" before chapter numbers (e.g. Psalms). The subsequent text is the chapter label.',\n fontSize: 18,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n cd: {\n marker: \"cd\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Chapter Description (Publishing option D, e.g. in Russian Bibles)\",\n fontSize: 11,\n spaceBefore: 8,\n spaceAfter: 4,\n },\n v: {\n marker: \"v\",\n styleType: \"character\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"s3\",\n \"d\",\n \"sp\",\n ],\n textType: \"VerseNumber\",\n textProperties: [\"verse\"],\n description: \"A verse number (Necessary for normal paratext operation) (basic)\",\n fontSize: 12,\n superscript: true,\n },\n va: {\n marker: \"va\",\n styleType: \"character\",\n endMarker: \"va*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"s3\",\n \"d\",\n \"sp\",\n ],\n textType: \"Other\",\n description: \"Second (alternate) verse number (for coding dual numeration in Psalms; see also NRSV Exo 22.1-4)\",\n fontSize: 12,\n superscript: true,\n color: \"#228B22\",\n },\n vp: {\n marker: \"vp\",\n styleType: \"character\",\n endMarker: \"vp*\",\n occursUnder: [\n \"cd\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"s3\",\n \"d\",\n \"sp\",\n ],\n textType: \"Other\",\n description: \"Published verse marker (verse string that should appear in the published text)\",\n fontSize: 12,\n superscript: true,\n color: \"#0000FF\",\n },\n p: {\n marker: \"p\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with first line indent (basic)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n },\n m: {\n marker: \"m\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with no first line indent (may occur after poetry) (basic)\",\n fontSize: 12,\n },\n po: {\n marker: \"po\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Letter opening\",\n fontSize: 12,\n firstLineIndent: 0.125,\n spaceBefore: 4,\n spaceAfter: 4,\n },\n pr: {\n marker: \"pr\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Text refrain (paragraph text, right aligned)\",\n fontSize: 12,\n justification: \"right\",\n },\n cls: {\n marker: \"cls\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Letter Closing\",\n fontSize: 12,\n justification: \"right\",\n },\n pmo: {\n marker: \"pmo\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Embedded text opening\",\n fontSize: 12,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pm: {\n marker: \"pm\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"po\",\n \"psi\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Embedded text paragraph\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pmc: {\n marker: \"pmc\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Embedded text closing\",\n fontSize: 12,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pmr: {\n marker: \"pmr\",\n styleType: \"paragraph\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: 'Embedded text refrain (e.g. Then all the people shall say, \"Amen!\")',\n fontSize: 12,\n justification: \"right\",\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pi: {\n marker: \"pi\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Paragraph text, level 1 indent (if single level), with first line indent; often used for discourse (basic)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pi1: {\n marker: \"pi1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Paragraph text, level 1 indent (if multiple levels), with first line indent; often used for discourse\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n pi2: {\n marker: \"pi2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Paragraph text, level 2 indent, with first line indent; often used for discourse\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.5,\n rightMargin: 0.25,\n },\n pi3: {\n marker: \"pi3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"Paragraph text, level 3 indent, with first line indent; often used for discourse\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.75,\n rightMargin: 0.25,\n },\n pc: {\n marker: \"pc\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, centered (for Inscription)\",\n fontSize: 12,\n justification: \"center\",\n },\n mi: {\n marker: \"mi\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, indented, with no first line indent; often used for discourse\",\n fontSize: 12,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n nb: {\n marker: \"nb\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with no break from previous paragraph text (at chapter boundary) (basic)\",\n fontSize: 12,\n },\n q: {\n marker: \"q\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_1\"],\n description: \"Poetry text, level 1 indent (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.5,\n leftMargin: 0.75,\n },\n q1: {\n marker: \"q1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_1\"],\n description: \"Poetry text, level 1 indent (if multiple levels) (basic)\",\n fontSize: 12,\n firstLineIndent: -0.5,\n leftMargin: 0.75,\n },\n q2: {\n marker: \"q2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_2\"],\n description: \"Poetry text, level 2 indent (basic)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.75,\n },\n q3: {\n marker: \"q3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_3\"],\n description: \"Poetry text, level 3 indent\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.75,\n },\n q4: {\n marker: \"q4\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_4\"],\n description: \"Poetry text, level 4 indent\",\n fontSize: 12,\n firstLineIndent: -0.125,\n leftMargin: 0.75,\n },\n qc: {\n marker: \"qc\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, centered\",\n fontSize: 12,\n justification: \"center\",\n },\n qr: {\n marker: \"qr\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, Right Aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n qs: {\n marker: \"qs\",\n styleType: \"character\",\n endMarker: \"qs*\",\n occursUnder: [\"q\", \"q1\", \"q2\", \"q3\", \"q4\", \"qc\", \"qr\", \"qd\", \"NEST\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, Selah\",\n fontSize: 12,\n italic: true,\n },\n qa: {\n marker: \"qa\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, Acrostic marker/heading\",\n fontSize: 12,\n italic: true,\n },\n qac: {\n marker: \"qac\",\n styleType: \"character\",\n endMarker: \"qac*\",\n occursUnder: [\"q\", \"q1\", \"q2\", \"q3\", \"q4\", \"qc\", \"qr\", \"\", \"NEST\"],\n rank: 4,\n textType: \"Other\",\n textProperties: [\"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, Acrostic markup of the first character of a line of acrostic poetry\",\n fontSize: 12,\n italic: true,\n },\n qm: {\n marker: \"qm\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text, embedded, level 1 indent (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.75,\n leftMargin: 1,\n },\n qm1: {\n marker: \"qm1\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_1\"],\n description: \"Poetry text, embedded, level 1 indent (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: -0.75,\n leftMargin: 1,\n },\n qm2: {\n marker: \"qm2\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_2\"],\n description: \"Poetry text, embedded, level 2 indent\",\n fontSize: 12,\n firstLineIndent: -0.5,\n leftMargin: 1,\n },\n qm3: {\n marker: \"qm3\",\n styleType: \"paragraph\",\n occursUnder: [\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"b\",\n ],\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\", \"level_3\"],\n description: \"Poetry text, embedded, level 3 indent\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 1,\n },\n qd: {\n marker: \"qd\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"A Hebrew musical performance annotation, similar in content to Hebrew descriptive title.\",\n fontSize: 12,\n italic: true,\n leftMargin: 0.25,\n },\n b: {\n marker: \"b\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Poetry text stanza break (e.g. stanza break) (basic)\",\n fontSize: 10,\n },\n mt: {\n marker: \"mt\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 3,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"The main title of the book (if single level)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n mt1: {\n marker: \"mt1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 3,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"The main title of the book (if multiple levels) (basic)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 2,\n spaceAfter: 4,\n },\n mt2: {\n marker: \"mt2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 3,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"A secondary title usually occurring before the main title (basic)\",\n fontSize: 16,\n italic: true,\n justification: \"center\",\n spaceAfter: 2,\n },\n mt3: {\n marker: \"mt3\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 3,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"A secondary title occurring after the main title\",\n fontSize: 16,\n bold: true,\n justification: \"center\",\n spaceBefore: 2,\n spaceAfter: 2,\n },\n mt4: {\n marker: \"mt4\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 3,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"A small secondary title sometimes occurring within parentheses\",\n fontSize: 12,\n justification: \"center\",\n spaceBefore: 2,\n spaceAfter: 2,\n },\n mte: {\n marker: \"mte\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 2,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"The main title of the book repeated at the end of the book, level 1 (if single level)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n mte1: {\n marker: \"mte1\",\n styleType: \"paragraph\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"s3\",\n \"d\",\n ],\n rank: 2,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"The main title of the book repeated at the end of the book, level 1 (if multiple levels)\",\n fontSize: 20,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n mte2: {\n marker: \"mte2\",\n styleType: \"paragraph\",\n occursUnder: [\"mte1\"],\n rank: 2,\n textType: \"Title\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"A secondary title occurring before or after the 'ending' main title\",\n fontSize: 16,\n italic: true,\n justification: \"center\",\n spaceAfter: 2,\n },\n ms: {\n marker: \"ms\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A major section division heading, level 1 (if single level) (basic)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 16,\n spaceAfter: 4,\n },\n ms1: {\n marker: \"ms1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A major section division heading, level 1 (if multiple levels)\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 16,\n spaceAfter: 4,\n },\n ms2: {\n marker: \"ms2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A major section division heading, level 2\",\n fontSize: 14,\n bold: true,\n justification: \"center\",\n spaceBefore: 16,\n spaceAfter: 4,\n },\n ms3: {\n marker: \"ms3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A major section division heading, level 3\",\n fontSize: 14,\n italic: true,\n justification: \"center\",\n spaceBefore: 16,\n spaceAfter: 4,\n },\n mr: {\n marker: \"mr\",\n styleType: \"paragraph\",\n occursUnder: [\"ms\", \"ms1\", \"ms2\", \"ms3\"],\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A major section division references range heading (basic)\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n spaceAfter: 4,\n },\n s: {\n marker: \"s\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A section heading, level 1 (if single level) (basic)\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n s1: {\n marker: \"s1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A section heading, level 1 (if multiple levels)\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n s2: {\n marker: \"s2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"A section heading, level 2 (e.g. Proverbs 22-24)\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n s3: {\n marker: \"s3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: 'A section heading, level 3 (e.g. Genesis \"The First Day\")',\n fontSize: 12,\n italic: true,\n justification: \"left\",\n spaceBefore: 6,\n spaceAfter: 3,\n },\n s4: {\n marker: \"s4\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"A section heading, level 4\",\n fontSize: 12,\n italic: true,\n justification: \"left\",\n spaceBefore: 6,\n spaceAfter: 3,\n },\n sr: {\n marker: \"sr\",\n styleType: \"paragraph\",\n occursUnder: [\"s\", \"s1\", \"s2\", \"s3\", \"s4\"],\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A section division references range heading\",\n fontSize: 12,\n bold: true,\n justification: \"center\",\n spaceAfter: 4,\n },\n r: {\n marker: \"r\",\n styleType: \"paragraph\",\n occursUnder: [\"c\", \"s\", \"s1\", \"s2\", \"s3\", \"s4\"],\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Parallel reference(s) (basic)\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n spaceAfter: 4,\n },\n sp: {\n marker: \"sp\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A heading, to identify the speaker (e.g. Job)\",\n fontSize: 12,\n italic: true,\n justification: \"left\",\n spaceBefore: 8,\n spaceAfter: 4,\n },\n d: {\n marker: \"d\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A Hebrew text heading, to provide description (e.g. Psalms)\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n spaceBefore: 4,\n spaceAfter: 4,\n },\n sd: {\n marker: \"sd\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Vertical space used to divide the text into sections, level 1 (if single level)\",\n spaceBefore: 24,\n spaceAfter: 24,\n },\n sd1: {\n marker: \"sd1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"Vertical space used to divide the text into sections, level 1 (if multiple levels)\",\n spaceBefore: 24,\n spaceAfter: 24,\n },\n sd2: {\n marker: \"sd2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"Vertical space used to divide the text into sections, level 2\",\n spaceBefore: 18,\n spaceAfter: 18,\n },\n sd3: {\n marker: \"sd3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"Vertical space used to divide the text into sections, level 3\",\n spaceBefore: 12,\n spaceAfter: 12,\n },\n sd4: {\n marker: \"sd4\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Section\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"Vertical space used to divide the text into sections, level 4\",\n spaceBefore: 8,\n spaceAfter: 8,\n },\n tr: {\n marker: \"tr\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"A new table row\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.5,\n },\n th1: {\n marker: \"th1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 1\",\n fontSize: 12,\n italic: true,\n },\n th2: {\n marker: \"th2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 2\",\n fontSize: 12,\n italic: true,\n },\n th3: {\n marker: \"th3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 3\",\n fontSize: 12,\n italic: true,\n },\n th4: {\n marker: \"th4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 4\",\n fontSize: 12,\n italic: true,\n },\n th5: {\n marker: \"th5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 5\",\n fontSize: 12,\n italic: true,\n },\n th6: {\n marker: \"th6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 6\",\n fontSize: 12,\n italic: true,\n },\n th7: {\n marker: \"th7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 7\",\n fontSize: 12,\n italic: true,\n },\n th8: {\n marker: \"th8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 8\",\n fontSize: 12,\n italic: true,\n },\n th9: {\n marker: \"th9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 9\",\n fontSize: 12,\n italic: true,\n },\n th10: {\n marker: \"th10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 10\",\n fontSize: 12,\n italic: true,\n },\n th11: {\n marker: \"th11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 11\",\n fontSize: 12,\n italic: true,\n },\n th12: {\n marker: \"th12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 12\",\n fontSize: 12,\n italic: true,\n },\n tc1: {\n marker: \"tc1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 1\",\n fontSize: 12,\n },\n tc2: {\n marker: \"tc2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 2\",\n fontSize: 12,\n },\n tc3: {\n marker: \"tc3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 3\",\n fontSize: 12,\n },\n tc4: {\n marker: \"tc4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 4\",\n fontSize: 12,\n },\n tc5: {\n marker: \"tc5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 5\",\n fontSize: 12,\n },\n tc6: {\n marker: \"tc6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 6\",\n fontSize: 12,\n },\n tc7: {\n marker: \"tc7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 7\",\n fontSize: 12,\n },\n tc8: {\n marker: \"tc8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 8\",\n fontSize: 12,\n },\n tc9: {\n marker: \"tc9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 9\",\n fontSize: 12,\n },\n tc10: {\n marker: \"tc10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 10\",\n fontSize: 12,\n },\n tc11: {\n marker: \"tc11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 11\",\n fontSize: 12,\n },\n tc12: {\n marker: \"tc12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 12\",\n fontSize: 12,\n },\n thc1: {\n marker: \"thc1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 1, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc2: {\n marker: \"thc2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 2, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc3: {\n marker: \"thc3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 3, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc4: {\n marker: \"thc4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 4, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc5: {\n marker: \"thc5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 5, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc6: {\n marker: \"thc6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 6, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc7: {\n marker: \"thc7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 7, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc8: {\n marker: \"thc8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 8, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc9: {\n marker: \"thc9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 9, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc10: {\n marker: \"thc10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 10, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc11: {\n marker: \"thc11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 11, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n thc12: {\n marker: \"thc12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 12, center aligned\",\n fontSize: 12,\n italic: true,\n justification: \"center\",\n },\n tcc1: {\n marker: \"tcc1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 1, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc2: {\n marker: \"tcc2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 2, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc3: {\n marker: \"tcc3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 3, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc4: {\n marker: \"tcc4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 4, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc5: {\n marker: \"tcc5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 5, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc6: {\n marker: \"tcc6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 6, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc7: {\n marker: \"tcc7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 7, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc8: {\n marker: \"tcc8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 8, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc9: {\n marker: \"tcc9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 9, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc10: {\n marker: \"tcc10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 10, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc11: {\n marker: \"tcc11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 11, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n tcc12: {\n marker: \"tcc12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 12, center aligned\",\n fontSize: 12,\n justification: \"center\",\n },\n thr1: {\n marker: \"thr1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 1, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr2: {\n marker: \"thr2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 2, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr3: {\n marker: \"thr3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 3, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr4: {\n marker: \"thr4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 4, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr5: {\n marker: \"thr5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 5, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr6: {\n marker: \"thr6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 6, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr7: {\n marker: \"thr7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 7, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr8: {\n marker: \"thr8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 8, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr9: {\n marker: \"thr9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 9, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr10: {\n marker: \"thr10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 10, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr11: {\n marker: \"thr11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 11, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n thr12: {\n marker: \"thr12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table heading, column 12, right aligned\",\n fontSize: 12,\n italic: true,\n justification: \"right\",\n },\n tcr1: {\n marker: \"tcr1\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 1, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr2: {\n marker: \"tcr2\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 2, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr3: {\n marker: \"tcr3\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 3, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr4: {\n marker: \"tcr4\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 4, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr5: {\n marker: \"tcr5\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 5, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr6: {\n marker: \"tcr6\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 6, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr7: {\n marker: \"tcr7\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 7, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr8: {\n marker: \"tcr8\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 8, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr9: {\n marker: \"tcr9\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 9, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr10: {\n marker: \"tcr10\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 10, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr11: {\n marker: \"tcr11\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 11, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n tcr12: {\n marker: \"tcr12\",\n styleType: \"character\",\n occursUnder: [\"tr\"],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A table cell item, column 12, right aligned\",\n fontSize: 12,\n justification: \"right\",\n },\n lh: {\n marker: \"lh\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"List header (introductory remark)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n },\n li: {\n marker: \"li\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A list entry, level 1 (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.5,\n },\n li1: {\n marker: \"li1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"A list entry, level 1 (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.5,\n },\n li2: {\n marker: \"li2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"A list entry, level 2\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.75,\n },\n li3: {\n marker: \"li3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"A list entry, level 3\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 1,\n },\n li4: {\n marker: \"li4\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"A list entry, level 4\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 1.25,\n },\n lf: {\n marker: \"lf\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"List footer (concluding remark)\",\n fontSize: 12,\n },\n lim: {\n marker: \"lim\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"An embedded list entry, level 1 (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.75,\n rightMargin: 0.25,\n },\n lim1: {\n marker: \"lim1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_1\"],\n description: \"An embedded list entry, level 1 (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 0.75,\n rightMargin: 0.25,\n },\n lim2: {\n marker: \"lim2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_2\"],\n description: \"An embedded list entry, level 2\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 1,\n },\n lim3: {\n marker: \"lim3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_3\"],\n description: \"An embedded list item, level 3\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 1.25,\n },\n lim4: {\n marker: \"lim4\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"level_4\"],\n description: \"An embedded list entry, level 4\",\n fontSize: 12,\n firstLineIndent: -0.375,\n leftMargin: 1.5,\n },\n litl: {\n marker: \"litl\",\n styleType: \"character\",\n endMarker: \"litl*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"List entry total text\",\n fontSize: 12,\n italic: true,\n },\n lik: {\n marker: \"lik\",\n styleType: \"character\",\n endMarker: \"lik*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry key text\",\n fontSize: 12,\n italic: true,\n },\n liv: {\n marker: \"liv\",\n styleType: \"character\",\n endMarker: \"liv*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 1 content (if single value)\",\n fontSize: 12,\n },\n liv1: {\n marker: \"liv1\",\n styleType: \"character\",\n endMarker: \"liv1*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 1 content (if multiple values)\",\n fontSize: 12,\n },\n liv2: {\n marker: \"liv2\",\n styleType: \"character\",\n endMarker: \"liv2*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 2 content\",\n fontSize: 12,\n },\n liv3: {\n marker: \"liv3\",\n styleType: \"character\",\n endMarker: \"liv3*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 3 content\",\n fontSize: 12,\n },\n liv4: {\n marker: \"liv4\",\n styleType: \"character\",\n endMarker: \"liv4*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 4 content\",\n fontSize: 12,\n },\n liv5: {\n marker: \"liv5\",\n styleType: \"character\",\n endMarker: \"liv5*\",\n occursUnder: [\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Structured list entry value 5 content\",\n fontSize: 12,\n },\n f: {\n marker: \"f\",\n styleType: \"note\",\n endMarker: \"f*\",\n occursUnder: [\n \"c\",\n \"cp\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"qs\",\n \"sp\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"mt\",\n \"mt1\",\n \"mt2\",\n \"mt3\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"ms3\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n ],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A Footnote text item (basic)\",\n fontSize: 12,\n },\n fe: {\n marker: \"fe\",\n styleType: \"note\",\n endMarker: \"fe*\",\n occursUnder: [\n \"c\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"ms3\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n ],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"An Endnote text item\",\n fontSize: 12,\n },\n fr: {\n marker: \"fr\",\n styleType: \"character\",\n endMarker: \"fr*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"The origin reference for the footnote (basic)\",\n fontSize: 12,\n bold: true,\n },\n ft: {\n marker: \"ft\",\n styleType: \"character\",\n endMarker: \"ft*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Footnote text, Protocanon (basic)\",\n fontSize: 12,\n },\n fk: {\n marker: \"fk\",\n styleType: \"character\",\n endMarker: \"fk*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A footnote keyword (basic)\",\n fontSize: 12,\n bold: true,\n italic: true,\n },\n fq: {\n marker: \"fq\",\n styleType: \"character\",\n endMarker: \"fq*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A footnote scripture quote or alternate rendering (basic)\",\n fontSize: 12,\n italic: true,\n },\n fqa: {\n marker: \"fqa\",\n styleType: \"character\",\n endMarker: \"fqa*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A footnote alternate rendering for a portion of scripture text\",\n fontSize: 12,\n italic: true,\n },\n fl: {\n marker: \"fl\",\n styleType: \"character\",\n endMarker: \"fl*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: 'A footnote label text item, for marking or \"labelling\" the type or alternate translation being provided in the note.',\n fontSize: 12,\n bold: true,\n italic: true,\n },\n fw: {\n marker: \"fw\",\n styleType: \"character\",\n endMarker: \"fw*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A footnote witness list, for distinguishing a list of sigla representing witnesses in critical editions.\",\n fontSize: 12,\n },\n fp: {\n marker: \"fp\",\n styleType: \"character\",\n endMarker: \"fp*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A Footnote additional paragraph marker\",\n fontSize: 12,\n },\n fv: {\n marker: \"fv\",\n styleType: \"character\",\n endMarker: \"fv*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A verse number within the footnote text\",\n fontSize: 12,\n superscript: true,\n },\n fdc: {\n marker: \"fdc\",\n styleType: \"character\",\n endMarker: \"fdc*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Footnote text, applies to Deuterocanon only\",\n fontSize: 12,\n },\n fm: {\n marker: \"fm\",\n styleType: \"character\",\n endMarker: \"fm*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n ],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"An additional footnote marker location for a previous footnote\",\n fontSize: 12,\n superscript: true,\n },\n x: {\n marker: \"x\",\n styleType: \"note\",\n endMarker: \"x*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"qs\",\n \"sp\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"mt\",\n \"mt1\",\n \"mt2\",\n \"mt3\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n ],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\", \"crossreference\"],\n description: \"A list of cross references (basic)\",\n fontSize: 12,\n },\n xo: {\n marker: \"xo\",\n styleType: \"character\",\n endMarker: \"xo*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"The cross reference origin reference (basic)\",\n fontSize: 12,\n bold: true,\n },\n xop: {\n marker: \"xop\",\n styleType: \"character\",\n endMarker: \"xop*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Published cross reference origin reference (origin reference that should appear in the published text)\",\n fontSize: 12,\n },\n xt: {\n marker: \"xt\",\n styleType: \"character\",\n endMarker: \"xt*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"ef\",\n \"ex\",\n \"NEST\",\n ],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"The cross reference target reference(s), protocanon only (basic)\",\n fontSize: 12,\n },\n xta: {\n marker: \"xta\",\n styleType: \"character\",\n endMarker: \"xta*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Cross reference target references added text\",\n fontSize: 12,\n },\n xk: {\n marker: \"xk\",\n styleType: \"character\",\n endMarker: \"xk*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A cross reference keyword\",\n fontSize: 12,\n italic: true,\n },\n xq: {\n marker: \"xq\",\n styleType: \"character\",\n endMarker: \"xq*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A cross-reference quotation from the scripture text\",\n fontSize: 12,\n italic: true,\n },\n xot: {\n marker: \"xot\",\n styleType: \"character\",\n endMarker: \"xot*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Cross-reference target reference(s), Old Testament only\",\n fontSize: 12,\n },\n xnt: {\n marker: \"xnt\",\n styleType: \"character\",\n endMarker: \"xnt*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Cross-reference target reference(s), New Testament only\",\n fontSize: 12,\n },\n xdc: {\n marker: \"xdc\",\n styleType: \"character\",\n endMarker: \"xdc*\",\n occursUnder: [\"x\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"Cross-reference target reference(s), Deuterocanon only\",\n fontSize: 12,\n },\n rq: {\n marker: \"rq\",\n styleType: \"character\",\n endMarker: \"rq*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"NEST\",\n ],\n textType: \"Other\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A cross-reference indicating the source text for the preceding quotation.\",\n fontSize: 10,\n italic: true,\n },\n qt: {\n marker: \"qt\",\n styleType: \"character\",\n endMarker: \"qt*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For Old Testament quoted text appearing in the New Testament (basic)\",\n fontSize: 12,\n italic: true,\n },\n nd: {\n marker: \"nd\",\n styleType: \"character\",\n endMarker: \"nd*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For name of deity (basic)\",\n fontSize: 12,\n underline: true,\n },\n tl: {\n marker: \"tl\",\n styleType: \"character\",\n endMarker: \"tl*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"nonvernacular\"],\n description: \"For transliterated words\",\n fontSize: 12,\n italic: true,\n },\n dc: {\n marker: \"dc\",\n styleType: \"character\",\n endMarker: \"dc*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Deuterocanonical/LXX additions or insertions in the Protocanonical text\",\n italic: true,\n },\n bk: {\n marker: \"bk\",\n styleType: \"character\",\n endMarker: \"bk*\",\n occursUnder: [\n \"imt\",\n \"imt1\",\n \"imt2\",\n \"imt3\",\n \"imt4\",\n \"imte\",\n \"imte1\",\n \"imte2\",\n \"is\",\n \"is1\",\n \"is2\",\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For the quoted name of a book\",\n fontSize: 12,\n italic: true,\n },\n sig: {\n marker: \"sig\",\n styleType: \"character\",\n endMarker: \"sig*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For the signature of the author of an Epistle\",\n fontSize: 12,\n italic: true,\n },\n pn: {\n marker: \"pn\",\n styleType: \"character\",\n endMarker: \"pn*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For a proper name\",\n fontSize: 12,\n bold: true,\n underline: true,\n },\n png: {\n marker: \"png\",\n styleType: \"character\",\n endMarker: \"png*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For a geographic proper name\",\n fontSize: 12,\n underline: true,\n },\n addpn: {\n marker: \"addpn\",\n styleType: \"character\",\n endMarker: \"addpn*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For chinese words to be dot underline & underline\",\n fontSize: 12,\n bold: true,\n italic: true,\n underline: true,\n },\n wj: {\n marker: \"wj\",\n styleType: \"character\",\n endMarker: \"wj*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For marking the words of Jesus\",\n fontSize: 12,\n color: \"#FF0000\",\n },\n k: {\n marker: \"k\",\n styleType: \"character\",\n endMarker: \"k*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For a keyword\",\n fontSize: 12,\n bold: true,\n italic: true,\n },\n sls: {\n marker: \"sls\",\n styleType: \"character\",\n endMarker: \"sls*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"To represent where the original text is in a secondary language or from an alternate text source\",\n fontSize: 12,\n italic: true,\n },\n ord: {\n marker: \"ord\",\n styleType: \"character\",\n endMarker: \"ord*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For the text portion of an ordinal number\",\n fontSize: 12,\n superscript: true,\n },\n add: {\n marker: \"add\",\n styleType: \"character\",\n endMarker: \"add*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"cls\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"For a translational addition to the text\",\n bold: true,\n italic: true,\n },\n lit: {\n marker: \"lit\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"For a comment or note inserted for liturgical use\",\n fontSize: 12,\n bold: true,\n justification: \"right\",\n },\n no: {\n marker: \"no\",\n styleType: \"character\",\n endMarker: \"no*\",\n occursUnder: [\n \"is\",\n \"ip\",\n \"ipi\",\n \"im\",\n \"imi\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"imq\",\n \"ipq\",\n \"iex\",\n \"iq\",\n \"iot\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, use normal text\",\n fontSize: 12,\n },\n it: {\n marker: \"it\",\n styleType: \"character\",\n endMarker: \"it*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, use italic text\",\n fontSize: 12,\n italic: true,\n },\n bd: {\n marker: \"bd\",\n styleType: \"character\",\n endMarker: \"bd*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, use bold text\",\n fontSize: 12,\n bold: true,\n },\n bdit: {\n marker: \"bdit\",\n styleType: \"character\",\n endMarker: \"bdit*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, use bold + italic text\",\n fontSize: 12,\n bold: true,\n italic: true,\n },\n em: {\n marker: \"em\",\n styleType: \"character\",\n endMarker: \"em*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, use emphasized text style\",\n fontSize: 12,\n italic: true,\n },\n sc: {\n marker: \"sc\",\n styleType: \"character\",\n endMarker: \"sc*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, for small capitalization text\",\n fontSize: 12,\n smallCaps: true,\n },\n sup: {\n marker: \"sup\",\n styleType: \"character\",\n endMarker: \"sup*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A character style, for superscript text. Typically for use in critical edition footnotes.\",\n fontSize: 12,\n superscript: true,\n },\n pb: {\n marker: \"pb\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"Other\",\n textProperties: [\"publishable\"],\n description: \"Page Break used for new reader portions and children's bibles where content is controlled by the page\",\n fontSize: 12,\n },\n fig: {\n marker: \"fig\",\n styleType: \"character\",\n endMarker: \"fig*\",\n occursUnder: [\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n ],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Illustration [Columns to span, height, filename, caption text]\",\n fontSize: 12,\n },\n jmp: {\n marker: \"jmp\",\n styleType: \"character\",\n endMarker: \"jmp*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"Other\",\n description: \"For associating linking attributes to a span of text\",\n underline: true,\n color: \"#0000FF\",\n },\n pro: {\n marker: \"pro\",\n styleType: \"character\",\n endMarker: \"pro*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"Other\",\n textProperties: [\"Nonpublishable\"],\n description: \"For indicating pronunciation in CJK texts\",\n fontSize: 10,\n },\n rb: {\n marker: \"rb\",\n styleType: \"character\",\n endMarker: \"rb*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"sp\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"d\",\n \"ip\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Most often used to provide a reading / pronunciation guide in ideographic scripts\",\n },\n w: {\n marker: \"w\",\n styleType: \"character\",\n endMarker: \"w*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A wordlist text item\",\n fontSize: 12,\n },\n wh: {\n marker: \"wh\",\n styleType: \"character\",\n endMarker: \"wh*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A Hebrew wordlist text item\",\n fontSize: 12,\n },\n wg: {\n marker: \"wg\",\n styleType: \"character\",\n endMarker: \"wg*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A Greek Wordlist text item\",\n fontSize: 12,\n },\n wa: {\n marker: \"wa\",\n styleType: \"character\",\n endMarker: \"wa*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"An Aramaic Wordlist text item\",\n fontSize: 12,\n },\n ndx: {\n marker: \"ndx\",\n styleType: \"character\",\n endMarker: \"ndx*\",\n occursUnder: [\n \"ip\",\n \"im\",\n \"ipi\",\n \"imi\",\n \"ipq\",\n \"imq\",\n \"ipr\",\n \"iq\",\n \"iq1\",\n \"iq2\",\n \"iq3\",\n \"ili\",\n \"ili1\",\n \"ili2\",\n \"io\",\n \"io1\",\n \"io2\",\n \"io3\",\n \"io4\",\n \"ms\",\n \"ms1\",\n \"ms2\",\n \"s\",\n \"s1\",\n \"s2\",\n \"s3\",\n \"s4\",\n \"cd\",\n \"sp\",\n \"d\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"pmo\",\n \"pm\",\n \"pmc\",\n \"pmr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"qm\",\n \"qm1\",\n \"qm2\",\n \"qm3\",\n \"tr\",\n \"th1\",\n \"th2\",\n \"th3\",\n \"th4\",\n \"thr1\",\n \"thr2\",\n \"thr3\",\n \"thr4\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"tcr1\",\n \"tcr2\",\n \"tcr3\",\n \"tcr4\",\n \"f\",\n \"fe\",\n \"x\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A subject index text item\",\n fontSize: 12,\n },\n periph: {\n marker: \"periph\",\n styleType: \"paragraph\",\n textType: \"Section\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"vernacular\"],\n description: \"Peripheral content division marker which should be followed by an additional division argument/title.\",\n fontSize: 14,\n bold: true,\n color: \"#FF8000\",\n spaceBefore: 16,\n spaceAfter: 4,\n },\n p1: {\n marker: \"p1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Front or back matter text paragraph, level 1 (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n },\n p2: {\n marker: \"p2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Front or back matter text paragraph, level 2 (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.125,\n },\n k1: {\n marker: \"k1\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Concordance main entry text or keyword, level 1\",\n fontSize: 12,\n },\n k2: {\n marker: \"k2\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Concordance main entry text or keyword, level 2\",\n fontSize: 12,\n },\n xtSee: {\n marker: \"xtSee\",\n styleType: \"character\",\n endMarker: \"xtSee*\",\n occursUnder: [\"p\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Concordance and Names Index markup for an alternate entry target reference.\",\n fontSize: 12,\n italic: true,\n color: \"#0000FF\",\n },\n xtSeeAlso: {\n marker: \"xtSeeAlso\",\n styleType: \"character\",\n endMarker: \"xtSeeAlso*\",\n occursUnder: [\"p\"],\n textType: \"Other\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"Concordance and Names Index markup for an additional entry target reference.\",\n fontSize: 12,\n italic: true,\n color: \"#0000FF\",\n },\n \"qt-s\": {\n marker: \"qt-s\",\n styleType: \"milestone\",\n endMarker: \"qt-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 1 (if single level)\",\n },\n \"qt1-s\": {\n marker: \"qt1-s\",\n styleType: \"milestone\",\n endMarker: \"qt1-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 1 (if multiple levels)\",\n },\n \"qt2-s\": {\n marker: \"qt2-s\",\n styleType: \"milestone\",\n endMarker: \"qt2-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 2\",\n },\n \"qt3-s\": {\n marker: \"qt3-s\",\n styleType: \"milestone\",\n endMarker: \"qt3-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 3\",\n },\n \"qt4-s\": {\n marker: \"qt4-s\",\n styleType: \"milestone\",\n endMarker: \"qt4-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 4\",\n },\n \"qt5-s\": {\n marker: \"qt5-s\",\n styleType: \"milestone\",\n endMarker: \"qt5-e\",\n occursUnder: [\"id\"],\n description: \"Quotation start/end milestone, level 5\",\n },\n \"ts-s\": {\n marker: \"ts-s\",\n styleType: \"milestone\",\n endMarker: \"ts-e\",\n occursUnder: [\"id\"],\n description: \"Translator's section start/end milestone\",\n },\n ph: {\n marker: \"ph\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with level 1 hanging indent (if single level)\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.5,\n },\n ph1: {\n marker: \"ph1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with level 1 hanging indent (if multiple levels)\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.5,\n },\n ph2: {\n marker: \"ph2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with level 2 hanging indent\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.75,\n },\n ph3: {\n marker: \"ph3\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, with level 3 hanging indent\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 1,\n },\n phi: {\n marker: \"phi\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, indented with hanging indent\",\n leftMargin: 1,\n },\n tr1: {\n marker: \"tr1\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"A table Row\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.5,\n },\n tr2: {\n marker: \"tr2\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"A table Row\",\n fontSize: 12,\n firstLineIndent: -0.25,\n leftMargin: 0.75,\n },\n ps: {\n marker: \"ps\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, no break with next paragraph text at chapter boundary\",\n fontSize: 12,\n firstLineIndent: 0.125,\n },\n psi: {\n marker: \"psi\",\n styleType: \"paragraph\",\n occursUnder: [\"c\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Paragraph text, indented, with no break with next paragraph text (at chapter boundary)\",\n fontSize: 12,\n firstLineIndent: 0.125,\n leftMargin: 0.25,\n rightMargin: 0.25,\n },\n fs: {\n marker: \"fs\",\n styleType: \"character\",\n endMarker: \"fs*\",\n occursUnder: [\"f\", \"fe\"],\n textType: \"NoteText\",\n textProperties: [\"publishable\", \"vernacular\", \"note\"],\n description: \"A summary text for the concept/idea/quotation from the scripture translation for which the note is being provided.\",\n fontSize: 12,\n italic: true,\n },\n wr: {\n marker: \"wr\",\n styleType: \"character\",\n endMarker: \"wr*\",\n occursUnder: [\n \"ms\",\n \"s\",\n \"lh\",\n \"li\",\n \"li1\",\n \"li2\",\n \"li3\",\n \"li4\",\n \"lf\",\n \"lim\",\n \"lim1\",\n \"lim2\",\n \"lim3\",\n \"lim4\",\n \"m\",\n \"mi\",\n \"nb\",\n \"p\",\n \"pc\",\n \"ph\",\n \"phi\",\n \"pi\",\n \"pi1\",\n \"pi2\",\n \"pi3\",\n \"pr\",\n \"po\",\n \"q\",\n \"q1\",\n \"q2\",\n \"q3\",\n \"q4\",\n \"qc\",\n \"qr\",\n \"qd\",\n \"tc1\",\n \"tc2\",\n \"tc3\",\n \"tc4\",\n \"f\",\n \"fe\",\n \"NEST\",\n ],\n textType: \"VerseText\",\n textProperties: [\"publishable\", \"vernacular\"],\n description: \"A Wordlist text item\",\n fontSize: 12,\n italic: true,\n },\n pub: {\n marker: \"pub\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Front matter publication data\",\n fontSize: 10,\n },\n toc: {\n marker: \"toc\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Front matter table of contents\",\n fontSize: 10,\n },\n pref: {\n marker: \"pref\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Front matter preface\",\n fontSize: 10,\n },\n intro: {\n marker: \"intro\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Front matter introduction\",\n fontSize: 10,\n },\n conc: {\n marker: \"conc\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Back matter concordance\",\n fontSize: 10,\n },\n glo: {\n marker: \"glo\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Back matter glossary\",\n fontSize: 10,\n },\n idx: {\n marker: \"idx\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Back matter index\",\n fontSize: 10,\n },\n maps: {\n marker: \"maps\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Back matter map index\",\n fontSize: 10,\n },\n cov: {\n marker: \"cov\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Other peripheral materials - cover\",\n fontSize: 10,\n },\n spine: {\n marker: \"spine\",\n styleType: \"paragraph\",\n occursUnder: [\"id\"],\n rank: 4,\n textType: \"VerseText\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\", \"poetic\"],\n description: \"Other peripheral materials - spine\",\n fontSize: 10,\n },\n pubinfo: {\n marker: \"pubinfo\",\n styleType: \"paragraph\",\n occursUnder: [\"id\", \"ide\"],\n textType: \"Other\",\n textProperties: [\"paragraph\", \"nonpublishable\", \"nonvernacular\"],\n description: \"Publication information - Lang,Credit,Version,Copies,Publisher,Id,Logo\",\n fontSize: 12,\n color: \"#0000FF\",\n },\n \"zpa-xb\": {\n marker: \"zpa-xb\",\n styleType: \"character\",\n endMarker: \"zpa-xb*\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Book Ref\",\n fontSize: 12,\n },\n \"zpa-xc\": {\n marker: \"zpa-xc\",\n styleType: \"character\",\n endMarker: \"zpa-xc*\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Chapter Ref\",\n fontSize: 12,\n bold: true,\n },\n \"zpa-xv\": {\n marker: \"zpa-xv\",\n styleType: \"character\",\n endMarker: \"zpa-xv*\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Verse Ref\",\n fontSize: 12,\n },\n \"zpa-d\": {\n marker: \"zpa-d\",\n styleType: \"character\",\n endMarker: \"zpa-d*\",\n occursUnder: [\"id\"],\n rank: 1,\n textType: \"Other\",\n textProperties: [\"paragraph\", \"publishable\", \"vernacular\"],\n description: \"Description\",\n fontSize: 12,\n },\n },\n};\n","/**\n * Project StyleInfo — the host-agnostic shape of a Paratext project's merged\n * stylesheet (usfm.sty + custom.sty).\n *\n * Unit conventions (match usfm.sty as parsed, not PT9's internal ints):\n * - fontSize, spaceBefore, spaceAfter: points\n * - firstLineIndent, leftMargin, rightMargin: inches (PT9 ScrTag stores\n * thousandths of an inch; hosts divide by 1000 when serializing)\n * - color: \"#RRGGBB\", omitted when black (PT9 CSSCreator skips black)\n * - lineSpacing: PT9 quirk — 1 renders as line-height 1.5, 2 as 2, else nothing\n */\nimport getMarker from \"./getMarker.js\";\nimport { CategoryType, MarkerType } from \"./usfmTypes.js\";\nconst STYLE_TYPE_TO_MARKER_TYPE = {\n paragraph: MarkerType.Paragraph,\n character: MarkerType.Character,\n note: MarkerType.Note,\n milestone: MarkerType.Milestone,\n};\n/**\n * StyleInfo-backed replacement for the bundled `getMarker`. With `styleInfo`,\n * the project sheet is authoritative for a marker's existence and\n * classification: markers absent from it return `undefined` (PT9: unknown to\n * the stylesheet), and `usfmMarkersOverwrites` never overrides those fields.\n * `children` (submenu structure) is editor data keyed by marker name, not\n * stylesheet data, so it still comes from the bundled path (table +\n * overwrites) — children-dependent consumers keep working. Without\n * `styleInfo`, the bundled `getMarker` is returned unchanged so non-project\n * consumers keep today's behavior exactly.\n */\nexport function createMarkerLookup(styleInfo) {\n if (!styleInfo)\n return getMarker;\n const cache = new Map();\n return (marker) => {\n if (cache.has(marker))\n return cache.get(marker);\n // Own-property guard: `markers` is a plain object off the wire, so a bare index resolves\n // Object.prototype members (`constructor`, `toString`, …) as if they were stylesheet\n // entries — handing back a Marker built from a Function.\n const entry = Object.hasOwn(styleInfo.markers, marker) ? styleInfo.markers[marker] : undefined;\n const result = entry\n ? {\n // Through `getMarker`, not the raw generated table: `usfmMarkersOverwrites` supplies\n // markers the generated data lacks (`w`, `rb`, `jmp`), and reading the table directly\n // demoted exactly those to Uncategorized whenever a project StyleInfo was active.\n category: getMarker(marker)?.category ?? CategoryType.Uncategorized,\n type: STYLE_TYPE_TO_MARKER_TYPE[entry.styleType] ?? MarkerType.Unknown,\n description: entry.description ?? \"\",\n hasEndMarker: Boolean(entry.endMarker),\n children: getMarker(marker)?.children,\n }\n : undefined;\n cache.set(marker, result);\n return result;\n };\n}\n","import { USJ_TYPE, USJ_VERSION, } from \"@eten-tech-foundation/scripture-utilities\";\nimport { isSerializedImpliedParaNode } from \"../../nodes/usj/ImpliedParaNode.js\";\nexport function createLexicalUsjNode(content, editorAdaptor, viewOptions) {\n const usj = {\n type: USJ_TYPE,\n version: USJ_VERSION,\n content,\n };\n const lexicalSerializedRoot = editorAdaptor.serializeEditorState(usj, viewOptions);\n const lexicalSerializedNode = isSerializedImpliedParaNode(lexicalSerializedRoot.root.children[0])\n ? lexicalSerializedRoot.root.children[0].children[0]\n : lexicalSerializedRoot.root.children[0];\n return lexicalSerializedNode;\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\n/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/cv/v.html */\nimport { $applyNodeReplacement, DecoratorNode, isHTMLElement, } from \"lexical\";\nimport { useLexicalNodeSelection } from \"@lexical/react/useLexicalNodeSelection\";\nimport { getVisibleOpenMarkerText, isSelectionStartNodeExpectedError, VERSE_CLASS_NAME, ZWSP, } from \"shared\";\nexport const VERSE_MARKER = \"v\";\nexport const IMMUTABLE_VERSE_VERSION = 1;\n/**\n * Class applied to the rendered verse marker while it is in a NodeSelection (e.g. armed for the\n * two-step intentional delete). Themeable by the host app; a default selection-style background\n * ships in the platform stylesheet.\n */\nexport const VERSE_SELECTED_CLASS_NAME = \"verse-selected\";\nexport class ImmutableVerseNode extends DecoratorNode {\n __marker;\n __number;\n __showMarker;\n __sid;\n __altnumber;\n __pubnumber;\n __unknownAttributes;\n constructor(verseNumber = \"\", showMarker = false, sid, altnumber, pubnumber, unknownAttributes, key) {\n super(key);\n this.__marker = VERSE_MARKER;\n this.__number = verseNumber;\n this.__showMarker = showMarker;\n this.__sid = sid;\n this.__altnumber = altnumber;\n this.__pubnumber = pubnumber;\n this.__unknownAttributes = unknownAttributes;\n }\n static getType() {\n return \"immutable-verse\";\n }\n static clone(node) {\n const { __number, __showMarker, __sid, __altnumber, __pubnumber, __unknownAttributes, __key } = node;\n return new ImmutableVerseNode(__number, __showMarker, __sid, __altnumber, __pubnumber, __unknownAttributes, __key);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isVerseElement(node))\n return null;\n return {\n conversion: $convertImmutableVerseElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createImmutableVerseNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setMarker(serializedNode.marker)\n .setNumber(serializedNode.number)\n .setShowMarker(serializedNode.showMarker)\n .setSid(serializedNode.sid)\n .setAltnumber(serializedNode.altnumber)\n .setPubnumber(serializedNode.pubnumber)\n .setUnknownAttributes(serializedNode.unknownAttributes);\n }\n setMarker(marker) {\n if (this.__marker === marker)\n return this;\n const self = this.getWritable();\n self.__marker = marker;\n return self;\n }\n getMarker() {\n const self = this.getLatest();\n return self.__marker;\n }\n setNumber(verseNumber) {\n if (this.__number === verseNumber)\n return this;\n const self = this.getWritable();\n self.__number = verseNumber;\n return self;\n }\n getNumber() {\n const self = this.getLatest();\n return self.__number;\n }\n setShowMarker(showMarker = false) {\n if (this.__showMarker === showMarker)\n return this;\n const self = this.getWritable();\n self.__showMarker = showMarker;\n return self;\n }\n getShowMarker() {\n const self = this.getLatest();\n return self.__showMarker;\n }\n setSid(sid) {\n if (this.__sid === sid)\n return this;\n const self = this.getWritable();\n self.__sid = sid;\n return self;\n }\n getSid() {\n const self = this.getLatest();\n return self.__sid;\n }\n setAltnumber(altnumber) {\n if (this.__altnumber === altnumber)\n return this;\n const self = this.getWritable();\n self.__altnumber = altnumber;\n return self;\n }\n getAltnumber() {\n const self = this.getLatest();\n return self.__altnumber;\n }\n setPubnumber(pubnumber) {\n if (this.__pubnumber === pubnumber)\n return this;\n const self = this.getWritable();\n self.__pubnumber = pubnumber;\n return self;\n }\n getPubnumber() {\n const self = this.getLatest();\n return self.__pubnumber;\n }\n setUnknownAttributes(unknownAttributes) {\n const self = this.getWritable();\n self.__unknownAttributes = unknownAttributes;\n return self;\n }\n getUnknownAttributes() {\n const self = this.getLatest();\n return self.__unknownAttributes;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.setAttribute(\"data-marker\", this.__marker);\n dom.classList.add(VERSE_CLASS_NAME, `usfm_${this.__marker}`);\n if (this.__showMarker)\n dom.classList.add(\"marker\");\n dom.setAttribute(\"data-number\", this.__number);\n return dom;\n }\n updateDOM() {\n // Returning false tells Lexical that this node does not need its\n // DOM element replacing with a new copy from createDOM.\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.setAttribute(\"data-marker\", this.getMarker());\n element.classList.add(VERSE_CLASS_NAME, `usfm_${this.getMarker()}`);\n element.setAttribute(\"data-number\", this.getNumber());\n }\n return { element };\n }\n decorate() {\n const text = this.getShowMarker()\n ? getVisibleOpenMarkerText(this.getMarker(), this.getNumber())\n : // ZWSP added so double click word selection works without including this number.\n ZWSP + this.getNumber() + ZWSP;\n return _jsx(VerseDecorator, { nodeKey: this.getKey(), text: text });\n }\n exportJSON() {\n return {\n type: this.getType(),\n marker: this.getMarker(),\n number: this.getNumber(),\n showMarker: this.getShowMarker(),\n sid: this.getSid(),\n altnumber: this.getAltnumber(),\n pubnumber: this.getPubnumber(),\n unknownAttributes: this.getUnknownAttributes(),\n version: IMMUTABLE_VERSE_VERSION,\n };\n }\n isSelected(selection) {\n // The base implementation calls `selection.getNodes()`, which throws when a RangeSelection\n // has an element-type point anchored on this DecoratorNode (the \"cursor on the verse number\"\n // case). Treat that expected throw as \"not selected\", consistent with how\n // `getSelectionStartNode` handles the same selection shape.\n try {\n return super.isSelected(selection);\n }\n catch (err) {\n if (isSelectionStartNodeExpectedError(err))\n return false;\n throw err;\n }\n }\n // Mutation\n isKeyboardSelectable() {\n return false;\n }\n}\n/**\n * Renders the verse marker text and reflects its node-selection state. DecoratorNodes do not get\n * selection styling for free, so this subscribes to the node's selection and toggles\n * {@link VERSE_SELECTED_CLASS_NAME}.\n */\nfunction VerseDecorator({ nodeKey, text }) {\n const [isSelected] = useLexicalNodeSelection(nodeKey);\n // ZWSPs stay inside this span so double-click word selection still excludes the number.\n return _jsx(\"span\", { className: isSelected ? VERSE_SELECTED_CLASS_NAME : undefined, children: text });\n}\nfunction $convertImmutableVerseElement(element) {\n const verseNumber = element.getAttribute(\"data-number\") ?? \"0\";\n const node = $createImmutableVerseNode(verseNumber);\n return { node };\n}\nexport function $createImmutableVerseNode(verseNumber, showMarker, sid, altnumber, pubnumber, unknownAttributes) {\n return $applyNodeReplacement(new ImmutableVerseNode(verseNumber, showMarker, sid, altnumber, pubnumber, unknownAttributes));\n}\nfunction isVerseElement(node) {\n const marker = node?.getAttribute(\"data-marker\") ?? undefined;\n return marker === VERSE_MARKER;\n}\nexport function $isImmutableVerseNode(node) {\n return node instanceof ImmutableVerseNode;\n}\nexport function isSerializedImmutableVerseNode(node) {\n return node?.type === ImmutableVerseNode.getType();\n}\n","import { $isImmutableVerseNode, isSerializedImmutableVerseNode, } from \"./ImmutableVerseNode\";\nimport { $getNodeByKey, $isElementNode, $isRangeSelection, $isTextNode, } from \"lexical\";\nimport { $findNearestPreviousNode, $isNodeWithMarker, $isParaNode, $isSomeChapterNode, $isVerseBlockNode, $isVerseNode, isSerializedVerseNode, isVerseInRange, NBSP, } from \"shared\";\n/**\n * Checks if the given node is a VerseNode or ImmutableVerseNode.\n * @param node - The node to check.\n * @returns `true` if the node is a VerseNode or ImmutableVerseNode, `false` otherwise.\n */\nexport function $isSomeVerseNode(node) {\n return $isVerseNode(node) || $isImmutableVerseNode(node);\n}\n/**\n * Checks if the given node is a SerializedVerseNode or SerializedImmutableVerseNode.\n * @param node - The serialized node to check.\n * @returns `true` if the node is a SerializedVerseNode or SerializedImmutableVerseNode, `false` otherwise.\n */\nexport function isSomeSerializedVerseNode(node) {\n return isSerializedVerseNode(node) || isSerializedImmutableVerseNode(node);\n}\n/**\n * Finds the first paragraph that is not a book or chapter node.\n * @param nodes - Nodes to look in.\n * @returns the first paragraph node.\n */\nexport function $getFirstPara(nodes) {\n return $expandVerseBlocks(nodes).find((node) => $isParaNode(node));\n}\n/**\n * The given nodes with any `VerseBlockNode` replaced by its own paragraphs.\n *\n * In the block verse layout a verse sits two levels below the root - `VerseBlockNode > ParaNode >\n * verse` - so anything searching for verses or paragraphs has to see through the block. Returns\n * the input unchanged when there are no verse blocks, leaving the inline layouts untouched.\n */\nfunction $expandVerseBlocks(nodes) {\n if (!nodes.some($isVerseBlockNode))\n return nodes;\n return nodes.flatMap((node) => ($isVerseBlockNode(node) ? node.getChildren() : node));\n}\n/**\n * The nodes among which a verse marker would sit, for a container that might hold one.\n *\n * For a paragraph that is its own children. For a verse block it is the children of each of its\n * paragraphs, in document order, since the block holds paragraphs and the markers live inside\n * those. Callers can then treat both the same way.\n */\nfunction $getVerseSiblings(node) {\n if (!$isElementNode(node))\n return [];\n if ($isVerseBlockNode(node))\n return node.getChildren().flatMap($getVerseSiblings);\n return node.getChildren();\n}\n/**\n * Find the given verse in the children of the node.\n * @param node - Node with potential verses in children.\n * @param verseNum - Verse number to look for.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findVerseInNode(node, verseNum) {\n const children = $getVerseSiblings(node);\n const verseNode = children.find((child) => $isSomeVerseNode(child) && isVerseInRange(verseNum, child.getNumber()));\n return verseNode;\n}\n/**\n * Finds the verse node with the given verse number amongst the children of nodes.\n * @param nodes - Nodes to look in.\n * @param verseNum - Verse number to look for.\n * @returns the verse node if found, or the first paragraph if verse 0, `undefined` otherwise.\n */\nexport function $findVerseOrPara(nodes, verseNum) {\n return verseNum === 0\n ? $getFirstPara(nodes)\n : nodes\n .map((node) => $findVerseInNode(node, verseNum))\n // remove any undefined results and take the first found\n .filter((verseNode) => verseNode)[0];\n}\n/**\n * Find the next verse in the children of the node.\n * @param node - Node with potential verses in children.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findNextVerseInNode(node) {\n const verseNode = $getVerseSiblings(node).find((child) => $isSomeVerseNode(child));\n return verseNode;\n}\n/**\n * Finds the next verse node amongst the children of nodes.\n * @param nodes - Nodes to look in.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findNextVerse(nodes) {\n return (nodes\n .map((node) => $findNextVerseInNode(node))\n // remove any undefined results and take the first found\n .filter((verseNode) => verseNode)[0]);\n}\n/**\n * Find the previous verse node in a parent's children, walking backward from the given index.\n * @param parent - Element node whose children to search.\n * @param fromIndex - Start index (exclusive); search from fromIndex - 1 down to 0.\n * @returns The verse node if found, `undefined` otherwise.\n */\nexport function $findPreviousVerseInSiblings(parent, fromIndex) {\n if (!$isElementNode(parent) || fromIndex <= 0)\n return;\n const children = parent.getChildren();\n for (let i = fromIndex - 1; i >= 0; i--) {\n const child = children[i];\n if ($isSomeVerseNode(child))\n return child;\n }\n return undefined;\n}\n/**\n * Find the next verse node after `verseNode` in document order, stopping at the next chapter\n * boundary (or the end of the document). Forward counterpart to `$findPreviousVerseInSiblings` /\n * the backward walk in `$findThisVerse`.\n * @param verseNode - The verse node to search forward from.\n * @returns The next verse node, or `undefined` if none exists before the next chapter/document end.\n */\nexport function $findNextVerseAfter(verseNode) {\n const parent = verseNode.getParent();\n if (parent && $isElementNode(parent)) {\n const children = parent.getChildren();\n for (let i = verseNode.getIndexWithinParent() + 1; i < children.length; i++) {\n const child = children[i];\n if ($isSomeVerseNode(child))\n return child;\n }\n }\n let nextPara = parent?.getNextSibling();\n while (nextPara && !$isSomeChapterNode(nextPara)) {\n const verse = $findNextVerseInNode(nextPara);\n if (verse)\n return verse;\n nextPara = nextPara.getNextSibling();\n }\n return undefined;\n}\n/**\n * Find the last verse in the children of the node.\n * @param node - Node with potential verses in children.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findLastVerseInNode(node) {\n return $getVerseSiblings(node).findLast((child) => $isSomeVerseNode(child));\n}\n/**\n * Finds the last verse node amongst the children of nodes.\n * @param nodes - Nodes to look in.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findLastVerse(nodes) {\n const verseNodes = nodes\n .map((node) => $findLastVerseInNode(node))\n // remove any undefined results\n .filter((verseNode) => verseNode);\n if (verseNodes.length <= 0)\n return;\n return verseNodes[verseNodes.length - 1];\n}\n/**\n * Length of verse number prefix in verse text for BCV \"before vs after\" check.\n * If text doesn't start with the verse number (e.g. $createVerseNode(\"1\", \" verse one\")\n * or node is non-VerseNode (e.g. ImmutableVerseNode), returns 0 — treats all positions\n * as \"after\" and shows the current verse.\n */\nfunction getVerseNumberPrefixLength(verseNode) {\n if (!$isVerseNode(verseNode))\n return 0;\n const verseNumber = verseNode.getNumber();\n const text = verseNode.getTextContent();\n return text.startsWith(verseNumber) ? verseNumber.length : 0;\n}\n/**\n * Returns true when the selection anchor is positioned before the given verse node in document\n * order. Handles: (1) cursor inside the verse's parent with offset before this verse's index,\n * (2) cursor in the verse's previous sibling.\n */\nfunction $isSelectionBeforeVerseNode(selection, verseNode, anchorNode) {\n if (!anchorNode)\n return false;\n const parent = verseNode.getParent();\n if (anchorNode === parent && $isElementNode(anchorNode)) {\n const verseIndex = verseNode.getIndexWithinParent();\n const anchorOffset = selection.anchor.offset;\n return anchorOffset <= verseIndex;\n }\n if (anchorNode.getNextSibling() === verseNode)\n return true;\n return false;\n}\n/**\n * Returns true when BCV should show the previous verse (cursor is before the verse number).\n * Encapsulates: anchor in parent/previous sibling before verse; anchor in verse node before\n * verse number (TextNode) or on whole node (DecoratorNode).\n */\nfunction $shouldShowPreviousVerseForBcv(verseNode, selection) {\n const anchorNode = selection.anchor.getNode();\n // Anchor not on verse node: check if cursor is before verse (parent offset or previous sibling)\n if (anchorNode !== verseNode) {\n return $isSelectionBeforeVerseNode(selection, verseNode, anchorNode);\n }\n // Anchor on verse node: show previous if cursor is before verse number\n if ($isTextNode(verseNode)) {\n const prefixLength = getVerseNumberPrefixLength(verseNode);\n return selection.anchor.offset < prefixLength;\n }\n // ImmutableVerseNode (DecoratorNode): whole node is verse number; show previous\n return true;\n}\n/** Build result for current verse (no selection or cursor after verse number). */\nfunction currentVerseResult(verseNode) {\n const verse = verseNode.getNumber();\n const selectedVerseNum = Number.parseInt(verse ?? \"0\", 10);\n return {\n verseNum: selectedVerseNum,\n verse: verse != null && selectedVerseNum.toString() !== verse ? verse : undefined,\n };\n}\n/**\n * Returns the verse number (and optional verse range) for BCV display. When the cursor is\n * before the verse number, returns the previous verse so BCV only updates after the number.\n * For \"previous\" verse, only `verseNum` is set (no `verse` range); e.g. cursor before \"2-3\" → `{ verseNum: 1 }`.\n *\n * @param verseNode - The verse node that contains or precedes the cursor.\n * @param selection - The current editor selection.\n * @returns Effective verse number and optional verse range string for BCV display.\n */\nexport function $getEffectiveVerseForBcv(verseNode, selection) {\n if (!verseNode)\n return { verseNum: 0 };\n // No selection or not range: use verse node as-is\n if (!$isRangeSelection(selection)) {\n return currentVerseResult(verseNode);\n }\n const selectedVerseNum = Number.parseInt(verseNode.getNumber() ?? \"0\", 10);\n const prevNum = selectedVerseNum <= 1 ? 0 : selectedVerseNum - 1;\n // Anchor before verse number: show previous verse\n if ($shouldShowPreviousVerseForBcv(verseNode, selection))\n return { verseNum: prevNum };\n return currentVerseResult(verseNode);\n}\n/**\n * Checks if the node has a `getMarker` method. Includes all React nodes.\n * @param node - LexicalNode to check.\n * @returns `true` if the node has a `getMarker` method, `false` otherwise.\n */\nexport function $isReactNodeWithMarker(node) {\n return $isNodeWithMarker(node) || $isImmutableVerseNode(node);\n}\n/**\n * Add trailing space to a TextNode\n * @param node - Text node to add trailing space to.\n */\nexport function $addTrailingSpace(node) {\n if ($isTextNode(node)) {\n const text = node.getTextContent();\n if (!text.endsWith(\" \") && !text.endsWith(NBSP))\n node.setTextContent(`${text} `);\n }\n}\n/**\n * Removes the any leading space from a TextNode.\n * @param node - Text node to remove leading space from.\n */\nexport function $removeLeadingSpace(node) {\n if ($isTextNode(node)) {\n const text = node.getTextContent();\n if (text.startsWith(\" \"))\n node.setTextContent(text.trimStart());\n }\n}\n/**\n * Checks if the node was created since the previous editor state.\n * @param editor - The lexical editor instance.\n * @param nodeKey - The key of the node.\n * @returns `true` if the node was created, and `false` otherwise.\n */\nexport function wasNodeCreated(editor, nodeKey) {\n return editor.getEditorState().read(() => !$getNodeByKey(nodeKey));\n}\n/**\n * Moves the selection to the start of the next verse's content (after the verse marker).\n * Used for ArrowDown navigation so the cursor lands on a position that ScriptureReferencePlugin\n * can resolve for BCV display.\n * @param selection - The current range selection (must be collapsed).\n * @returns `true` if the selection was moved, `false` if not collapsed or no next verse.\n */\nexport function $selectNextVerse(selection) {\n if (!selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n const currentVerse = $resolveVerseNode(anchorNode, selection);\n let nextVerse;\n if (currentVerse) {\n const parent = currentVerse.getParent();\n // When the cursor is on a block (e.g. para) before the first verse in that block,\n // $resolveVerseNode falls back to the first verse in the paragraph. That verse is\n // ahead of the caret — it should be the ArrowDown target, not skipped as \"current\".\n if (parent &&\n $isElementNode(parent) &&\n $isElementNode(anchorNode) &&\n anchorNode === parent &&\n selection.anchor.offset < currentVerse.getIndexWithinParent()) {\n nextVerse = currentVerse;\n }\n if (!nextVerse && parent && $isElementNode(parent)) {\n const children = parent.getChildren();\n const currentIndex = currentVerse.getIndexWithinParent();\n for (let i = currentIndex + 1; i < children.length; i++) {\n const child = children[i];\n if ($isSomeVerseNode(child)) {\n nextVerse = child;\n break;\n }\n }\n }\n if (!nextVerse && parent) {\n let nextPara = $getNextSearchSibling(parent);\n while (nextPara && !$isSomeChapterNode(nextPara)) {\n const verse = $findNextVerseInNode(nextPara);\n if (verse) {\n nextVerse = verse;\n break;\n }\n nextPara = $getNextSearchSibling(nextPara);\n }\n }\n }\n else {\n const topLevel = anchorNode.getTopLevelElement();\n let para = topLevel ?? anchorNode;\n while (para) {\n const verse = $findNextVerseInNode(para);\n if (verse) {\n nextVerse = verse;\n break;\n }\n para = para.getNextSibling();\n if (para && $isSomeChapterNode(para))\n break;\n }\n }\n if (!nextVerse)\n return false;\n nextVerse.selectNext(0, 0);\n return true;\n}\n/**\n * Moves the selection to the start of the previous verse's content (after the verse marker).\n * Used for ArrowUp navigation so the cursor lands on a position that ScriptureReferencePlugin\n * can resolve for BCV display.\n * @param selection - The current range selection (must be collapsed).\n * @returns `true` if the selection was moved, `false` if not collapsed or no previous verse.\n */\nexport function $selectPreviousVerse(selection) {\n if (!selection.isCollapsed())\n return false;\n const anchorNode = selection.anchor.getNode();\n const currentVerse = $resolveVerseNode(anchorNode, selection);\n let prevVerse;\n if (currentVerse) {\n const parent = currentVerse.getParent();\n // When the cursor is in a different (later) paragraph than currentVerse's parent,\n // $resolveVerseNode found the verse via backward traversal across paragraphs.\n // That verse is behind the caret — it should be the ArrowUp target directly.\n const topLevel = anchorNode.getTopLevelElement();\n // Compare the caret's block against the verse's own block. Everywhere but the block verse\n // layout a paragraph is its own top-level element - including inside a table, whose cells are\n // shadow roots - so this is the same test as comparing against the paragraph. In block verse\n // the paragraph's top-level element is its verse block, which is what makes it correct there.\n if (parent && topLevel && topLevel !== parent.getTopLevelElement()) {\n prevVerse = currentVerse;\n }\n if (!prevVerse && parent && $isElementNode(parent)) {\n prevVerse = $findPreviousVerseInSiblings(parent, currentVerse.getIndexWithinParent());\n }\n if (!prevVerse && parent) {\n let prevPara = $getPreviousSearchSibling(parent);\n while (prevPara && !$isSomeChapterNode(prevPara)) {\n const verse = $findLastVerseInNode(prevPara);\n if (verse) {\n prevVerse = verse;\n break;\n }\n prevPara = $getPreviousSearchSibling(prevPara);\n }\n }\n }\n else {\n const topLevel = anchorNode.getTopLevelElement();\n let prevPara = topLevel?.getPreviousSibling() ?? null;\n while (prevPara && !$isSomeChapterNode(prevPara)) {\n const verse = $findLastVerseInNode(prevPara);\n if (verse) {\n prevVerse = verse;\n break;\n }\n prevPara = prevPara.getPreviousSibling();\n }\n }\n if (!prevVerse)\n return false;\n prevVerse.selectNext(0, 0);\n return true;\n}\n/**\n * The next node to search when walking forward looking for a verse.\n *\n * Normally the node's next sibling. In the block verse layout a paragraph's siblings run out at\n * the end of its verse block, so the walk continues from the block's own next sibling rather than\n * stopping inside it. Behaves exactly like `getNextSibling` for a top-level node.\n */\nfunction $getNextSearchSibling(node) {\n const nextSibling = node.getNextSibling();\n if (nextSibling)\n return nextSibling;\n const topLevel = node.getTopLevelElement();\n return topLevel && topLevel !== node ? topLevel.getNextSibling() : null;\n}\n/** Mirror of {@link $getNextSearchSibling} for a backward walk. */\nfunction $getPreviousSearchSibling(node) {\n const previousSibling = node.getPreviousSibling();\n if (previousSibling)\n return previousSibling;\n const topLevel = node.getTopLevelElement();\n return topLevel && topLevel !== node ? topLevel.getPreviousSibling() : null;\n}\n/**\n * Resolves the verse node for the given start node. When the cursor is on an element\n * (e.g. para) rather than inside a verse, looks at the child at offset or walks backward\n * within that element before falling back to $findThisVerse (which may walk to prior paras).\n */\nexport function $resolveVerseNode(startNode, selection) {\n const isCursorOnElement = $isElementNode(startNode) &&\n $isRangeSelection(selection) &&\n selection.anchor.key === startNode.getKey();\n if (isCursorOnElement) {\n const childAtOffset = startNode.getChildAtIndex(selection.anchor.offset);\n if (childAtOffset && $isSomeVerseNode(childAtOffset))\n return childAtOffset;\n const prev = $findPreviousVerseInSiblings(startNode, selection.anchor.offset);\n if (prev)\n return prev;\n const firstVerseInPara = $findNextVerseInNode(startNode);\n if (firstVerseInPara)\n return firstVerseInPara;\n }\n return $findThisVerse(startNode);\n}\n/**\n * Find the verse that this node is in.\n * @param node - Node to find the verse it's in.\n * @returns the verse node if found, `undefined` otherwise.\n */\nexport function $findThisVerse(node) {\n if (!node || $isSomeChapterNode(node))\n return;\n // is this node a verse\n if ($isSomeVerseNode(node))\n return node;\n let previousSiblingOrParent = $findNearestPreviousNode(node);\n while (previousSiblingOrParent) {\n // If this node is a chapter node, stop searching as we've reached the start of this chapter\n if ($isSomeChapterNode(previousSiblingOrParent))\n return;\n // If this node is a verse node, return it\n if ($isSomeVerseNode(previousSiblingOrParent))\n return previousSiblingOrParent;\n // If this node contains a verse node, return that\n const verseNode = $findLastVerseInNode(previousSiblingOrParent);\n if (verseNode)\n return verseNode;\n previousSiblingOrParent = $findNearestPreviousNode(previousSiblingOrParent);\n }\n return undefined;\n}\n","/**\n * Models for the rich-text Operational Transform documents used in Scripture Forge.\n * `OT_???_PROPS` are the properties that can be set on the corresponding Lexical node. The rest go\n * into unknownAttributes.\n */\nexport const OT_PARA_PROPS = [\"style\"];\nexport const OT_BOOK_PROPS = [\"style\", \"code\"];\nexport const OT_CHAR_PROPS = [\"style\", \"cid\"];\nexport const OT_CHAPTER_PROPS = [\n \"style\",\n \"number\",\n \"sid\",\n \"altnumber\",\n \"pubnumber\",\n];\nexport const OT_VERSE_PROPS = [\n \"style\",\n \"number\",\n \"sid\",\n \"altnumber\",\n \"pubnumber\",\n];\nexport const OT_MILESTONE_PROPS = [\n \"style\",\n \"sid\",\n \"eid\",\n \"attributeOrder\",\n];\n// Note that `contents` is not a property of a NoteNode, but we don't want it in unknownAttributes.\nexport const OT_NOTE_PROPS = [\"style\", \"caller\", \"category\", \"contents\"];\nexport const OT_UNKNOWN_PROPS = [\"tag\", \"marker\", \"contents\"];\nexport const validOTEmbedTypes = [\n \"chapter\",\n \"immutable-chapter\",\n \"verse\",\n \"immutable-verse\",\n \"ms\",\n \"note\",\n \"unknown\",\n \"unmatched\",\n];\n","/** Common utilities used for OT Delta realtime collaborative editing. */\nimport { $isSomeVerseNode } from \"../../../nodes/usj/node-react.utils\";\nimport { validOTEmbedTypes } from \"./rich-text-ot.model\";\nimport { $dfsIterator, $findMatchingParent } from \"@lexical/utils\";\nimport { $getNodeByKey, $getState, $isElementNode, $isTextNode, } from \"lexical\";\nimport { $isAttributeRunNode, $isCharNode, $isCursorPlaceholderOnlyText, $isDescendantOf, $isImmutableUnmatchedNode, $isMarkerNode, $isMilestoneNode, $isNoteNode, $isParaLikeNode, $isParaNode, $isDisplayRunPiece, $isSomeChapterNode, $isSynthesizedMarkerNode, $isUnknownNode, EMPTY_CHAR_PLACEHOLDER_TEXT, getEditableCallerText, NODE_ATTRIBUTE_PREFIX, textTypeState, } from \"shared\";\n/** Line Feed character used to close para-like nodes.*/\nexport const LF = \"\\n\";\n/**\n * Get the replace embed operations for a given embed node key.\n *\n * @remarks\n * The returned ops are host-local: they are meant to be fed straight to `$applyUpdate`, so\n * the retain is computed in `\"apply\"` coordinates (see {@link OTCoordinateSystem}) to agree\n * with `$applyUpdate`'s insert/delete traversals.\n *\n * @param embedNodeKey - The key of the embed node to replace.\n * @param insertEmbedOps - The operations to insert the new embed node.\n * @returns The replace embed operations, or `undefined` if the node is not found.\n */\nexport function $getReplaceEmbedOps(embedNodeKey, insertEmbedOps) {\n const node = $getNodeByKey(embedNodeKey);\n if (!$isEmbedNode(node))\n return;\n const retain = $getOTPositionOfNode(node, \"apply\");\n if (retain === undefined)\n return;\n const ops = [{ retain }, ...insertEmbedOps, { delete: 1 }];\n return ops;\n}\n/**\n * Calculate the OT (Operational Transform) position of a given node in the document.\n *\n * @remarks\n * - Text nodes return their start position\n * - Embed nodes (chapter, verse, milestone, note, unmatched) return their position (length 1)\n * - Para-like nodes (ParaNode, BookNode, ImpliedParaNode) return their closing position (length 1)\n * - CharNodes have no OT length contribution\n *\n * @param node - The Lexical node to find the position for.\n * @param coordinates - The OT coordinate system to count in (see {@link OTCoordinateSystem}).\n * Defaults to the legacy `\"delta-doc\"` counting; pass `\"apply\"` for positions consumed by\n * `$applyUpdate`.\n * @returns The OT position of the node, or `undefined` if the node is not found.\n */\nexport function $getOTPositionOfNode(node, coordinates = \"delta-doc\") {\n if (!node)\n return undefined;\n // LAZY traversal, terminated by the early returns below: this runs per keystroke on\n // DeltaOnChangePlugin's fast path, and the eager `$dfs()` array materialized the ENTIRE\n // document each call regardless of where the target sat.\n const dfsNodes = $dfsIterator();\n let currentIndex = 0;\n const openParaLikeNodes = [];\n const openContentEmbeds = [];\n const targetKey = node.getKey();\n let targetParaLikeNode;\n for (const dfsNode of dfsNodes) {\n const currentNode = dfsNode.node;\n // Before processing the current node, check if any previously opened para-like nodes are\n // closing.\n for (let j = openParaLikeNodes.length - 1; j >= 0; j--) {\n if ($isElementNodeClosing(openParaLikeNodes[j], dfsNode)) {\n const closingPara = openParaLikeNodes[j];\n openParaLikeNodes.splice(j, 1);\n currentIndex += 1;\n // If this is the target para-like node closing, return its position\n if (targetParaLikeNode && closingPara.getKey() === targetParaLikeNode.getKey()) {\n return currentIndex - 1; // Return the position we just incremented\n }\n }\n }\n // Check if any open content embed nodes (note/unknown) are closing\n for (let j = openContentEmbeds.length - 1; j >= 0; j--) {\n if ($isElementNodeClosing(openContentEmbeds[j].node, dfsNode)) {\n openContentEmbeds.splice(j, 1);\n }\n }\n const activeContentEmbed = openContentEmbeds[openContentEmbeds.length - 1];\n if (activeContentEmbed) {\n if (currentNode.getKey() === targetKey) {\n return activeContentEmbed.position;\n }\n continue;\n }\n // Check if we've found the target node\n if (currentNode.getKey() === targetKey) {\n // For text nodes, return the start position (an editable verse is an embed, not text —\n // see $isOTTextNode — so it falls to the embed check below)\n if ($isOTTextNode(currentNode))\n return currentIndex;\n // For embed nodes, return their position\n if ($isEmbedNode(currentNode))\n return currentIndex;\n // For para-like nodes, mark it and continue to find its closing position\n if ($isParaLikeNode(currentNode)) {\n targetParaLikeNode = currentNode;\n // Continue processing to find where this para closes\n }\n // For CharNodes or other nodes that don't have OT positions, continue searching\n // (CharNodes don't have their own position, their text content does)\n }\n // Track opening of para-like nodes after checking for target\n if ($isParaLikeNode(currentNode)) {\n if (!openParaLikeNodes.includes(currentNode)) {\n openParaLikeNodes.push(currentNode);\n }\n }\n // Track when we enter an opaque content container (note/unknown always; any element\n // embed such as an editable chapter in \"apply\" coordinates)\n if ($isOpaqueContentNode(currentNode, coordinates)) {\n if (currentNode.getKey() === targetKey)\n return currentIndex;\n openContentEmbeds.push({ node: currentNode, position: currentIndex });\n currentIndex += 1; // Embeds contribute 1 to OT length\n continue; // Skip normal OT contribution calculation for embed contents\n }\n // Calculate OT length contribution of current node\n currentIndex += $getNodeOTContribution(currentNode, coordinates);\n }\n // If we're looking for a para-like node that didn't close, return current position\n if (targetParaLikeNode)\n return currentIndex;\n // Node not found\n return undefined;\n}\n/**\n * Get the key of the inserted node from the OT delta operations.\n * @param ops - The OT delta operations with potential insertion.\n * @param editorState - The current editor state.\n * @param coordinates - The OT coordinate system the retain in `ops` is expressed in (see\n * {@link OTCoordinateSystem}). Use `\"apply\"` when the ops were applied by `$applyUpdate`\n * (the node was placed at the retain in apply coordinates); use the default `\"delta-doc\"`\n * for retains produced by doc-delta diffs (e.g. `DeltaOnChangePlugin` local-edit ops).\n * @returns The key of the inserted node if found, `undefined` otherwise.\n */\nexport function getInsertedNodeKey(ops, editorState, coordinates = \"delta-doc\") {\n if (ops.length < 2 || !isRetainOp(ops[0]) || !isInsertEmbedOp(ops[1]))\n return undefined;\n const retain = ops[0].retain;\n return editorState.read(() => {\n const node = $getNodeFromOTPosition(retain, coordinates);\n return node?.getKey();\n });\n}\n/**\n * Get the Lexical node at a specific OT delta position.\n *\n * @remarks\n * This is the reverse of {@link $getOTPositionOfNode}: both must count in the SAME\n * coordinate system for round trips to resolve to the same node.\n *\n * @param otPosition - The OT delta position in the doc.\n * @param coordinates - The OT coordinate system to count in (see {@link OTCoordinateSystem}).\n * @returns The Lexical node if found, `undefined` otherwise.\n */\nexport function $getNodeFromOTPosition(otPosition, coordinates = \"delta-doc\") {\n // LAZY traversal, terminated by the early returns below — same reasoning as\n // $getOTPositionOfNode: the walk stops at the resolved position instead of first\n // materializing the entire document.\n const dfsNodes = $dfsIterator();\n let currentIndex = 0;\n const openParaLikeNodes = [];\n const openContentEmbeds = [];\n for (const dfsNode of dfsNodes) {\n const currentNode = dfsNode.node;\n // Before processing the current node, check if any previously opened para-like nodes are\n // closing.\n for (let j = openParaLikeNodes.length - 1; j >= 0; j--) {\n if ($isElementNodeClosing(openParaLikeNodes[j], dfsNode)) {\n const closingPara = openParaLikeNodes[j];\n openParaLikeNodes.splice(j, 1);\n // Check if this closing position matches our target\n if (currentIndex === otPosition) {\n return closingPara;\n }\n currentIndex += 1;\n }\n }\n // Check if any open content embed nodes (note/unknown) are closing\n for (let j = openContentEmbeds.length - 1; j >= 0; j--) {\n if ($isElementNodeClosing(openContentEmbeds[j].node, dfsNode)) {\n openContentEmbeds.splice(j, 1);\n }\n }\n const activeContentEmbed = openContentEmbeds[openContentEmbeds.length - 1];\n if (activeContentEmbed) {\n if (activeContentEmbed.position === otPosition) {\n return activeContentEmbed.node;\n }\n continue;\n }\n // Track opening of para-like nodes\n if ($isParaLikeNode(currentNode)) {\n if (!openParaLikeNodes.includes(currentNode)) {\n openParaLikeNodes.push(currentNode);\n }\n }\n // Track when we enter an opaque content container (note/unknown always; any element\n // embed such as an editable chapter in \"apply\" coordinates)\n if ($isOpaqueContentNode(currentNode, coordinates)) {\n if (currentIndex === otPosition) {\n return currentNode;\n }\n openContentEmbeds.push({ node: currentNode, position: currentIndex });\n currentIndex += 1;\n continue;\n }\n // Calculate OT length contribution of current node\n const contribution = $getNodeOTContribution(currentNode, coordinates);\n // For text nodes, check if the position falls within this node's range (an editable verse is\n // an embed, not text — see $isOTTextNode — so it is matched by the embed check below instead)\n if ($isOTTextNode(currentNode) && contribution > 0) {\n if (otPosition >= currentIndex && otPosition < currentIndex + contribution) {\n return currentNode;\n }\n }\n // For embed nodes (contribution === 1), check exact position\n if ($isEmbedNode(currentNode)) {\n if (currentIndex === otPosition) {\n return currentNode;\n }\n }\n currentIndex += contribution;\n }\n // Check if any remaining open para-like nodes close at the final position\n for (const paraNode of openParaLikeNodes) {\n if (currentIndex === otPosition) {\n return paraNode;\n }\n currentIndex += 1;\n }\n // Position not found or out of bounds\n return undefined;\n}\n/**\n * Check if an element node is being closed at this point in the DFS traversal.\n */\nexport function $isElementNodeClosing(node, nextDfsNode) {\n if (!node)\n return false;\n // An element node is closing if the next node in DFS is not a descendant.\n // In DFS, all descendants of a node appear consecutively after the node.\n if (!nextDfsNode) {\n // End of traversal, so this node is closing\n return true;\n }\n // Check if the next node is a descendant of the current node\n return !$isDescendantOf(nextDfsNode.node, node.getKey());\n}\n/**\n * Type guard for a node that contributes its glyph TEXT to OT length — a genuine text node, and\n * NOT an embed that merely subclasses `TextNode`.\n *\n * The only embed that is also a `TextNode` is an editable `VerseNode` (its `__text` IS the\n * `\"\\v 1 \"` glyph). Like every embed it counts as ONE opaque OT unit, so OT-counting traversals\n * must classify it via {@link $isEmbedNode} and never measure or split its glyph text. Use this\n * in place of `$isTextNode` wherever a text branch precedes an embed branch, so an editable\n * verse falls through to the embed branch. See {@link OTCoordinateSystem}.\n */\nexport function $isOTTextNode(node) {\n return $isTextNode(node) && !$isEmbedNode(node);\n}\n/**\n * Type guard to check if a node is an embed. Embeds have an OT length of 1 and are self-contained\n * (no children to process).\n */\nexport function $isEmbedNode(node) {\n return ($isSomeChapterNode(node) ||\n $isSomeVerseNode(node) ||\n $isMilestoneNode(node) ||\n $isNoteNode(node) ||\n $isUnknownNode(node) ||\n $isImmutableUnmatchedNode(node));\n}\n/**\n * Type guard to check if the given insert embed operation is for the specified embed type.\n *\n * @param embedType - The type of embed to check for, e.g. \"note\".\n * @param op - The OT delta operation to check.\n * @returns `true` if the operation is for the specified embed type, `false` otherwise.\n *\n * @public\n */\nexport function isInsertEmbedOpOfType(embedType, op) {\n return op?.insert != null && typeof op.insert === \"object\" && embedType in op.insert;\n}\n/**\n * Type guard to check if the given insert embed operation is for an embed type.\n * @param op - The OT delta operation to check.\n * @returns `true` if the operation is for an embed type, `false` otherwise.\n */\nfunction isInsertEmbedOp(op) {\n if (op.insert == null || typeof op.insert !== \"object\")\n return false;\n const embedType = Object.keys(op.insert)[0];\n return (op.insert != null &&\n typeof op.insert === \"object\" &&\n embedType in op.insert &&\n validOTEmbedTypes.includes(embedType));\n}\n/**\n * Type guard to check if the given operation is a retain operation.\n * @param op - The OT delta operation to check.\n * @returns `true` if it is a retain operation, `false` otherwise.\n */\nfunction isRetainOp(op) {\n return op.retain != null && typeof op.retain === \"number\";\n}\n/**\n * Whether the node is an opaque content container in the given coordinate system: it\n * contributes exactly 1 OT unit and its descendants are skipped.\n *\n * Note and unknown contents are opaque in BOTH systems (their contents ops nest inside the\n * embed insert op in the doc delta). Other element-based embeds with presentation glyph\n * children — an editable `ChapterNode` — are opaque only in `\"apply\"` coordinates:\n * `$applyUpdate`'s traversals never descend into ANY embed, while the doc delta serializes\n * a chapter's glyph text child as a body text op. See {@link OTCoordinateSystem}.\n */\nfunction $isOpaqueContentNode(node, coordinates) {\n if ($isNoteNode(node) || $isUnknownNode(node))\n return true;\n return coordinates === \"apply\" && $isElementNode(node) && $isEmbedNode(node);\n}\n/**\n * True when `node` is a paragraph's own marker-prefix glyph — the `\\p`-style MarkerNode a\n * ParaNode carries as its first child in editable marker mode ($createMarkerPrefix,\n * markerEditDeletion.utils.ts). The ONE definition, shared by this file's own OT-length\n * accounting below and by `editor-delta.adaptor.ts`'s content-ops gate, so the two coordinate\n * systems can never drift apart on what counts as the prefix glyph. The position check (first\n * child of a ParaNode) is load-bearing, since {@link $isSynthesizedMarkerNode} identifies the node\n * SHAPE, which is reused for every other glyph in the tree too (a char span's own opener/closer,\n * a note's glyphs, a milestone's or verse's bare attribute glyph) — only a MarkerNode sitting in\n * the paragraph's own prefix slot is presentation scaffolding.\n */\nexport function $isOwnParaPrefixGlyph(node) {\n const parent = node.getParent();\n return $isSynthesizedMarkerNode(node) && $isParaNode(parent) && parent.getFirstChild() === node;\n}\n/**\n * True when `node` sits inside an `AttributeRunNode` wrapper (a verse's/milestone's display run,\n * when wrapped — see `AttributeRunNode.ts`). The wrapper is pure presentation scaffolding — its\n * children are the SAME run pieces (glyphs, attribute text) that also ride as loose siblings when\n * nothing has wrapped the run — so this is the wrapped-shape counterpart of\n * `$isOwnParaPrefixGlyph` above: an ANCESTRY check rather than a sibling-adjacency one, so it also\n * catches shapes the loose-piece exclusions in `editor-delta.adaptor.ts` can miss by adjacency\n * alone (e.g. a milestone's glyph pair with no attribute text between them, where neither glyph\n * has an attribute-tagged sibling to key off of).\n */\nexport function $hasAttributeRunAncestor(node) {\n // The walk starts at the PARENT deliberately: an AttributeRunNode is not \"inside\" itself.\n const parent = node.getParent();\n return parent !== null && $findMatchingParent(parent, $isAttributeRunNode) !== null;\n}\n/**\n * Mirror of editor-delta.adaptor.ts's empty-char-placeholder skip, in delta-doc coordinates: the\n * lone stand-in text of an otherwise childless char span, which the ops stream never emits.\n */\nfunction $isEmptyCharPlaceholderText(node) {\n const parent = node.getParent();\n return ($isCharNode(parent) &&\n node.getTextContent() === EMPTY_CHAR_PLACEHOLDER_TEXT &&\n parent.getChildrenSize() === 1);\n}\n/**\n * Mirror of editor-delta.adaptor.ts's positional note-caller skip: the editable-mode caller text\n * directly after a glyph-fronted note's opening glyph, which the ops stream never emits. The\n * positional guard keeps a pathological content text that merely EQUALS the caller text\n * (elsewhere in the note) counting normally.\n */\nfunction $isEditableNoteCallerText(node) {\n const parent = node.getParent();\n if (!$isNoteNode(parent))\n return false;\n const previousSibling = node.getPreviousSibling();\n return ($isMarkerNode(previousSibling) &&\n previousSibling === parent.getFirstChild() &&\n node.getTextContent() === getEditableCallerText(parent.getCaller()));\n}\n/**\n * Whether the single-dirty-leaf fast path (DeltaOnChangePlugin) may emit `node`'s raw text as a\n * content op. ONE authority rather than a re-implemented exclusion list: eligibility derives\n * from the same delta-doc counting the length side uses ({@link $getNodeOTContribution}) — a\n * node whose bytes are presentation (contribution 0: para-prefix glyphs, marker-trailing-space,\n * attribute text and runs, placeholders, caller text, legacy `⍽|`-prefixed attribute text) or an\n * opaque embed (contribution 1 ≠ text size: an editable verse glyph) counts differently from its\n * raw bytes, so an op built from those bytes would be in a different currency than its retain —\n * such an edit must take the full-diff fallback, which applies `$handleTextNodes`' exclusions.\n * `$isDisplayRunPiece` is checked on top because the ops stream excludes a LOOSE display run's\n * glyphs while delta-doc counting deliberately keeps them (the documented asymmetry in\n * editor-delta.adaptor.ts).\n */\nexport function $isFastPathContentText(node) {\n return (!$isDisplayRunPiece(node) &&\n $getNodeOTContribution(node, \"delta-doc\") === node.getTextContentSize());\n}\n/**\n * Calculate the OT length contribution of a single node.\n *\n * @param coordinates - The OT coordinate system to count in (see {@link OTCoordinateSystem}).\n * A paragraph's own marker-prefix glyph and its NBSP separator are presentation scaffolding\n * that `editor-delta.adaptor.ts`'s `$handleTextNodes` excludes from content ops — but ONLY in\n * `\"delta-doc\"` coordinates, which must agree with that ops stream. `\"apply\"` coordinates are\n * DEFINED as whatever `$applyUpdate`'s own insert/delete/attribute traversals do\n * (delta-apply-update.utils.ts), and none of them skip these nodes — every one counts an OT\n * text node's raw `getTextContentSize()` unconditionally. So `\"apply\"` coordinates must keep\n * counting the prefix and separator too, or a replace-embed retain computed here would\n * disagree with where `$applyUpdate` actually walks to (a note \"replace\" landing one-plus-\n * prefix-length short of the note it meant to delete, deleting the wrong node and leaving the\n * replacement appended instead). If `$applyUpdate`'s traversals are ever taught to skip these\n * nodes too, this exclusion should extend to `\"apply\"` at the same time — not before.\n *\n * The SAME reasoning governs the `$hasAttributeRunAncestor` exclusion below: `$applyUpdate`'s\n * own traversal functions (`$traverseAndApplyAttributesRecursive`, `$traverseAndDelete`,\n * `$insertNodeAtCharacterOffset`) do not special-case `AttributeRunNode` at all — every\n * editable-mode verse/milestone run the adaptor builds rides wrapped in one, so this traversal\n * gap is live on every real document, not a hypothetical one. Each traversal treats a wrapper as\n * an ordinary, un-special-cased `ElementNode` (zero contribution of its own, descend into\n * children) and counts each child's raw text length exactly as it already does for a LOOSE run's\n * pieces — i.e. wrapping changes nothing about what `$applyUpdate` actually does. `\"apply\"`\n * coordinates must therefore keep counting a wrapped piece's text too, matching that (unchanged)\n * traversal; only `\"delta-doc\"` excludes it, mirroring `editor-delta.adaptor.ts`'s existing ops\n * exclusion for the identical bytes.\n */\nfunction $getNodeOTContribution(node, coordinates) {\n // Embeds are checked FIRST: an editable VerseNode is a TextNode subclass but counts as one\n // opaque OT unit (its glyph text is engine-owned display, excluded from content ops), the same\n // as it counts in the doc delta and in `$applyUpdate`'s traversals. See {@link $isOTTextNode}.\n if ($isEmbedNode(node))\n return 1;\n if ($isTextNode(node)) {\n // Read before the guard chain below: its type guards narrow `node` in later `||` operands\n // (a false branch of a `node is TextNode` guard leaves `never`), so member access there\n // does not typecheck even though the value is unchanged.\n const nodeText = node.getTextContent();\n if (coordinates === \"delta-doc\" &&\n // A bare cursor host (EmptyVerseCaretGuardPlugin) is a transient, collab-invisible node:\n // its insertion is never emitted, so it contributes nothing to DOC-DELTA positions or the\n // local doc would drift one position ahead of every peer while a host rests. In `\"apply\"`\n // coordinates it MUST count, per the rule in the doc comment above: none of\n // `$applyUpdate`'s traversals skip a placeholder (each classifies with `$isOTTextNode`\n // and adds raw `getTextContentSize()`), so excluding it here left a replace-embed retain\n // one short whenever a host rested before the target — a footnote-popover save then\n // deleted the unit BEFORE the note instead of the note itself.\n ($isCursorPlaceholderOnlyText(node) ||\n $isOwnParaPrefixGlyph(node) ||\n $getState(node, textTypeState) === \"marker-trailing-space\" ||\n // An attribute value keyed by its own state, not by ancestry: a CHAR span's run is a direct\n // TextNode child of the span, never wrapped (`displayRunRegistry.ts`'s char descriptor\n // writes \"owner-children\"), so `$hasAttributeRunAncestor` cannot see it. That shape is at\n // rest on every `\\w …|strong=\"…\"\\w*`, and the ops stream already omits those bytes\n // (`isNodeAttributeText` in editor-delta.adaptor.ts), so counting them here would put this\n // side out of step with the op stream on ordinary Scripture.\n $getState(node, textTypeState) === \"attribute\" ||\n $hasAttributeRunAncestor(node) ||\n // The remaining ops-stream exclusions, so this side and $handleTextNodes count the same\n // bytes (docs/standard-view-invariants.md §II — extend the shared list, never fork it):\n // the legacy NBSP-`|` byte-prefixed attribute text the ops stream still honors for\n // pre-state-tag peers and persisted deltas, the empty-char placeholder, and the\n // editable-mode note caller in caller position.\n nodeText.startsWith(NODE_ATTRIBUTE_PREFIX) ||\n $isEmptyCharPlaceholderText(node) ||\n $isEditableNoteCallerText(node)))\n return 0;\n return node.getTextContentSize();\n }\n // CharNodes and other nodes don't contribute to OT length\n return 0;\n}\n","import { $isSomeVerseNode } from \"../../../nodes/usj/node-react.utils\";\nimport { $hasAttributeRunAncestor, $isElementNodeClosing, $isOwnParaPrefixGlyph, LF, } from \"./delta-common.utils\";\nimport { $dfs } from \"@lexical/utils\";\nimport { $getRoot, $getState, $isTextNode } from \"lexical\";\nimport Delta from \"quill-delta\";\nimport { $findFirstAncestorNoteNode, $isBookNode, $isCharNode, $isDisplayRunPiece, $isImmutableUnmatchedNode, $isImpliedParaNode, $isMarkerNode, $isMilestoneNode, $isNoteNode, $isParaLikeNode, $isParaNode, $isSomeChapterNode, $isUnknownNode, $isVerseNode, BOOK_MARKER, CHAPTER_MARKER, charIdState, EMPTY_CHAR_PLACEHOLDER_TEXT, getEditableCallerText, isCursorPlaceholderOnly, NBSP, NODE_ATTRIBUTE_PREFIX, segmentState, textTypeState, VERSE_MARKER, } from \"shared\";\nexport function $getTextOp(node, openCharNodes) {\n const op = { insert: node.__text };\n const segment = $getState(node, segmentState);\n if (segment)\n op.attributes = { segment };\n if (openCharNodes && openCharNodes.length > 0) {\n const char = $buildCharAttribute(openCharNodes);\n if (char) {\n op.attributes = {\n ...op.attributes,\n char,\n };\n }\n }\n return op;\n}\nexport function getEditorDelta(editorState) {\n const update = new Delta();\n if (editorState.isEmpty())\n return update;\n editorState.read(() => {\n const root = $getRoot();\n if (!root || root.isEmpty())\n return;\n // check for default empty implied-para node\n const rootChildren = root.getChildren();\n if (rootChildren.length === 1 &&\n $isImpliedParaNode(rootChildren[0]) &&\n (!rootChildren[0].getChildren() || rootChildren[0].getChildrenSize() === 0)) {\n return;\n }\n const ops = $getAllNodeOps();\n for (const op of ops)\n update.push(op);\n });\n return update;\n}\n/**\n * Get the operational transform (OT) delta operations for a specific node or range of nodes.\n * Pass nothing to get all nodes.\n *\n * @param startNode - The node to start the search, if omitted, it will start at the root node.\n * @param endNode - The node to end the search, if omitted, it will find all descendants of the\n * startingNode.\n * @returns An array of DeltaOp objects representing the OT operations for the specified nodes.\n */\nexport function $getParticularNodeOps(startNode, endNode) {\n const ops = [];\n const dfsNodes = $dfs(startNode, endNode);\n const openParaLikeNodes = [];\n const openCharNodes = [];\n const openEmbeds = [];\n const charContentProduced = new Set();\n for (let i = 0; i < dfsNodes.length; i++) {\n const currentNode = dfsNodes[i].node;\n ops.push(...$getNodeOps(currentNode, i, dfsNodes, openParaLikeNodes, openCharNodes, openEmbeds, charContentProduced));\n }\n // Close any remaining open nodes\n for (const openNode of openParaLikeNodes) {\n ops.push(...$getNodeOps(openNode, dfsNodes.length, dfsNodes, openParaLikeNodes, openCharNodes, openEmbeds, charContentProduced));\n }\n return ops;\n}\nfunction $getAllNodeOps() {\n return $getParticularNodeOps();\n}\nfunction $getNodeOps(currentNode, currentIndex, dfsNodes, openParaLikeNodes, openCharNodes, openEmbeds, charContentProduced) {\n if (!currentNode)\n return [];\n const ops = [];\n const nextDfsNode = dfsNodes[currentIndex + 1];\n $handleBlockNodes(currentNode, ops, openParaLikeNodes);\n $handleTextNodes(currentNode, ops, openCharNodes, openEmbeds, charContentProduced);\n $handleCharNodes(currentNode, currentIndex, dfsNodes, openCharNodes, charContentProduced, openEmbeds, ops);\n // is an EmbedNode\n if ($isSomeChapterNode(currentNode))\n ops.push($getChapterOp(currentNode));\n if ($isSomeVerseNode(currentNode))\n ops.push($getVerseOp(currentNode));\n if ($isMilestoneNode(currentNode))\n ops.push($getMilestoneOp(currentNode));\n if ($isImmutableUnmatchedNode(currentNode))\n ops.push($getImmutableUnmatchedOp(currentNode));\n $handleUnknownNodes(currentNode, ops, openEmbeds);\n $handleNoteNodes(currentNode, ops, openEmbeds);\n $closeCompletedEmbeds(nextDfsNode, openEmbeds);\n return ops;\n}\nfunction $handleBlockNodes(currentNode, ops, openParaLikeNodes) {\n if (!currentNode.isInline()) {\n // Handle block nodes\n const openNode = openParaLikeNodes.pop();\n if ($isBookNode(openNode))\n ops.push($getBookOp(openNode));\n else if ($isParaNode(openNode))\n ops.push($getParaOp(openNode));\n else if ($isImpliedParaNode(openNode))\n ops.push({ insert: LF });\n }\n if ($isParaLikeNode(currentNode)) {\n // Track when we open para-like nodes\n if (!openParaLikeNodes.includes(currentNode)) {\n openParaLikeNodes.push(currentNode);\n }\n }\n}\nfunction $handleTextNodes(currentNode, ops, openCharNodes, openEmbeds, charContentProduced) {\n if (!$isTextNode(currentNode))\n return;\n // An editable VerseNode's own `__text` is its marker glyph (`\\v 1 `) — VerseNode extends\n // TextNode so the glyph can sit inline for caret placement, but the glyph is engine-owned\n // display, not content. The verse is already conveyed by its own embed op ($getVerseOp,\n // pushed by the caller once $isSomeVerseNode matches). Skip the glyph here so it never ALSO\n // surfaces as a content text op, which would double-count the verse's length in the OT\n // content stream (once as the embed's implicit 1 unit, once as the leaked glyph bytes) and\n // shift every offset that follows it.\n if ($isVerseNode(currentNode))\n return;\n // An ImmutableUnmatchedNode's bytes are its embed's presentation, the same shape as the\n // editable VerseNode glyph above: the node extends TextNode so the flagged `\\nd*` bytes stay\n // caret-addressable and editable, but the construct is conveyed by its own embed op\n // ($getImmutableUnmatchedOp, pushed by the caller once $isImmutableUnmatchedNode matches).\n // Letting the bytes ALSO flow as a content text op would double-count the embed's OT length\n // (once as the embed's 1 unit, once as the leaked bytes) and shift every offset after it.\n if ($isImmutableUnmatchedNode(currentNode))\n return;\n // Skip a note's first text child: in editable modes this is the note's opening marker glyph\n // (MarkerNode extends TextNode), which shouldn't flow into ops. Caller text, when present as a\n // plain text child (expanded editable mode), is never the first child and is not skipped here.\n const parent = currentNode.getParent();\n if ($isNoteNode(parent) && parent.getFirstChild() === currentNode)\n return;\n // Canonical, glyph-free note ops in editable marker mode: note contents ops carry CONTENT\n // only, the same shape non-editable marker modes produce. Presentation-only nodes that\n // `$applyUpdate` re-synthesizes when materializing the note (`$createWholeNote` /\n // `$createNestedChars`) must not flow into ops, otherwise a round-trip doubles them:\n // - MarkerNode glyphs (char-span openers/closers and the note's own closing glyph);\n // - the expanded editable caller text (presentation of the note's `caller` attribute).\n // A char span's OWN opener/closer glyphs OUTSIDE a note legitimately flow through as literal\n // editable-mode text (the `char` attribute wrapper is layered on top, not a substitute) — only\n // a milestone's or a verse's \\va/\\vp display-run glyphs (presentation that duplicates state the\n // embed op already carries) must never leak, in or out of a note. TWO checks are needed together\n // here, neither a superset of the other:\n // - $hasAttributeRunAncestor walks EVERY ancestor, so it catches a wrapped glyph regardless of\n // how deep it rides or whether the wrapper sits directly after its owner — an intervening\n // node between the owner and its wrapper (a remote insert landing at that boundary, an undo\n // stack, a mid-edit tree) still leaves the wrapper's own contents ancestor-reachable, but the\n // registry's per-kind `ownerOf` walks require exactly that adjacency and give up on the first\n // non-run-piece sibling, so it would miss this shape. (A few lines below, `isNodeAttributeText`\n // applies this same ancestor walk unconditionally to every surviving TextNode, wrapped glyph\n // or not — that independently backstops a wrapped piece even if this arm were removed, but\n // keeping it here too means this gate's OWN stated exclusion holds on its own, rather than by\n // incidental coupling to a check several lines away that could itself change independently.)\n // - $isDisplayRunPiece is keyed on the glyph's KIND (the display-run registry's owner walk), so\n // it catches a run's glyphs riding LOOSE — caret-grace, an undo stack, and a\n // collab-materialized bare owner each leave a run's glyphs loose for at least one commit, and\n // a loose glyph is exactly as much engine-owned display as a wrapped one — a shape\n // $hasAttributeRunAncestor (ancestry into a wrapper) cannot see at all.\n // Being kind-keyed rather than shape-keyed, $isDisplayRunPiece also needs no per-piece exemption\n // for a char span's own opener/closer or its own nested `|…` run: neither is a registered piece\n // of any OTHER owner's run.\n //\n // This union widens only the ops-stream exclusion, restoring the historical contract: the\n // delta-doc length side (`$getNodeOTContribution` in delta-common.utils.ts) deliberately keeps\n // counting a LOOSE run's glyphs (via $hasAttributeRunAncestor alone, unchanged) even though the\n // ops stream now excludes them — that is not a drift to fix by widening the length side to\n // match, it is the same asymmetry the ops stream honored before the earlier loose-glyph\n // exclusion was removed.\n const isInNote = $findFirstAncestorNoteNode(currentNode) !== undefined;\n if ($isMarkerNode(currentNode) &&\n (isInNote ||\n $isOwnParaPrefixGlyph(currentNode) ||\n $hasAttributeRunAncestor(currentNode) ||\n $isDisplayRunPiece(currentNode)))\n return;\n // The para prefix's NBSP separator is presentation scaffolding ($createMarkerPrefix,\n // markerEditDeletion.utils.ts); the apply side re-synthesizes the whole prefix, so its text\n // must never enter content ops.\n if ($getState(currentNode, textTypeState) === \"marker-trailing-space\")\n return;\n let text = currentNode.getTextContent();\n // A bare cursor host (EmptyVerseCaretGuardPlugin) is collab-invisible: its insertion is never\n // emitted, so it must never appear in a delta op either.\n if (isCursorPlaceholderOnly(text))\n return;\n // A glyph-fronted note (first child is a MarkerNode) is the editable-mode shape; only\n // there does the caller render as a plain text child, and always in CALLER POSITION —\n // immediately after the opening glyph. The positional guard keeps a pathological content\n // text node that merely EQUALS the caller text (elsewhere in the note) flowing into ops.\n const previousSibling = currentNode.getPreviousSibling();\n if ($isNoteNode(parent) &&\n $isMarkerNode(previousSibling) &&\n previousSibling === parent.getFirstChild() &&\n text === getEditableCallerText(parent.getCaller())) {\n return;\n }\n const parentCharNode = $isCharNode(parent) ? parent : undefined;\n // Strip the structural NBSP separator that editable-mode char spans glue onto their\n // content text after the opening glyph (added by the USJ adaptor's `createChar` and\n // re-added by `$applyUpdate`'s `$createNote`); it is display-only, not content. POSITIONAL,\n // like the reverse adaptor's `content[0]` strip: the separator is prepended solely to the text\n // directly after the span's opening glyph, so only THAT node's leading NBSP is structural — a\n // leading NBSP on any later child (after a nested closer in `\\ft A\\+nd x\\+nd*~B`) is the\n // author's own `~` and must flow into the op on every emit.\n const parentCharFirstChild = parentCharNode?.getFirstChild();\n if (isInNote &&\n !!parentCharNode &&\n $isMarkerNode(parentCharFirstChild) &&\n previousSibling === parentCharFirstChild &&\n text.startsWith(NBSP)) {\n text = text.slice(1);\n }\n // Char-span attribute display runs (bare `|…`, no NBSP prefix — see usj-editor.adaptor's\n // `addCharAttributes`) carry no NBSP prefix to strip against, so the prefix check alone can't\n // catch them; the textType state tag is the other signal, kept alongside the prefix check for\n // the legacy NBSP-prefixed (milestone) attribute text. The legacy byte arm is LOAD-BEARING for\n // compatibility, not a leftover: deployed peers and persisted OT documents (ScriptureForge\n // shares these collab documents) predate the state-tagged format, and their stored deltas\n // replay through here — without the byte check, that replayed attribute text would leak display\n // bytes into content. Do not retire it while any pre-tag document or peer can reach this\n // editor. Text inside an AttributeRunNode wrapper\n // is excluded regardless of its own textType tag: the wrapper is an engine-owned presentation\n // region (see AttributeRunNode.ts), so anything riding inside it is presentation, not content,\n // whether or not it happens to also carry the \"attribute\" state tag.\n const isNodeAttributeText = text.startsWith(NODE_ATTRIBUTE_PREFIX) ||\n $getState(currentNode, textTypeState) === \"attribute\" ||\n $hasAttributeRunAncestor(currentNode);\n const isPlaceholderText = !!parentCharNode &&\n text === EMPTY_CHAR_PLACEHOLDER_TEXT &&\n parentCharNode.getChildrenSize() === 1;\n const activeEmbed = $getActiveEmbedContext(currentNode, openEmbeds);\n // An embed's `contents` ops are a SELF-CONTAINED SUB-DOCUMENT: they are materialized on the\n // receive side into a freshly built note/unknown, with no surrounding document to inherit from,\n // and read back out by `$getParticularNodeOps(embedNode)`, whose DFS starts AT the embed and so\n // has no ambient char stack at all. `openCharNodes` is the whole walk's stack, so inside an\n // embed it still holds char spans opened OUTSIDE it — spans that are not part of the embed's\n // content and have no representation in the sub-document. Scoping the stack to spans within the\n // embed is what makes this producer agree with `$getParticularNodeOps`; without it a note\n // inserted mid-span (`\\nd asdf\\nd*`) shipped `char: [{nd}, {fr}]` on its own first\n // content op, and the apply side built the enclosing `\\nd` INSIDE the note with a nested\n // `\\+fr` under it. `children` is the embed's entire subtree (`$dfs` from the embed node), not\n // just its direct children, so a span opened at any depth inside the embed is kept.\n const embedScopedCharNodes = activeEmbed\n ? openCharNodes.filter((charNode) => activeEmbed.children.includes(charNode))\n : openCharNodes;\n const textOp = $getTextOp(currentNode, embedScopedCharNodes);\n textOp.insert = text;\n if (activeEmbed) {\n if (!text || text === NBSP || isNodeAttributeText)\n return;\n activeEmbed.contentsOps?.push(textOp);\n }\n else {\n // Attribute display text is presentation-only regardless of WHERE it rides — inside a char\n // span (the original, narrower rule) or, like a milestone's or a verse's \\va/\\vp value, as a\n // plain sibling with no CharNode parent at all.\n const shouldSkipTextOp = isPlaceholderText || isNodeAttributeText;\n if (!shouldSkipTextOp) {\n ops.push(textOp);\n }\n }\n const hasMeaningfulText = text !== \"\" && !isPlaceholderText && !(isNodeAttributeText && !!parentCharNode);\n if (openCharNodes.length > 0 && hasMeaningfulText) {\n for (const charNode of openCharNodes) {\n charContentProduced.add(charNode);\n }\n }\n}\nfunction $handleCharNodes(currentNode, currentIndex, dfsNodes, openCharNodes, charContentProduced, openEmbeds, ops) {\n if ($isCharNode(currentNode) && !openCharNodes.includes(currentNode)) {\n openCharNodes.push(currentNode);\n }\n const nextDfsNode = dfsNodes[currentIndex + 1];\n for (const openCharNode of openCharNodes.toReversed()) {\n if ($isElementNodeClosing(openCharNode, nextDfsNode)) {\n openCharNodes.pop();\n if (!charContentProduced.has(openCharNode)) {\n const emptyCharOp = $getEmptyCharOp(openCharNode);\n const activeEmbed = $getActiveEmbedContext(openCharNode, openEmbeds);\n if (activeEmbed) {\n activeEmbed.contentsOps?.push(emptyCharOp);\n }\n else {\n ops.push(emptyCharOp);\n }\n }\n charContentProduced.delete(openCharNode);\n }\n }\n}\nfunction $handleNoteNodes(currentNode, ops, openEmbeds) {\n if (!$isNoteNode(currentNode))\n return;\n const noteOp = $getNoteOp(currentNode);\n const parentEmbed = $getActiveEmbedContext(currentNode, openEmbeds);\n const embedContext = {\n node: currentNode,\n children: $dfs(currentNode).map((n) => n.node),\n contentsOps: noteOp.insert.note?.contents?.ops,\n };\n openEmbeds.push(embedContext);\n if (parentEmbed?.contentsOps)\n parentEmbed.contentsOps.push(noteOp);\n else\n ops.push(noteOp);\n}\nfunction $handleUnknownNodes(currentNode, ops, openEmbeds) {\n if (!$isUnknownNode(currentNode))\n return;\n const unknownOp = $getUnknownOp(currentNode);\n const parentEmbed = $getActiveEmbedContext(currentNode, openEmbeds);\n const embedContext = {\n node: currentNode,\n children: $dfs(currentNode).map((n) => n.node),\n contentsOps: unknownOp.insert.unknown?.contents?.ops,\n };\n openEmbeds.push(embedContext);\n if (parentEmbed?.contentsOps)\n parentEmbed.contentsOps.push(unknownOp);\n else\n ops.push(unknownOp);\n}\n/**\n * Copies `node`'s unknown attributes onto `payload` — the embed or attribute object it is emitted\n * in — so they reach the peer. The node holds them either way; dropping them here is invisible to\n * every USJ round-trip and loses them only on the wire.\n *\n * Reads node state: call inside `editorState.read()`, as every op builder in this module is.\n */\nfunction $assignUnknownAttributes(payload, node) {\n const unknownAttributes = node.getUnknownAttributes();\n if (unknownAttributes)\n Object.assign(payload, unknownAttributes);\n}\nfunction $getBookOp(currentNode) {\n const book = { style: BOOK_MARKER, code: currentNode.__code };\n $assignUnknownAttributes(book, currentNode);\n return { insert: LF, attributes: { book } };\n}\nfunction $getChapterOp(currentNode) {\n const chapter = { style: CHAPTER_MARKER, number: currentNode.__number };\n if (currentNode.__sid) {\n chapter.sid = currentNode.__sid;\n }\n if (currentNode.__altnumber) {\n chapter.altnumber = currentNode.__altnumber;\n }\n if (currentNode.__pubnumber) {\n chapter.pubnumber = currentNode.__pubnumber;\n }\n $assignUnknownAttributes(chapter, currentNode);\n return { insert: { chapter } };\n}\nexport function $getParaOp(node) {\n const para = { style: node.__marker };\n $assignUnknownAttributes(para, node);\n return { insert: LF, attributes: { para } };\n}\nfunction $getVerseOp(currentNode) {\n const verse = { style: VERSE_MARKER, number: currentNode.__number };\n if (currentNode.__sid) {\n verse.sid = currentNode.__sid;\n }\n if (currentNode.__altnumber) {\n verse.altnumber = currentNode.__altnumber;\n }\n if (currentNode.__pubnumber) {\n verse.pubnumber = currentNode.__pubnumber;\n }\n $assignUnknownAttributes(verse, currentNode);\n return { insert: { verse } };\n}\nfunction $getMilestoneOp(currentNode) {\n const milestone = { style: currentNode.__marker };\n if (currentNode.__sid) {\n milestone.sid = currentNode.__sid;\n }\n if (currentNode.__eid) {\n milestone.eid = currentNode.__eid;\n }\n if (currentNode.__attributeOrder) {\n milestone.attributeOrder = currentNode.__attributeOrder;\n }\n $assignUnknownAttributes(milestone, currentNode);\n return { insert: { milestone } };\n}\nfunction $getImmutableUnmatchedOp(currentNode) {\n return { insert: { unmatched: { marker: currentNode.__marker } } };\n}\nfunction $getNoteOp(currentNode) {\n const note = {\n style: currentNode.__marker,\n caller: currentNode.__caller,\n };\n if (currentNode.__category) {\n note.category = currentNode.__category;\n }\n // Carry unknown attributes (e.g. the unclosed-note `closed=\"false\"`) so the round-trip is\n // lossless — `$applyUpdate`'s `$createNote` already reads them back via\n // `getUnknownAttributes(…, OT_NOTE_PROPS)`, matching how unknown-embed ops behave.\n $assignUnknownAttributes(note, currentNode);\n if (currentNode.getChildrenSize() > 1) {\n note.contents = { ops: [] };\n }\n const op = { insert: { note } };\n const segment = $getState(currentNode, segmentState);\n if (segment) {\n op.attributes = { segment };\n }\n return op;\n}\nfunction $getEmptyCharOp(charNode) {\n const op = { insert: \"\" };\n const char = $buildCharAttribute([charNode]);\n if (char) {\n op.attributes = { char };\n }\n return op;\n}\nfunction $getUnknownOp(currentNode) {\n const unknown = { tag: currentNode.getTag() };\n const marker = currentNode.getMarker();\n if (marker)\n unknown.marker = marker;\n $assignUnknownAttributes(unknown, currentNode);\n if (currentNode.getChildrenSize() > 0)\n unknown.contents = { ops: [] };\n return { insert: { unknown } };\n}\nfunction $getActiveEmbedContext(node, openEmbeds) {\n for (let i = openEmbeds.length - 1; i >= 0; i--) {\n const embed = openEmbeds[i];\n if (embed.children.includes(node))\n return embed;\n }\n return undefined;\n}\nfunction $closeCompletedEmbeds(nextDfsNode, openEmbeds) {\n for (let i = openEmbeds.length - 1; i >= 0; i--) {\n if ($isElementNodeClosing(openEmbeds[i].node, nextDfsNode)) {\n openEmbeds.splice(i, 1);\n }\n }\n}\nfunction $buildCharAttribute(charNodes) {\n if (charNodes.length === 0)\n return undefined;\n const items = charNodes.map($buildCharItem);\n return items.length === 1 ? items[0] : items;\n}\nfunction $buildCharItem(charNode) {\n const charItem = { style: charNode.__marker };\n const cid = $getState(charNode, charIdState);\n if (cid)\n charItem.cid = cid;\n $assignUnknownAttributes(charItem, charNode);\n return charItem;\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\n/** Conforms with USJ v3.1 @see https://docs.usfm.bible/usfm/3.1/note/index.html */\nimport { $getParticularNodeOps } from \"../../plugins/usj/collab/editor-delta.adaptor\";\nimport { $dfs } from \"@lexical/utils\";\nimport { $applyNodeReplacement, $getNodeByKey, DecoratorNode, isHTMLElement, } from \"lexical\";\nimport { $isNoteNode, GENERATOR_NOTE_CALLER, HIDDEN_NOTE_CALLER } from \"shared\";\nexport const IMMUTABLE_NOTE_CALLER_VERSION = 1;\nexport class ImmutableNoteCallerNode extends DecoratorNode {\n __caller;\n __previewText;\n __onClick;\n constructor(caller = GENERATOR_NOTE_CALLER, previewText = \"\", onClick, key) {\n super(key);\n this.__caller = caller;\n this.__previewText = previewText;\n this.__onClick = onClick ?? (() => undefined);\n }\n static getType() {\n return \"immutable-note-caller\";\n }\n static clone(node) {\n const { __caller, __previewText, __onClick, __key } = node;\n return new ImmutableNoteCallerNode(__caller, __previewText, __onClick, __key);\n }\n static importDOM() {\n return {\n span: (node) => {\n if (!isNoteCallerElement(node))\n return null;\n return {\n conversion: $convertNoteCallerElement,\n priority: 1,\n };\n },\n };\n }\n static importJSON(serializedNode) {\n return $createImmutableNoteCallerNode().updateFromJSON(serializedNode);\n }\n updateFromJSON(serializedNode) {\n return super\n .updateFromJSON(serializedNode)\n .setCaller(serializedNode.caller)\n .setPreviewText(serializedNode.previewText)\n .setOnClick(serializedNode.onClick);\n }\n setCaller(caller) {\n if (this.__caller === caller)\n return this;\n const self = this.getWritable();\n self.__caller = caller;\n return self;\n }\n getCaller() {\n const self = this.getLatest();\n return self.__caller;\n }\n setPreviewText(previewText) {\n if (this.__previewText === previewText)\n return this;\n const self = this.getWritable();\n self.__previewText = previewText;\n return self;\n }\n getPreviewText() {\n const self = this.getLatest();\n return self.__previewText;\n }\n setOnClick(onClick) {\n if (this.__onClick === onClick)\n return this;\n const self = this.getWritable();\n self.__onClick = onClick;\n return self;\n }\n getOnClick() {\n const self = this.getLatest();\n return self.__onClick;\n }\n createDOM() {\n const dom = document.createElement(\"span\");\n dom.classList.add(this.__type);\n dom.setAttribute(\"data-caller\", this.__caller);\n dom.setAttribute(\"data-preview-text\", this.__previewText);\n return dom;\n }\n updateDOM(prevNode) {\n if (prevNode.__caller !== this.__caller)\n return true;\n return false;\n }\n exportDOM(editor) {\n const { element } = super.exportDOM(editor);\n if (element && isHTMLElement(element)) {\n element.classList.add(this.getType());\n element.setAttribute(\"data-caller\", this.getCaller());\n element.setAttribute(\"data-preview-text\", this.getPreviewText());\n }\n return { element };\n }\n decorate(editor) {\n const noteNode = this.getParent();\n if (!noteNode)\n return null;\n const noteNodeKey = noteNode.getKey();\n const noteIsCollapsed = noteNode.getIsCollapsed();\n const callerNodeKey = this.__key;\n const onClick = (event) => this.__onClick?.(event, noteNodeKey, noteIsCollapsed, () => getNoteCaller(editor, noteNodeKey), (caller) => setNoteCaller(editor, noteNodeKey, callerNodeKey, caller), () => getNoteOps(editor, noteNodeKey), () => getNoteIndex(editor, noteNodeKey));\n const callerId = `${this.__caller}_${this.__previewText}}`.replace(/\\s+/g, \"\").substring(0, 25);\n return (_jsx(\"button\", { onClick: onClick, title: this.__previewText, \"data-caller-id\": callerId, children: this.__caller === GENERATOR_NOTE_CALLER && noteIsCollapsed\n ? // Caller is generated by CSS (footnote or cross-reference sequence, per note marker)\n \"\"\n : this.__caller === HIDDEN_NOTE_CALLER && noteIsCollapsed\n ? // PT9: the hidden caller displays as `*` when collapsed\n \"*\"\n : this.__caller }));\n }\n exportJSON() {\n return {\n type: this.getType(),\n caller: this.getCaller(),\n previewText: this.getPreviewText(),\n onClick: this.getOnClick(),\n version: IMMUTABLE_NOTE_CALLER_VERSION,\n };\n }\n // Mutation\n isKeyboardSelectable() {\n return false;\n }\n}\nfunction $convertNoteCallerElement(element) {\n const caller = element.getAttribute(\"data-caller\") ?? \"\";\n const previewText = element.getAttribute(\"data-preview-text\") ?? \"\";\n const node = $createImmutableNoteCallerNode(caller, previewText);\n return { node };\n}\nexport function $createImmutableNoteCallerNode(caller, previewText, onClick) {\n return $applyNodeReplacement(new ImmutableNoteCallerNode(caller, previewText, onClick));\n}\nfunction isNoteCallerElement(node) {\n if (!node)\n return false;\n return node.classList.contains(ImmutableNoteCallerNode.getType());\n}\nexport function $isImmutableNoteCallerNode(node) {\n return node instanceof ImmutableNoteCallerNode;\n}\nexport function isSerializedImmutableNoteCallerNode(node) {\n return node?.type === ImmutableNoteCallerNode.getType();\n}\n// `getEditorState().read`, NOT `editor.read`, in this and the two getters below: all three are\n// handed to the caller-click callback as lazy thunks, and `editor.read()` force-flushes any\n// in-flight update when invoked mid-dispatch — the frozen-state hazard the project rule exists\n// to prevent.\nfunction getNoteCaller(editor, noteNodeKey) {\n return editor.getEditorState().read(() => {\n const noteNode = $getNodeByKey(noteNodeKey);\n if (!$isNoteNode(noteNode))\n throw new Error(`getNoteCaller: Note node not found: ${noteNodeKey}`);\n return noteNode.getCaller();\n });\n}\nfunction setNoteCaller(editor, noteNodeKey, callerNodeKey, caller) {\n editor.update(() => {\n const noteNode = $getNodeByKey(noteNodeKey);\n if (!$isNoteNode(noteNode))\n throw new Error(`setNoteCaller: Note node not found: ${noteNodeKey}`);\n noteNode.setCaller(caller);\n const callerNode = $getNodeByKey(callerNodeKey);\n if (!$isImmutableNoteCallerNode(callerNode))\n throw new Error(`setNoteCaller: Caller node not found: ${callerNodeKey}`);\n callerNode.setCaller(caller);\n });\n}\nfunction getNoteOps(editor, noteNodeKey) {\n return editor.getEditorState().read(() => {\n const noteNode = $getNodeByKey(noteNodeKey);\n if (!$isNoteNode(noteNode))\n throw new Error(`getNoteOps: Note node not found: ${noteNodeKey}`);\n return $getParticularNodeOps(noteNode);\n });\n}\n/**\n * The note's document-order index among all of the document's notes — the same order a USJ walk\n * yields them, so it addresses the corresponding entry of a USJ-built notes list (e.g. a footnotes\n * pane). `undefined` when the note is no longer attached.\n */\nfunction getNoteIndex(editor, noteNodeKey) {\n return editor.getEditorState().read(() => {\n let index = 0;\n for (const { node } of $dfs()) {\n if (!$isNoteNode(node))\n continue;\n if (node.getKey() === noteNodeKey)\n return index;\n index += 1;\n }\n return undefined;\n });\n}\n/** Possible note callers to use when caller is '+'. Up to 2 characters are used, e.g. a-zz */\nexport const defaultNoteCallers = [\n \"a\",\n \"b\",\n \"c\",\n \"d\",\n \"e\",\n \"f\",\n \"g\",\n \"h\",\n \"i\",\n \"j\",\n \"k\",\n \"l\",\n \"m\",\n \"n\",\n \"o\",\n \"p\",\n \"q\",\n \"r\",\n \"s\",\n \"t\",\n \"u\",\n \"v\",\n \"w\",\n \"x\",\n \"y\",\n \"z\",\n];\n/** Default cross-reference caller to use when caller is '+' on a cross-reference note and no\n * `crossRefCallers` option is set. */\nexport const defaultCrossRefCallers = [\"†\"];\n","import { getUsjDocumentLocationTypeName, indexesFromUsjJsonPath, isUsjAttributeKeyLocation, isUsjAttributeMarkerLocation, isUsjClosingAttributeMarkerLocation, isUsjClosingMarkerLocation, isUsjMarkerLocation, isUsjPropertyValueLocation, isUsjTextContentLocation, usjJsonPathFromIndexes, } from \"@eten-tech-foundation/scripture-utilities\";\nimport { $createPoint, $createRangeSelection, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, } from \"lexical\";\nimport { $getElementOffsetFromLogicalIndex, $getLogicalContentItems, $getLogicalIndexOfChild, $getLogicalParent, $getLogicalPointFromElementPoint, $getLogicalTextLocation, $getTextNodeAtLogicalOffset, $isMarkerNode, $isParaLikeNode, $isTypedMarkNode, $isVerseBlockNode, $isVisibleMarkerNode, $shouldIgnoreNodeForContentIndexes, } from \"shared\";\n/**\n * Converts a USJ SelectionRange or AnnotationRange to an editor RangeSelection.\n *\n * This function takes a USJ selection object and creates a corresponding editor RangeSelection.\n * It determines the start and end nodes based on the provided selection range and creates a new\n * RangeSelection with appropriate anchor and focus points.\n *\n * @param selection - The USJ selection range to convert. Can be either a SelectionRange or\n * AnnotationRange.\n * @returns A new editor RangeSelection object if the conversion is successful, or `undefined` if\n * the required nodes or offsets cannot be found.\n *\n * @remarks\n * - If the 'end' property of the selection is undefined (indicating this is a location rather than\n * a range), it defaults to the 'start' value.\n * - If either the start or end node cannot be found, or if their offsets are undefined, the\n * function returns undefined.\n * - In the block verse layout it always returns `undefined`: that layout splits a paragraph\n * spanning verses across their blocks, so the editor's content indexes no longer match the\n * source USJ's and no location can be resolved. Callers that need to tell a host why report it\n * through their own logger - these are `$` functions with none threaded in.\n */\nexport function $getRangeFromUsjSelection(selection) {\n if ($hasVerseBlocks())\n return undefined;\n const { start } = selection;\n let { end } = selection;\n end ??= start;\n // Find the start and end nodes with offsets based on the location.\n let [startNode, startOffset] = $getNodeFromLocation(start);\n let [endNode, endOffset] = $getNodeFromLocation(end);\n if (!startNode || !endNode || startOffset === undefined || endOffset === undefined)\n return undefined;\n [startNode, startOffset] = $normalizeVisibleMarkerPoint(startNode, startOffset);\n [endNode, endOffset] = $normalizeVisibleMarkerPoint(endNode, endOffset);\n // Create selection range.\n const editorSelection = $createRangeSelection();\n editorSelection.anchor = $createPoint(startNode.getKey(), startOffset, $getPointType(startNode));\n editorSelection.focus = $createPoint(endNode.getKey(), endOffset, $getPointType(endNode));\n return editorSelection;\n}\n/**\n * Retrieves the current USJ selection range from the editor.\n *\n * This function extracts the selection range from the editor's current state. It handles both\n * forward and backward selections, as well as collapsed (single point) selections.\n *\n * @returns A USJ `SelectionRange` object containing the start and end positions of the selection,\n * or `undefined` if there is no valid range selection. Always `undefined` in the block verse\n * layout - see {@link $getRangeFromUsjSelection} for why.\n */\nexport function $getUsjSelectionFromEditor() {\n if ($hasVerseBlocks())\n return undefined;\n const editorSelection = $getSelection();\n if (!editorSelection || !$isRangeSelection(editorSelection))\n return;\n const startNode = editorSelection.isBackward()\n ? editorSelection.focus.getNode()\n : editorSelection.anchor.getNode();\n const startOffset = editorSelection.isBackward()\n ? editorSelection.focus.offset\n : editorSelection.anchor.offset;\n const start = $getLocationFromNode(startNode, startOffset);\n if (editorSelection.isCollapsed())\n return { start };\n const endNode = editorSelection.isBackward()\n ? editorSelection.anchor.getNode()\n : editorSelection.focus.getNode();\n const endOffset = editorSelection.isBackward()\n ? editorSelection.anchor.offset\n : editorSelection.focus.offset;\n const end = $getLocationFromNode(endNode, endOffset);\n return { start, end };\n}\nfunction $getNodeFromLocation(location) {\n // Handle UsjTextContentLocation first (most common case)\n if (isUsjTextContentLocation(location)) {\n const jsonPathIndexes = indexesFromUsjJsonPath(location.jsonPath);\n let currentNode = $getRoot();\n for (let i = 0; i < jsonPathIndexes.length; i++) {\n if (!currentNode || !$isElementNode(currentNode))\n return [undefined, undefined];\n const item = $getLogicalContentItems(currentNode)[jsonPathIndexes[i]];\n if (!item)\n return [undefined, undefined];\n if (item.type === \"text\") {\n // Text items are terminal — the path must end here.\n if (i !== jsonPathIndexes.length - 1)\n return [undefined, undefined];\n return $getTextNodeAtLogicalOffset(item, location.offset) ?? [undefined, undefined];\n }\n currentNode = item.node;\n }\n // The jsonPath resolved to an ElementNode (e.g. \"$.content[0]\"): interpret offset as a\n // logical child boundary offset and return an element point.\n if (currentNode && $isElementNode(currentNode)) {\n return [currentNode, $getElementOffsetFromLogicalIndex(currentNode, location.offset)];\n }\n return [undefined, undefined];\n }\n // Handle UsjAttributeKeyLocation and UsjAttributeMarkerLocation BEFORE UsjMarkerLocation\n // because UsjAttributeMarkerLocation has keyName but no offsets, similar to UsjMarkerLocation.\n // Checking for keyName first ensures correct type narrowing.\n // Note: Attribute markers are not yet represented in the editor, so we position at the closest\n // available location (the end of the element's content, since attributes come after content).\n if (isUsjAttributeKeyLocation(location) || isUsjAttributeMarkerLocation(location)) {\n const node = $navigateToNode(location.jsonPath);\n if (!node)\n return [undefined, undefined];\n // For ElementNodes, position at end of last text child\n if ($isElementNode(node)) {\n const lastChild = node.getLastChild();\n if (lastChild && $isTextNode(lastChild))\n return [lastChild, lastChild.getTextContent().length];\n }\n // For decorator nodes (e.g., ImmutableChapterNode) or elements with no children,\n // position at the start of the next sibling\n const nextSibling = node.getNextSibling();\n if (nextSibling && $isElementNode(nextSibling))\n return [nextSibling, 0];\n return [undefined, undefined];\n }\n // Handle UsjClosingAttributeMarkerLocation BEFORE UsjMarkerLocation/UsjClosingMarkerLocation.\n // Note: Attribute markers are not yet represented in the editor, so we position at the closest\n // available location (the end of the element's content).\n if (isUsjClosingAttributeMarkerLocation(location)) {\n const node = $navigateToNode(location.jsonPath);\n if (!node)\n return [undefined, undefined];\n // For ElementNodes, position at end of last text child if it exists\n if ($isElementNode(node)) {\n const lastChild = node.getLastChild();\n if (lastChild && $isTextNode(lastChild))\n return [lastChild, lastChild.getTextContent().length];\n }\n // For decorator nodes (e.g., ImmutableChapterNode) or elements with no children,\n // position at the start of the next sibling\n const nextSibling = node.getNextSibling();\n if (nextSibling && $isElementNode(nextSibling))\n return [nextSibling, 0];\n return [undefined, undefined];\n }\n // Handle UsjMarkerLocation - position at the beginning of the opening marker\n if (isUsjMarkerLocation(location)) {\n const node = $navigateToNode(location.jsonPath);\n if (!node || !$isElementNode(node))\n return [undefined, undefined];\n const markerNode = $findMarkerNode(node, \"opening\");\n if (markerNode)\n return [markerNode, 0];\n // Fallback: if no marker node, position at start of first child\n const firstChild = node.getFirstChild();\n if (firstChild && $isTextNode(firstChild))\n return [firstChild, 0];\n return [undefined, undefined];\n }\n // Handle UsjClosingMarkerLocation - position within the closing marker\n if (isUsjClosingMarkerLocation(location)) {\n const node = $navigateToNode(location.jsonPath);\n if (!node || !$isElementNode(node))\n return [undefined, undefined];\n const markerNode = $findMarkerNode(node, \"closing\");\n if (markerNode) {\n // Validate offset is within bounds\n const text = markerNode.getTextContent();\n const offset = Math.min(location.closingMarkerOffset, text.length);\n return [markerNode, offset];\n }\n // Fallback: if no closing marker, position at end of last text child\n const lastChild = node.getLastChild();\n if (lastChild && $isTextNode(lastChild))\n return [lastChild, lastChild.getTextContent().length];\n return [undefined, undefined];\n }\n // Handle UsjPropertyValueLocation - position within a property value (e.g., marker name)\n if (isUsjPropertyValueLocation(location)) {\n // Extract the property name from the jsonPath (e.g., \"$.content[0].marker\" -> \"marker\")\n const propertyMatch = location.jsonPath.match(/\\.(\\w+)$|^\\$\\.(\\w+)$|\\['([^']+)'\\]$/);\n const propertyName = propertyMatch?.[1] ?? propertyMatch?.[2] ?? propertyMatch?.[3];\n const node = $navigateToNode(location.jsonPath);\n if (!node || !$isElementNode(node))\n return [undefined, undefined];\n if (propertyName === \"marker\") {\n // Position within the marker name in the opening MarkerNode\n const markerNode = $findMarkerNode(node, \"opening\");\n if (markerNode) {\n // The text is \"\\marker \" - propertyOffset 0 maps to offset 1 (after backslash)\n const offset = location.propertyOffset + 1;\n const text = markerNode.getTextContent();\n return [markerNode, Math.min(offset, text.length)];\n }\n }\n // Fallback for other properties or if marker node not found\n const firstChild = node.getFirstChild();\n if (firstChild && $isTextNode(firstChild))\n return [firstChild, 0];\n return [undefined, undefined];\n }\n // All UsjDocumentLocation subtypes should be handled above\n throw new Error(`Unsupported UsjDocumentLocation type: ${getUsjDocumentLocationTypeName(location)}. ` +\n \"All UsjDocumentLocation subtypes should be supported: UsjMarkerLocation, \" +\n \"UsjClosingMarkerLocation, UsjTextContentLocation, UsjPropertyValueLocation, \" +\n \"UsjAttributeKeyLocation, UsjAttributeMarkerLocation, and\" +\n `UsjClosingAttributeMarkerLocation. Received: ${JSON.stringify(location)}`);\n}\nfunction $normalizeVisibleMarkerPoint(node, offset) {\n if (!$isVisibleMarkerNode(node))\n return [node, offset];\n const textLength = node.getTextContent().length;\n // If selection resolves inside or at the beginning of a visible marker,\n // normalize to a parent ElementNode point at the marker's child index.\n if (offset < 0 || offset >= textLength)\n return [node, offset];\n const parent = node.getParent();\n if (!parent || !$isElementNode(parent))\n return [node, offset];\n const indexWithinParent = node.getIndexWithinParent();\n if (indexWithinParent < 0)\n return [node, offset];\n return [parent, indexWithinParent];\n}\nfunction $getPointType(node) {\n return $isElementNode(node) ? \"element\" : \"text\";\n}\n/**\n * Finds a MarkerNode or ImmutableTypedTextNode marker child with the specified syntax.\n * @param parent - The parent element node to search in.\n * @param syntax - The marker syntax to find (\"opening\" or \"closing\").\n * @returns The MarkerNode or ImmutableTypedTextNode if found, undefined otherwise.\n */\nfunction $findMarkerNode(parent, syntax) {\n const children = parent.getChildren();\n for (const child of children) {\n // Check for editable MarkerNode\n if ($isMarkerNode(child) && child.getMarkerSyntax() === syntax)\n return child;\n // Also check for selfClosing when looking for closing\n if (syntax === \"closing\" && $isMarkerNode(child) && child.getMarkerSyntax() === \"selfClosing\") {\n return child;\n }\n if ($isVisibleMarkerNode(child)) {\n const text = child.getTextContent();\n const isClosing = text.endsWith(\"*\");\n if ((syntax === \"opening\" && !isClosing) || (syntax === \"closing\" && isClosing)) {\n return child;\n }\n }\n }\n return undefined;\n}\n/**\n * Navigates to a node using jsonPath indexes.\n * @param jsonPath - The jsonPath string to navigate.\n * @returns The node at the path, or undefined if not found.\n */\nfunction $navigateToNode(jsonPath) {\n // Extract just the content path portion (strip property suffix if present)\n const contentPathMatch = new RegExp(/^(\\$(?:\\.content\\[\\d+\\])*)(?:\\.|$|\\[)/).exec(jsonPath);\n const contentPath = contentPathMatch ? contentPathMatch[1] : jsonPath;\n const jsonPathIndexes = indexesFromUsjJsonPath(contentPath);\n let currentNode = $getRoot();\n for (const index of jsonPathIndexes) {\n if (!currentNode || !$isElementNode(currentNode))\n return undefined;\n const item = $getLogicalContentItems(currentNode)[index];\n currentNode = item?.type === \"element\" ? item.node : undefined;\n }\n return currentNode;\n}\n/**\n * Gets the location from a Lexical node and offset, emitting the appropriate UsjDocumentLocation\n * subtype based on the node type.\n *\n * - For MarkerNode with \"opening\" syntax at offset 0: UsjMarkerLocation\n * - For MarkerNode with \"opening\" syntax at offset > 0: UsjPropertyValueLocation (within marker name)\n * - For MarkerNode with \"closing\" syntax: UsjClosingMarkerLocation\n * - For an element point before a read-only marker glyph: UsjMarkerLocation for the opening glyph,\n * UsjClosingMarkerLocation for the closing one\n * - For regular TextNode: UsjTextContentLocation\n *\n * @param node - The Lexical node.\n * @param offset - The offset within the node's text content.\n * @returns The appropriate UsjDocumentLocation subtype.\n */\nfunction $getLocationFromNode(node, offset) {\n if ($isMarkerNode(node)) {\n const markerSyntax = node.getMarkerSyntax();\n // Prefer anchoring to the previous node if the marker is scaffolding for it.\n const anchorNode = $getMarkerAnchorNode(node);\n const anchorJsonPath = anchorNode\n ? usjJsonPathFromIndexes($getJsonPathIndexes(anchorNode))\n : usjJsonPathFromIndexes($getJsonPathIndexes(node));\n if (markerSyntax === \"closing\" || markerSyntax === \"selfClosing\") {\n // UsjClosingMarkerLocation: position within the closing marker (e.g., \\nd*)\n return {\n jsonPath: anchorJsonPath,\n closingMarkerOffset: offset,\n };\n }\n // Opening marker\n if (offset === 0) {\n // UsjMarkerLocation: at the very beginning (the backslash)\n return {\n jsonPath: anchorJsonPath,\n };\n }\n // Within the marker name text (after the backslash)\n // The text is \"\\marker \" so offset 1 is the first char of the marker name\n // UsjPropertyValueLocation points to the marker property value\n const propertyJsonPath = `${anchorJsonPath}.marker`;\n // propertyOffset is the offset within the marker name itself (not including backslash)\n // Text is \"\\p \" - offset 1 is 'p', so propertyOffset = offset - 1\n const propertyOffset = Math.max(0, offset - 1);\n return {\n jsonPath: propertyJsonPath,\n propertyOffset,\n };\n }\n if ($isTypedMarkNode(node)) {\n // An element point on the annotation wrapper: convert to the equivalent point as if the\n // mark did not exist.\n const childrenSize = node.getChildrenSize();\n const childAtOffset = node.getChildAtIndex(Math.min(offset, childrenSize - 1));\n if ($isTextNode(childAtOffset)) {\n const localOffset = offset >= childrenSize ? childAtOffset.getTextContentSize() : 0;\n return $getLocationFromNode(childAtOffset, localOffset);\n }\n // Non-text child (e.g. a CharNode wrapped in the mark) or an empty mark (childAtOffset is\n // null): anchor on the logical parent at the mark's own position instead of falling through\n // to treat the mark itself as the logical parent, which would drop the mark's content index.\n // The mark contributes no content of its own, so the boundary before/after it is the\n // boundary before/after its own position in the logical parent.\n // Known approximation: for a mark with several children of different kinds, an INTERIOR\n // boundary (offset between two of the mark's children) does not place the point between\n // those exact children — it snaps to the front (or back) edge of the whole mark, so the\n // reported position can be off by the length of the mark's preceding text. That is a valid\n // nearby point in the correct text run; placing it exactly would require the resolution\n // side to express points inside a mark.\n const logicalParent = $getLogicalParent(node);\n if (logicalParent?.is(node.getParent())) {\n const markIndex = node.getIndexWithinParent();\n const elementOffset = offset >= childrenSize ? markIndex + 1 : markIndex;\n return $getLocationFromNode(logicalParent, elementOffset);\n }\n }\n // Element selection - offset is a child index, convert to a logical point.\n if ($isElementNode(node)) {\n const childAtOffset = node.getChildAtIndex(offset);\n if ($isVisibleMarkerNode(childAtOffset)) {\n // Which glyph the point sits before decides which location this is, by the same\n // trailing-`*` test `$findMarkerNode` resolves them back with. Keyed only on the parent, an\n // element point before the CLOSING glyph resolved to the same location as one before the\n // opening glyph, so a range framing a whole char span (or note) mapped to a collapsed one.\n const isClosingGlyph = childAtOffset.getTextContent().endsWith(\"*\");\n const jsonPath = usjJsonPathFromIndexes($getJsonPathIndexes(node));\n // Offset 0: the point is before the closing glyph's first byte, not inside it.\n return isClosingGlyph\n ? { jsonPath, closingMarkerOffset: 0 }\n : { jsonPath };\n }\n const logicalPoint = $getLogicalPointFromElementPoint(node, offset);\n if (logicalPoint.type === \"text\") {\n // The boundary falls inside a coalesced USJ text item (e.g. at an annotation edge).\n return {\n jsonPath: usjJsonPathFromIndexes([...$getJsonPathIndexes(node), logicalPoint.index]),\n offset: logicalPoint.offset,\n };\n }\n return {\n jsonPath: usjJsonPathFromIndexes($getJsonPathIndexes(node)),\n offset: logicalPoint.index,\n };\n }\n // Regular text node - UsjTextContentLocation in coalesced-USJ coordinates.\n if ($isTextNode(node)) {\n const logicalTextLocation = $getLogicalTextLocation(node, offset);\n if (logicalTextLocation) {\n return {\n jsonPath: usjJsonPathFromIndexes([\n ...$getJsonPathIndexes(logicalTextLocation.parent),\n logicalTextLocation.index,\n ]),\n offset: logicalTextLocation.offset,\n };\n }\n }\n // Fallback for nodes outside the logical content model (e.g. presentation-only text).\n return { jsonPath: usjJsonPathFromIndexes($getJsonPathIndexes(node)), offset };\n}\nfunction $getMarkerAnchorNode(markerNode) {\n const parent = markerNode.getParent();\n if (!parent || !$isElementNode(parent))\n return undefined;\n const previousContentSibling = $getPreviousContentSibling(markerNode);\n if (previousContentSibling &&\n !$isParaLikeNode(previousContentSibling) &&\n !$isTextNode(previousContentSibling) &&\n !$isTypedMarkNode(previousContentSibling)) {\n return previousContentSibling;\n }\n return parent;\n}\nfunction $getPreviousContentSibling(child) {\n let sibling = child.getPreviousSibling();\n while (sibling) {\n if (!$shouldIgnoreNodeForContentIndexes(sibling))\n return sibling;\n sibling = sibling.getPreviousSibling();\n }\n return undefined;\n}\n/**\n * Gets the jsonPath indexes from a node by traversing up to the root using logical\n * (annotation-transparent) content indexes.\n * @param node - The node to get the path for.\n * @returns An array of indexes representing the path from root to node.\n */\nfunction $getJsonPathIndexes(node) {\n const jsonPathIndexes = [];\n let current = node;\n while (current) {\n const parent = $getLogicalParent(current);\n if (!parent)\n break;\n const index = $getLogicalIndexOfChild(parent, current);\n if (index >= 0)\n jsonPathIndexes.unshift(index);\n current = parent;\n }\n return jsonPathIndexes;\n}\n/**\n * Whether the document uses the block verse layout.\n *\n * USJ locations are indexes into the source USJ's content. That layout regroups verses into blocks,\n * splitting any paragraph that spans verses into one fragment per verse, so the editor's content\n * indexes no longer line up with the USJ's - and no amount of treating the block itself as\n * transparent fixes the renumbering underneath. Locations are therefore unavailable there, rather\n * than confidently wrong.\n */\nfunction $hasVerseBlocks() {\n // Both callers run on every selection change, so this walks siblings and exits at the first\n // block rather than calling `getChildren()`, which would build an array of every root child.\n //\n // The layout is known from `ViewOptions.verseLayout`, and an editor registers `VerseBlockNode`\n // only for that layout, so `editor.hasNodes([VerseBlockNode])` looks like a cheaper answer. It\n // is not available here: `$getEditor()` needs an active *editor*, and these functions are\n // called from `editorState.read()` as well, which establishes only an active editor state.\n // Reading the layout instead would mean threading `ViewOptions` through every caller.\n for (let child = $getRoot().getFirstChild(); child; child = child.getNextSibling()) {\n if ($isVerseBlockNode(child))\n return true;\n }\n return false;\n}\n","import { $getRangeFromUsjSelection } from \"../../plugins/usj/annotation/selection.utils\";\nimport { $createImmutableNoteCallerNode, $isImmutableNoteCallerNode, } from \"./ImmutableNoteCallerNode\";\nimport { $isImmutableVerseNode } from \"./ImmutableVerseNode\";\nimport { $isSomeVerseNode } from \"./node-react.utils\";\nimport { $dfs, $findMatchingParent } from \"@lexical/utils\";\nimport { $createTextNode, $getCharacterOffsets, $getNodeByKey, $getSelection, $getState, $isElementNode, $isRangeSelection, $isTextNode, $setState, } from \"lexical\";\nimport { $createCharNode, $createImmutableTypedTextNode, $createMarkerNode, $createMarkerTrailingSeparator, $createNoteNode, $getNoteCallerPreviewText, $isCharNode, $isImmutableTypedTextNode, $isImmutableUnmatchedNode, $isMarkerNode, $isNoteNode, $moveSelectionToEnd, $normalizeSelectionOutOfGlyphText, closingMarkerText, EMPTY_CHAR_PLACEHOLDER_TEXT, getEditableCallerText, getNoteKind, NBSP, NoteNode, openingMarkerText, segmentState, textTypeState, } from \"shared\";\n/**\n * Find all ImmutableNoteCallerNodes in the given nodes tree.\n * @param nodes - Lexical node array to look in.\n * @returns an array of all ImmutableNoteCallerNodes in the tree.\n */\nexport function $findImmutableNoteCallerNodes(nodes) {\n const immutableNoteCallerNodes = [];\n function $traverse(node) {\n if ($isImmutableNoteCallerNode(node))\n immutableNoteCallerNodes.push(node);\n if (!$isElementNode(node))\n return;\n const children = node.getChildren();\n children.forEach($traverse);\n }\n nodes.forEach($traverse);\n return immutableNoteCallerNodes;\n}\n/**\n * Inserts a note at the specified selection, e.g. footnote, cross-reference, endnote.\n * @param marker - The marker type for the note.\n * @param caller - Optional note caller to override the default for the given marker.\n * @param selectionRange - Optional selection range where the note should be inserted. By default it will\n * use the current selection in the editor.\n * @param scriptureReference - Scripture reference for the note.\n * @param viewOptions - The current editor view options.\n * @param nodeOptions - The current editor node options.\n * @param logger - Logger instance.\n * @returns The inserted note node, or `undefined` if insertion failed.\n * @throws Will throw an error if the marker is not a valid note marker.\n */\nexport function $insertNote(marker, caller, selectionRange, scriptureReference, viewOptions, nodeOptions, logger) {\n if (!NoteNode.isValidMarker(marker))\n throw new Error(`$insertNote: Invalid note marker '${marker}'`);\n const selection = selectionRange ? $getRangeFromUsjSelection(selectionRange) : $getSelection();\n if (!$isRangeSelection(selection))\n return undefined;\n const children = $createNoteChildren(selection, marker, scriptureReference, viewOptions, nodeOptions, logger);\n if (children === undefined)\n return undefined;\n // PT9's caller-family rule (see `getNoteKind`): custom note markers deliberately take the\n // cross-reference default, not the footnote one.\n const resolvedCaller = caller ??\n (getNoteKind(marker) === \"crossref\"\n ? (nodeOptions.defaultCrossRefCaller ?? \"-\")\n : (nodeOptions.defaultFootnoteCaller ?? \"+\"));\n const noteNode = $createWholeNote(marker, resolvedCaller, children, viewOptions, nodeOptions, undefined, undefined);\n $insertNoteWithSelect(noteNode, selection, viewOptions);\n return noteNode;\n}\n/**\n * Whether notes BUILD collapsed under the given note mode: only `\"expanded\"` builds expanded\n * notes; `\"collapsed\"`, `\"expandInline\"`, and an unset mode all build collapsed ones (under\n * `expandInline` the NoteNodePlugin expands a note only while the caret is adjacent).\n *\n * This is the ONE predicate for constructing a note's collapsed flag and child layout — used at\n * document load (the platform adaptor's `createNote`) and at insert time (`$createWholeNote`,\n * `$insertNoteWithSelect`) so a freshly inserted note is indistinguishable from a loaded one.\n * When the two ever disagree, the flag and the layout drift apart (e.g. a collapsed-layout note\n * flagged expanded). Note: this governs CONSTRUCTION only — `$selectNote` intentionally uses a\n * different rule for expanding an existing note on selection.\n *\n * @param noteMode - The note display mode from the editor view options.\n * @returns `true` when notes are built collapsed under this mode.\n */\nexport function isCollapsedNoteMode(noteMode) {\n return noteMode !== \"expanded\";\n}\n/**\n * The closing marker glyph a collapsed caret sits immediately before, when that glyph closes the\n * char span the caret is in — otherwise `undefined`.\n *\n * This is the one insertion point where Lexical's `selection.insertNodes()` SPLITS the enclosing\n * char span rather than inserting into it, because the caret is at a child boundary with nothing\n * but the closer beyond it. The split leaves a second span holding nothing but the orphaned\n * closing glyph, which the standard-view marker-edit engine reads as a span whose opener was\n * deleted and dissolves — taking the closer's bytes with it. A caret in the MIDDLE of the span's\n * text, or outside the span entirely, does not split it and needs none of this.\n *\n * Both spellings of the same boundary are recognized: the caret at the end of the span's content\n * text (a click at the end of the word) and the caret at offset 0 of the closing glyph itself\n * (an arrow-left back off the closer).\n *\n * Read-only: safe inside `editor.update()` or either read form.\n */\nfunction $closingGlyphAfterCaret(selection) {\n if (!selection.isCollapsed())\n return undefined;\n const { anchor } = selection;\n if (anchor.type !== \"text\")\n return undefined;\n const node = anchor.getNode();\n if (!$isTextNode(node) || !$isCharNode(node.getParent()))\n return undefined;\n // MarkerNode extends TextNode, so the caret may be parked in the closing glyph itself.\n if ($isMarkerNode(node))\n return anchor.offset === 0 && node.getMarkerSyntax() === \"closing\" ? node : undefined;\n if (anchor.offset !== node.getTextContentSize())\n return undefined;\n const next = node.getNextSibling();\n return $isMarkerNode(next) && next.getMarkerSyntax() === \"closing\" ? next : undefined;\n}\n/**\n * Insert note node at the given selection, and select the note content if expanded.\n *\n * Whether the note lands collapsed is `isCollapsedNoteMode` — the same predicate that governs\n * both the child structure and the collapsed flag when notes are built at document load — so a\n * freshly inserted note matches a loaded one.\n *\n * @param noteNode - The note node to insert.\n * @param selection - The selection where to insert the note.\n * @param viewOptions - The current editor view options.\n */\nexport function $insertNoteWithSelect(noteNode, selection, viewOptions) {\n const isCollapsed = isCollapsedNoteMode(viewOptions?.noteMode);\n noteNode.setIsCollapsed(isCollapsed);\n if (!selection.isCollapsed())\n $moveSelectionToEnd(selection);\n // The caret may be parked between two of a glyph's display bytes — inside `\\v 5 `, inside a\n // closing `\\add*`. Those bytes are a picture of the node's own state, so a note dropped between\n // them would cut the picture in half and hand the right-hand half to the document as content\n // (the reported verse number arriving in the file twice). One place decides where such a point\n // really is; here it resolves to the glyph's trailing end, the ordinary position just past it.\n $normalizeSelectionOutOfGlyphText(selection);\n // At a char span's content end, place the note explicitly rather than letting `insertNodes`\n // split the span there and strand its closing glyph (see `$closingGlyphAfterCaret`). The\n // resulting shape — note inside the span, ahead of the closer — is what re-tokenizing the\n // displayed bytes gives, and what a mid-content caret already produced.\n const closingGlyph = $closingGlyphAfterCaret(selection);\n if (closingGlyph) {\n closingGlyph.insertBefore(noteNode);\n noteNode.selectNext(0, 0); // caret past the note, where `insertNodes` leaves it\n }\n else {\n selection.insertNodes([noteNode]);\n }\n if (!isCollapsed) {\n const lastCharChild = noteNode.getChildren().reverse().find($isCharNode);\n lastCharChild?.selectEnd();\n }\n}\n/**\n * Build a single note-content char span matching the reverse adaptor's `createChar` output for\n * the active `markerMode`. In editable markerMode a char span MUST begin with its opening\n * MarkerNode glyph and carry a structural NBSP content prefix; otherwise the standard-view\n * marker-edit engine's `$charNodeDeletionTransform` treats it as \"opener deleted\" and\n * unwraps it back to plain text in the same commit — which was silently emptying freshly\n * inserted footnotes. `content === \"\"` yields the lone-NBSP empty-char placeholder (matching\n * `createChar`, which prepends the NBSP prefix only to real content, then adds the placeholder).\n */\nfunction $createNoteContentChar(marker, content, viewOptions) {\n const char = $createCharNode(marker);\n // Note-content chars are built without their own closing markers, i.e. they are implicitly\n // closed — exactly what ParatextData records as closed=\"false\" (near universal on \\fr/\\ft/\n // \\xo/\\xt). Carrying the flag from creation keeps these nodes signature-identical to what\n // Tier-2 re-tokenization produces (the rebuild's fixed-point refusal depends on that) and\n // round-trips the correct USJ shape.\n char.setUnknownAttributes({ closed: \"false\" });\n const isEditable = viewOptions?.markerMode === \"editable\";\n if (isEditable)\n char.append($createMarkerNode(marker));\n // Visible marker mode shows a bare opening glyph inside the span, matching `createChar` in\n // the load adaptor (implicitly-closed note-content chars get no closer there either).\n else if (viewOptions?.markerMode === \"visible\")\n char.append($createImmutableTypedTextNode(\"marker\", openingMarkerText(marker)));\n const text = content === \"\" ? EMPTY_CHAR_PLACEHOLDER_TEXT : isEditable ? NBSP + content : content;\n char.append($createTextNode(text));\n return char;\n}\nexport function $createNoteChildren(selection, marker, scriptureReference, viewOptions, nodeOptions, logger) {\n const children = [];\n const { chapterNum, verseNum, verse } = scriptureReference ?? {};\n const chapterVerseSeparator = nodeOptions.chapterVerseSeparator ?? \":\";\n const verseRangeSeparator = nodeOptions.verseRangeSeparator ?? \"-\";\n // `verse` (e.g. \"16-18\") is only populated for a verse bridge; replace the raw \"-\" bridge\n // separator with the project's configured verseRangeSeparator (PT9 `GetFormattedVerse`). Passed as\n // a REPLACER FUNCTION, not a replacement string, so a separator containing `$` is inserted\n // literally instead of being read as a `$&`/`$1` substitution pattern.\n const referenceText = chapterNum !== undefined && verseNum !== undefined\n ? `${chapterNum}${chapterVerseSeparator}${(verse ?? `${verseNum}`).replace(/-/g, () => verseRangeSeparator)} `\n : undefined;\n switch (marker) {\n case \"f\":\n case \"fe\":\n case \"ef\":\n case \"efe\":\n if (referenceText !== undefined) {\n children.push($createNoteContentChar(\"fr\", referenceText, viewOptions));\n }\n if (!selection.isCollapsed()) {\n const quotation = $stripSelectionToQuotation(selection);\n if (quotation.length > 0) {\n children.push($createNoteContentChar(\"fq\", quotation, viewOptions));\n }\n }\n children.push($createNoteContentChar(\"ft\", \"\", viewOptions));\n break;\n case \"x\":\n case \"ex\":\n if (referenceText !== undefined) {\n children.push($createNoteContentChar(\"xo\", referenceText, viewOptions));\n }\n if (!selection.isCollapsed()) {\n const quotation = $stripSelectionToQuotation(selection);\n if (quotation.length > 0) {\n children.push($createNoteContentChar(\"xq\", quotation, viewOptions));\n }\n }\n children.push($createNoteContentChar(\"xt\", \"\", viewOptions));\n break;\n default:\n logger?.warn(`$createNoteChildren: Unsupported note marker '${marker}'`);\n return undefined;\n }\n return children;\n}\n/**\n * Creates a note node including children with the given parameters.\n * @param marker - The marker for the note.\n * @param caller - The caller for the note.\n * @param contentNodes - The content nodes for the note.\n * @param viewOptions - The view options for the note.\n * @param nodeOptions - The node options for the note.\n * @param segment - The segment for the note.\n * @param closed - The source `closed` attribute (`\"false\"` for an unterminated note). Unclosed\n * notes render expanded inline (PT9 `opennote`) regardless of `noteMode`. `undefined` (the\n * default for freshly inserted notes) behaves as closed.\n * @returns The created note node.\n */\n// Keep this function updated with logic from\n// `packages/platform/src/editor/adaptors/usj-editor.adaptor.ts` > `createNote`\nexport function $createWholeNote(marker, caller, contentNodes, viewOptions, nodeOptions, segment, closed) {\n // Unclosed notes (closed=\"false\") render expanded inline (PT9 `opennote`); only closed\n // notes honor noteMode collapse.\n const isUnclosed = closed === \"false\";\n const isCollapsed = isUnclosed ? false : isCollapsedNoteMode(viewOptions?.noteMode);\n const note = $createNoteNode(marker, caller, isCollapsed);\n if (segment)\n $setState(note, segmentState, () => segment);\n // The note's shell — its opening glyph and its caller — is atomic when the host governs those\n // two through its own UI (see ViewOptions.isNoteShellEditable). Mirrors the load path\n // (`createNote`, usj-editor.adaptor): an INSERTED note must be shaped like a reloaded one.\n const isShellAtomic = viewOptions?.isNoteShellEditable === false;\n let openingMarkerNode;\n let closingMarkerNode;\n if (viewOptions?.markerMode === \"editable\") {\n openingMarkerNode = $createMarkerNode(marker);\n if (isShellAtomic)\n openingMarkerNode.setMode(\"token\");\n // An unclosed note has no closer to display.\n if (!isUnclosed)\n closingMarkerNode = $createMarkerNode(marker, \"closing\");\n }\n else if (viewOptions?.markerMode === \"visible\") {\n // Same glyph text shapes as the load path (`createNote`): opening glyph with a plain\n // trailing space, closer bare. Glyphs are presentation-only (never serialized), so a\n // reloaded note shows the load path's shape — an inserted note must look identical.\n openingMarkerNode = $createImmutableTypedTextNode(\"marker\", openingMarkerText(marker) + \" \");\n if (!isUnclosed)\n closingMarkerNode = $createImmutableTypedTextNode(\"marker\", closingMarkerText(marker));\n }\n let callerNode;\n if (openingMarkerNode)\n note.append(openingMarkerNode);\n // Expanded layout whenever the note is expanded (either noteMode expanded OR unclosed).\n // Unlike the load path (`createNote`, usj-editor.adaptor), no `\\cat` category run is built\n // here: this constructs NEW notes, which never carry a category at insert time — there is no\n // category input on the insert path. A category acquired later heals its run through the\n // shared display-run sync.\n if (viewOptions?.markerMode === \"editable\" && !isCollapsed) {\n if (caller === \"\")\n note.append(...contentNodes);\n else {\n callerNode = $createTextNode(getEditableCallerText(note.__caller));\n if (isShellAtomic)\n callerNode.setMode(\"token\");\n note.append(callerNode, ...contentNodes);\n }\n }\n else {\n // The engine-owned NBSP separators of a collapsed note's layout, in the same tagged token\n // shape as the para-marker prefix separator (and as the load path's `createNote` builds\n // them): a bare NBSP TextNode merged into adjacent plain content on the first normalization\n // pass, after which serialization's exact-NBSP drop could no longer see the separator and\n // one display byte leaked into USJ as a data space.\n const $createSpaceNodeFn = () => $createMarkerTrailingSeparator();\n const spacedContentNodes = contentNodes.flatMap($addSpaceNodes($createSpaceNodeFn));\n if (caller === \"\")\n note.append(...spacedContentNodes);\n else {\n const previewText = $getNoteCallerPreviewText(contentNodes);\n let onClick = () => undefined;\n if (nodeOptions?.noteCallerOnClick) {\n onClick = nodeOptions.noteCallerOnClick;\n }\n callerNode = $createImmutableNoteCallerNode(note.__caller, previewText, onClick);\n note.append(callerNode, $createSpaceNodeFn(), ...spacedContentNodes);\n }\n }\n if (closingMarkerNode)\n note.append(closingMarkerNode);\n return note;\n}\n/**\n * Gets the note using the editor key or at the specified note index.\n * @param noteKeyOrIndex - The note key or index, e.g. 1 would select the second note in the editor.\n * @returns The note at the specified index, or `undefined` if not found.\n */\nexport function $getNoteByKeyOrIndex(noteKeyOrIndex) {\n if (typeof noteKeyOrIndex === \"string\") {\n const node = $getNodeByKey(noteKeyOrIndex);\n if (!$isNoteNode(node))\n return;\n return node;\n }\n const dfsNodes = $dfs();\n if (dfsNodes.length <= 0)\n return;\n const dfsNotes = dfsNodes.filter((dfsNode) => $isNoteNode(dfsNode.node));\n const note = dfsNotes[noteKeyOrIndex]?.node;\n if (!$isNoteNode(note))\n return;\n return note;\n}\n/**\n * Selects the given note node, expanding or collapsing it based on the current view options.\n *\n * Deliberately NOT `isCollapsedNoteMode` (nor its inverse): that predicate is for CONSTRUCTING\n * notes, where `expandInline` builds collapsed. Here the user is navigating INTO the note, so\n * `expandInline` must expand it (the caret is about to be adjacent — the same condition under\n * which the NoteNodePlugin keeps it open); only an always-`\"collapsed\"` mode keeps it closed.\n *\n * @param noteNode - The note node to select.\n * @param viewOptions - The current editor view options.\n */\nexport function $selectNote(noteNode, viewOptions) {\n const isCollapsed = viewOptions?.noteMode === \"collapsed\";\n noteNode.setIsCollapsed(isCollapsed);\n if (isCollapsed) {\n const nodeBefore = noteNode.getPreviousSibling();\n if ($isImmutableVerseNode(nodeBefore) || !nodeBefore) {\n const parent = noteNode.getParent();\n if (parent) {\n const nodeIndex = noteNode.getIndexWithinParent();\n parent.select(nodeIndex, nodeIndex);\n }\n }\n else\n nodeBefore.selectEnd();\n }\n else {\n const lastCharChild = noteNode.getChildren().reverse().find($isCharNode);\n lastCharChild?.selectEnd();\n }\n}\n/** Add the given space node after each child node */\nfunction $addSpaceNodes($createSpaceNodeFn) {\n return (node) => {\n if ($isImmutableTypedTextNode(node))\n return [node];\n return [node, $createSpaceNodeFn()];\n };\n}\n/**\n * Returns `true` when `node` is inside a NoteNode (i.e. an existing footnote/cross-reference\n * embedded in the selected body text) — its entire content, markers included, must be dropped.\n * `selection.getNodes()` flattens a NoteNode's descendants into the returned list alongside the\n * note itself, so skipping just the NoteNode entry is not enough; every descendant must also be\n * excluded by walking its ancestor chain.\n */\nfunction $isInsideNote(node) {\n // The walk starts at the PARENT deliberately: the NoteNode entry itself is handled by the\n // caller, and a note is not \"inside\" itself.\n const parent = node.getParent();\n return parent !== null && $findMatchingParent(parent, $isNoteNode) !== null;\n}\n/**\n * TS port of PT9 `RemoveMarkersAndFootnotes(text, isFootnote=true)`\n * (`UsfmSnippetInserter.cs:444-489`): builds a footnote/cross-reference quotation (`\\fq`/`\\xq`)\n * from a selection over body text — plain text only, with USFM markers and any nested\n * notes stripped, and embedded verse numbers converted to `\\+fv \\+fv*`.\n *\n * Endpoint handling: Lexical's `RangeSelection.getNodes()` returns whole boundary nodes even for\n * a partial selection, so the first/last selected plain TextNode is sliced to the\n * anchor/focus offset here — reusing Lexical's own `$getCharacterOffsets` (which normalizes\n * \"element\"-type points, e.g. a whole-paragraph `select(0, childrenSize)`, to real character\n * offsets) and the same anchor/focus-order slicing Lexical's `RangeSelection.getTextContent()`\n * uses internally, rather than raw `.offset` values (a raw element-point offset is a child\n * index, not a character offset, and slicing with it truncates the last node's text).\n *\n * @param selection - The selection to build the quotation from.\n * @returns The stripped, trimmed quotation text.\n */\nexport function $stripSelectionToQuotation(selection) {\n if (!$isRangeSelection(selection))\n return \"\";\n const nodes = selection.getNodes();\n if (nodes.length === 0)\n return \"\";\n const firstNode = nodes[0];\n const lastNode = nodes[nodes.length - 1];\n const isBefore = selection.anchor.isBefore(selection.focus);\n const [anchorOffset, focusOffset] = $getCharacterOffsets(selection);\n let result = \"\";\n for (const node of nodes) {\n if ($isNoteNode(node) || $isImmutableNoteCallerNode(node) || $isInsideNote(node))\n continue;\n if ($isMarkerNode(node))\n continue;\n // A stray closer's glyph bytes (`\\nd*`) are display text, not quotation content.\n // ImmutableUnmatchedNode is a TextNode subclass, so without this skip it falls through to\n // the TextNode branch below and its bytes land verbatim in the quotation.\n if ($isImmutableUnmatchedNode(node))\n continue;\n // Attribute display text is display too: a char span's `|lemma=\"…\"` run, a verse's `\\va`\n // value, and a milestone's attribute run are engine-owned bytes, not Scripture — without\n // this skip they land verbatim in the inserted note's quotation and reach the file as note\n // content.\n if ($getState(node, textTypeState) === \"attribute\")\n continue;\n // Check verse nodes before TextNode, via the union: in editable markerMode a VerseNode IS a\n // TextNode subclass, so a TextNode-first check would emit its raw glyph text instead of\n // `\\+fv` — and in visible/hidden marker mode the verse is an ImmutableVerseNode (a\n // DecoratorNode), which a bare $isVerseNode check misses entirely, silently dropping the\n // verse number from the quotation.\n if ($isSomeVerseNode(node)) {\n result += `\\\\+fv ${node.getNumber()}\\\\+fv*`;\n continue;\n }\n if ($isTextNode(node)) {\n let text = node.getTextContent();\n if (node === firstNode && node === lastNode) {\n text =\n anchorOffset < focusOffset\n ? text.slice(anchorOffset, focusOffset)\n : text.slice(focusOffset, anchorOffset);\n }\n else if (node === firstNode) {\n text = isBefore ? text.slice(anchorOffset) : text.slice(focusOffset);\n }\n else if (node === lastNode) {\n text = isBefore ? text.slice(0, focusOffset) : text.slice(0, anchorOffset);\n }\n result += text;\n }\n }\n // Collapse ASCII whitespace runs only (line joins and indentation from the source paragraph).\n // NBSP, ZWSP, and the other Unicode spaces are authored content that Paratext preserves, so\n // folding them into a plain space here would quietly rewrite the quoted text.\n return result.replace(/[ \\t\\r\\n\\f\\v]+/g, \" \").trim();\n}\n","import { ImmutableNoteCallerNode } from \"./ImmutableNoteCallerNode\";\nimport { ImmutableVerseNode } from \"./ImmutableVerseNode\";\nimport { usjBaseNodes, VerseBlockNode } from \"shared\";\nexport * from \"./ImmutableNoteCallerNode\";\nexport * from \"./ImmutableVerseNode\";\nexport * from \"./node-react.utils\";\nexport * from \"./note.utils\";\nexport * from \"./usj-node-options.model\";\n// AttributeRunNode rides in via usjBaseNodes (shared) — every USJ-shaped editor needs it, not only\n// a react host, since the shared self-healing syncs construct one directly.\nexport const usjReactNodes = [\n ImmutableNoteCallerNode,\n ImmutableVerseNode,\n ...usjBaseNodes,\n];\n/**\n * Nodes for the block verse layout (`ViewOptions.verseLayout`). Register these instead of\n * {@link usjReactNodes} when that layout is active, so every other editor's node registry is\n * unchanged.\n *\n * A Lexical editor's node types are fixed when it is created, so an editor registered with\n * `usjReactNodes` cannot be switched to the block verse layout - it has to be recreated.\n */\nexport const usjBlockVerseNodes = [\n VerseBlockNode,\n ...usjReactNodes,\n];\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { forwardRef } from \"react\";\nexport const FloatingBox = forwardRef((props, ref) => {\n const { coords, children, style, ...extraProps } = props;\n const shouldShow = coords !== undefined;\n return (_jsx(\"div\", { ref: ref, className: \"floating-box\", \"aria-hidden\": !shouldShow, style: {\n ...style,\n position: \"absolute\",\n zIndex: 1000,\n top: coords?.y,\n left: coords?.x,\n visibility: shouldShow ? \"visible\" : \"hidden\",\n opacity: shouldShow ? 1 : 0,\n }, ...extraProps, children: children }));\n});\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { autoUpdate, computePosition, shift, flip } from \"@floating-ui/dom\";\nexport function useFloatingPosition() {\n const [coords, setCoords] = useState(undefined);\n const [placement, setPlacement] = useState();\n const cleanupRef = useRef(null);\n const updatePosition = useCallback((domRange, anchorElement) => {\n if (cleanupRef.current) {\n cleanupRef.current();\n }\n const referenceElement = domRange.commonAncestorContainer.nodeType === domRange.commonAncestorContainer.TEXT_NODE\n ? domRange\n : domRange.commonAncestorContainer;\n cleanupRef.current = autoUpdate(referenceElement, anchorElement, () => {\n computePosition(referenceElement, anchorElement, {\n placement: \"bottom-start\",\n middleware: [shift(), flip()],\n })\n .then((pos) => {\n setPlacement(pos.placement);\n setCoords((prevCoords) => prevCoords?.x === pos.x && prevCoords?.y === pos.y\n ? prevCoords\n : { x: pos.x, y: pos.y });\n })\n .catch(() => {\n setCoords(undefined);\n });\n });\n }, []);\n const cleanup = useCallback(() => {\n if (cleanupRef.current) {\n setCoords(undefined);\n cleanupRef.current();\n cleanupRef.current = null;\n }\n }, []);\n useEffect(() => {\n return cleanup;\n }, [cleanup]);\n return { coords, placement, updatePosition, cleanup };\n}\n","import { useEffect } from \"react\";\nimport { useFloatingPosition } from \"./useFloatingPosition\";\nexport default function useCursorCoords({ isOpen, floatingBoxRef, }) {\n const { coords, updatePosition, cleanup, placement } = useFloatingPosition();\n useEffect(() => {\n if (!isOpen || !floatingBoxRef.current) {\n cleanup();\n return undefined;\n }\n const domRange = window.getSelection()?.getRangeAt(0);\n if (!domRange) {\n cleanup();\n return undefined;\n }\n updatePosition(domRange, floatingBoxRef.current);\n return cleanup;\n }, [cleanup, isOpen, floatingBoxRef, updatePosition]);\n return { coords, placement };\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { memo, useMemo, useRef } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FloatingBox } from \"./FloatingBox\";\nimport useCursorCoords from \"./useCursorCoords\";\nconst MemoizedFloatingBox = memo(FloatingBox);\n/**\n * FloatingBoxAtCursor component is responsible for rendering a floating menu\n * at the cursor position when the isOpen prop is true\n */\nexport default function FloatingBoxAtCursor({ isOpen = false, children }) {\n const floatingBoxRef = useRef(null);\n const { coords, placement } = useCursorCoords({ isOpen, floatingBoxRef });\n const renderChildren = useMemo(() => (coords ? (typeof children === \"function\" ? children : () => children) : () => null), [children, coords]);\n return createPortal(_jsx(MemoizedFloatingBox, { ref: floatingBoxRef, coords: coords, style: coords ? undefined : { display: \"none\" }, children: renderChildren({ isOpen, placement }) }), \n // Read at render rather than at module scope: this module sits in the import graph of the\n // package's utility entry points, so touching `document` on load throws for any consumer that\n // imports one of them outside a DOM environment (a Node-environment unit test, SSR).\n document.body);\n}\n","import { createContext, useContext } from \"react\";\nconst MenuContext = createContext(undefined);\nexport function useMenuContext() {\n const context = useContext(MenuContext);\n if (!context) {\n throw new Error(\"useMenuContext must be used within a MenuProvider\");\n }\n return context;\n}\nexport { MenuContext };\n","import { useState, useCallback, useMemo } from \"react\";\nexport function useMenuCore(initialMenuItems, onSelectOption) {\n const [activeIndex, setActiveIndex] = useState(0);\n const [selectedIndex, setSelectedIndex] = useState(-1);\n const menuItems = useMemo(() => initialMenuItems ?? [], [initialMenuItems]);\n const state = {\n menuItems,\n activeIndex,\n selectedIndex,\n onSelectOption: onSelectOption ?? (() => undefined),\n };\n const moveUp = useCallback(() => {\n setActiveIndex((prev) => {\n const optionsCount = menuItems.length;\n return optionsCount ? (prev - 1 + optionsCount) % optionsCount : 0;\n });\n }, [menuItems.length]);\n const moveDown = useCallback(() => {\n setActiveIndex((prev) => {\n const optionsCount = menuItems.length;\n return optionsCount ? (prev + 1) % optionsCount : 0;\n });\n }, [menuItems.length]);\n const select = useCallback(() => {\n const optionsCount = menuItems.length;\n if (activeIndex >= 0 && activeIndex < optionsCount) {\n const selectedOption = menuItems[activeIndex];\n onSelectOption?.(selectedOption);\n setSelectedIndex(activeIndex);\n }\n }, [activeIndex, menuItems, onSelectOption]);\n return {\n state,\n moveUp,\n moveDown,\n select,\n setActiveIndex,\n setSelectedIndex,\n };\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { MenuContext } from \"./MenuContext\";\nimport { useMenuCore } from \"./useMenuCore\";\nexport function MenuRoot({ children, menuItems, onSelectOption, ...divProps }) {\n const menuContext = useMenuCore(menuItems, onSelectOption);\n return (_jsx(MenuContext.Provider, { value: menuContext, children: _jsx(\"div\", { ...divProps, children: children }) }));\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { forwardRef, useCallback } from \"react\";\nimport { useMenuContext } from \"./MenuContext\";\nexport const MenuOption = forwardRef(({ index, children, onMouseEnter, onClick, ...props }, ref) => {\n const { state: { activeIndex }, setActiveIndex, setSelectedIndex, select, } = useMenuContext();\n const handleClick = useCallback((event) => {\n select();\n setSelectedIndex(-1);\n onClick?.(event);\n }, [onClick, select, setSelectedIndex]);\n const handleMouseEnter = useCallback((event) => {\n setActiveIndex(index);\n onMouseEnter?.(event);\n }, [index, setActiveIndex, onMouseEnter]);\n return (_jsx(\"button\", { ref: ref, role: \"menuitem\", ...props, onClick: handleClick, onMouseEnter: handleMouseEnter, \"aria-selected\": index !== undefined && activeIndex === index ? \"true\" : undefined, tabIndex: -1, children: children }));\n});\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { Children, cloneElement, isValidElement, useEffect, useMemo, useRef, } from \"react\";\nimport { useMenuContext } from \"./MenuContext\";\nimport { MenuOption } from \"./Option\";\nexport function MenuOptions({ children, autoIndex = true, ...divProps }) {\n const menuRef = useRef(null);\n const { state: { activeIndex, menuItems }, } = useMenuContext();\n const renderChildren = useMemo(() => (menuItems ? (typeof children === \"function\" ? children : () => children) : () => null), [children, menuItems]);\n const mappedChildren = useMemo(() => {\n const children = renderChildren(menuItems);\n if (!autoIndex)\n return children;\n return Children.map(children, (child, index) => {\n if (isValidElement(child) &&\n child.type === MenuOption &&\n child.props.index === undefined) {\n return cloneElement(child, { index });\n }\n return child;\n });\n }, [renderChildren, autoIndex, menuItems]);\n useEffect(() => {\n if (menuRef.current) {\n const menuElement = menuRef.current;\n const selectedElement = menuElement.children[activeIndex];\n if (selectedElement) {\n const menuRect = menuElement.getBoundingClientRect();\n const selectedRect = selectedElement.getBoundingClientRect();\n if (selectedRect.bottom > menuRect.bottom) {\n menuElement.scrollTop += selectedRect.bottom - menuRect.bottom;\n }\n else if (selectedRect.top < menuRect.top) {\n menuElement.scrollTop -= menuRect.top - selectedRect.top;\n }\n }\n }\n }, [activeIndex]);\n return (_jsx(\"div\", { ref: menuRef, role: \"menu\", ...divProps, children: mappedChildren }));\n}\n","// Default filter function\nconst defaultFilter = (item, query, filterBy) => {\n return getSafeValue(item, filterBy).toLowerCase().includes(query.toLowerCase());\n};\n// Helper function to get the first string key of an object\nconst getFirstStringKey = (obj) => {\n return Object.keys(obj).find((key) => typeof obj[key] === \"string\") || \"\";\n};\n// Helper function to safely get a string value from an item\nconst getSafeValue = (item, key) => {\n const value = item[key];\n return typeof value === \"string\" ? value : String(value);\n};\n/**\n * Filters `items` by `query` and ranks the matches exact-first — THE ranking behind the editor's\n * marker palettes (`NodeSelectionMenu` filters with `filterBy: \"name\"`, the marker code): exact\n * match, then prefix matches, then containment matches (nearest occurrence first), with ties\n * keeping the caller's item order (stable sort). Hosts rendering their own palette UI over the\n * editor's marker items should reuse this rather than reimplementing the ordering.\n *\n * @public\n */\nexport function filterAndRankItems(options) {\n const { query, items, filterBy, filter, sortBy, sortingOptions } = options;\n const { caseSensitive = false, priorityOrder = [\"exact\", \"startsWith\", \"contains\"] } = sortingOptions || {};\n const compareQuery = caseSensitive ? query : query.toLowerCase();\n let actualFilterBy;\n let actualFilter;\n if (filter) {\n actualFilter = filter;\n actualFilterBy = items.length > 0 ? getFirstStringKey(items[0]) : \"\";\n }\n else {\n actualFilterBy = filterBy || (items.length > 0 ? getFirstStringKey(items[0]) : \"\");\n actualFilter = (item, query) => defaultFilter(item, query, actualFilterBy);\n }\n const actualSortBy = sortBy || actualFilterBy;\n // Create a Map to cache lowercase versions of sortBy values\n const sortByCache = new Map();\n return items\n .filter((item) => {\n try {\n return actualFilter(item, query);\n }\n catch (error) {\n console.warn(`Error filtering item:`, item, error);\n return false;\n }\n })\n .sort((a, b) => {\n const getTextLower = (item) => {\n if (!sortByCache.has(item)) {\n sortByCache.set(item, getSafeValue(item, actualSortBy).toLowerCase());\n }\n return sortByCache.get(item) ?? \"\";\n };\n const textA = caseSensitive ? getSafeValue(a, actualSortBy) : getTextLower(a);\n const textB = caseSensitive ? getSafeValue(b, actualSortBy) : getTextLower(b);\n for (const priority of priorityOrder) {\n switch (priority) {\n case \"exact\":\n if (textA === compareQuery && textB !== compareQuery)\n return -1;\n if (textB === compareQuery && textA !== compareQuery)\n return 1;\n break;\n case \"startsWith\":\n if (textA.startsWith(compareQuery) && !textB.startsWith(compareQuery))\n return -1;\n if (textB.startsWith(compareQuery) && !textA.startsWith(compareQuery))\n return 1;\n break;\n case \"contains\": {\n const indexA = textA.indexOf(compareQuery);\n const indexB = textB.indexOf(compareQuery);\n if (indexA !== -1 && indexB === -1)\n return -1;\n if (indexB !== -1 && indexA === -1)\n return 1;\n if (indexA !== -1 && indexB !== -1)\n return indexA - indexB;\n break;\n }\n }\n }\n return textA.localeCompare(textB);\n });\n}\n","import { MenuRoot } from \"./Root\";\nimport { MenuOptions } from \"./Options\";\nimport { MenuOption } from \"./Option\";\nexport { filterAndRankItems } from \"./filterAndRankItems\";\nexport default {\n Root: MenuRoot,\n Options: MenuOptions,\n Option: MenuOption,\n};\n","import { useMemo } from \"react\";\nimport { filterAndRankItems } from \"./filterAndRankItems\";\nexport function useFilteredItems(props) {\n const { query, items, filterBy, filter, sortBy, sortingOptions } = props;\n const filteredItems = useMemo(() => {\n return filterAndRankItems({\n query,\n items,\n filterBy,\n filter,\n sortBy,\n sortingOptions,\n });\n }, [query, items, filterBy, filter, sortBy, sortingOptions]);\n return filteredItems;\n}\n","import { useMemo } from \"react\";\nimport { useMenuContext } from \"./MenuContext\";\nexport function useMenuActions() {\n const { moveUp, moveDown, select } = useMenuContext();\n return useMemo(() => ({\n moveUp,\n moveDown,\n select,\n }), [moveUp, moveDown, select]);\n}\n","import { useEffect } from \"react\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useMenuActions } from \"./useMenuActions\";\nimport { COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from \"lexical\";\nexport const useLexicalMenuNavigation = () => {\n const menu = useMenuActions();\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n const handleEvent = (event) => {\n const actions = {\n ArrowDown: () => menu?.moveDown(),\n ArrowUp: () => menu?.moveUp(),\n Enter: () => menu?.select(),\n Tab: () => menu?.select(),\n };\n const action = actions[event.key];\n if (action) {\n action();\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n return false;\n };\n return editor.registerCommand(KEY_DOWN_COMMAND, handleEvent, COMMAND_PRIORITY_HIGH);\n }, [editor, menu]);\n};\n","import { useLexicalMenuNavigation } from \"./Menu/useLexicalMenuNavigation\";\nexport default function LexicalMenuNavigation() {\n useLexicalMenuNavigation();\n return null;\n}\n","import { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nimport { useEffect, useState } from \"react\";\nimport Menu from \"./Menu\";\nimport { useFilteredItems } from \"./Menu/useFilteredItems\";\nimport { COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from \"lexical\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport LexicalMenuNavigation from \"./LexicalMenuNavigation\";\n/**\n * Keys that are a modifier's OWN keydown rather than input. A modifier fires its own keydown\n * before the chord (or the shifted character) it is part of arrives, so the menu must sit still\n * for it: closing here would end the menu mid-chord, e.g. on the Shift of a `\\+w` nested marker.\n */\nconst MODIFIER_KEYS = [\"Shift\", \"Control\", \"Alt\", \"Meta\"];\nexport function NodeSelectionMenu(props) {\n const { options, onSelectOption, onClose, inverse, query: controlledQuery, menuOpenKey, onFilterChange, passthroughKeys, } = props;\n const [editor] = useLexicalComposerContext();\n const isControlled = controlledQuery !== undefined;\n const [query, setQuery] = useState(\"\");\n const localQuery = isControlled ? (controlledQuery ?? \"\") : query;\n const filteredOptions = useFilteredItems({ query: localQuery, items: options, filterBy: \"name\" });\n const handleOptionSelection = (option) => {\n onClose?.();\n if (onSelectOption)\n onSelectOption(option);\n else\n option.action(editor);\n };\n useEffect(() => {\n onFilterChange?.(localQuery, filteredOptions);\n }, [onFilterChange, localQuery, filteredOptions]);\n useEffect(() => {\n return editor.registerCommand(KEY_DOWN_COMMAND, (event) => {\n if (isControlled)\n return false;\n if (passthroughKeys?.includes(event.key))\n return false;\n if (MODIFIER_KEYS.includes(event.key))\n return false;\n if ((event.ctrlKey || event.metaKey || event.altKey) &&\n !event.getModifierState(\"AltGraph\")) {\n // A real chord (Ctrl+Z, Ctrl+C, Cmd+V, Ctrl+A, …) is never query input: ingesting it\n // would append its letter to the filter, and claiming it would leave undo, copy, paste\n // and select-all dead for as long as the menu is open. Close the menu — the marker it\n // was offering is not what the user is reaching for — and let the chord through\n // unclaimed, to whatever handles it. Shift is deliberately absent from the check: a\n // shifted character is still a character, and capitalized markers filter with it —\n // and so is AltGr, which Windows/Linux layouts dispatch as Ctrl+Alt: `@` on a German\n // layout or `ł` on Polish is ordinary character input, not a command chord.\n onClose?.();\n return false;\n }\n const actions = {\n Escape: () => onClose?.(),\n Backspace: () => {\n if (localQuery.length === 0) {\n onClose?.();\n }\n else {\n setQuery((prev) => prev.slice(0, -1));\n }\n },\n };\n const action = actions[event.key];\n if (action) {\n event.stopPropagation();\n event.preventDefault();\n action();\n return true;\n }\n else if (event.key.length === 1) {\n event.stopPropagation();\n event.preventDefault();\n if (event.key !== menuOpenKey)\n setQuery((prev) => prev + event.key);\n return true;\n }\n return false;\n }, COMMAND_PRIORITY_HIGH);\n }, [editor, isControlled, localQuery, menuOpenKey, onClose, passthroughKeys]);\n return (_jsxs(Menu.Root, { className: `autocomplete-menu-container ${inverse ? \"inverse\" : \"\"}`, menuItems: filteredOptions, onSelectOption: (item) => handleOptionSelection(item), children: [!isControlled && _jsx(\"input\", { value: localQuery, type: \"text\", disabled: true }), _jsx(LexicalMenuNavigation, {}), _jsx(Menu.Options, { className: \"autocomplete-menu-options\", autoIndex: false, children: (options) => {\n const mappedOptions = options.map((option, index) => (_jsxs(Menu.Option, { index: index, children: [_jsx(\"span\", { className: \"label\", children: option.label ?? option.name }), _jsx(\"span\", { className: \"description\", children: option.description })] }, option.name)));\n return mappedOptions;\n } })] }));\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { $getSelection, $isRangeSelection } from \"lexical\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport FloatingBoxAtCursor from \"../FloatingBox/FloatingBoxAtCursor\";\nimport { NodeSelectionMenu } from \"./NodeSelectionMenu\";\nexport * from \"./Menu\";\nexport * from \"./LexicalMenuNavigation\";\nexport * from \"./NodeSelectionMenu\";\nexport default function NodesMenu({ trigger, items }) {\n const [editor] = useLexicalComposerContext();\n const [isOpen, setIsOpen] = useState(false);\n const handleKeyDown = useCallback((e) => {\n if (e.key === \"Escape\" && isOpen) {\n setIsOpen(false);\n editor.focus();\n }\n else if (e.key === trigger && !isOpen) {\n e.preventDefault();\n setIsOpen(true);\n }\n }, [editor, trigger, isOpen]);\n useEffect(() => {\n return editor.registerRootListener((root) => {\n if (!root)\n return;\n root.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n root.removeEventListener(\"keydown\", handleKeyDown);\n };\n });\n }, [editor, handleKeyDown]);\n // Close the menu when the selection changes\n useEffect(() => {\n return editor.registerUpdateListener(({ prevEditorState, editorState }) => {\n const prevSelection = prevEditorState.read(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return;\n return selection;\n });\n editorState.read(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || prevSelection?.is(selection))\n return;\n setIsOpen(false);\n });\n });\n }, [editor]);\n return (items && (_jsx(FloatingBoxAtCursor, { isOpen: isOpen, children: ({ placement }) => (_jsx(NodeSelectionMenu, { options: items, onClose: () => setIsOpen(false), inverse: placement === \"top-start\", menuOpenKey: trigger })) })));\n}\n","import { useMemo } from \"react\";\nimport { getMarker } from \"shared\";\n// getMarker() takes a marker string and gets its data from a usfm markers map object that is merged with overwrites that fit the PERF editor context.\n// getMarkerAction() returns a function to generate a LexicalNode and insert it in the editor, this lexical node is a custom node made for the PERF editor\n// NOTE: You can create your own typeahead plugin by creating your own getMarker() and getMarkerAction() functions adapted to your editor needs.\nexport default function useUsfmMarkersForMenu({ scriptureReference, contextMarker, getMarkerAction, }) {\n const markersMenuItems = useMemo(() => {\n if (!contextMarker || !scriptureReference)\n return;\n const marker = getMarker(contextMarker);\n if (!marker?.children)\n return;\n return Object.values(marker.children).flatMap((markers) => markers.map((marker) => {\n const markerData = getMarker(marker);\n const { action } = getMarkerAction(marker, markerData);\n return {\n name: marker,\n label: marker,\n description: markerData?.description ?? \"\",\n action: (editor) => {\n action({ editor, reference: scriptureReference });\n },\n };\n }));\n }, [contextMarker, getMarkerAction, scriptureReference]);\n return { markersMenuItems };\n}\n","import { $getRangeFromUsjSelection } from \"./selection.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { mergeRegister, registerNestedElementResolver } from \"@lexical/utils\";\nimport { $getNodeByKey } from \"lexical\";\nimport { forwardRef, useEffect, useImperativeHandle, useMemo } from \"react\";\nimport { $createTypedMarkNode, $isTypedMarkNode, $unwrapTypedMarkNode, $wrapSelectionInTypedMarkNode, ANNOTATION_CHANGE_TAG, TypedMarkNode, } from \"shared\";\nfunction getTypeIDMapKey(type, id) {\n return `${type}:${id}`;\n}\nfunction useAnnotations(editor, markNodeMap) {\n useEffect(() => {\n if (!editor.hasNodes([TypedMarkNode])) {\n throw new Error(\"AnnotationPlugin: TypedMarkNode not registered on editor!\");\n }\n const markNodeKeysToTypedIDs = new Map();\n return mergeRegister(registerNestedElementResolver(editor, TypedMarkNode, (from) => {\n return $createTypedMarkNode(from.getTypedIDs(), from.getTypedOnClicks(), from.getTypedOnRemoves(), from.getTypedOnMouseEnters(), from.getTypedOnMouseLeaves());\n }, (from, to) => {\n // Merge the IDs\n const fromOnClicks = from.getTypedOnClicks();\n const fromOnRemoves = from.getTypedOnRemoves();\n const fromOnMouseEnters = from.getTypedOnMouseEnters();\n const fromOnMouseLeaves = from.getTypedOnMouseLeaves();\n for (const [type, ids] of Object.entries(from.getTypedIDs())) {\n ids.forEach((id) => {\n const onClick = fromOnClicks[type]?.[id];\n const onRemove = fromOnRemoves[type]?.[id];\n const onMouseEnter = fromOnMouseEnters[type]?.[id];\n const onMouseLeave = fromOnMouseLeaves[type]?.[id];\n to.addID(type, id, onClick, onRemove, onMouseEnter, onMouseLeave);\n });\n }\n // The resolver replaces the original node with a new one; suppress callbacks so the\n // transferred IDs do not emit \"destroyed\" notifications during the teardown.\n from.getWritable().__suppressOnRemoveCallbacks = true;\n }), editor.registerMutationListener(TypedMarkNode, (mutations) => {\n editor.getEditorState().read(() => {\n // Keep track of mutated mark node keys so they can be removed later.\n for (const [key, mutation] of mutations) {\n const node = $getNodeByKey(key);\n let typedIDs = {};\n if (mutation === \"destroyed\") {\n typedIDs = markNodeKeysToTypedIDs.get(key) ?? {};\n }\n else if ($isTypedMarkNode(node)) {\n typedIDs = node.getTypedIDs();\n }\n for (const [type, ids] of Object.entries(typedIDs)) {\n // Skip reserved types as they will handle their own keys.\n if (TypedMarkNode.isReservedType(type))\n continue;\n for (const id of ids) {\n let markNodeKeys = markNodeMap.get(getTypeIDMapKey(type, id));\n typedIDs[type] = ids;\n markNodeKeysToTypedIDs.set(key, typedIDs);\n if (mutation === \"destroyed\") {\n if (markNodeKeys !== undefined) {\n markNodeKeys.delete(key);\n if (markNodeKeys.size === 0) {\n markNodeMap.delete(getTypeIDMapKey(type, id));\n }\n }\n }\n else {\n if (markNodeKeys === undefined) {\n markNodeKeys = new Set();\n markNodeMap.set(getTypeIDMapKey(type, id), markNodeKeys);\n }\n if (!markNodeKeys.has(key)) {\n markNodeKeys.add(key);\n }\n }\n }\n }\n }\n });\n }, { skipInitialization: true }));\n }, [editor, markNodeMap]);\n}\nexport const AnnotationPlugin = forwardRef(function AnnotationPlugin({ logger }, ref) {\n const [editor] = useLexicalComposerContext();\n const markNodeMap = useMemo(() => {\n return new Map();\n }, []);\n useAnnotations(editor, markNodeMap);\n /**\n * Removes all mark nodes associated with the given type/id pair.\n *\n * @param type - Annotation type to remove.\n * @param id - Annotation ID to remove.\n * @param nodeKeys - Optional set of known node keys for this type/id. When omitted, keys are\n * computed from the shared mark node map.\n */\n const $removeMarkNodesForTypeID = (type, id, nodeKeys) => {\n const keys = Array.from(nodeKeys ?? markNodeMap.get(getTypeIDMapKey(type, id)) ?? []);\n if (keys.length === 0)\n return;\n for (const key of keys) {\n const node = $getNodeByKey(key);\n if ($isTypedMarkNode(node)) {\n node.deleteID(type, id);\n if (node.hasNoIDsForEveryType()) {\n $unwrapTypedMarkNode(node);\n }\n }\n }\n };\n useImperativeHandle(ref, () => ({\n setAnnotation(selection, type, id, onClick, onRemove, onMouseEnter, onMouseLeave) {\n if (TypedMarkNode.isReservedType(type))\n throw new Error(`setAnnotation: Can't directly set this reserved annotation type '${type}'.` +\n \" Use the appropriate plugin instead.\");\n editor.update(() => {\n // Apply the annotation to the selected range.\n const editorSelection = $getRangeFromUsjSelection(selection);\n if (editorSelection === undefined) {\n logger?.error(\"Failed to find start or end node of the annotation.\");\n return;\n }\n $removeMarkNodesForTypeID(type, id);\n $wrapSelectionInTypedMarkNode(editorSelection, type, id, onClick, onRemove, onMouseEnter, onMouseLeave);\n }, { tag: ANNOTATION_CHANGE_TAG });\n },\n removeAnnotation(type, id) {\n if (TypedMarkNode.isReservedType(type))\n throw new Error(`removeAnnotation: Can't directly remove this reserved annotation type '${type}'.` +\n \" Use the appropriate plugin instead.\");\n const markNodeKeys = markNodeMap.get(getTypeIDMapKey(type, id));\n if (markNodeKeys === undefined || markNodeKeys.size === 0)\n return;\n editor.update(() => {\n $removeMarkNodesForTypeID(type, id, markNodeKeys);\n }, { tag: ANNOTATION_CHANGE_TAG });\n },\n }));\n return null;\n});\n","/**\n * Adapted from https://github.com/facebook/lexical/blob/d0456a81955bc6fef7cc7f87907f2a172d41bbf2/packages/lexical-react/src/LexicalOnChangePlugin.ts\n */\nimport { $getOTPositionOfNode, $isFastPathContentText } from \"./delta-common.utils\";\nimport { $getTextOp, getEditorDelta } from \"./editor-delta.adaptor\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $getNodeByKey, $isTextNode, HISTORY_MERGE_TAG } from \"lexical\";\nimport Delta from \"quill-delta\";\nimport { useLayoutEffect } from \"react\";\nimport { $findFirstAncestorNoteNode, MARKER_SETTLE_TAG } from \"shared\";\n/** Stable default for {@link DeltaOnChangePlugin}'s `ignoreTags` so the effect deps stay stable. */\nconst EMPTY_TAGS = [];\n/** Adapted from the LexicalOnChangePlugin to include collaborative editing operations. */\nexport function DeltaOnChangePlugin({ ignoreHistoryMergeTagChange = true, ignoreSelectionChange = false, ignoreTags = EMPTY_TAGS, onChange, }) {\n const [editor] = useLexicalComposerContext();\n useLayoutEffect(() => {\n if (!onChange)\n return;\n return editor.registerUpdateListener((payload) => {\n const { editorState, dirtyElements, dirtyLeaves, prevEditorState, tags } = payload;\n if ((ignoreSelectionChange && dirtyElements.size === 0 && dirtyLeaves.size === 0) ||\n // A `MARKER_SETTLE_TAG` commit carries the merge tag only to stay out of the undo\n // stack — its bytes really did change, so it must reach `onChange` like any edit.\n // Without this exemption the cached USJ and the emitted delta both keep showing the\n // pre-settle bytes, and the host saves a document the editor is no longer displaying.\n (ignoreHistoryMergeTagChange &&\n tags.has(HISTORY_MERGE_TAG) &&\n !tags.has(MARKER_SETTLE_TAG)) ||\n ignoreTags.some((tag) => tags.has(tag)) ||\n prevEditorState.isEmpty()) {\n return;\n }\n const ops = $getUpdateOps(editor, payload);\n // TODO: this may have been added because nodes are made dirty when they shouldn't be as a\n // result of NoteNode collapsing/expanding. If so, we should fix that instead.\n if (ops.length === 0)\n return;\n onChange(editorState, editor, tags, ops);\n });\n }, [editor, ignoreHistoryMergeTagChange, ignoreSelectionChange, ignoreTags, onChange]);\n return null;\n}\nfunction $getUpdateOps(editor, { dirtyLeaves, prevEditorState }) {\n let update = new Delta();\n editor.getEditorState().read(() => {\n const nodeKey = dirtyLeaves.values().next().value ?? \"\";\n const dirtyNode = $getNodeByKey(nodeKey);\n // Note-internal edits must NOT take the fast path: a note is ONE opaque embed unit in\n // delta-doc coordinates, so $getOTPositionOfNode for a text node INSIDE it resolves to the\n // note's OUTER position and the emitted op would land the edit AFTER the note. The full-diff\n // fallback replaces the note embed wholesale instead.\n const isInsideNote = dirtyNode !== null && $findFirstAncestorNoteNode(dirtyNode) !== undefined;\n // Presentation-carrying text must not take the fast path either: the fast path's insert is\n // the node's RAW bytes while its retain is counted in delta-doc coordinates, and for a node\n // the ops-stream exclusions treat specially (a `\\q1` prefix glyph, a `\\va 2\\va*` run,\n // attribute text) those are different currencies — a peer would receive display bytes as\n // Scripture and every later offset would shift. $isFastPathContentText derives eligibility\n // from the same delta-doc counting instead of re-listing the exclusions; anything ineligible\n // falls to the full diff, whose $handleTextNodes applies the one authoritative list.\n if (dirtyLeaves.size === 1 &&\n $isTextNode(dirtyNode) &&\n !isInsideNote &&\n $isFastPathContentText(dirtyNode)) {\n // Handle the most common case of text changing in a single text node.\n // Default \"delta-doc\" coordinates (NOT \"apply\"): this fast path and the `getEditorDelta`\n // diff fallback below feed the same doc-delta op stream emitted to the host via\n // `onChange`, so they must agree. They do: `$getOTPositionOfNode` counts a preceding\n // editable verse as its 1-unit embed (matching the doc delta, which emits only the verse\n // embed op — the glyph text is engine-owned display, excluded from content ops), and a\n // preceding editable chapter as its glyph-length body text (matching the doc delta too).\n // See `OTCoordinateSystem` in delta-common.utils.ts.\n const retain = $getOTPositionOfNode(dirtyNode);\n if (retain !== undefined) {\n const prevTextDoc = prevEditorState.read(() => {\n const prevNode = $getNodeByKey(nodeKey);\n return new Delta([$isTextNode(prevNode) ? $getTextOp(prevNode) : { insert: \"\" }]);\n });\n const textDoc = new Delta([$getTextOp(dirtyNode)]);\n const nodePositionRetain = new Delta(retain > 0 ? [{ retain }] : []);\n update = update.concat(nodePositionRetain).concat(prevTextDoc.diff(textDoc));\n }\n }\n else {\n const prevDoc = getEditorDelta(prevEditorState);\n const currentDoc = getEditorDelta(editor.getEditorState());\n update = prevDoc.diff(currentDoc);\n }\n });\n return update.ops;\n}\n","/**\n * Constant representing the formatted view mode.\n * Used to display content with formatting applied.\n *\n * @public\n */\nexport const FORMATTED_VIEW_MODE = \"formatted\";\n/**\n * Constant representing the unformatted view mode.\n * Used to display content without formatting applied.\n *\n * @public\n */\nexport const UNFORMATTED_VIEW_MODE = \"unformatted\";\n/**\n * Constant representing the paragraph structure view mode.\n * Displays formatted text with visible USFM paragraph markers in a gutter column,\n * styled verse numbers, decorative chapter numbers, and an active-text outline on\n * the focused paragraph section.\n *\n * @public\n */\nexport const PARAGRAPH_STRUCTURE_VIEW_MODE = \"paragraph-structure\";\n/**\n * Constant representing the standard view mode (PT9 \"Standard\" equivalent).\n * Displays formatted text with USFM markers visible inline as editable text and\n * notes collapsed to callers.\n *\n * @public\n */\nexport const STANDARD_VIEW_MODE = \"standard\";\n/**\n * Constant representing the block verse view mode.\n * Displays formatted text with each verse wrapped in a block-level element, so a verse can be\n * placed on a layout row - for example a grid aligning the same verse across several resources.\n * This view is read-only; see `ViewOptions.verseLayout`.\n *\n * @public\n */\nexport const BLOCK_VERSE_VIEW_MODE = \"block-verse\";\n/**\n * Maps view mode keys to their human-readable display names.\n * Used for UI components that need to show view mode options to users.\n *\n * @public\n */\nexport const viewModeToViewNames = {\n [FORMATTED_VIEW_MODE]: \"Formatted\",\n [UNFORMATTED_VIEW_MODE]: \"Unformatted\",\n [PARAGRAPH_STRUCTURE_VIEW_MODE]: \"Paragraph Structure\",\n [STANDARD_VIEW_MODE]: \"Standard\",\n [BLOCK_VERSE_VIEW_MODE]: \"Block Verse\",\n};\n","import { TEXT_SPACING_CLASS_NAME, FORMATTED_FONT_CLASS_NAME, MARKER_MODE_CLASS_NAME_PREFIX, VerseNode, } from \"shared\";\nimport { ImmutableVerseNode } from \"../nodes/usj/ImmutableVerseNode\";\nimport { BLOCK_VERSE_VIEW_MODE, FORMATTED_VIEW_MODE, UNFORMATTED_VIEW_MODE, PARAGRAPH_STRUCTURE_VIEW_MODE, STANDARD_VIEW_MODE, viewModeToViewNames, } from \"./view-mode.model\";\nimport { deepEqual } from \"fast-equals\";\n/**\n * Whether `viewOptions` renders paragraph marker prefixes at all — the single spelling of the\n * `showParaMarkerPrefixes !== false` default. The adaptor (which builds the glyph), the\n * marker-edit transforms (which police or heal it), and the prefix-deletion guard (which reacts\n * to its absence) must all answer this identically: a surface that never builds the prefix must\n * never treat its absence as user intent to change the paragraph.\n *\n * @public\n */\nexport function showParaMarkerPrefix(viewOptions) {\n return viewOptions?.showParaMarkerPrefixes !== false;\n}\nlet defaultViewMode;\nlet defaultViewOptions;\n/**\n * Sets the default view mode and options.\n *\n * @param viewMode - View mode of the editor.\n *\n * @public\n */\nexport function setDefaultView(viewMode) {\n const _viewOptions = getViewOptions(viewMode);\n if (!_viewOptions)\n throw new Error(`Invalid view mode: ${viewMode}`);\n defaultViewMode = viewMode;\n defaultViewOptions = _viewOptions;\n}\nsetDefaultView(FORMATTED_VIEW_MODE);\n/**\n * Gets the default view mode.\n *\n * @returns the default view mode.\n *\n * @public\n */\nexport const getDefaultViewMode = () => defaultViewMode;\n/**\n * Gets the default view options.\n *\n * @returns the default view options.\n *\n * @public\n */\nexport const getDefaultViewOptions = () => defaultViewOptions;\n/**\n * Get view option properties based on the view mode.\n *\n * @param viewMode - View mode of the editor.\n * @returns the view options if the view exists, the default options if the viewMode is undefined,\n * `undefined` otherwise.\n *\n * @public\n */\nexport function getViewOptions(viewMode) {\n let viewOptions;\n switch (viewMode ?? defaultViewMode) {\n case FORMATTED_VIEW_MODE:\n viewOptions = {\n markerMode: \"hidden\",\n noteMode: \"collapsed\",\n hasSpacing: true,\n isFormattedFont: true,\n };\n break;\n case UNFORMATTED_VIEW_MODE:\n viewOptions = {\n markerMode: \"editable\",\n noteMode: \"expanded\",\n hasSpacing: false,\n isFormattedFont: false,\n };\n break;\n case PARAGRAPH_STRUCTURE_VIEW_MODE:\n viewOptions = {\n markerMode: \"hidden\",\n noteMode: \"collapsed\",\n hasSpacing: true,\n isFormattedFont: true,\n hasGutterParaMarkers: true,\n hasActiveTextFocusBox: true,\n };\n break;\n case STANDARD_VIEW_MODE:\n viewOptions = {\n markerMode: \"editable\",\n noteMode: \"collapsed\",\n hasSpacing: true,\n isFormattedFont: true,\n };\n break;\n case BLOCK_VERSE_VIEW_MODE:\n viewOptions = {\n markerMode: \"hidden\",\n noteMode: \"collapsed\",\n hasSpacing: true,\n isFormattedFont: true,\n verseLayout: \"block\",\n };\n break;\n default:\n break;\n }\n return viewOptions;\n}\n/**\n * Convert view options to view mode if the view exists.\n *\n * Inverts {@link getViewOptions} by comparison, so a view option added later cannot be forgotten\n * here and leave two modes indistinguishable. Matching is exact once each unset optional field is\n * filled in with its default, so spelling a default out still matches, but options derived from a\n * mode and then genuinely tweaked describe a view that is no longer that mode and yield\n * `undefined`.\n *\n * @remarks\n * This is narrower than the field-by-field matching it replaced, which tested only `markerMode`,\n * `hasSpacing`, `isFormattedFont`, `hasGutterParaMarkers` and `hasActiveTextFocusBox`. Every other\n * field now counts, `noteMode` included - it has no default to fill in (call sites read `undefined`\n * inconsistently, some as collapsed and some as not), so it is part of what identifies a mode and\n * has to be given. Options built from a mode with `noteMode` changed - say\n * `{ ...getViewOptions(PARAGRAPH_STRUCTURE_VIEW_MODE), noteMode: \"expanded\" }` - used to return the\n * mode they started from and now return `undefined`.\n *\n * @param viewOptions - View options of the editor.\n * @returns the view mode if the view is defined, `undefined` otherwise.\n *\n * @public\n */\nexport function getViewMode(viewOptions) {\n if (!viewOptions)\n return undefined;\n const normalized = canonicalize(viewOptions);\n return Object.keys(viewModeToViewNames).find((viewMode) => deepEqual(canonicalize(getViewOptions(viewMode)), normalized));\n}\n/**\n * Each optional field's documented default, so options that spell a default out compare equal to\n * options that leave it out - they describe the same view, and the comparison counts keys.\n *\n * `noteMode` is absent deliberately: it has no default (call sites read `undefined` inconsistently,\n * some as collapsed and some as not), so it is part of what identifies a mode and has to be given.\n */\nconst optionalViewOptionDefaults = {\n showCharMarkerTitles: true,\n hasGutterParaMarkers: false,\n hasActiveTextFocusBox: false,\n verseLayout: \"inline\",\n};\n/** The options with every unset optional field filled in with its default. */\nfunction canonicalize(viewOptions) {\n if (!viewOptions)\n return viewOptions;\n // Strip the `undefined`-valued keys first - spreading them over the defaults would reinstate the\n // very \"not set\" state the defaults exist to resolve.\n const setOptions = Object.fromEntries(Object.entries(viewOptions).filter(([, value]) => value !== undefined));\n return { ...optionalViewOptionDefaults, ...setOptions };\n}\n/**\n * Whether the standard-view whitespace/display normalization rules apply to these view options.\n *\n * These rules — the display NBSP/`~` mapping at load time, the live display-whitespace transform\n * and clipboard normalization, and the inverse normalization on serialization — are all gated on\n * this ONE predicate, so a document always serializes under the same whitespace regime it was\n * loaded with; no combination of options can apply the display mapping without its inversion.\n *\n * The invariant is the STANDARD view fingerprint with the `noteMode` axis dropped: editable\n * markers in a spacing+formatted view with NEITHER `hasGutterParaMarkers` NOR\n * `hasActiveTextFocusBox`. It is `true` for both collapsed (the named `standard` mode) and\n * expanded notes. It is `false` for the Unformatted view (editable but neither spaced nor\n * formatted, where whitespace is shown literally) and for gutter/focus-box views: those render\n * paragraph markers as immutable typed text — a different whitespace regime with no\n * display-mapped text to invert (their named mode hides markers entirely, so the editable\n * engine's separators never combine with them). Deliberately NOT expressed via\n * {@link getViewMode}: expanded is not the named `standard` mode, and overloading `getViewMode`\n * would break its invertibility contract and the user-facing mode labels. `getViewMode` compares\n * whole option objects against {@link getViewOptions}, so this predicate stays independent of it:\n * it is the one place the whitespace fingerprint — the STANDARD options with the `noteMode` axis\n * dropped — is written down.\n *\n * @param viewOptions - View options of the editor.\n * @returns `true` when standard-view whitespace normalization applies.\n *\n * @public\n */\nexport function hasStandardViewWhitespace(viewOptions) {\n if (!viewOptions)\n return false;\n const { markerMode, hasSpacing, isFormattedFont, hasGutterParaMarkers, hasActiveTextFocusBox } = viewOptions;\n return (markerMode === \"editable\" &&\n hasSpacing &&\n isFormattedFont &&\n !hasGutterParaMarkers &&\n !hasActiveTextFocusBox);\n}\n/**\n * Get the verse node class for the given view options.\n *\n * @param viewOptions - View options of the editor.\n * @returns the verse node class if the view is defined, `undefined` otherwise.\n *\n * @public\n */\nexport function getVerseNodeClass(viewOptions) {\n if (!viewOptions)\n return;\n // Block verse is read-only, so its marker is never the editable `VerseNode`. Today the marker\n // mode below would reach the same answer - block verse hides markers - but dispatching on the\n // layout first keeps that independent of how markers happen to be configured.\n if (isBlockVerseLayout(viewOptions))\n return ImmutableVerseNode;\n return viewOptions.markerMode === \"editable\" ? VerseNode : ImmutableVerseNode;\n}\n/**\n * Whether the view options select the block verse layout.\n *\n * That layout is read-only: its paragraphs are split across verse blocks, so an edit has no\n * correct USJ to go back to. Anything that offers editing - or an affordance that depends on\n * editing, such as comment authoring - should treat it as read-only whatever `isReadonly` says.\n *\n * @param viewOptions - View options of the editor.\n * @returns `true` if verses are laid out as blocks.\n *\n * @public\n */\nexport function isBlockVerseLayout(viewOptions) {\n return viewOptions?.verseLayout === \"block\";\n}\n/**\n * Get the class name list for the given view options.\n *\n * @param viewOptions - View options of the editor.\n * @returns the element class name list based on view options.\n *\n * @public\n */\nexport function getViewClassList(viewOptions) {\n const classList = [];\n const _viewOptions = viewOptions ?? defaultViewOptions;\n if (_viewOptions) {\n classList.push(`${MARKER_MODE_CLASS_NAME_PREFIX}${_viewOptions.markerMode}`);\n if (_viewOptions.hasSpacing)\n classList.push(TEXT_SPACING_CLASS_NAME);\n if (_viewOptions.isFormattedFont)\n classList.push(FORMATTED_FONT_CLASS_NAME);\n }\n return classList;\n}\n","import { $createImmutableVerseNode } from \"../../../nodes/usj/ImmutableVerseNode\";\nimport { $isSomeVerseNode } from \"../../../nodes/usj/node-react.utils\";\nimport { $createWholeNote } from \"../../../nodes/usj/note.utils\";\nimport { showParaMarkerPrefix } from \"../../../views/view-options.utils\";\nimport { $isEmbedNode, $isOTTextNode, isInsertEmbedOpOfType, LF, } from \"./delta-common.utils\";\nimport { OT_BOOK_PROPS, OT_CHAPTER_PROPS, OT_CHAR_PROPS, OT_MILESTONE_PROPS, OT_NOTE_PROPS, OT_PARA_PROPS, OT_UNKNOWN_PROPS, OT_VERSE_PROPS, } from \"./rich-text-ot.model\";\nimport { $unwrapNode } from \"@lexical/utils\";\nimport { $getRoot, $createTextNode, $isElementNode, $isTextNode, $setState, } from \"lexical\";\nimport { $createBookNode, $createChapterNode, $createCharNode, $createImmutableChapterNode, $createImmutableTypedTextNode, $createImmutableUnmatchedNode, $createImpliedParaNode, $createMarkerNode, $createMarkerTrailingSeparator, $createMilestoneNode, $createParaNode, $createUnknownNode, $createVerseNode, $hasSameCharAttributes, $isBookNode, $isCharNode, $isImmutableTypedTextNode, $isImpliedParaNode, $isMarkerNode, $isMilestoneNode, $isNoteNode, $isParaLikeNode, $isParaNode, $isSomeChapterNode, $isSomeParaNode, $isUnknownNode, BOOK_MARKER, BookNode, charIdState, closingMarkerText, EMPTY_CHAR_PLACEHOLDER_TEXT, getUnknownAttributes, getVisibleOpenMarkerText, NBSP, $createGutterMarkerNode, openingMarkerText, segmentState, } from \"shared\";\n/*\nFor implied paragraphs, we use the following logic:\n - An ImpliedParaNode (or ParaNode) takes up OT index space 1 but only at the end of the block.\n - An ImpliedParaNode is created when an inline node is inserted where there is no ParaNode.\n - If an LF is inserted, it closes the ImpliedParaNode if there are no attributes or it is replaced\n by a ParaNode specified by the attributes.\n - Our empty Lexical editor defaults to an empty ImpliedParaNode, so the first inline insertion\n should go inside it.\n\nFor CharNodes, we use the following logic:\n - CharNodes are created when attributes.char is present in a text insert operation.\n - CharNodes are inserted at the current index, and they can contain TextNodes with additional\n formatting attributes.\n - CharNodes have no OT length contribution themselves, but their text content does.\n - CharNodes can be nested inside SomeParaNode or other CharNodes.\n - CharNodes use the attributes style and cid to uniquely identify themselves.\n - A single CharNode can use an attributes object `{ char: { style: \"bd\", cid: \"456\" } }`.\n - A nested CharNode will use attributes.char object\n `{ char: [{ style: \"it\", cid: \"123\" }, { style: \"bd\", cid: \"456\" }] }`\n where \"it\" is the parent CharNode and \"bd\" is the child CharNode.\n*/\n/**\n * Apply Operational Transform rich-text updates to the editor.\n * @param ops - Operations array.\n * @param viewOptions - View options of the editor.\n * @param nodeOptions - Node options for USJ nodes.\n * @param logger - Logger to use, if any.\n *\n * @see https://github.com/ottypes/rich-text\n */\nexport function $applyUpdate(ops, viewOptions, nodeOptions, logger) {\n /** Tracks the current position in the OT document */\n let currentIndex = 0;\n ops.forEach((op) => {\n if (\"retain\" in op) {\n currentIndex += $retain(op, currentIndex, viewOptions, logger);\n }\n else if (\"delete\" in op) {\n if (typeof op.delete !== \"number\" || op.delete <= 0) {\n logger?.error(`Invalid delete operation: ${JSON.stringify(op)}`);\n return; // Skip malformed operation\n }\n logger?.debug(`Delete: ${op.delete}`);\n $delete(currentIndex, op.delete, logger);\n // Delete operations do not advance the currentIndex in the OT Delta model\n }\n else if (\"insert\" in op) {\n if (typeof op.insert === \"string\") {\n logger?.debug(`Insert: '${op.insert}'`);\n currentIndex += $insertTextAtCurrentIndex(currentIndex, op.insert, op.attributes, viewOptions, logger);\n }\n else if (typeof op.insert === \"object\" && op.insert !== null) {\n logger?.debug(`Insert embed: ${JSON.stringify(op.insert)}`);\n if ($insertEmbedAtCurrentIndex(currentIndex, op, viewOptions, nodeOptions, logger)) {\n currentIndex += 1;\n }\n else {\n // If embed insertion fails, currentIndex is not advanced to prevent de-sync.\n logger?.error(`Failed to process insert embed operation: ${JSON.stringify(op.insert)} at index ${currentIndex}. Document may be inconsistent.`);\n }\n }\n else {\n logger?.error(`Insert of unknown type: ${JSON.stringify(op.insert)}`);\n }\n }\n else {\n logger?.error(`Unknown operation: ${JSON.stringify(op)}`);\n }\n });\n}\nfunction $retain(op, currentIndex, viewOptions, logger) {\n if (typeof op.retain !== \"number\" || op.retain < 0) {\n logger?.error(`Invalid retain operation: ${JSON.stringify(op)}`);\n return 0;\n }\n logger?.debug(`Retain: ${op.retain}`);\n if (op.attributes) {\n logger?.debug(`Retain attributes: ${JSON.stringify(op.attributes)}`);\n $applyAttributes(currentIndex, op.retain, op.attributes, viewOptions, logger);\n }\n return op.retain;\n}\n/** Traverse and apply attributes to the retained range, or transform text to CharNode */\nfunction $applyAttributes(targetIndex, retain, attributes, viewOptions, logger) {\n // Apply attributes using standard traversal logic\n logger?.debug(`Applying attributes for range [${targetIndex}, ${targetIndex + retain - 1}] with attributes: ${JSON.stringify(attributes)}`);\n let lengthToFormat = retain;\n let currentIndex = 0;\n /** The nested CharNode depth */\n let nestedCharCount = -1;\n const root = $getRoot();\n function $traverseAndApplyAttributesRecursive(currentNode) {\n if (lengthToFormat <= 0)\n return true;\n if ($isOTTextNode(currentNode)) {\n const textLength = currentNode.getTextContentSize();\n if (targetIndex < currentIndex + textLength && currentIndex < targetIndex + retain) {\n const offsetInNode = Math.max(0, targetIndex - currentIndex);\n const lengthAvailableInNodeAfterOffset = textLength - offsetInNode;\n const lengthToApplyInThisNode = Math.min(lengthToFormat, lengthAvailableInNodeAfterOffset);\n if (lengthToApplyInThisNode > 0) {\n let targetNode = currentNode;\n const needsSplitAtStart = offsetInNode > 0;\n const needsSplitAtEnd = lengthToApplyInThisNode < textLength - offsetInNode;\n if (needsSplitAtStart && needsSplitAtEnd) {\n const [, middleNode] = currentNode.splitText(offsetInNode);\n [targetNode] = middleNode.splitText(lengthToApplyInThisNode);\n }\n else if (needsSplitAtStart) {\n [, targetNode] = currentNode.splitText(offsetInNode);\n }\n else if (needsSplitAtEnd) {\n [targetNode] = currentNode.splitText(lengthToApplyInThisNode);\n }\n // Check if we need to convert TextNode to CharNode\n if (hasCharAttributes(attributes)) {\n // Apply new non-char attributes to TextNode as well\n // Check if this text node is already inside a CharNode\n const parentNode = targetNode.getParent();\n if ($isCharNode(parentNode)) {\n const charAttr = attributes.char;\n let charAttrItem;\n if (Array.isArray(charAttr)) {\n if (nestedCharCount >= 0 && nestedCharCount <= charAttr.length - 1) {\n charAttrItem = charAttr[nestedCharCount];\n }\n }\n else if (nestedCharCount === 0) {\n // Single char attribute\n charAttrItem = charAttr;\n }\n const hasSameCharAttributes = charAttrItem\n ? $hasSameCharAttributes(charAttrItem, parentNode)\n : false;\n if (hasSameCharAttributes && Array.isArray(charAttr) && charAttr.length > 1) {\n const placeholderNode = $createTextNode(\"\");\n targetNode.replace(placeholderNode);\n const segment = typeof attributes.segment === \"string\" ? attributes.segment : undefined;\n const nestedCharNodes = $createNestedChars(charAttr.slice(1), viewOptions, targetNode, segment);\n // Insert all nodes (markers + CharNode) as siblings\n let currentPlaceholder = placeholderNode;\n for (const node of nestedCharNodes) {\n currentPlaceholder.insertAfter(node);\n currentPlaceholder = node;\n }\n placeholderNode.remove();\n // Apply text attributes to the innermost node\n $applyTextAttributes(attributes, targetNode);\n // No need to update parent marker/cid, as it already matches\n }\n else if (!hasSameCharAttributes) {\n // If parent does not match, extract text and create new CharNode as sibling\n // Remove the text node from inside the parent CharNode\n targetNode.remove();\n // Create new CharNode(s) with the text\n const charNodes = $wrapInNestedCharNodes(targetNode, attributes, viewOptions, logger);\n // Insert the new CharNodes as siblings to the parent CharNode\n if (charNodes && charNodes.length > 0) {\n let currentNode = parentNode;\n for (const node of charNodes) {\n currentNode.insertAfter(node);\n currentNode = node;\n }\n }\n }\n else {\n // Parent CharNode matches and no further nesting needed, just apply attributes\n $applyTextAttributes(attributes, targetNode);\n }\n }\n else {\n const placeholderNode = $createTextNode(\"\");\n targetNode.replace(placeholderNode);\n const charNodes = $wrapInNestedCharNodes(targetNode, attributes, viewOptions, logger);\n if (charNodes && charNodes.length > 0) {\n let currentNode = placeholderNode;\n for (const node of charNodes) {\n currentNode.insertAfter(node);\n currentNode = node;\n }\n placeholderNode.remove();\n }\n else {\n placeholderNode.replace(targetNode);\n }\n }\n }\n else {\n $applyTextAttributes(attributes, targetNode);\n }\n lengthToFormat -= lengthToApplyInThisNode;\n }\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(currentNode)) {\n const embedNodeOtLength = 1;\n if (targetIndex <= currentIndex &&\n currentIndex < targetIndex + retain &&\n lengthToFormat > 0) {\n $applyEmbedAttributes(currentNode, attributes);\n lengthToFormat -= embedNodeOtLength;\n }\n currentIndex += embedNodeOtLength;\n }\n else if ($isCharNode(currentNode)) {\n // CharNodes don't contribute to OT length, they're just formatted text containers\n nestedCharCount += 1;\n let shouldRemoveCharNode = false;\n if (targetIndex <= currentIndex &&\n currentIndex < targetIndex + retain &&\n lengthToFormat > 0) {\n if (hasCharAttributes(attributes)) {\n // Support nested char arrays for deep char attribute application\n const charAttr = attributes.char;\n let charAttrItem;\n if (Array.isArray(charAttr)) {\n if (nestedCharCount >= 0 && nestedCharCount <= charAttr.length - 1) {\n charAttrItem = charAttr[nestedCharCount];\n }\n }\n else if (nestedCharCount === 0) {\n // Single char attribute\n charAttrItem = charAttr;\n }\n // Only set attributes if needed\n if (charAttrItem) {\n // Update the CharNode's marker (and its glyphs, in editable marker mode) and\n // attributes to match the retain attributes\n $syncCharMarkerGlyphs(currentNode, charAttrItem.style);\n if (typeof charAttrItem.cid === \"string\") {\n $setState(currentNode, charIdState, () => charAttrItem.cid);\n }\n const unknownAttributes = getUnknownAttributes(charAttrItem, OT_CHAR_PROPS);\n if (unknownAttributes && Object.keys(unknownAttributes).length > 0) {\n currentNode.setUnknownAttributes({\n ...(currentNode.getUnknownAttributes() ?? {}),\n ...unknownAttributes,\n });\n }\n else {\n // If no unknown attributes, clear them\n // TODO: this was added - review if this is right and add elsewhere?\n currentNode.setUnknownAttributes(undefined);\n }\n }\n }\n else if (attributes.char === false ||\n attributes.char === null ||\n isEmptyObject(attributes.char)) {\n shouldRemoveCharNode = true;\n }\n }\n // Process children of CharNodes (no OT length contribution)\n if (lengthToFormat > 0) {\n const children = currentNode.getChildren();\n for (const child of children) {\n if (lengthToFormat <= 0)\n break;\n if ($traverseAndApplyAttributesRecursive(child)) {\n if (lengthToFormat <= 0) {\n if (shouldRemoveCharNode)\n $unwrapNode(currentNode);\n return true;\n }\n }\n }\n }\n if (shouldRemoveCharNode) {\n $unwrapNode(currentNode);\n }\n nestedCharCount -= 1;\n }\n else if ($isParaLikeNode(currentNode)) {\n // Process children first, then account for the block's own closing OT length.\n const children = currentNode.getChildren();\n for (const child of children) {\n if (lengthToFormat <= 0)\n break;\n if ($traverseAndApplyAttributesRecursive(child)) {\n if (lengthToFormat <= 0)\n return true; // Early exit if formatting complete\n }\n }\n // After children, account for the block's closing marker (OT length 1)\n const blockClosingOtLength = 1;\n // currentIndex is now positioned after all children of this block node.\n // Check if the retain operation targets this closing marker.\n if (targetIndex <= currentIndex &&\n currentIndex < targetIndex + lengthToFormat &&\n lengthToFormat > 0) {\n if (!$isImpliedParaNode(currentNode))\n $applyEmbedAttributes(currentNode, attributes);\n else if (hasParaAttributes(attributes)) {\n const newPara = $createPara(attributes.para, viewOptions);\n // `replace(…, true)` appends the implied para's children AFTER the new paragraph's\n // marker prefix, so content stays on the content side of the glyphs.\n if (newPara)\n currentNode.replace(newPara, true);\n }\n lengthToFormat -= blockClosingOtLength;\n }\n currentIndex += blockClosingOtLength;\n }\n else if ($isElementNode(currentNode)) {\n // Other ElementNodes that don't contribute to the OT length (like RootNode)\n const children = currentNode.getChildren();\n for (const child of children) {\n if (lengthToFormat <= 0)\n break;\n if ($traverseAndApplyAttributesRecursive(child)) {\n if (lengthToFormat <= 0)\n return true;\n }\n }\n }\n // Else: Non-text, non-element, non-handled nodes (e.g. LineBreakNode, DecoratorNode if not\n // explicitly handled). These typically don't contribute to OT length in this model or are\n // handled by Lexical internally.\n return lengthToFormat <= 0;\n }\n $traverseAndApplyAttributesRecursive(root);\n if (lengthToFormat > 0) {\n logger?.warn(`$applyAttributes: Not all characters in the retain operation (length ${retain}) could be processed. Remaining: ${lengthToFormat}. targetIndex: ${targetIndex}, final currentIndex: ${currentIndex}`);\n }\n}\n/**\n * Applies the given attributes to the specified text node wrapped in nested CharNodes.\n * @param textNode - The text node to wrap and to which attributes should be applied.\n * @param attributes - The attributes to apply.\n * @param textAttributes - The text attributes to apply.\n * @param logger - The logger to use for logging, if any.\n * @returns A CharNode if the operation was successful, otherwise undefined.\n */\nfunction $wrapInNestedCharNodes(textNode, attributes, viewOptions, logger) {\n // Create new CharNode(s) with the attributes, supporting nested char arrays\n const segment = typeof attributes.segment === \"string\" ? attributes.segment : undefined;\n const newCharNodes = $createNestedChars(attributes.char, viewOptions, textNode, segment);\n const charNode = newCharNodes.find($isCharNode);\n if (!charNode) {\n logger?.error(`Failed to create CharNode for text transformation. Style: ${Array.isArray(attributes.char) ? attributes.char[0].style : attributes.char?.style}. Falling back to standard text attributes.`);\n $applyTextAttributes(attributes, textNode);\n return undefined;\n }\n // Copy original text formatting to CharNode's unknownAttributes\n const textFormatAttributes = {};\n TEXT_FORMAT_TYPES.forEach((format) => {\n if (textNode.hasFormat(format)) {\n textFormatAttributes[format] = \"true\";\n }\n });\n // Convert attributes to string values for unknownAttributes\n const stringifiedAttributes = {};\n Object.entries(attributes).forEach(([key, value]) => {\n if (key === \"segment\" || key === \"char\")\n return;\n if (typeof value === \"string\") {\n stringifiedAttributes[key] = value;\n }\n else if (value === true) {\n stringifiedAttributes[key] = \"true\";\n }\n else if (value === false) {\n stringifiedAttributes[key] = \"false\";\n }\n // Skip other types that can't be serialized to string\n });\n // Combine all attributes for the CharNode\n const combinedUnknownAttributes = {\n ...(charNode.getUnknownAttributes() ?? {}),\n ...textFormatAttributes,\n ...stringifiedAttributes,\n };\n if (Object.keys(combinedUnknownAttributes).length > 0) {\n charNode.setUnknownAttributes(combinedUnknownAttributes);\n }\n $applyTextAttributes(attributes, textNode);\n return newCharNodes;\n}\n/**\n * Sets a paragraph's marker and rewrites its visible marker-glyph prefix to match, when one is\n * present. In editable marker mode a paragraph carries its marker as an editable `MarkerNode`\n * first child (`\\q1`); in visible marker mode and gutter para-marker rendering it carries an\n * immutable typed-text first child instead (`\\q1` glyph + NBSP separator in one node). Either\n * way, marker state and glyph text must change together: a stale editable glyph re-tokenizes\n * as a DIFFERENT marker than the paragraph claims on the next serialization, and a stale\n * typed-text glyph keeps displaying the old marker. Purely structural — trees whose paragraphs\n * carry no glyph prefix (hidden marker rendering) get the bare marker state change, and a\n * missing prefix is never injected. The glyph text is restored unconditionally so drifted\n * glyph text (an abandoned in-glyph rename literal) canonicalizes even when the marker value\n * itself is unchanged.\n *\n * @param para - The paragraph to retag. Must be called inside `editor.update()`.\n * @param marker - The new paragraph marker (e.g. `\"q1\"`).\n *\n * @public\n */\nexport function $syncParaMarkerGlyph(para, marker) {\n para.setMarker(marker);\n const glyph = para.getFirstChild();\n if ($isMarkerNode(glyph)) {\n glyph.setMarker(marker);\n glyph.setTextContent(openingMarkerText(marker));\n }\n else if ($isImmutableTypedTextNode(glyph) && glyph.getTextType() === \"marker\") {\n // Keep in sync with the adaptor's visible/gutter para-prefix shape in `createPara`\n // (usj-editor.adaptor.ts): opening marker text + NBSP.\n glyph.setTextContent(openingMarkerText(marker) + NBSP);\n }\n}\n/**\n * Sets a char span's marker and rewrites its own opening/closing marker glyphs to match, when\n * present. In editable marker mode a char span carries its `\\wj`…`\\wj*` glyph pair as\n * `MarkerNode` children; renaming the span without rewriting the pair leaves glyph text that\n * re-tokenizes as a different span than the node claims. In visible marker mode the pair is\n * immutable typed-text nodes instead (bare `\\wj` opener and `\\wj*` closer, as the USJ adaptor\n * and `$addOpeningMarker`/`$addClosingMarker` build them); their display text still names the\n * marker, so it is rewritten too. Only DIRECT children matching the span's OLD marker are\n * rewritten: glyphs that an ancestor span carries for a nested child (the delta-materialized\n * flattened shape) name the nested span's marker, not this one's, and are deliberately left\n * alone — the marker-edit engine's in-place rename refuses that shape for the same reason.\n * Glyph-less trees (hidden marker mode) get the bare marker state change only, and a missing\n * glyph (e.g. an unclosed span's absent closer) is never injected.\n *\n * @param char - The char span to retag. Must be called inside `editor.update()`.\n * @param marker - The new char marker (e.g. `\"nd\"`).\n *\n * @public\n */\nexport function $syncCharMarkerGlyphs(char, marker) {\n const oldMarker = char.getMarker();\n char.setMarker(marker);\n if (marker === oldMarker)\n return;\n char.getChildren().forEach((child) => {\n if ($isMarkerNode(child) && child.getMarker() === oldMarker)\n child.setMarker(marker);\n });\n // Visible-mode typed-text pair: the span's own opener/closer are its first/last children\n // (a flattened nested child's glyphs sit between them). Matching the OLD marker's exact\n // glyph text keeps this to the span's own pair, like the MarkerNode path above. A span nested\n // inside another char renders `\\+marker`, so match and rewrite the nested-aware text.\n const nested = $isCharNode(char.getParent());\n const opener = char.getFirstChild();\n if ($isImmutableTypedTextNode(opener) &&\n opener.getTextType() === \"marker\" &&\n opener.getTextContent() === openingMarkerText(oldMarker, nested)) {\n opener.setTextContent(openingMarkerText(marker, nested));\n }\n const closer = char.getLastChild();\n if ($isImmutableTypedTextNode(closer) &&\n closer.getTextType() === \"marker\" &&\n closer.getTextContent() === closingMarkerText(oldMarker, nested)) {\n closer.setTextContent(closingMarkerText(marker, nested));\n }\n}\n// Apply attributes to the given embed node\nfunction $applyEmbedAttributes(node, attributes) {\n for (const key of Object.keys(attributes)) {\n const value = attributes[key];\n // Special handling for char attributes on CharNodes\n if (key === \"char\" && $isCharNode(node) && hasCharAttributes(attributes)) {\n const charAttributes = cleanCharStyle(value);\n $syncCharMarkerGlyphs(node, charAttributes.style);\n // Set charIdState if cid is present\n if (typeof charAttributes.cid === \"string\") {\n const cid = charAttributes.cid;\n $setState(node, charIdState, () => cid);\n }\n // Apply other char attributes to unknownAttributes\n const unknownAttributes = getUnknownAttributes(charAttributes, OT_CHAR_PROPS);\n if (unknownAttributes && Object.keys(unknownAttributes).length > 0) {\n node.setUnknownAttributes({\n ...(node.getUnknownAttributes() ?? {}),\n ...unknownAttributes,\n });\n }\n continue;\n }\n if (typeof value !== \"string\") {\n // Skip non-string attributes (except char which is handled above)\n continue;\n }\n if ($isSomeChapterNode(node) ||\n $isSomeVerseNode(node) ||\n $isMilestoneNode(node) ||\n $isNoteNode(node) ||\n $isUnknownNode(node)) {\n node.setUnknownAttributes({\n ...(node.getUnknownAttributes() ?? {}),\n [key]: value,\n });\n }\n else if ($isBookNode(node) || $isParaNode(node) || $isCharNode(node)) {\n if (key === \"style\" && $isParaNode(node)) {\n $syncParaMarkerGlyph(node, value);\n }\n else if (key === \"style\" && $isCharNode(node)) {\n $syncCharMarkerGlyphs(node, value);\n }\n else if (key === \"code\" && $isBookNode(node)) {\n node.setCode(value);\n }\n else {\n node.setUnknownAttributes({\n ...(node.getUnknownAttributes() ?? {}),\n [key]: value,\n });\n }\n }\n if (key === \"segment\") {\n $setState(node, segmentState, () => value);\n }\n }\n}\n// Helper function to delete items starting at a given flat index from the document\nfunction $delete(targetIndex, otLength, logger) {\n if (otLength <= 0)\n return;\n const root = $getRoot();\n let currentIndex = 0; // Tracks characters traversed so far in the document's text content\n let remainingToDelete = otLength;\n // Inner recursive function to find and delete text\n function $traverseAndDelete(currentNode) {\n if (remainingToDelete <= 0)\n return true;\n if ($isOTTextNode(currentNode)) {\n let textLength = currentNode.getTextContentSize();\n if (targetIndex < currentIndex + textLength &&\n currentIndex < targetIndex + remainingToDelete) {\n const offsetInNode = Math.max(0, targetIndex - currentIndex);\n const deletableLengthInNode = textLength - offsetInNode;\n const lengthToDeleteFromThisNode = Math.min(remainingToDelete, deletableLengthInNode);\n if (lengthToDeleteFromThisNode > 0) {\n currentNode.spliceText(offsetInNode, lengthToDeleteFromThisNode, \"\");\n // Remove the TextNode if it becomes empty\n if (currentNode.getTextContentSize() === 0) {\n currentNode.remove();\n }\n logger?.debug(`Deleted ${lengthToDeleteFromThisNode} length from TextNode ` +\n `(key: ${currentNode.getKey()}) at nodeOffset ${offsetInNode}. ` +\n `Original targetIndex: ${targetIndex}, current currentIndex: ${currentIndex}.`);\n remainingToDelete -= lengthToDeleteFromThisNode;\n // Adjust textLength to account for the text that was deleted\n textLength -= lengthToDeleteFromThisNode;\n }\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(currentNode)) {\n // Check if the deletion should remove this embed\n if (targetIndex <= currentIndex && currentIndex < targetIndex + remainingToDelete) {\n // The deletion spans this embed - remove it\n currentNode.remove();\n logger?.debug(`Deleted embed node (key: ${currentNode.getKey()}) at currentIndex: ${currentIndex}. ` +\n `Original targetIndex: ${targetIndex}, remainingToDelete: ${remainingToDelete}.`);\n remainingToDelete -= 1;\n }\n else {\n // Deletion doesn't affect this embed, just advance past it\n currentIndex += 1;\n }\n }\n else if ($isParaLikeNode(currentNode)) {\n // Process children first, then handle the symbolic close.\n const childrenBefore = currentNode.getChildren().slice(); // Save original children\n // Process children\n const children = currentNode.getChildren();\n for (const child of children) {\n if (remainingToDelete <= 0)\n break;\n if ($traverseAndDelete(child)) {\n if (remainingToDelete <= 0)\n return true;\n }\n }\n // Check if the deletion targets the symbolic close of this block node\n if (targetIndex <= currentIndex &&\n currentIndex < targetIndex + remainingToDelete &&\n $isParaLikeNode(currentNode)) {\n // Deleting the symbolic close of a block node\n remainingToDelete -= 1;\n // Determine if this entire paragraph should be removed\n const currentChildrenLength = currentNode.getChildren().length;\n const hadChildren = childrenBefore.length > 0;\n const deletedAllContent = hadChildren && currentChildrenLength === 0;\n if (deletedAllContent) {\n // This paragraph had content that was entirely deleted, and now we're deleting its symbolic close\n // Remove the entire paragraph\n const parent = currentNode.getParent();\n const siblings = parent?.getChildren() ?? [];\n if (siblings.length > 1) {\n // There are other paragraphs, safe to remove this one\n currentNode.remove();\n logger?.debug(`Removed entire ParaNode that had all its content deleted at currentIndex: ${currentIndex}. ` +\n `Original targetIndex: ${targetIndex}, remainingToDelete: ${remainingToDelete}.`);\n }\n else {\n // This is the only paragraph, replace with ImpliedParaNode instead of removing\n currentNode.replace($createImpliedParaNode(), true);\n logger?.debug(`Replaced last ParaNode with ImpliedParaNode at currentIndex: ${currentIndex}. ` +\n `Original targetIndex: ${targetIndex}, remainingToDelete: ${remainingToDelete}.`);\n }\n }\n else if (remainingToDelete > 0) {\n // We're deleting the symbolic close and continuing to next content\n const nextSibling = currentNode.getNextSibling();\n if (nextSibling && $isSomeParaNode(nextSibling)) {\n // Standard merge logic: merge next paragraph into current one\n let tempCurrentIndex = currentIndex + 1;\n const nextChildren = nextSibling.getChildren();\n for (const nextChild of nextChildren) {\n if (remainingToDelete <= 0)\n break;\n const originalCurrentIndex = currentIndex;\n currentIndex = tempCurrentIndex;\n if ($traverseAndDelete(nextChild)) {\n currentIndex = originalCurrentIndex;\n break;\n }\n if ($isOTTextNode(nextChild)) {\n tempCurrentIndex += nextChild.getTextContentSize();\n }\n else if ($isEmbedNode(nextChild)) {\n tempCurrentIndex += 1;\n }\n currentIndex = originalCurrentIndex;\n }\n // Move remaining content from next paragraph to current paragraph\n const remainingNextChildren = nextSibling.getChildren();\n for (const remainingChild of remainingNextChildren) {\n remainingChild.remove();\n currentNode.append(remainingChild);\n }\n nextSibling.remove();\n logger?.debug(`Merged next paragraph into current one after deleting symbolic close at currentIndex: ${currentIndex}. ` +\n `Original targetIndex: ${targetIndex}, remainingToDelete: ${remainingToDelete}.`);\n }\n else {\n // No next paragraph to merge, replace with ImpliedParaNode\n currentNode.replace($createImpliedParaNode(), true);\n }\n }\n else if ($isParaNode(currentNode)) {\n // Only deleting the symbolic close, replace with ImpliedParaNode\n currentNode.replace($createImpliedParaNode(), true);\n }\n else {\n currentNode.remove();\n }\n }\n currentIndex += 1;\n }\n else if ($isElementNode(currentNode)) {\n // Other ElementNodes that don't contribute to the OT length (like RootNode, CharNode)\n const children = currentNode.getChildren();\n for (const child of children) {\n if (remainingToDelete <= 0)\n break;\n if ($traverseAndDelete(child)) {\n if (remainingToDelete <= 0)\n return true;\n }\n }\n }\n return remainingToDelete <= 0;\n }\n $traverseAndDelete(root);\n if (remainingToDelete > 0) {\n logger?.warn(`Delete operation could not remove all requested characters. Remaining to delete: ${remainingToDelete}. Original targetIndex: ${targetIndex}, OT length: ${otLength}. Final currentIndex: ${currentIndex}`);\n }\n}\n/**\n * Inserts text or a CharNode at a given flat index in the document.\n * If attributes.char is present, a CharNode is created and inserted.\n * Otherwise, rich text is inserted, potentially with formatting attributes.\n * @param targetIndex - The index in the document's flat representation.\n * @param textToInsert - The string to insert.\n * @param attributes - Optional attributes for the insert operation.\n * @param logger - Logger to use, if any.\n * @returns The length to advance the currentIndex in $applyUpdate (1 for CharNode, text.length for\n * text).\n */\nfunction $insertTextAtCurrentIndex(targetIndex, textToInsert, attributes, viewOptions, logger) {\n if (textToInsert === LF) {\n return $handleNewline(targetIndex, attributes, viewOptions, logger);\n }\n else if (textToInsert.endsWith(LF) && !hasParaAttributes(attributes)) {\n // Split the operation: insert text without LF, then handle the LF separately as an implied para\n const textWithoutLF = textToInsert.slice(0, -1);\n let deltaOTLength = 0;\n if (textWithoutLF.length > 0) {\n if (hasCharAttributes(attributes))\n throw new Error(\"Text + LF should not have char attributes\");\n deltaOTLength += $insertRichText(targetIndex, textWithoutLF, attributes, logger);\n }\n deltaOTLength += $handleNewline(targetIndex + deltaOTLength, attributes, viewOptions, logger);\n return deltaOTLength;\n }\n else if (hasCharAttributes(attributes)) {\n return $handleCharText(targetIndex, textToInsert, attributes, viewOptions, logger);\n }\n else {\n return $insertRichText(targetIndex, textToInsert, attributes, logger);\n }\n}\nfunction $handleCharText(targetIndex, textToInsert, attributes, viewOptions, logger) {\n logger?.debug(`Attempting to insert CharNode with text \"${textToInsert}\" and attributes ${JSON.stringify(attributes.char)} at index ${targetIndex}`);\n const textNode = $createTextNode(textToInsert === \"\" ? EMPTY_CHAR_PLACEHOLDER_TEXT : textToInsert);\n // Apply other non-char attributes to the TextNode inside the CharNode if necessary.\n $applyTextAttributes(attributes, textNode);\n // Find parent CharNode at insertion point, if any\n let parentCharNode;\n {\n // Traverse to find the parent node at the insertion point\n const root = $getRoot();\n let currentIndex = 0;\n function findParentCharNode(node) {\n if ($isOTTextNode(node)) {\n const textLength = node.getTextContentSize();\n if (targetIndex >= currentIndex && targetIndex < currentIndex + textLength) {\n const parent = node.getParent();\n if ($isCharNode(parent)) {\n parentCharNode = parent;\n }\n return true;\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(node)) {\n currentIndex += 1;\n }\n else if ($isCharNode(node)) {\n // CharNodes don't contribute to OT length, but may contain text\n const children = node.getChildren();\n for (const child of children) {\n if (findParentCharNode(child))\n return true;\n }\n }\n else if ($isElementNode(node)) {\n const children = node.getChildren();\n for (const child of children) {\n if (findParentCharNode(child))\n return true;\n }\n if ($isParaLikeNode(node)) {\n currentIndex += 1;\n }\n }\n return false;\n }\n findParentCharNode(root);\n }\n // If inserting a nested char array, and parent matches the first char, skip nesting that one\n // If parent doesn't match, clear it so we insert as sibling instead\n let charAttr = attributes.char;\n if (Array.isArray(charAttr)) {\n if (parentCharNode) {\n const first = charAttr[0];\n if (first && $hasSameCharAttributes(first, parentCharNode)) {\n // Only nest the remaining char attributes\n charAttr = charAttr.slice(1);\n // If only one left, treat as single\n if (charAttr.length === 1)\n charAttr = charAttr[0];\n // Keep parentCharNode - we're nesting into it\n }\n else {\n // Parent doesn't match, don't use it\n parentCharNode = undefined;\n }\n }\n }\n else if (parentCharNode) {\n // Single char attribute - check if it matches parent\n if (!$hasSameCharAttributes(charAttr, parentCharNode)) {\n // Parent doesn't match, don't use it\n parentCharNode = undefined;\n }\n }\n const segment = typeof attributes.segment === \"string\" ? attributes.segment : undefined;\n const existingNodes = parentCharNode ? [parentCharNode] : undefined;\n const charNodes = $createNestedChars(charAttr, viewOptions, textNode, segment, existingNodes);\n // If charNodes is empty, it means we merged into existingNodes - no insertion needed\n if (charNodes.length === 0) {\n return textToInsert.length; // Successfully merged into existing CharNode\n }\n const charNode = charNodes.find($isCharNode);\n if (!charNode) {\n logger?.error(`CharNode style is missing for text \"${textToInsert}\". Attributes: ${JSON.stringify(attributes.char)}. Falling back to rich text insertion.`);\n // Fallback to rich text insertion\n return $insertRichText(targetIndex, textToInsert, undefined, logger);\n }\n // Set unknownAttributes for non-char, non-segment attributes\n const unknownAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (key !== \"char\" && key !== \"segment\" && typeof value === \"string\") {\n unknownAttributes[key] = value;\n }\n }\n if (Object.keys(unknownAttributes).length > 0) {\n charNode.setUnknownAttributes(unknownAttributes);\n }\n // Insert all nodes (might include markers) at the target position\n let allInserted = true;\n for (const node of charNodes) {\n if (!$insertNodeAtCharacterOffset(targetIndex, node, logger)) {\n allInserted = false;\n break;\n }\n // Only advance index for non-marker nodes (markers don't contribute to OT length)\n // CharNodes themselves don't contribute, only their text content does\n }\n if (allInserted) {\n return textToInsert.length; // CharNode itself has no OT length, just its text content\n }\n else {\n logger?.error(`Failed to insert CharNode with text \"${textToInsert}\" at index ${targetIndex}. Falling back to rich text.`);\n // Fallback to rich text insertion if CharNode insertion fails\n return $insertRichText(targetIndex, textToInsert, undefined, logger);\n }\n}\n/**\n * Helper to insert rich text, i.e. potentially with formatting or other attributes.\n * This function contains the core logic for text node insertion and splitting.\n * @returns The length of the inserted text.\n */\nfunction $insertRichText(targetIndex, textToInsert, attributes, logger) {\n if (textToInsert.length <= 0) {\n logger?.debug(\"Attempted to insert empty string. No action taken.\");\n return 0;\n }\n const root = $getRoot();\n let currentIndex = 0;\n let insertionPointFound = false;\n function $findAndInsertRecursive(currentNode) {\n if (insertionPointFound)\n return true;\n if ($isOTTextNode(currentNode)) {\n const textLength = currentNode.getTextContentSize();\n // Check if targetIndex is within this TextNode's range\n if (targetIndex >= currentIndex && targetIndex <= currentIndex + textLength) {\n const offsetInNode = targetIndex - currentIndex;\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n if (offsetInNode === 0) {\n currentNode.insertBefore(newTextNode);\n }\n else if (offsetInNode === textLength) {\n // Special case: if this TextNode is inside a CharNode and we're inserting plain text at the end,\n // check if we should insert after the CharNode instead of inside it\n const parent = currentNode.getParent();\n if ($isCharNode(parent) && !hasCharAttributes(attributes)) {\n // Plain text (no char attributes) should not be inserted inside CharNodes\n // Instead, insert after the CharNode at the parent level\n parent.insertAfter(newTextNode);\n }\n else {\n // Normal case: insert after this TextNode\n currentNode.insertAfter(newTextNode);\n }\n }\n else {\n const [, tailNode] = currentNode.splitText(offsetInNode);\n tailNode.insertBefore(newTextNode);\n }\n logger?.debug(`Inserted text \"${textToInsert}\" in/around TextNode ` +\n `(key: ${currentNode.getKey()}) at nodeOffset ${offsetInNode}. Original targetIndex: ${targetIndex}, currentIndex at node start: ${currentIndex}.`);\n insertionPointFound = true;\n return true;\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(currentNode)) {\n // If targetIndex is exactly at currentIndex, means insert *before* this embed node.\n // This function is for rich text; inserting before/after embed nodes usually involves\n // $insertNodeAtCharacterOffset or ensuring a Para wrapper.\n // For now, just advance offset.\n if (targetIndex === currentIndex && !insertionPointFound) {\n // Potentially insert into a new para before this node if context allows,\n // or let caller handle creating appropriate structure.\n // This function's primary goal is inserting into existing text-compatible locations.\n }\n currentIndex += 1;\n }\n else if ($isCharNode(currentNode)) {\n // CharNodes don't contribute to OT length, they're just formatted text containers\n const offsetAtCharNodeStart = currentIndex;\n // Try inserting at the beginning of the CharNode's content\n if (!insertionPointFound && targetIndex === offsetAtCharNodeStart) {\n // This implies inserting as the first child inside the CharNode\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n const firstChild = currentNode.getFirstChild();\n if (firstChild) {\n firstChild.insertBefore(newTextNode);\n }\n else {\n currentNode.append(newTextNode);\n }\n logger?.debug(`Inserted text \"${textToInsert}\" at beginning of CharNode ` +\n `${currentNode.getType()} (key: ${currentNode.getKey()}).`);\n insertionPointFound = true;\n return true;\n }\n // No OT length contribution for CharNodes themselves\n const children = currentNode.getChildren();\n for (const child of children) {\n if ($findAndInsertRecursive(child))\n return true;\n if (insertionPointFound)\n break;\n }\n // Try appending to the CharNode if targetIndex matches after children\n if (!insertionPointFound && targetIndex === currentIndex) {\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n currentNode.append(newTextNode);\n logger?.debug(`Appended text \"${textToInsert}\" to end of CharNode ` +\n `${currentNode.getType()} (key: ${currentNode.getKey()}).`);\n insertionPointFound = true;\n return true;\n }\n }\n else if ($isParaLikeNode(currentNode)) {\n const offsetAtParaStart = currentIndex;\n // Try inserting at the beginning of the block node\n if (!insertionPointFound && targetIndex === offsetAtParaStart) {\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n const firstChild = currentNode.getFirstChild();\n if (firstChild) {\n firstChild.insertBefore(newTextNode);\n }\n else {\n currentNode.append(newTextNode);\n }\n logger?.debug(`Inserted text \"${textToInsert}\" at beginning of container ` +\n `${currentNode.getType()} (key: ${currentNode.getKey()}).`);\n insertionPointFound = true;\n return true;\n }\n const children = currentNode.getChildren();\n for (const child of children) {\n if ($findAndInsertRecursive(child))\n return true;\n if (insertionPointFound)\n break;\n }\n // After children, currentIndex is at the end of the *content* of the ParaNode.\n // Try appending text if targetIndex matches (before para's own closing marker)\n if (!insertionPointFound && targetIndex === currentIndex) {\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n currentNode.append(newTextNode);\n logger?.debug(`Appended text \"${textToInsert}\" to end of container ` +\n `${currentNode.getType()} (key: ${currentNode.getKey()}).`);\n insertionPointFound = true;\n return true;\n }\n // After children and potential append, account for ParaNode's closing marker.\n currentIndex += 1;\n }\n else if ($isElementNode(currentNode)) {\n // Other ElementNodes (e.g. RootNode)\n const children = currentNode.getChildren();\n for (const child of children) {\n if ($findAndInsertRecursive(child))\n return true;\n if (insertionPointFound)\n break;\n }\n }\n return insertionPointFound;\n }\n $findAndInsertRecursive(root);\n if (!insertionPointFound && targetIndex === currentIndex) {\n logger?.debug(`Insertion point matches end of document (targetIndex: ${targetIndex}, final currentIndex: ${currentIndex}). Appending text to new ParaNode.`);\n const newTextNode = $createTextNode(textToInsert);\n $applyTextAttributes(attributes, newTextNode);\n const newParaNode = $createImpliedParaNode().append(newTextNode);\n root.append(newParaNode);\n insertionPointFound = true;\n }\n if (!insertionPointFound) {\n logger?.warn(`$insertRichText: Could not find insertion point for text \"${textToInsert}\" at targetIndex ${targetIndex}. Final currentIndex: ${currentIndex}. Text not inserted.`);\n return 0; // Text not inserted\n }\n return textToInsert.length;\n}\n/**\n * Inserts a pre-constructed LexicalNode at a given character-based flat index in the document.\n * This is a complex operation that needs to correctly find the text-based offset.\n * @param targetIndex - The character offset in the document's flat text representation.\n * @param nodeToInsert - The LexicalNode to insert (e.g., a CharNode).\n * @param logger - Logger to use, if any.\n * @returns `true` if the node was successfully inserted, `false` otherwise.\n */\nfunction $insertNodeAtCharacterOffset(targetIndex, nodeToInsert, logger) {\n const root = $getRoot();\n /** Tracks the current OT position during traversal */\n let currentIndex = 0;\n let wasInserted = false;\n function $traverseAndInsertRecursive(currentNode) {\n if (wasInserted)\n return true;\n // Handle insertion at the beginning of the document or into an empty root.\n if (currentNode === root && targetIndex === 0) {\n const firstChild = root.getFirstChild();\n if (!firstChild) {\n // Root is empty\n if (nodeToInsert.isInline()) {\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ` +\n `${nodeToInsert.getType()} into empty root, wrapped in ImpliedParaNode. ` +\n `targetIndex: ${targetIndex}`);\n root.append($createImpliedParaNode().append(nodeToInsert));\n }\n else {\n // Block node, insert directly into root\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting block node ` +\n `${nodeToInsert.getType()} directly into empty root. targetIndex: ${targetIndex}`);\n root.append(nodeToInsert);\n }\n wasInserted = true;\n return true;\n }\n // If root is not empty, the loop below will handle inserting before the first child.\n }\n if (!$isElementNode(currentNode)) {\n return false; // Should not happen if called with ElementNode initially\n }\n const children = currentNode.getChildren();\n for (const child of children) {\n // Case 1: Insert *before* the current child\n if (targetIndex === currentIndex && !wasInserted) {\n // Check if we're inserting an inline node directly into the root\n if (currentNode === root && nodeToInsert.isInline()) {\n // If the child we're inserting before is a para-like node, insert into it\n if ($isSomeParaNode(child)) {\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ` +\n `${nodeToInsert.getType()} into existing ${child.getType()} at beginning. ` +\n `targetIndex: ${targetIndex}`);\n // Insert at the beginning of the para by appending to the beginning\n const firstChildOfPara = child.getFirstChild();\n if (firstChildOfPara) {\n firstChildOfPara.insertBefore(nodeToInsert);\n }\n else {\n child.append(nodeToInsert);\n }\n }\n else {\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ` +\n `${nodeToInsert.getType()} into root before ${child.getType()}, wrapping in ` +\n `ImpliedParaNode. targetIndex: ${targetIndex}`);\n child.insertBefore($createImpliedParaNode().append(nodeToInsert));\n }\n }\n else {\n child.insertBefore(nodeToInsert);\n logger?.debug(`$insertNodeAtCharacterOffset: Inserted node ${nodeToInsert.getType()} ` +\n `(key: ${nodeToInsert.getKey()}) before child ${child.getType()} ` +\n `(key: ${child.getKey()}) in ${currentNode.getType()} ` +\n `(key: ${currentNode.getKey()}). targetIndex: ${targetIndex}, currentIndex: ${currentIndex}`);\n }\n wasInserted = true;\n return true;\n }\n // Case 2: Process current `child` to advance `currentIndex` or insert within/after it.\n if ($isOTTextNode(child)) {\n const textLength = child.getTextContentSize();\n // Case 2a: Insert *within* this TextNode\n if (!wasInserted && targetIndex > currentIndex && targetIndex < currentIndex + textLength) {\n const splitOffset = targetIndex - currentIndex;\n const [headNode] = child.splitText(splitOffset);\n headNode.insertAfter(nodeToInsert);\n logger?.debug(`$insertNodeAtCharacterOffset: Inserted node ${nodeToInsert.getType()} ` +\n `(key: ${nodeToInsert.getKey()}) by splitting TextNode (key: ${child.getKey()}) ` +\n `at offset ${splitOffset}. targetIndex: ${targetIndex}, currentIndex at node start: ${currentIndex}`);\n wasInserted = true;\n return true;\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(child)) {\n currentIndex += 1;\n }\n else if ($isCharNode(child)) {\n // CharNodes don't contribute to OT length, they're just formatted text containers\n // No OT length contribution for the CharNode itself\n if ($traverseAndInsertRecursive(child))\n return true;\n // currentIndex is now after child's content and its own recursive calls\n }\n else if ($isParaLikeNode(child)) {\n const paraLikeChild = child;\n // currentIndex is currently at the START of paraLikeChild's content area (or its embed\n // point if empty)\n if ($traverseAndInsertRecursive(paraLikeChild))\n return true;\n // If not inserted inside, `currentIndex` is now at the end of `paraLikeChild`'s content.\n const otIndexForParaChildClosingMarker = currentIndex;\n // Check for replacement: if inserting a block node at the closing marker of an\n // ImpliedParaNode\n if ($isImpliedParaNode(paraLikeChild) &&\n $isParaLikeNode(nodeToInsert) &&\n // Target is at the ImpliedPara's implicit newline\n targetIndex === otIndexForParaChildClosingMarker &&\n !wasInserted // Ensure we haven't already inserted elsewhere\n ) {\n logger?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode ` +\n `(key: ${paraLikeChild.getKey()}) with block node '${nodeToInsert.getType()}' ` +\n `(key: ${nodeToInsert.getKey()}) at OT index ${targetIndex}.`);\n child.replace(nodeToInsert, true);\n // The replacement block node also has a closing marker.\n // currentIndex was at otIndexForParaChildClosingMarker (end of content).\n // Now, advance by 1 for the new block node's closing marker.\n currentIndex = otIndexForParaChildClosingMarker + 1;\n wasInserted = true;\n return true;\n }\n // If not replaced, add 1 for the original paraLikeChild's closing marker.\n currentIndex += 1;\n }\n else if ($isElementNode(child)) {\n // Other ElementNode children (e.g. custom, or nested root-like)\n if ($traverseAndInsertRecursive(child))\n return true; // Recurse\n }\n // Else: other node types (LineBreakNode, DecoratorNode) - typically 0 OT length or handled by\n // Lexical.\n if (wasInserted)\n return true;\n } // End for loop over children\n // After iterating all children of `currentNode`, `currentIndex` reflects the OT position\n // *after* `currentNode`'s content and its children's closing markers.\n // This means `targetIndex === currentIndex` implies appending to `currentNode` or inserting\n // after it if `currentNode` is not root. For out-of-bounds cases where\n // `targetIndex > currentIndex`, we also handle appending to root.\n if ($isElementNode(currentNode) &&\n !wasInserted &&\n (targetIndex === currentIndex || (currentNode === root && targetIndex > currentIndex))) {\n if (currentNode === root) {\n // Appending to the root. currentIndex is total document OT length (or targetIndex is beyond\n // document end).\n if (nodeToInsert.isInline()) {\n logger?.debug(`$insertNodeAtCharacterOffset: Appending inline node ` +\n `${nodeToInsert.getType()} to root. Wrapping in new ImpliedParaNode. targetIndex: ${targetIndex}, current document OT length: ${currentIndex}.`);\n root.append($createImpliedParaNode().append(nodeToInsert));\n }\n else {\n // nodeToInsert is block\n logger?.debug(`$insertNodeAtCharacterOffset: Appending block node ${nodeToInsert.getType()} to ` +\n `root. targetIndex: ${targetIndex}, current document OT length: ${currentIndex}.`);\n root.append(nodeToInsert);\n }\n wasInserted = true;\n return true;\n }\n else if (\n // Appending to an existing container (ParaNode, ImpliedParaNode)\n // currentNode here is the container itself. currentIndex is at the point of currentNode's\n // closing marker. targetIndex === currentIndex means we are inserting at the conceptual end\n // of this container.\n $isSomeParaNode(currentNode)) {\n // If trying to insert a ParaNode at the closing marker of an ImpliedParaNode (this\n // container)\n if ($isImpliedParaNode(currentNode) &&\n $isParaNode(nodeToInsert) &&\n targetIndex === currentIndex) {\n logger?.debug(`$insertNodeAtCharacterOffset: Replacing ImpliedParaNode container ` +\n `(key: ${currentNode.getKey()}) with ParaNode ${nodeToInsert.getType()} ` +\n `(key: ${nodeToInsert.getKey()}) via append logic. targetIndex: ${targetIndex}`);\n currentNode.replace(nodeToInsert, true);\n // currentIndex remains correct relative to the start of this operation for the calling\n // $applyUpdate\n wasInserted = true;\n return true;\n }\n else if (nodeToInsert.isInline() || !$isSomeParaNode(nodeToInsert)) {\n // Append inline content, or non-para block content, into the container\n logger?.debug(`$insertNodeAtCharacterOffset: Appending node ${nodeToInsert.getType()} to existing ` +\n `container ${currentNode.getType()} (key: ${currentNode.getKey()}). targetIndex: ${targetIndex}, container end OT index: ${currentIndex}.`);\n currentNode.append(nodeToInsert);\n wasInserted = true;\n return true;\n }\n else {\n // Block node trying to append to a non-root container, insert *after* the container\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${nodeToInsert.getType()} after ` +\n `container ${currentNode.getType()} (key: ${currentNode.getKey()}). targetIndex: ${targetIndex}, container end OT index: ${currentIndex}.`);\n currentNode.insertAfter(nodeToInsert);\n wasInserted = true;\n return true;\n }\n }\n else {\n // Generic element, try to append, or insert after if block\n // Special case: When at the end of a CharNode, insert after it as a sibling\n // (nested CharNodes are handled elsewhere via $createNestedChars merging logic)\n if ($isCharNode(currentNode)) {\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting node ${nodeToInsert.getType()} after ` +\n `CharNode (key: ${currentNode.getKey()}). targetIndex: ${targetIndex}, element end OT index: ${currentIndex}.`);\n currentNode.insertAfter(nodeToInsert);\n }\n else if (nodeToInsert.isInline() || !$isSomeParaNode(nodeToInsert)) {\n logger?.debug(`$insertNodeAtCharacterOffset: Appending node ${nodeToInsert.getType()} to generic ` +\n `element ${currentNode.getType()} (key: ${currentNode.getKey()}). targetIndex: ${targetIndex}, element end OT index: ${currentIndex}.`);\n currentNode.append(nodeToInsert);\n }\n else {\n logger?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${nodeToInsert.getType()} after ` +\n `generic element ${currentNode.getType()} (key: ${currentNode.getKey()}). ` +\n `targetIndex: ${targetIndex}, element end OT index: ${currentIndex}.`);\n currentNode.insertAfter(nodeToInsert);\n }\n wasInserted = true;\n return true;\n }\n }\n return wasInserted;\n }\n $traverseAndInsertRecursive(root);\n if (!wasInserted) {\n logger?.warn(\"$insertNodeAtCharacterOffset: Could not find insertion point for node \" +\n `${nodeToInsert.getType()} (key: ${nodeToInsert.getKey()}) at targetIndex ${targetIndex}. Final currentIndex: ${currentIndex}. Node not inserted.`);\n }\n return wasInserted;\n}\nfunction $insertEmbedAtCurrentIndex(targetIndex, op, viewOptions, nodeOptions, logger) {\n let newNodeToInsert;\n // Determine the LexicalNode to create based on the embedObject structure\n if (isInsertEmbedOpOfType(\"chapter\", op)) {\n newNodeToInsert = $createChapter(op.insert.chapter, viewOptions);\n }\n else if (isInsertEmbedOpOfType(\"verse\", op)) {\n newNodeToInsert = $createVerse(op.insert.verse, viewOptions);\n }\n else if (isInsertEmbedOpOfType(\"ms\", op)) {\n newNodeToInsert = $createMilestone(op.insert.ms);\n }\n else if (isInsertEmbedOpOfType(\"note\", op)) {\n newNodeToInsert = $createNote(op, viewOptions, nodeOptions, logger);\n }\n else if (isInsertEmbedOpOfType(\"unknown\", op)) {\n newNodeToInsert = $createUnknown(op, viewOptions, nodeOptions, logger);\n }\n else if (isInsertEmbedOpOfType(\"unmatched\", op)) {\n newNodeToInsert = $createImmutableUnmatched(op.insert.unmatched, viewOptions);\n }\n // While it would be technically and structurally possible to add a ParaNode here, it's not the\n // way Quill (and therefore flat rich-text docs) handles paragraphs which is always by inserting a\n // newline (LF) character with a `para` attribute.\n if (!newNodeToInsert) {\n logger?.error(`$insertEmbedAtCurrentIndex: Cannot create LexicalNode for embed object: ${JSON.stringify(op.insert)}`);\n return false;\n }\n return $insertNodeAtCharacterOffset(targetIndex, newNodeToInsert, logger);\n}\n/**\n * Handles inserting a newline (LF) character.\n * This can replace an ImpliedParaNode with a ParaNode or BookNode, or split a regular ParaNode\n * if the para attributes differ from the containing paragraph.\n * When there are no attributes it splits a regular ParaNode into an ImpliedParaNode for the first\n * part and keeps the second part as a ParaNode.\n * @param targetIndex - The index in the document's flat representation.\n * @param attributes - The attributes to use for creating the ParaNode or BookNode.\n * @param viewOptions - View options of the editor (determines the new paragraph's marker prefix).\n * @param logger - Logger to use, if any.\n * @returns Always returns 1 (the LF character's OT length).\n */\nfunction $handleNewline(targetIndex, attributes, viewOptions, logger) {\n let _newBlockNode;\n if (hasParaAttributes(attributes)) {\n _newBlockNode = $createPara(attributes.para, viewOptions);\n }\n else if (hasBookAttributes(attributes)) {\n const attributesWithBook = attributes;\n _newBlockNode = $createBook(attributesWithBook.book);\n }\n _newBlockNode ??= $createImpliedParaNode();\n const newBlockNode = _newBlockNode;\n const isNewParaNode = $isParaNode(newBlockNode);\n const isNewImpliedParaNode = $isImpliedParaNode(newBlockNode);\n let currentIndex = 0;\n let foundTargetBlock = false;\n function $traverseAndHandleNewline(currentNode) {\n if (foundTargetBlock)\n return true;\n if ($isOTTextNode(currentNode)) {\n const textLength = currentNode.getTextContentSize();\n // Check if targetIndex is within this text node\n if (targetIndex >= currentIndex && targetIndex <= currentIndex + textLength) {\n // Split is happening within a text node - need to check if we're in a ParaNode\n const parentPara = currentNode.getParent();\n if ($isParaNode(parentPara) && (isNewParaNode || isNewImpliedParaNode)) {\n // LF with attributes should ALWAYS split a regular ParaNode\n logger?.debug(`Splitting ParaNode (marker: ${parentPara.getMarker()}) with LF attributes at ` +\n `targetIndex ${targetIndex}`);\n // Split the text node at the target position\n const splitOffset = targetIndex - currentIndex;\n const [headNode] = splitOffset > 0 ? currentNode.splitText(splitOffset) : [undefined];\n // Move all content before the split to the new ParaNode. Anchor on the first MOVED\n // node, not the block's first child: the new paragraph may already carry its marker\n // prefix, and content must land after those glyphs, not before them.\n let firstMovedNode;\n let prevSibling = headNode?.getPreviousSibling();\n while (prevSibling) {\n const siblingToMove = prevSibling;\n prevSibling = prevSibling.getPreviousSibling();\n if (firstMovedNode)\n firstMovedNode.insertBefore(siblingToMove);\n else\n newBlockNode.append(siblingToMove);\n firstMovedNode = siblingToMove;\n }\n if (headNode)\n newBlockNode.append(headNode);\n // Insert the new paragraph before the existing one\n parentPara.insertBefore(newBlockNode);\n foundTargetBlock = true;\n return true;\n }\n }\n currentIndex += textLength;\n }\n else if ($isEmbedNode(currentNode)) {\n currentIndex += 1;\n }\n else if ($isParaLikeNode(currentNode)) {\n // First, process children to find current position\n const children = currentNode.getChildren();\n for (const child of children) {\n if ($traverseAndHandleNewline(child))\n return true;\n if (foundTargetBlock)\n break;\n }\n // currentIndex is now at the end of this para's content\n // Check if targetIndex matches the para's closing marker position\n if (targetIndex === currentIndex) {\n if ($isImpliedParaNode(currentNode) && newBlockNode) {\n logger?.debug(`Replacing ImpliedParaNode (key: ${currentNode.getKey()}) with ParaNode at ` +\n `targetIndex ${targetIndex}`);\n // Replace the ImpliedParaNode with the new block node\n currentNode.replace(newBlockNode, true);\n foundTargetBlock = true;\n return true;\n }\n else if ($isParaNode(currentNode) && newBlockNode) {\n const paraNode = currentNode;\n // LF with attributes should ALWAYS create a new block node after regular ParaNode\n logger?.debug(\"Creating new block node with LF attributes after existing ParaNode \" +\n `(marker: ${paraNode.getMarker()}) at targetIndex ${targetIndex}`);\n // Insert the new block node with LF attributes after the current one\n paraNode.insertAfter(newBlockNode);\n foundTargetBlock = true;\n return true;\n }\n }\n // Advance by 1 for the para's closing marker\n currentIndex += 1;\n // Check if targetIndex matches the position after this para (for inserting after the para)\n if (targetIndex === currentIndex) {\n if ($isParaNode(currentNode) && newBlockNode) {\n // LF with attributes should create a new block node after this ParaNode\n logger?.debug(`Creating new block node after existing ParaNode (marker: ${currentNode.getMarker()}) ` +\n `at targetIndex ${targetIndex}`);\n // Insert the new block node with LF attributes after the current one\n currentNode.insertAfter(newBlockNode);\n foundTargetBlock = true;\n return true;\n }\n }\n }\n else if ($isElementNode(currentNode)) {\n // Other ElementNodes that don't contribute to the OT length (like RootNode, CharNode)\n const children = currentNode.getChildren();\n for (const child of children) {\n if ($traverseAndHandleNewline(child))\n return true;\n if (foundTargetBlock)\n break;\n }\n }\n return foundTargetBlock;\n }\n $traverseAndHandleNewline($getRoot());\n if (!foundTargetBlock) {\n logger?.warn(`Could not find location to handle newline with para attributes at targetIndex ${targetIndex}. Final currentIndex: ${currentIndex}.`);\n }\n return 1; // LF always contributes 1 to the OT index\n}\nfunction $createBook(bookAttributes) {\n const { style, code } = bookAttributes;\n if (!style || style !== BOOK_MARKER || !code || !BookNode.isValidBookCode(code))\n return;\n const unknownAttributes = getUnknownAttributes(bookAttributes, OT_BOOK_PROPS);\n return $createBookNode(code, unknownAttributes);\n}\n/**\n * Creates a delta-materialized paragraph with the marker-mode-appropriate prefix the USJ\n * adaptor builds at load time (`createPara` in the platform adaptor): editable marker mode gets\n * the `[MarkerNode glyph, exact-NBSP token separator]` pair — without it the paragraph renders\n * bare AND the marker-edit engine's deletion transform reads the missing prefix as \"marker\n * deleted\" and merges the paragraph into its predecessor; visible marker mode and gutter\n * para-marker rendering get the immutable typed-text `\\marker + NBSP` prefix; hidden markers\n * get no prefix.\n */\nfunction $createPara(paraAttributes, viewOptions) {\n const { style } = paraAttributes;\n if (!style)\n return;\n const unknownAttributes = getUnknownAttributes(paraAttributes, OT_PARA_PROPS);\n const para = $createParaNode(style, unknownAttributes);\n // Gated on the shared prefix predicate, not raw markerMode alone: a surface that suppresses\n // paragraph prefixes (showParaMarkerPrefixes: false — the footnote editor's scaffolding\n // paragraph) must not grow a `\\p ` glyph from a remote op either.\n if (!showParaMarkerPrefix(viewOptions))\n return para;\n if (viewOptions.markerMode === \"editable\") {\n para.append($createMarkerNode(style), $createMarkerTrailingSeparator());\n }\n else if (viewOptions.markerMode === \"visible\" || viewOptions.hasGutterParaMarkers) {\n // A gutter glyph is a non-selectable aid, so it must carry the flag the caret guard keys on —\n // a paragraph arriving from a peer has to be as unclickable as one the load adaptor built.\n // markerMode \"visible\" renders the same node kind INLINE, where the flag must stay off.\n const glyph = openingMarkerText(style) + NBSP;\n para.append(viewOptions.hasGutterParaMarkers\n ? $createGutterMarkerNode(glyph)\n : $createImmutableTypedTextNode(\"marker\", glyph));\n }\n return para;\n}\nfunction $createChapter(chapterData, viewOptions) {\n if (!chapterData)\n return;\n const { number, sid, altnumber, pubnumber } = chapterData;\n if (!number)\n return;\n const unknownAttributes = getUnknownAttributes(chapterData, OT_CHAPTER_PROPS);\n let newNodeToInsert;\n if (viewOptions.markerMode === \"editable\") {\n newNodeToInsert = $createChapterNode(number, sid, altnumber, pubnumber, unknownAttributes);\n }\n else {\n const showMarker = viewOptions.markerMode === \"visible\";\n newNodeToInsert = $createImmutableChapterNode(number, showMarker, sid, altnumber, pubnumber, unknownAttributes);\n }\n return newNodeToInsert;\n}\nfunction $createVerse(verseData, viewOptions) {\n if (!verseData)\n return;\n const { style, number, sid, altnumber, pubnumber } = verseData;\n if (!number)\n return;\n const unknownAttributes = getUnknownAttributes(verseData, OT_VERSE_PROPS);\n let newNodeToInsert;\n if (viewOptions.markerMode === \"editable\") {\n if (!style)\n return;\n const text = getVisibleOpenMarkerText(style, number);\n newNodeToInsert = $createVerseNode(number, text, sid, altnumber, pubnumber, unknownAttributes);\n }\n else {\n const showMarker = viewOptions.markerMode === \"visible\";\n newNodeToInsert = $createImmutableVerseNode(number, showMarker, sid, altnumber, pubnumber, unknownAttributes);\n }\n return newNodeToInsert;\n}\nfunction $createMilestone(msData) {\n if (!msData)\n return;\n const { style, sid, eid, attributeOrder } = msData;\n if (!style)\n return;\n const unknownAttributes = getUnknownAttributes(msData, OT_MILESTONE_PROPS);\n return $createMilestoneNode(style, sid, eid, unknownAttributes, attributeOrder);\n}\nfunction $createNote(op, viewOptions, nodeOptions, logger) {\n const noteEmbed = op.insert;\n if (!noteEmbed.note)\n return;\n const { style, caller, category, contents } = noteEmbed.note;\n if (!style || caller == null)\n return;\n if (caller === \"\")\n logger?.warn(\"Note has empty caller. Only use for note editing.\");\n const unknownAttributes = getUnknownAttributes(noteEmbed.note, OT_NOTE_PROPS);\n // An unclosed note (closed=\"false\") materializes without a closer glyph and renders\n // expanded inline, exactly as the USJ adaptor builds it.\n const closed = typeof unknownAttributes?.closed === \"string\" ? unknownAttributes.closed : undefined;\n const segment = op.attributes?.segment;\n let nodeSegment;\n if (segment && typeof segment === \"string\")\n nodeSegment = segment;\n const contentNodes = [];\n for (const childOp of contents?.ops ?? []) {\n if (typeof childOp.insert !== \"string\")\n continue;\n if (hasCharAttributes(childOp.attributes)) {\n // Note contents ops carry CONTENT only; in editable marker mode a char span's FIRST content\n // text carries a structural NBSP separator after the opening glyph (mirror the USJ\n // adaptor's `createChar`). `$createNestedChars` owns the prepend because only it knows\n // whether this op starts a fresh span (separator) or merges into the preceding span's tail\n // (mid-span content — an NBSP there is fabricated `~` in the file). Empty content stays\n // empty so it inserts the empty-char placeholder instead.\n const charNodes = $createNestedChars(childOp.attributes.char, viewOptions, $createTextNode(childOp.insert), undefined, mergeableNodesForContentOp(childOp.attributes.char, contentNodes), false, viewOptions.markerMode === \"editable\");\n contentNodes.push(...charNodes);\n }\n else {\n contentNodes.push($createTextNode(childOp.insert));\n }\n }\n const note = $createWholeNote(style, caller, contentNodes, viewOptions, nodeOptions, nodeSegment, closed)\n .setCategory(category)\n .setUnknownAttributes(unknownAttributes);\n return note;\n}\nfunction $createUnknown(op, viewOptions, nodeOptions, logger) {\n const unknownData = op.insert.unknown;\n if (!unknownData)\n return;\n const { tag, marker, contents } = unknownData;\n if (!tag)\n return;\n const unknownAttributes = getUnknownAttributes(unknownData, OT_UNKNOWN_PROPS);\n const unknownNode = $createUnknownNode(tag, marker, unknownAttributes);\n const childOps = contents?.ops ?? [];\n if (childOps.length > 0) {\n const childNodes = $createInlineNodesFromOps(childOps, viewOptions, nodeOptions, logger);\n childNodes.forEach((child) => unknownNode.append(child));\n }\n const segment = op.attributes?.segment;\n if (typeof segment === \"string\")\n $setState(unknownNode, segmentState, () => segment);\n return unknownNode;\n}\nfunction $createInlineNodesFromOps(ops, viewOptions, nodeOptions, logger) {\n const nodes = [];\n for (const childOp of ops) {\n if (typeof childOp.insert === \"string\") {\n if (hasCharAttributes(childOp.attributes)) {\n const textNode = $createTextNode(childOp.insert);\n const charNodes = $createNestedChars(childOp.attributes.char, viewOptions, textNode, undefined, mergeableNodesForContentOp(childOp.attributes.char, nodes));\n nodes.push(...charNodes);\n }\n else {\n nodes.push($createTextNode(childOp.insert));\n }\n continue;\n }\n if (!childOp.insert || typeof childOp.insert !== \"object\")\n continue;\n if (isInsertEmbedOpOfType(\"unknown\", childOp)) {\n const nestedUnknown = $createUnknown(childOp, viewOptions, nodeOptions, logger);\n if (nestedUnknown)\n nodes.push(nestedUnknown);\n continue;\n }\n if (isInsertEmbedOpOfType(\"note\", childOp)) {\n const nestedNote = $createNote(childOp, viewOptions, nodeOptions, logger);\n if (nestedNote)\n nodes.push(nestedNote);\n continue;\n }\n logger?.warn(`$createInlineNodesFromOps: Unsupported embed inside unknown contents: ${JSON.stringify(childOp.insert)}`);\n }\n return nodes;\n}\nfunction $createImmutableUnmatched(unmatchedData, viewOptions) {\n if (!unmatchedData)\n return;\n const { marker } = unmatchedData;\n if (!marker)\n return;\n const node = $createImmutableUnmatchedNode(marker);\n // Shape-twin with the forward adaptor's `createUnmatched`: editable marker mode edits the\n // flagged bytes in place (the marker-edit engine settles them), so the node is ordinary\n // \"normal\" text there; every other mode keeps the constructor's atomic \"token\".\n if (viewOptions.markerMode === \"editable\")\n node.setMode(\"normal\");\n return node;\n}\n/**\n * The already-materialized sibling nodes a CONTENT op (a note's or unknown embed's contents)\n * may merge into when it continues the previous op's char span — or `undefined` when the op\n * must start its own span. `\\fp` (footnote-paragraph) spans never merge by style alone: `\\fp`\n * has no closer, so consecutive attribute-identical `\\fp` ops are consecutive footnote\n * PARAGRAPHS, not one split span — merging them collapses two paragraphs into one in the\n * serialized USJ (the same reason `CharNodePlugin` exempts `\\fp` from combining adjacent\n * spans). A `cid` restores merging: ops naming the same char id ARE the same span. Nested-char\n * arrays keep merging, since their outer item continues the enclosing span around a nested\n * child. Positioned edits (retain/insert-at-index) are untouched — there the merge target is\n * the span containing the edit position, not a preceding sibling op's span.\n */\nfunction mergeableNodesForContentOp(charAttr, materializedNodes) {\n if (!Array.isArray(charAttr) && charAttr.style === \"fp\" && !charAttr.cid)\n return undefined;\n return materializedNodes;\n}\n// Helper to create nested CharNodes from OTCharAttribute (array or single)\n// Returns an array of nodes: [opening marker?, CharNode, closing marker?]\n// If existingNodes is provided, will merge with the last CharNode if style/cid match\n/**\n * Defensively normalize an OT char style to a CLEAN marker. Nesting is conveyed by the char\n * ARRAY's position (outermost-first), never by a `+` in the style — the `+` belongs only to the\n * rendered glyph text. No production producer emits a `+`-prefixed style ($buildCharItem sends\n * the CharNode's clean marker verbatim), so this only guards against hand-authored or legacy\n * deltas polluting node markers (and, from there, the saved USJ).\n */\nfunction cleanCharStyle(item) {\n return item.style.startsWith(\"+\") ? { ...item, style: item.style.slice(1) } : item;\n}\nfunction $createNestedChars(charAttr, viewOptions, innerNode, segment, existingNodes, \n// True when the OUTERMOST span created here nests inside an already-open parent char (the\n// merge recursion below appends into an existing outer span), so its own glyphs need the `+`.\n// Every span deeper than the outermost is nested by construction and always gets the `+`.\nnestedInParent = false, \n// True when a text innerNode landing at the START of a NEWLY created span should take the\n// editable-mode structural NBSP separator after that span's opening glyph (mirroring the USJ\n// adaptor's `createChar`). Decided HERE, per branch, because only this function knows whether\n// the text starts a new span or appends into an existing span's tail — an NBSP prepended to a\n// tail-append is fabricated content (`~` in the file), not a separator.\naddEditableSeparator = false) {\n if ($isTextNode(innerNode) && innerNode.getTextContentSize() === 0) {\n innerNode.setTextContent(EMPTY_CHAR_PLACEHOLDER_TEXT);\n }\n // Prepend the structural separator to a text innerNode about to become a fresh span's first\n // content (never to the empty-char placeholder, which stands alone).\n const $prependEditableSeparator = () => {\n if (addEditableSeparator &&\n $isTextNode(innerNode) &&\n innerNode.getTextContent() !== EMPTY_CHAR_PLACEHOLDER_TEXT)\n innerNode.setTextContent(NBSP + innerNode.getTextContent());\n };\n if (Array.isArray(charAttr)) {\n if (charAttr.length === 0)\n throw new Error(\"Empty charAttr array\");\n const cleanAttrs = charAttr.map(cleanCharStyle);\n // Check if we can merge with existing CharNode\n const outerAttr = cleanAttrs[0];\n const lastNode = existingNodes?.[existingNodes.length - 1];\n if ($isCharNode(lastNode) && $hasSameCharAttributes(outerAttr, lastNode)) {\n // Merge into existing CharNode by creating inner nodes only. The inner spans nest inside\n // that existing outer char, so their outermost gets the `+` too (nestedInParent = true).\n if (cleanAttrs.length > 1) {\n // The inner spans are freshly created, so their first text still takes the separator —\n // the recursion's own creation branch prepends it.\n const innerCharNodes = $createNestedChars(cleanAttrs.slice(1), viewOptions, innerNode, undefined, undefined, true, addEditableSeparator);\n innerCharNodes.forEach((node) => lastNode.append(node));\n }\n else {\n // Tail-append into the existing span: mid-span content, NO separator.\n if (innerNode)\n lastNode.append(innerNode);\n }\n return []; // Return empty array since we merged into existing node\n }\n $prependEditableSeparator();\n // Build nested CharNodes from innermost to outermost using reduceRight\n // At each level, we add markers as children if needed\n const outermostCharNode = cleanAttrs.reduceRight((child, attr, idx) => {\n const charNode = $createCharNode(attr.style, getUnknownAttributes(attr, OT_CHAR_PROPS));\n if (typeof attr.cid === \"string\")\n $setState(charNode, charIdState, () => attr.cid);\n if (segment && idx === cleanAttrs.length - 1)\n $setState(charNode, segmentState, () => segment);\n // If there's a child, append it (with markers if it's a CharNode)\n if (child) {\n // If the child is a CharNode, it needs markers around it. The child nests inside this\n // span, so its glyphs carry the `+`.\n if ($isCharNode(child)) {\n // The child was created from attr at idx+1, so get its marker\n const childMarker = child.getMarker();\n const childMarkers = [];\n $addOpeningMarker(childMarker, childMarkers, viewOptions, true);\n childMarkers.forEach((marker) => charNode.append(marker));\n charNode.append(child);\n const closingMarkers = [];\n $addCharNodeClosingMarker(child, closingMarkers, viewOptions, true);\n closingMarkers.forEach((marker) => charNode.append(marker));\n }\n else {\n // Just append the child (it's the innermost text node)\n charNode.append(child);\n }\n }\n return charNode;\n }, innerNode);\n // Add markers inside the outermost CharNode (as children). The outermost gets the `+` only\n // when it in turn nests inside an existing parent char (the merge case above).\n $addOpeningMarker(outerAttr.style, outermostCharNode, viewOptions, nestedInParent);\n $addCharNodeClosingMarker(outermostCharNode, outermostCharNode, viewOptions, nestedInParent);\n return [outermostCharNode];\n }\n else {\n const cleanAttr = cleanCharStyle(charAttr);\n // Single char attribute\n // Check if we can merge with existing CharNode\n const lastNode = existingNodes?.[existingNodes.length - 1];\n if ($isCharNode(lastNode) && $hasSameCharAttributes(cleanAttr, lastNode)) {\n // Tail-append into the existing span: mid-span content, NO separator.\n if (innerNode)\n lastNode.append(innerNode);\n return []; // Return empty array since we merged into existing node\n }\n $prependEditableSeparator();\n const charNode = $createCharNode(cleanAttr.style, getUnknownAttributes(cleanAttr, OT_CHAR_PROPS));\n if (typeof cleanAttr.cid === \"string\")\n $setState(charNode, charIdState, () => cleanAttr.cid);\n if (segment)\n $setState(charNode, segmentState, () => segment);\n if (innerNode)\n charNode.append(innerNode);\n // Add markers inside the CharNode (as children)\n $addOpeningMarker(cleanAttr.style, charNode, viewOptions, nestedInParent);\n $addCharNodeClosingMarker(charNode, charNode, viewOptions, nestedInParent);\n return [charNode];\n }\n}\n/**\n * Add the closing glyph for a delta-materialized char span — or skip it when the span is not\n * explicitly closed. Mirrors `createChar` in the platform USJ adaptor: closer display keys on the\n * span's ACTUAL closed state, never on the marker family. A span carrying `closed=\"false\"` renders\n * WITHOUT a closing glyph (the glyph structure must agree with the node's state) — the delta\n * carries that flag for every genuinely-unclosed span, including footnote/cross-ref content chars\n * (`$buildCharItem` copies `unknownAttributes` into the char op, and `getUnknownAttributes(…,\n * OT_CHAR_PROPS)` reads it back here) — while an explicitly-closed `\\xt` (no `closed=\"false\"`)\n * keeps its closing glyph.\n */\nfunction $addCharNodeClosingMarker(charNode, target, viewOptions, nested = false) {\n const isUnclosed = charNode.getUnknownAttributes()?.closed === \"false\";\n if (isUnclosed)\n return;\n $addClosingMarker(charNode.getMarker(), target, viewOptions, false, nested);\n}\nfunction $addOpeningMarker(marker, target, viewOptions, \n// A span nested inside another char span renders its glyph with the `+` prefix (`\\+w`) — the\n// delta conveys nesting by char-array position, and the glyph must show it (see\n// nestedGlyphs.utils.ts in `shared` for the representation rules).\nnested = false) {\n let markerNode;\n if (viewOptions?.markerMode === \"editable\") {\n markerNode = $createMarkerNode(marker, \"opening\", nested);\n }\n else if (viewOptions?.markerMode === \"visible\") {\n markerNode = $createImmutableTypedTextNode(\"marker\", openingMarkerText(marker, nested));\n }\n if (markerNode) {\n if (Array.isArray(target)) {\n target.push(markerNode);\n }\n else {\n // Prepend to CharNode children\n const firstChild = target.getFirstChild();\n if (firstChild) {\n firstChild.insertBefore(markerNode);\n }\n else {\n target.append(markerNode);\n }\n }\n }\n}\nfunction $addClosingMarker(marker, target, viewOptions, isSelfClosing = false, nested = false) {\n let markerNode;\n if (viewOptions?.markerMode === \"editable\") {\n if (isSelfClosing)\n markerNode = $createMarkerNode(\"\", \"selfClosing\");\n else\n markerNode = $createMarkerNode(marker, \"closing\", nested);\n }\n else if (viewOptions?.markerMode === \"visible\") {\n markerNode = $createImmutableTypedTextNode(\"marker\", isSelfClosing ? closingMarkerText(\"\") : closingMarkerText(marker, nested));\n }\n if (markerNode) {\n if (Array.isArray(target)) {\n target.push(markerNode);\n }\n else {\n // Append to CharNode children\n target.append(markerNode);\n }\n }\n}\n/** Type guard for Book attributes. */\nfunction hasBookAttributes(attributes) {\n return (!!attributes &&\n !!attributes.book &&\n typeof attributes.book === \"object\" &&\n attributes.book !== null &&\n \"style\" in attributes.book &&\n typeof attributes.book.style === \"string\" &&\n \"code\" in attributes.book &&\n typeof attributes.book.code === \"string\");\n}\n/** Type guard for Para attributes. */\nfunction hasParaAttributes(attributes) {\n return (!!attributes &&\n !!attributes.para &&\n typeof attributes.para === \"object\" &&\n attributes.para !== null &&\n \"style\" in attributes.para &&\n typeof attributes.para.style === \"string\");\n}\n/** Type guard for Char attributes. */\nfunction hasCharAttributes(attributes) {\n return (!!attributes &&\n !!attributes.char &&\n typeof attributes.char === \"object\" &&\n attributes.char !== null &&\n ((!Array.isArray(attributes.char) &&\n \"style\" in attributes.char &&\n typeof attributes.char.style === \"string\") ||\n (Array.isArray(attributes.char) &&\n attributes.char.length > 0 &&\n \"style\" in attributes.char[0] &&\n typeof attributes.char[0].style === \"string\")));\n}\nfunction isEmptyObject(obj) {\n return (typeof obj === \"object\" && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 0);\n}\nfunction $applyTextAttributes(attributes, textNode) {\n if (!attributes)\n return;\n for (const key of Object.keys(attributes)) {\n // Handle segment attribute\n if (key === \"segment\" && typeof attributes[key] === \"string\") {\n const segment = attributes[key];\n $setState(textNode, segmentState, () => segment);\n continue;\n }\n // TODO: Text format attributes probably shouldn't be allowed but are helpful at the moment for\n // testing.\n if (isTextFormatType(key)) {\n const shouldSet = !!attributes[key];\n const formatKey = key;\n const isAlreadySet = textNode.hasFormat(formatKey);\n if ((shouldSet && !isAlreadySet) || (!shouldSet && isAlreadySet)) {\n textNode.toggleFormat(formatKey);\n }\n }\n }\n}\nconst TEXT_FORMAT_TYPES = [\n \"bold\",\n \"underline\",\n \"strikethrough\",\n \"italic\",\n \"highlight\",\n \"code\",\n \"subscript\",\n \"superscript\",\n \"lowercase\",\n \"uppercase\",\n \"capitalize\",\n];\nfunction isTextFormatType(key) {\n // This cast is safe because TEXT_FORMAT_TYPES is readonly TextFormatType[]\n return TEXT_FORMAT_TYPES.includes(key);\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $getNearestNodeFromDOMNode, $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, CLICK_COMMAND, COMMAND_PRIORITY_EDITOR, isDOMNode, } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { $isGutterMarkerNode, $isSomeParaNode, $isSynthesizedMarkerNode, $isVisibleMarkerNode, $placeCaretAtBoundary, NBSP, } from \"shared\";\nimport { $isImmutableVerseNode, $isSomeVerseNode } from \"../../nodes/usj\";\n/**\n * Keeps the cursor out of the places a paragraph's structural prefix occupies but no caret may\n * rest in, correcting a click to the first content position in the same update cycle.\n *\n * WHICH marker is caret territory is decided one NODE at a time, never per view: a marker rendered\n * in the gutter is an aid to reading, so it is never a caret position, while a marker rendered as\n * editable text in the flow IS content the user clicks into on purpose. A document can carry both\n * at once, so the two questions this asks — \"did the click land on a gutter marker?\" and \"does the\n * prefix at this paragraph's start host a caret at all?\" — are asked of the nodes in the tree.\n *\n * Using `CLICK_COMMAND` instead of `registerUpdateListener` + `editor.update` ensures the\n * correction is committed in a single cycle — other listeners (e.g. `OnSelectionChangePlugin`)\n * see only the corrected cursor, never the intermediate prefix position.\n */\nexport function ParaMarkerPrefixCursorGuardPlugin() {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n return editor.registerCommand(CLICK_COMMAND, (event) => {\n $guardCursorOnClick(event);\n return false;\n }, COMMAND_PRIORITY_EDITOR);\n }, [editor]);\n return null;\n}\n/**\n * The whole click policy, in the order the two corrections must be tried: a click that landed ON a\n * gutter marker is answered from the click's target, because such a click leaves NO selection to\n * inspect; everything else is judged from where the selection came to rest.\n *\n * Exported so the registration above is the only thing a test has to duplicate.\n *\n * @param event - The click that Lexical dispatched through `CLICK_COMMAND`.\n */\nexport function $guardCursorOnClick(event) {\n if ($guardCursorAtGutterMarker(event.target))\n return;\n const selection = $getSelection();\n if ($isRangeSelection(selection))\n $guardCursorAtParaStart(selection);\n}\n/**\n * Advances the cursor past all structural prefix nodes at the start of `para`:\n * - Para-marker prefix (`MarkerNode` or `ImmutableTypedTextNode`) and its trailing NBSP.\n * - Leading verse nodes (`VerseNode` or `ImmutableVerseNode`).\n *\n * Places the cursor at the content boundary just past them, under the shared convention for what a\n * boundary's caret position is (`$placeCaretAtBoundary`): the start of the first content `TextNode`\n * that follows, or an element point at that boundary when no `TextNode` hosts it yet.\n *\n * Also called directly when programmatically navigating to a verse whose paragraph has a\n * non-text first child (e.g. in `ScriptureReferencePlugin`).\n */\nexport function $advancePastParaPrefixes(para) {\n let child = para.getFirstChild();\n let skipCount = 0;\n while (child !== null) {\n if ($isSynthesizedMarkerNode(child)) {\n skipCount++;\n child = child.getNextSibling();\n // In editable mode the para-marker prefix is followed by a NBSP TextNode (marker-trailing-space).\n if ($isTextNode(child) && child.getTextContent() === NBSP) {\n skipCount++;\n child = child.getNextSibling();\n }\n }\n else if ($isSomeVerseNode(child)) {\n skipCount++;\n child = child.getNextSibling();\n }\n else {\n break;\n }\n }\n if (skipCount === 0)\n return false;\n $placeCaretAtBoundary(para, skipCount);\n return true;\n}\n/**\n * Corrects a click that landed ON a gutter marker glyph, moving the cursor to the next visible text\n * position — normally the first content text of the paragraph the glyph belongs to.\n *\n * Takes the click's DOM TARGET rather than the selection because a click on a gutter marker leaves\n * no selection at all to correct: the glyph is a decorator, which Lexical renders\n * `contenteditable=\"false\"`, so the browser's caret lands inside a node Lexical cannot resolve to\n * any point in its tree and the editor's selection is left null. (Measured in Chrome: the DOM\n * selection anchors in the glyph's own text with a drawn caret, while `$getSelection()` is null.)\n *\n * Scoped to the GUTTER flavor by {@link $isGutterMarkerNode}, not to the node class: markerMode\n * \"visible\" renders the same class of node INLINE among the words, and where that glyph is part of\n * the text this rule has no opinion about it.\n *\n * @param target - The click's `event.target`.\n * @returns `true` if the cursor was moved, `false` if the click was not on a gutter marker.\n */\nexport function $guardCursorAtGutterMarker(target) {\n if (!isDOMNode(target))\n return false;\n const glyph = $getNearestNodeFromDOMNode(target);\n if (!$isGutterMarkerNode(glyph))\n return false;\n const owner = glyph.getParent();\n if (!owner)\n return false;\n // A paragraph can carry further structure after its marker (a leading verse number), and its own\n // rule already knows how much of that to skip. Anywhere else a gutter marker appears — a book's\n // `\\id` line, a table cell — the boundary just past the glyph is the next content position.\n if ($isSomeParaNode(owner))\n return $advancePastParaPrefixes(owner);\n $placeCaretAtBoundary(owner, glyph.getIndexWithinParent() + 1);\n return true;\n}\n/**\n * Corrects the cursor when it lands at the very start of a paragraph, before a structural prefix\n * that renders no caret of its own: an immutable marker glyph (the gutter aid, or markerMode\n * \"visible\"'s inline glyph) or an `ImmutableVerseNode`. This happens when the user clicks in the\n * hanging-indent gutter of any marker that sets a negative `text-indent` (e.g. `\\li`, `\\li1`,\n * `\\li2`, `\\ili`, `\\ili1`, `\\ili2`, and poetry markers), which resolves to an element-typed anchor\n * at offset 0 of the `ParaNode`.\n *\n * The cursor is advanced past all structural prefix nodes (marker glyph, trailing NBSP, and leading\n * verse nodes) to the first content `TextNode`, or to the element offset just after all those\n * structural nodes when no content `TextNode` follows yet.\n *\n * An EDITABLE marker glyph (`MarkerNode`, markerMode \"editable\") is deliberately not corrected: it\n * is a `TextNode`, so it hosts a caret, and the user clicks into it on purpose to edit the marker.\n * Hauling that click to the content would both fight the intent and make a position the arrow keys\n * can reach unreachable by mouse. So the question this asks is about the NODE at the paragraph's\n * start — can it hold the caret? — never about which view is on screen.\n *\n * Returns `true` if the selection was corrected, `false` if no correction was needed.\n *\n * Exported only for direct unit testing; production callers reach it through\n * {@link $guardCursorOnClick}.\n */\nexport function $guardCursorAtParaStart(selection) {\n if (!selection.isCollapsed())\n return false;\n const { anchor } = selection;\n if (anchor.type !== \"element\" || anchor.offset !== 0)\n return false;\n const para = $getNodeByKey(anchor.key);\n if (!$isSomeParaNode(para))\n return false;\n const first = para.getFirstChild();\n if (!$isVisibleMarkerNode(first) && !$isImmutableVerseNode(first))\n return false;\n return $advancePastParaPrefixes(para);\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $findMatchingParent, mergeRegister } from \"@lexical/utils\";\nimport { $getNearestNodeFromDOMNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, CONTROLLED_TEXT_INSERTION_COMMAND, CUT_COMMAND, DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, DROP_COMMAND, KEY_DOWN_COMMAND, PASTE_COMMAND, } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { $isImmutableTableNode, $isUnknownNode } from \"shared\";\n/**\n * Refuses an edit aimed INSIDE a construct the editor carries opaquely — an `UnknownNode` (figure,\n * sidebar, `\\periph`, `\\ref`, `\\optbreak`) or a table — so a read-only block is genuinely read-only\n * rather than destructive on contact.\n *\n * Without this, a keystroke reaching such a block does not fail to apply; it applies CATASTROPHICALLY.\n * The adaptor renders an opaque block's content children in Lexical's TOKEN mode so the block reads\n * and navigates as one unit, and inserting into a token node REPLACES THE WHOLE NODE — one typed\n * character turns a figure's `My caption` into `Z`, taking the rest of the caption with it. Silently\n * accepting a keystroke and losing neighbouring content to it is the failure the no-silent-no-ops\n * rule exists to prevent; a refused keystroke, where the character simply never appears, is not.\n *\n * The rule is deliberately narrow: an edit is refused when an END of the selection lands INSIDE an\n * opaque construct. That covers a caret sitting in one, and a selection reaching in from the text\n * outside and stopping partway through — the shape that guts the construct's engine-owned bytes (a\n * row's `\\tr ` glyph and its separator) while leaving the construct itself in the document. Nothing\n * repairs that afterwards: `$settleScopeForNode` returns undefined inside an opaque construct, so no\n * re-tokenization ever reconciles the screen with the file again.\n *\n * A selection that CONTAINS a construct whole — both ends outside it — is deliberately still not\n * this guard's business. That is a request to replace a region of the document, which is the\n * structural-deletion question and is answered elsewhere; annexing it here would turn a targeted\n * guard into a blanket one.\n *\n * Navigation and copying stay untouched, because they are what a read-only block is FOR: the block's\n * bytes are selectable and copyable. Only keys that would insert or delete are refused, and\n * modifier chords (Ctrl+C, Ctrl+Z, the marker engine's Ctrl+Space) are never treated as text.\n * Dragging OUT of a block is likewise left alone so drag-to-copy keeps working; the destructive\n * clipboard directions (cut, and a drop landing inside) are refused by their own commands.\n *\n * The delete COMMANDS are guarded alongside `KEY_DOWN` rather than left to it, because a delete\n * chord never reaches the key filter as typing: Lexical routes Ctrl/Alt+Backspace straight to\n * `DELETE_WORD_COMMAND` and Cmd/Ctrl+Backspace to `DELETE_LINE_COMMAND`, and the filter must keep\n * treating a modifier chord as a command so Ctrl+C and Ctrl+Space still work. Guarding the delete\n * commands themselves closes that without reopening the chords.\n *\n * Mount alongside the other guards. Read-only is not a view mode — a construct the editor cannot\n * model is opaque in every marker mode — so this plugin takes no `viewOptions` and is never gated\n * on one.\n */\nexport function OpaqueBlockGuardPlugin() {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n /**\n * Refuse when the caret is inside an opaque construct. Command payloads differ by kind — the\n * clipboard and key commands carry an `Event` to `preventDefault` (which is what stops the\n * browser applying the keystroke itself), while `CONTROLLED_TEXT_INSERTION` carries a bare\n * string — so the event is guarded on rather than assumed.\n */\n const $refuseEditInsideOpaqueBlock = (payload) => {\n if (payload instanceof KeyboardEvent && !isEditingKey(payload))\n return false;\n if (!$selectionReachesIntoOpaqueBlock())\n return false;\n if (payload instanceof Event)\n payload.preventDefault();\n return true;\n };\n return mergeRegister(editor.registerCommand(KEY_DOWN_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_HIGH), editor.registerCommand(CONTROLLED_TEXT_INSERTION_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_HIGH), \n // CUT and PASTE run at CRITICAL because their standard-view handlers — the ones that\n // actually copy and then remove — are themselves registered at HIGH, where the winner is\n // decided by registration order rather than by intent. A refusal has to outrank the actor it\n // refuses, not tie with it. The engine's own CRITICAL cut arm, which records what a cut would\n // cover, TIES with this refusal, so it consults `$selectionReachesIntoOpaqueBlock` itself\n // rather than relying on order: an arm this refusal leaves behind would outlive the gesture.\n editor.registerCommand(PASTE_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_CRITICAL), editor.registerCommand(CUT_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_CRITICAL), \n // DROP is judged by the drop TARGET, not the live selection: Lexical dispatches\n // DROP_COMMAND straight from the DOM handler with no selection update, so at drop time\n // `$getSelection()` still holds whatever was selected when the drag STARTED. Testing that\n // inverted both promises above — dragging a caption OUT of a figure was refused (source\n // inside), while dragging outside text INTO a caption was allowed (source outside).\n editor.registerCommand(DROP_COMMAND, (event) => {\n if (!(event instanceof Event) || !(event.target instanceof Node))\n return false;\n const targetNode = $getNearestNodeFromDOMNode(event.target);\n if (!targetNode || !$opaqueBlockAncestor(targetNode))\n return false;\n event.preventDefault();\n return true;\n }, COMMAND_PRIORITY_HIGH), editor.registerCommand(DELETE_CHARACTER_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_HIGH), editor.registerCommand(DELETE_WORD_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_HIGH), editor.registerCommand(DELETE_LINE_COMMAND, $refuseEditInsideOpaqueBlock, COMMAND_PRIORITY_HIGH));\n }, [editor]);\n return null;\n}\n/**\n * Whether this keystroke would insert or delete text. A modifier chord is a command, not typing —\n * Ctrl+C and Ctrl+Z must keep working inside a read-only block, and the marker engine's Ctrl+Space\n * must reach its own handler — so any of Ctrl/Meta/Alt disqualifies the key outright. Everything\n * else is judged by what the key produces: a single character (a space included), or one of the\n * three keys that remove or break text.\n *\n * Two kinds of typing do not announce themselves as a single character and so are decided before\n * that rule:\n *\n * - An IME composition keystroke inserts CJK/complex-script text but arrives as `key === \"Process\"`\n * (`keyCode === 229` is the DOM's legacy \"handled by IME\" signal, needed because some engines\n * fire the first composition keydown before `isComposing` flips true).\n * - AltGr sets BOTH `ctrlKey` and `altKey` on Windows and Linux, yet it types — AltGr+Q is `@` on a\n * German keyboard. `getModifierState(\"AltGraph\")` is what tells it apart from a real Ctrl+Alt\n * chord, so Ctrl+Alt shortcuts keep reaching their handlers.\n *\n * Exported because it defines what \"typing\" means for the read-only guard, and that definition is\n * worth pinning directly rather than only through the plugin's command wiring.\n */\nexport function isEditingKey(event) {\n if (event.isComposing || event.keyCode === 229)\n return true;\n const typesThroughAltGraph = typeof event.getModifierState === \"function\" && event.getModifierState(\"AltGraph\");\n if (!typesThroughAltGraph && (event.ctrlKey || event.metaKey || event.altKey))\n return false;\n return (event.key.length === 1 ||\n event.key === \"Backspace\" ||\n event.key === \"Delete\" ||\n event.key === \"Enter\");\n}\n/**\n * Read-only: safe inside `editor.getEditorState().read()`, an `editor.update()`, or a command\n * handler — it only walks parents.\n *\n * The nearest opaque-construct ancestor of `node` (itself included), or `undefined`.\n *\n * Exported because it is the ONE place \"this node belongs to a read-only construct\" is decided, and\n * more than the edit guard needs the answer: `ArrowNavigationPlugin` asks it to decide that a\n * construct's marker glyphs are crossed whole rather than walked through. When table editability\n * lands, this predicate is where it comes back out.\n */\nexport function $opaqueBlockAncestor(node) {\n return ($findMatchingParent(node, (current) => $isUnknownNode(current) || $isImmutableTableNode(current)) ?? undefined);\n}\n/**\n * Mutating: read inside an `editor.update()` or a command handler.\n *\n * Whether an END of the selection lands inside an opaque construct — a caret parked in one, or a\n * range that reaches in from the outside and stops partway through. Both ends are resolved\n * separately, and either one being inside is enough, because an edit that lands partway into a\n * construct destroys engine-owned bytes the construct keeps in the file.\n *\n * A range whose ends are BOTH outside is not this guard's business even when it contains a\n * construct whole: that is a region replacement, answered by the structural-deletion rules.\n *\n * Exported for actors that TIE with this plugin's refusals at Lexical's ceiling priority\n * (CRITICAL has no rank above it, so the winner is registration order): the marker engine's\n * in-note paste claim consults this before claiming, so either registration order refuses.\n */\nexport function $selectionReachesIntoOpaqueBlock() {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return false;\n return ($opaqueBlockAncestor(selection.anchor.getNode()) !== undefined ||\n $opaqueBlockAncestor(selection.focus.getNode()) !== undefined);\n}\n","import { $isImmutableNoteCallerNode, $isImmutableVerseNode, $isSomeVerseNode, $selectNextVerse, $selectPreviousVerse, ImmutableVerseNode, } from \"../../nodes/usj\";\nimport { $advancePastParaPrefixes } from \"./ParaMarkerPrefixCursorGuardPlugin\";\nimport { $opaqueBlockAncestor } from \"./OpaqueBlockGuardPlugin\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport { $getRoot, $getSelection, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND, } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { $findFirstAncestorNoteNode, $getNextNode, $getPreviousNode, $isBookNode, $isCharNode, $isImmutableChapterNode, $isImmutableTypedTextNode, $isMarkerNode, $isMilestoneNode, $isNoteNode, $isSomeParaNode, $placeCaretAtBoundary, ImmutableChapterNode, NoteNode, } from \"shared\";\n/**\n * Is there a visual line beyond the caret in `direction`, among a set of candidate line rects?\n * Pure geometry, kept separate from the DOM so it is unit-testable ({@link $caretHasVisualLineBeyond}\n * supplies the rects). Returns `false` for a zero-height caret (e.g. jsdom has no layout) so callers\n * fall back to their default rather than act on a phantom line.\n *\n * A wrapped line sits clear of the caret's own line, so this compares the line gap (a rect that\n * starts below the caret / ends above it) rather than raw top/bottom — otherwise a taller inline on\n * the caret's *own* line (a verse number or note caller) would read as a wrapped line. The\n * tolerance scales with caret height so it holds across font sizes and zoom.\n *\n * @param caretRect - The collapsed caret's bounding rect.\n * @param lineRects - Candidate per-line rects to test (e.g. from `Range.getClientRects()`).\n * @param direction - `\"down\"` looks for a line below the caret; `\"up\"` a line above.\n */\nexport function hasVisualLineBeyondCaret(caretRect, lineRects, direction) {\n if (caretRect.height === 0)\n return false;\n const tolerance = caretRect.height / 4; // sub-pixel slack, well under a full line gap.\n return lineRects.some((rect) => direction === \"down\"\n ? rect.top >= caretRect.bottom - tolerance\n : rect.bottom <= caretRect.top + tolerance);\n}\n/**\n * Whether the current verse's text has a wrapped line beyond the caret in `direction`. Custom\n * verse-to-verse navigation only fires from a verse's first visual line, so this stops ArrowDown\n * from skipping the rest of a wrapped verse and jumping to the next one.\n *\n * The verse's content is measured across blocks — bounded by the surrounding `[data-marker=\"v\"]`\n * verse markers — because a verse can wrap across several `\\q` poetry paragraphs; measuring only the\n * caret's own paragraph would miss those lines. Returns `false` when layout cannot be measured\n * (e.g. jsdom), so the caller keeps the existing verse-jump.\n */\nfunction $caretHasVisualLineBeyond(editor, direction) {\n if (typeof window === \"undefined\")\n return false;\n const domSelection = window.getSelection();\n if (!domSelection || domSelection.rangeCount === 0)\n return false;\n const root = editor.getRootElement();\n if (!root)\n return false;\n try {\n const caretRange = domSelection.getRangeAt(0);\n // Only measure this editor's caret: ignore a selection that lives elsewhere on the page or in\n // another document (an iframe host), which would otherwise be measured against our markers.\n if (!root.contains(caretRange.startContainer))\n return false;\n const caretRect = caretRange.getBoundingClientRect();\n const caretStart = caretRange.cloneRange();\n caretStart.collapse(true);\n // Bound the measurement to the current verse: the last marker strictly before the caret (so the\n // verse whose content the caret is in) and the first marker after it. The comparison is\n // position-based (a plain sibling walk can't handle element-point carets, e.g. a caret at an\n // element offset between decorator siblings), so it scans markers in document order. This runs\n // only on ArrowUp/ArrowDown at a verse boundary — not on every keystroke — over one editor's\n // worth of verses (a chapter), so the linear scan is not a hot path.\n const markers = Array.from(root.querySelectorAll('[data-marker=\"v\"]'));\n let current;\n let next;\n for (const marker of markers) {\n const markerRange = document.createRange();\n markerRange.selectNode(marker);\n if (caretStart.compareBoundaryPoints(Range.START_TO_START, markerRange) > 0) {\n current = marker;\n }\n else {\n next = marker;\n break;\n }\n }\n if (!current)\n return false;\n const contentRange = document.createRange();\n contentRange.setStartAfter(current);\n if (next)\n contentRange.setEndBefore(next);\n else\n contentRange.setEnd(root, root.childNodes.length);\n return hasVisualLineBeyondCaret(caretRect, Array.from(contentRange.getClientRects()), direction);\n }\n catch {\n // No layout engine (e.g. jsdom: Range has no getBoundingClientRect): cannot detect a wrapped\n // line, so fall back to the existing verse-jump rather than suppressing it.\n return false;\n }\n}\n/**\n * Handles an ArrowUp/ArrowDown press for verse-to-verse navigation. Intercepts only when the caret\n * is at a verse boundary and native movement would leave the verse; when the verse wraps onto\n * further lines it yields to the browser's visual-line movement instead.\n *\n * @returns `true` (and prevents default) when it moved the caret to an adjacent verse; otherwise\n * `false` so Lexical/the browser handles the key.\n */\nfunction $navigateVerseVertically(editor, selection, direction, event) {\n // Don't intercept when the caret isn't at a verse boundary, or when the verse wraps onto a\n // further line in this direction (let the browser move by visual line instead). `||` short-circuits\n // so the DOM measurement only runs once the cheap boundary check passes.\n if (!$shouldAttemptVerticalVerseNavigation(selection) ||\n $caretHasVisualLineBeyond(editor, direction))\n return false;\n const isHandled = direction === \"up\" ? $selectPreviousVerse(selection) : $selectNextVerse(selection);\n if (isHandled)\n event.preventDefault();\n return isHandled;\n}\n/**\n * Registers arrow-key handling for USJ scripture: verse-to-verse vertical movement when needed,\n * and horizontal movement around notes and chapter boundaries. In editable-marker mode it also\n * normalizes horizontal traversal — plain arrows and shift-extensions alike — so every press\n * crosses exactly one piece of rendered content.\n *\n * TODO: When the caret is before an empty verse number in an otherwise empty para, pressing up or\n * down moves the caret to after the verse number in the para above/below rather than staying\n * before the verse number.\n *\n * @param viewOptions - View options (e.g. collapsed note mode) affecting backward navigation.\n * @returns Always `null`; this component has no UI.\n */\nexport function ArrowNavigationPlugin({ viewOptions, }) {\n const [editor] = useLexicalComposerContext();\n useArrowKeys(editor, viewOptions);\n return null;\n}\n/**\n * When moving with arrow keys, it handles navigation around adjacent verse and note nodes.\n * It also handles not moving if a chapter node is the only thing at the beginning.\n * @param editor - The LexicalEditor instance used to access the DOM.\n * @param viewOptions - The current view options, which may affect navigation behavior.\n */\nfunction useArrowKeys(editor, viewOptions) {\n useEffect(() => {\n if (!editor.hasNodes([ImmutableChapterNode, ImmutableVerseNode, NoteNode])) {\n throw new Error(\"ArrowNavigationPlugin: ImmutableChapterNode, ImmutableVerseNode or NoteNode not registered on editor!\");\n }\n const $handleKeyDown = (event) => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return false;\n // Display runs and glyph text — the stacked invisible positions the normalizer exists for —\n // are built only in editable-marker mode; the other views keep the browser's own traversal.\n const normalizesStops = viewOptions?.markerMode === \"editable\";\n const rootElement = editor.getRootElement();\n // Shift+horizontal arrow grows the selection by the same visible stops the collapsed caret\n // walks: the FOCUS moves one rendered position, the anchor stays put. Without it, selecting\n // through a display run inherits the traversal the normalizer exists to replace — the focus\n // stalls on invisible stops, and at a run's left edge it could not move at all, because\n // Lexical hands an extend across a zero-width decorator to the browser exactly as it does a\n // collapsed move. Shift ONLY: ctrl/alt/meta arrows keep their own word and line granularity.\n if (normalizesStops &&\n rootElement &&\n (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") &&\n event.shiftKey &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey) {\n const textDirection = getEditorTextDirection(rootElement);\n const isHandled = $extendOneVisibleStop(selection, isMovingForward(textDirection, event.key) ? \"next\" : \"previous\");\n if (isHandled)\n event.preventDefault();\n return isHandled;\n }\n if (!selection.isCollapsed())\n return false;\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)\n return false;\n const direction = event.key === \"ArrowUp\" ? \"up\" : \"down\";\n return $navigateVerseVertically(editor, selection, direction, event);\n }\n if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\")\n return false;\n if (!rootElement)\n return false;\n const direction = getEditorTextDirection(rootElement);\n // The `\\fp` boundary hops apply only to plain arrow moves: modified arrows (shift range\n // extension, word/line jumps) keep native semantics.\n const hasModifier = event.shiftKey || event.altKey || event.ctrlKey || event.metaKey;\n let isHandled = false;\n if (isMovingForward(direction, event.key)) {\n isHandled =\n (!hasModifier && $crossOpaqueConstruct(selection, \"next\")) ||\n (!hasModifier && $handleForwardFpNavigation(selection)) ||\n $handleForwardNavigation(selection) ||\n (!hasModifier && normalizesStops && $moveOneVisibleStop(selection, \"next\"));\n }\n else if (isMovingBackward(direction, event.key)) {\n isHandled =\n (!hasModifier && $crossOpaqueConstruct(selection, \"previous\")) ||\n (!hasModifier && $handleBackwardFpNavigation(selection)) ||\n $handleBackwardNavigation(selection, viewOptions) ||\n (!hasModifier && normalizesStops && $moveOneVisibleStop(selection, \"previous\"));\n }\n if (isHandled)\n event.preventDefault();\n return isHandled;\n };\n return editor.registerCommand(KEY_DOWN_COMMAND, $handleKeyDown, COMMAND_PRIORITY_HIGH);\n }, [editor, viewOptions]);\n}\n// --- Helper functions for direction checking ---\n/**\n * The direction the editor's content reads in, from its root element — what \"forward\" means for a\n * horizontal arrow key. It lives here because arrow navigation is the only caller; it is exported\n * only so the unit test can reach it, as `hasVisualLineBeyondCaret` above is.\n *\n * KNOWN GAP: a project configured for \"auto\" direction reads as `\"ltr\"` here. `TextDirectionPlugin`\n * returns early without setting `dir` on the root when the configured direction is `\"auto\"`, and\n * the host forwards `\"auto\"` through unchanged — so `dir` stays empty and every direction-sensitive\n * behavior below moves the wrong logical way for an RTL script. The likely fix is\n * `getComputedStyle(rootElement).direction`, which reflects what `dir=\"auto\"` resolved to and picks\n * up a direction set in CSS as well; it changes caret behavior for every RTL user and jsdom has no\n * layout to resolve it, so it needs validating against a real RTL project first. Every direction\n * read goes through here, so that is a one-place change.\n */\nexport function getEditorTextDirection(rootElement) {\n return rootElement.dir || \"ltr\";\n}\nfunction isMovingForward(direction, key) {\n return ((direction === \"ltr\" && key === \"ArrowRight\") || (direction === \"rtl\" && key === \"ArrowLeft\"));\n}\nfunction isMovingBackward(direction, key) {\n return ((direction === \"ltr\" && key === \"ArrowLeft\") || (direction === \"rtl\" && key === \"ArrowRight\"));\n}\n// --- Two caret stops around the `\\fp` visual line break ---\n//\n// An expanded note's `\\fp` (footnote paragraph) span renders with a CSS-generated line break\n// before it (`.note.expanded .usfm_fp::before`). The pseudo content has no DOM position, so the\n// browser collapses the caret positions on either side of the visual newline and skips one stop:\n// moving forward it jumps from the end of the previous line straight past the start of the `\\fp`\n// span, and moving backward it can skip the span start on the way out. These handlers restore the\n// two stops: end of the previous line, then the very start of the `\\fp` span on the new line.\n/** The given node if it is a `\\fp` span whose leading line break renders (expanded note). */\nfunction $getFpBoundaryCharNode(node) {\n if (!$isCharNode(node) || node.getMarker() !== \"fp\")\n return undefined;\n const note = $findFirstAncestorNoteNode(node);\n if (!note || note.getIsCollapsed())\n return undefined;\n return node;\n}\n/** Helper to handle the forward hop onto the start of a `\\fp` span at its visual line break. */\nfunction $handleForwardFpNavigation(selection) {\n const fpNode = $getFpBoundaryCharNode($getNextNode(selection));\n if (!fpNode)\n return false;\n // A text caret is only at the boundary when it sits at the very end of its text.\n const anchor = selection.anchor;\n if (anchor.type === \"text\" && anchor.offset !== anchor.getNode().getTextContentSize()) {\n return false;\n }\n // Land at the start of the new visual line: the span's first content boundary — offset 0 of its\n // first text (the editable marker glyph, or content text when glyphs are hidden), or an element\n // point when a non-text first child (the non-editable marker glyph) hosts no caret there.\n $placeCaretAtBoundary(fpNode, 0);\n return true;\n}\n/** Helper to handle backward hops at the start of a `\\fp` span and its visual line break. */\nfunction $handleBackwardFpNavigation(selection) {\n const anchor = selection.anchor;\n const anchorNode = anchor.getNode();\n if (anchor.type === \"text\") {\n const fpNode = $getFpBoundaryCharNode(anchorNode.getParent());\n if (!fpNode || !anchorNode.is(fpNode.getFirstChild()))\n return false;\n if (anchor.offset === 1) {\n // caret after first character of the span's first text → stop at the span start (start of\n // the new visual line) instead of letting the browser collapse the boundary and skip it\n anchorNode.select(0, 0);\n return true;\n }\n if (anchor.offset !== 0)\n return false;\n // caret at span start → hop over the visual newline to the end of the previous line\n return $selectBeforeFpSpan(fpNode);\n }\n // Element caret at the very start of the `\\fp` span (before a non-text first child, e.g. the\n // non-editable marker glyph).\n if (anchor.offset === 0) {\n const fpNode = $getFpBoundaryCharNode(anchorNode);\n if (!fpNode)\n return false;\n return $selectBeforeFpSpan(fpNode);\n }\n return false;\n}\n/** Place the caret at the end of the content preceding the `\\fp` span (end of the previous line). */\nfunction $selectBeforeFpSpan(fpNode) {\n const prevNode = fpNode.getPreviousSibling();\n if (!prevNode)\n return false;\n if ($isTextNode(prevNode)) {\n prevNode.select();\n return true;\n }\n if ($isElementNode(prevNode)) {\n // end of the previous span's content (e.g. the `\\ft` span, or a preceding `\\fp`)\n const lastDescendant = prevNode.getLastDescendant();\n if ($isTextNode(lastDescendant))\n lastDescendant.select();\n else\n prevNode.selectEnd();\n return true;\n }\n // Decorator sibling (e.g. the note caller): place the caret between it and the `\\fp` span so\n // the existing note-boundary handling can take over from there on the next press.\n const parent = fpNode.getParent();\n if (!parent)\n return false;\n const fpIndex = fpNode.getIndexWithinParent();\n parent.select(fpIndex, fpIndex);\n return true;\n}\n/**\n * Segments text into user-perceived characters. The type is in the lib target, but the runtime is\n * not guaranteed to have it — where it is missing, the code-point fallbacks below still keep the\n * caret off the inside of a surrogate pair.\n */\nconst graphemeSegmenter = typeof Intl.Segmenter === \"undefined\"\n ? undefined\n : new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n/** The offset just past `text`'s first grapheme — where a forward crossing into it lands. */\nfunction firstGraphemeEnd(text) {\n if (graphemeSegmenter) {\n for (const { segment } of graphemeSegmenter.segment(text))\n return segment.length;\n }\n const codePoint = text.codePointAt(0);\n return codePoint === undefined ? 0 : String.fromCodePoint(codePoint).length;\n}\n/**\n * The offset where `text`'s last grapheme begins — where a backward crossing into it lands.\n *\n * Scans the whole string: `Intl.Segmenter` only walks forward, and a bounded tail scan can be wrong,\n * because whether a trailing code point begins a grapheme depends on what precedes it. The strings\n * are marker glyphs and short attribute values, and one runs per boundary press.\n */\nfunction lastGraphemeStart(text) {\n if (graphemeSegmenter) {\n let start = 0;\n for (const { index } of graphemeSegmenter.segment(text))\n start = index;\n return start;\n }\n const codePoint = text.codePointAt(Math.max(0, text.length - 2));\n const isSurrogatePair = codePoint !== undefined && codePoint > 0xffff;\n return Math.max(0, text.length - (isSurrogatePair ? 2 : 1));\n}\n/** The block the traversal is confined to — arrows leave a block through the handlers above. */\nfunction $blockOf(node) {\n for (let current = node; current; current = current.getParent()) {\n if ($isElementNode(current) && !current.isInline())\n return current;\n }\n return undefined;\n}\n/**\n * A marker glyph belonging to a READ-ONLY construct — a table's `\\tr` and `\\tc1`, and whatever a\n * future opaque kind renders the same way.\n *\n * Everywhere else in standard view a marker glyph is editable text, and walking through it IS the\n * affordance: retyping `\\q1` to `\\q2` is how a paragraph gets retagged. Inside an opaque construct\n * that affordance is a lie — every gesture that would edit those bytes is refused\n * (`OpaqueBlockGuardPlugin`), and a table has no settle scope to reconcile a change with the file\n * even if one landed. A caret resting between the `\\` and the `t` of `\\tr` is a position from which\n * nothing is possible, so the glyph is treated as what it is: display, crossed whole.\n */\nfunction $isOpaqueConstructGlyph(node) {\n return !!node && $isMarkerNode(node) && $opaqueBlockAncestor(node) !== undefined;\n}\n/** Text the caret walks through one character at a time. */\nfunction $isTraversableText(node) {\n return ($isTextNode(node) &&\n !node.isToken() &&\n !$isOpaqueConstructGlyph(node) &&\n node.getTextContentSize() > 0);\n}\n/**\n * Occupies space on screen but holds no caret positions of its own: crossing it is a single stop,\n * and the caret never lands inside.\n *\n * The list is deliberately explicit, because a node wrongly called invisible is stepped over\n * silently — the caret sails past it and the press lands a stop too far. Anything new that takes up\n * room without offering caret positions belongs here.\n */\nfunction $isVisibleAtom(node) {\n // A line break occupies the rest of its line and ends it. Its two sides are genuinely different\n // places, so it is crossed like any other glyph — never skipped. The unformatted view puts one\n // before every verse, so getting this wrong strands a whole view's line starts and ends.\n if ($isLineBreakNode(node))\n return true;\n // A COLLAPSED note shows only its caller; its hidden content must not be walked into. The flag is\n // undefined until the note plugin settles it, and an unsettled note counts as expanded, matching\n // what is on screen before the collapse lands.\n if ($isNoteNode(node))\n return node.getIsCollapsed() === true;\n // Token-mode text is indivisible by Lexical's own rule, and a read-only construct's marker glyph\n // is indivisible by ours — see `$isOpaqueConstructGlyph`.\n if ($isTextNode(node))\n return (node.isToken() || $isOpaqueConstructGlyph(node)) && node.getTextContentSize() > 0;\n // Every decorator renders SOMETHING — a note caller, an immutable glyph, a verse or chapter\n // number — except the zero-width anchors listed here, which render nothing at all. A new\n // zero-width decorator must join this list, or traversal will come to rest on it.\n if ($isDecoratorNode(node))\n return !$isMilestoneNode(node);\n return false;\n}\n/** The next node in document order in `direction`, stepping out of ancestors, bounded by `block`. */\nfunction $stepOver(from, direction, block) {\n for (let current = from; current && !current.is(block);) {\n const sibling = direction === \"next\" ? current.getNextSibling() : current.getPreviousSibling();\n if (sibling)\n return sibling;\n current = current.getParent();\n }\n return undefined;\n}\n/**\n * The first node rendering anything, starting AT `seed` and walking `direction` — descending into\n * elements that are not atoms, stepping over everything invisible.\n */\nfunction $scanForRendered(seed, direction, block) {\n for (let cursor = seed; cursor;) {\n if ($isVisibleAtom(cursor))\n return cursor;\n if ($isElementNode(cursor)) {\n const child = direction === \"next\" ? cursor.getFirstChild() : cursor.getLastChild();\n cursor = child ?? $stepOver(cursor, direction, block);\n continue;\n }\n if ($isTraversableText(cursor))\n return cursor;\n cursor = $stepOver(cursor, direction, block);\n }\n return undefined;\n}\n/** The node a scan should consider first when leaving the caret's position in `direction`. */\nfunction $scanSeed(anchorNode, anchorOffset, anchorType, direction, block) {\n if (anchorType === \"element\" && $isElementNode(anchorNode)) {\n const child = anchorNode.getChildAtIndex(direction === \"next\" ? anchorOffset : anchorOffset - 1);\n return child ?? $stepOver(anchorNode, direction, block);\n }\n // A text point INSIDE an atom — token-mode text such as a paragraph's marker-trailing separator\n // — sits at one of that atom's own edges, so the atom is the very thing this press has to cross.\n // Seeding a sibling skips it and lands a stop too far: from the right edge of `\\q1 `'s\n // separator, a backward press sailed past the separator into the glyph and came to rest between\n // `q` and `1`. Only seed the atom when it still has content on the side we are moving toward; at\n // its far edge the atom is already behind the caret and the scan should go on to the sibling.\n if (anchorType === \"text\" &&\n $isVisibleAtom(anchorNode) &&\n (direction === \"next\" ? anchorOffset < anchorNode.getTextContentSize() : anchorOffset > 0))\n return anchorNode;\n return $stepOver(anchorNode, direction, block);\n}\n/**\n * The single resting position for the screen location `landing` sits at.\n *\n * The preference is backward — the end of the nearest preceding visible text — and deliberately the\n * opposite of the forward one the content-boundary convention states (`$placeCaretAtBoundary`,\n * shared): what precedes an arrow landing is rendered content the caret has just walked over, so the\n * end of it is the outermost position at that location, whereas what precedes a content boundary is\n * structure that typed text must not merge into.\n */\nfunction $canonicalize(landing, block) {\n // A text point with a character before it in its own node is already the outermost position at\n // its location — nothing invisible separates it from rendered content on its left.\n if (landing.kind === \"text\" && landing.offset > 0)\n return landing;\n const seed = $scanSeed(landing.node, landing.offset, landing.kind, \"previous\", block);\n const rendered = $scanForRendered(seed, \"previous\", block);\n // Nothing rendered precedes it (the block's leading edge): the landing is already outermost.\n if (!rendered)\n return landing;\n if ($isTraversableText(rendered))\n return { kind: \"text\", node: rendered, offset: rendered.getTextContentSize() };\n const parent = rendered.getParent();\n if (!parent)\n return landing;\n return { kind: \"element\", node: parent, offset: rendered.getIndexWithinParent() + 1 };\n}\n/** Where a single press lands from `point`, or `undefined` when the block edge leaves nothing to cross. */\nfunction $resolveOneVisibleStop(point, direction) {\n const anchorNode = point.getNode();\n const block = $blockOf(anchorNode);\n if (!block)\n return undefined;\n // Inside a text node the browser's own grapheme and bidi handling is better than a tree walk, so\n // those moves are declined — except a backward step off the first character, whose landing at\n // offset 0 is one of the stacked positions and has to be canonicalized.\n if (point.type === \"text\" && $isTraversableText(anchorNode)) {\n if (direction === \"next\" && point.offset < anchorNode.getTextContentSize())\n return undefined;\n if (direction === \"previous\" && point.offset > 1)\n return undefined;\n if (direction === \"previous\" && point.offset === 1)\n return $canonicalize({ kind: \"text\", node: anchorNode, offset: 0 }, block);\n }\n const seed = $scanSeed(anchorNode, point.offset, point.type, direction, block);\n const rendered = $scanForRendered(seed, direction, block);\n if (!rendered)\n return undefined;\n if ($isTraversableText(rendered)) {\n const text = rendered.getTextContent();\n const offset = direction === \"next\" ? firstGraphemeEnd(text) : lastGraphemeStart(text);\n return $canonicalize({ kind: \"text\", node: rendered, offset }, block);\n }\n const parent = rendered.getParent();\n if (!parent)\n return undefined;\n const index = rendered.getIndexWithinParent();\n return $canonicalize({ kind: \"element\", node: parent, offset: direction === \"next\" ? index + 1 : index }, block);\n}\n/**\n * Applies one visible stop in `direction`. `collapse` walks the caret; `extend` moves only the\n * selection's focus, so a shift-arrow grows the range by the same stops. `false` leaves the press to\n * other handling.\n */\nfunction $applyOneVisibleStop(selection, direction, alter) {\n // A collapsed move reads the anchor and an extend reads the focus — the live end of the range,\n // so it stays correct for a selection that was already extended backwards.\n const point = alter === \"collapse\" ? selection.anchor : selection.focus;\n const landing = $resolveOneVisibleStop(point, direction);\n if (!landing)\n return false;\n // Already there (a canonicalization that resolved back onto the point): report the press as\n // unhandled rather than claiming a keystroke that changes nothing.\n if (landing.node.is(point.getNode()) &&\n landing.offset === point.offset &&\n landing.kind === point.type) {\n return false;\n }\n if (alter === \"collapse\") {\n landing.node.select(landing.offset, landing.offset);\n return true;\n }\n selection.focus.set(landing.node.getKey(), landing.offset, landing.kind);\n return true;\n}\n/** Moves the caret one visible stop in `direction`; `false` leaves the press to other handling. */\nfunction $moveOneVisibleStop(selection, direction) {\n return $applyOneVisibleStop(selection, direction, \"collapse\");\n}\n/** Extends the selection's focus one visible stop, leaving its anchor where it is. */\nfunction $extendOneVisibleStop(selection, direction) {\n return $applyOneVisibleStop(selection, direction, \"extend\");\n}\n// --- Crossing a read-only construct whole ---\n//\n// A read-only construct is not a place a caret can BE. `ImmutableTableNode` (and `UnknownNode`)\n// render the whole block `contenteditable=\"false\"`, so the browser draws no caret anywhere inside\n// one and its own arrow handling will not move a caret out of one either. A caret that gets in is\n// therefore invisible AND unrecoverable: neither key brings it back, and ArrowUp/ArrowDown scroll\n// the view rather than move anything.\n//\n// Getting in was never the browser's doing, which is what made this hard to place. Lexical's own\n// `RangeSelection.modify` descends into the next BLOCK whenever a press would leave the caret's\n// block (`$modifySelectionAroundDecoratorsAndBlocks`, core), and `@lexical/rich-text` claims the\n// arrow key at EDITOR priority to apply it and calls `preventDefault` — so the press never reaches\n// the browser, and the caret is placed inside a block Lexical is happy to address and the browser\n// cannot render a caret in.\n//\n// The rule below is therefore about the ONE press that would enter: it crosses the construct WHOLE\n// and lands on the far side, which is the same treatment the construct's own marker glyphs already\n// get one level down. Everything else about read-only constructs is unchanged — the caret still\n// traverses inside one it is already in, shift-extension still reaches in (a construct's bytes are\n// selectable and copyable, which is what read-only is FOR), and nothing is hidden.\n//\n// Scoped by the caret's BLOCK EDGE, so an inline read-only construct — an `\\optbreak`, a `\\ref`\n// sitting among the words of a paragraph — is never what this rule sees: it is inside the caret's\n// own block, never the block's sibling, and the visible-stop rules above continue to own it.\n//\n// NOT gated on marker mode. A construct the editor cannot model is read-only in every view, which\n// is the same reasoning `OpaqueBlockGuardPlugin` records for taking no `viewOptions` at all.\n/**\n * Whether `point` has no rendered content left to cross within `bound` — the press leaves it.\n *\n * Asks the same two questions a visible-stop move asks, in the same vocabulary, so \"the press\n * leaves this block\" cannot drift from \"the normalizer found nothing more to cross in it\".\n */\nfunction $isAtEdgeOf(point, direction, bound) {\n const node = point.getNode();\n // A move that stays inside one text node never reaches an edge, whichever way it goes.\n if (point.type === \"text\" && $isTraversableText(node)) {\n if (direction === \"next\" ? point.offset < node.getTextContentSize() : point.offset > 0)\n return false;\n }\n const seed = $scanSeed(node, point.offset, point.type, direction, bound);\n return $scanForRendered(seed, direction, bound) === undefined;\n}\n/**\n * The first node rendering anything beyond `construct` in `direction`, stepping OVER any further\n * read-only construct rather than coming to rest in it.\n *\n * Two tables back to back have no position between them — there is nothing there to put a caret on\n * — so they are crossed together rather than one per press.\n */\nfunction $renderedBeyondConstructs(construct, direction) {\n const root = $getRoot();\n for (let skipping = construct; skipping;) {\n const seed = $stepOver(skipping, direction, root);\n const rendered = seed && $scanForRendered(seed, direction, root);\n if (!rendered)\n return undefined;\n skipping = $opaqueBlockAncestor(rendered);\n if (!skipping)\n return rendered;\n }\n return undefined;\n}\n/**\n * Crosses a read-only construct WHOLE when this press would otherwise enter it.\n *\n * Runs FIRST in both arrow chains, ahead of the note, `\\fp` and visible-stop handlers, because it\n * decides whether the press leaves the caret's block at all — and because one of those handlers\n * (the hop past a collapsed note at a paragraph's end) would otherwise place the caret inside the\n * construct itself. Its predicate is narrow enough to take that position safely: it claims only\n * when the caret is at its own block's edge AND the block's neighbour is a construct, which is a\n * press no other handler here has an opinion about.\n *\n * @returns `true` when the press was claimed — including the refusal when nothing beyond the\n * construct can hold a caret, where leaving the caret put is the point.\n */\nfunction $crossOpaqueConstruct(selection, direction) {\n const point = selection.anchor;\n const node = point.getNode();\n // From INSIDE a construct this rule has nothing to say: the caret is already somewhere it exists\n // to prevent, and the glyph-atom rules above own the traversal there.\n if ($opaqueBlockAncestor(node))\n return false;\n const block = $blockOf(node);\n if (!block || !$isAtEdgeOf(point, direction, block))\n return false;\n const neighbour = $stepOver(block, direction, $getRoot());\n const construct = neighbour && $opaqueBlockAncestor(neighbour);\n if (!construct)\n return false;\n const rendered = $renderedBeyondConstructs(construct, direction);\n // Nothing beyond it can hold a caret (a document ending in a table): refuse the move and leave\n // the caret where the user can see it. Letting the press through is how it gets lost.\n if (!rendered)\n return true;\n if ($isTraversableText(rendered)) {\n const offset = direction === \"next\" ? 0 : rendered.getTextContentSize();\n rendered.select(offset, offset);\n return true;\n }\n const parent = rendered.getParent();\n // An unparented landing cannot be selected; refuse rather than enter the construct.\n if (!parent)\n return true;\n const index = rendered.getIndexWithinParent() + (direction === \"next\" ? 0 : 1);\n parent.select(index, index);\n return true;\n}\n/**\n * Places the caret in the block just past a collapsed note that ends it.\n *\n * A note that is its block's last child has nothing after it, so the landing is an element point\n * with no text node of its own — and a browser draws no insertion point where there is no rendered\n * text. Giving that position something to render in is `TrailingNoteCaretGuardPlugin`'s job, not\n * this one's: it materializes a transient zero-width caret host past the note once the caret comes\n * to rest here, so the landing stays a plain element point in the tree and this rule stays about\n * WHERE the caret goes rather than what renders it.\n *\n * Which side of the note the caret belongs on is settled here and is not a rendering question. A\n * collapsed note's content is hidden, so a caret inside one is invisible AND typing silently edits\n * the note body instead of the paragraph — the wrong bytes change. Outside the note, nothing has\n * changed and one backward press recovers, so it is strictly the better landing.\n *\n * Mutating: call inside `editor.update()`; dispatched from the arrow handling below.\n */\nfunction $selectPastTrailingNote(note) {\n const parent = note.getParent();\n // Detached from any block: leave the caret where the user can still see it.\n if (!parent)\n return;\n const indexPastNote = note.getIndexWithinParent() + 1;\n parent.select(indexPastNote, indexPastNote);\n}\n/** Helper to handle forward arrow key navigation logic */\nfunction $handleForwardNavigation(selection) {\n const node = selection.anchor.getNode();\n const nextNode = $getNextNode(selection);\n if ($isNoteNode(nextNode) && !$isMarkerNode(nextNode.getFirstChild())) {\n // note is next and markers are not editable\n if ($isSomeParaNode(node)) {\n const isSelectionAtParaEnd = selection.anchor.offset === node.getChildrenSize();\n if (isSelectionAtParaEnd)\n return false;\n }\n else {\n const isSelectionAtNodeEnd = selection.anchor.offset === node.getTextContentSize();\n if (!isSelectionAtNodeEnd)\n return false;\n }\n if (!nextNode.getIsCollapsed()) {\n // caret at end of node before expanded note → move past note caller\n if ($isImmutableTypedTextNode(nextNode.getFirstChild()))\n nextNode.select(2, 2);\n else\n nextNode.select(1, 1);\n return true;\n }\n else if (nextNode.is(nextNode.getParent()?.getLastChild())) {\n // caret at end of node before collapsed note at end of para → move past note\n const nextPara = nextNode.getParent()?.getNextSibling();\n if (nextPara && !($isSomeParaNode(nextPara) && $advancePastParaPrefixes(nextPara)))\n nextPara.selectStart();\n return true;\n }\n }\n if ($isSomeParaNode(node) && $isNoteNode(nextNode) && nextNode.getIsCollapsed()) {\n // caret between verse and collapsed note → move past note\n const nodeAfterNote = nextNode.getNextSibling();\n if (nodeAfterNote)\n nodeAfterNote.selectStart();\n else\n $selectPastTrailingNote(nextNode);\n return true;\n }\n const nextNodeParent = nextNode?.getParent();\n if ($isImmutableTypedTextNode(nextNode) &&\n $isNoteNode(nextNodeParent) &&\n nextNode.is(nextNodeParent?.getLastChild())) {\n // caret before closing note marker → move past note\n const nodeAfterNote = nextNodeParent.getNextSibling();\n if (nodeAfterNote)\n nodeAfterNote.selectStart();\n else if (nextNodeParent.getIsCollapsed())\n $selectPastTrailingNote(nextNodeParent);\n // An EXPANDED note's own end is rendered, so it stays a legitimate resting place.\n else\n nextNodeParent.selectEnd();\n return true;\n }\n return false;\n}\n/** Helper to handle backward arrow key navigation logic */\nfunction $handleBackwardNavigation(selection, viewOptions) {\n const prevNode = $getPreviousNode(selection);\n // If a chapter node is the only thing at the beginning → don't move.\n if ($isImmutableChapterNode(prevNode) && !prevNode.getPreviousSibling())\n return true;\n // If not at the beginning of node text → skip.\n const isSelectionAtNodeStart = selection.anchor.offset === 0;\n if (!isSelectionAtNodeStart)\n return false;\n // If at the beginning of book node text → don't move.\n const node = selection.anchor.getNode();\n if ($isBookNode(node.getParent()))\n return true;\n if ($isNoteNode(prevNode) && prevNode.getIsCollapsed()) {\n // caret at end of collapsed note preceded by verse → move to start of note in para\n const nodeBeforeNote = prevNode.getPreviousSibling();\n if (!$isImmutableVerseNode(nodeBeforeNote))\n return false;\n const parent = prevNode.getParent();\n if (!parent)\n return false;\n const noteIndex = prevNode.getIndexWithinParent();\n parent.select(noteIndex, noteIndex);\n return true;\n }\n // Deliberately gated on the always-\"collapsed\" MODE, not just the note's own collapsed flag:\n // under \"expandInline\" the caret must instead land inside the note's end (Lexical's default\n // move), where the NoteNodePlugin expands it for inline editing — hopping over the note here\n // would defeat that enter-and-expand behavior.\n if ($isSomeParaNode(prevNode) && viewOptions?.noteMode === \"collapsed\") {\n // caret at beginning of para after collapsed note → move to start in previous para\n const lastChild = prevNode.getLastChild();\n if (!lastChild)\n return false;\n const note = $findMatchingParent(lastChild, (n) => $isNoteNode(n));\n if ($isNoteNode(note) && note.getIsCollapsed()) {\n const parent = note.getParent();\n if (!parent)\n return false;\n const noteIndex = note.getIndexWithinParent();\n parent.select(noteIndex, noteIndex);\n return true;\n }\n }\n const noteNode = $findFirstAncestorNoteNode(node);\n if (!noteNode || noteNode.getIsCollapsed())\n return false;\n if ($isImmutableNoteCallerNode(prevNode)) {\n // caret after caller in expanded note (markers hidden) → move to start of note in para\n const parent = noteNode.getParent();\n if (!parent)\n return false;\n const noteIndex = noteNode.getIndexWithinParent();\n parent.select(noteIndex, noteIndex);\n return true;\n }\n return false;\n}\n/**\n * Returns whether custom ArrowUp/ArrowDown verse navigation should run.\n *\n * The verse jump is a SUBSTITUTE for a position the browser cannot move a visual line from — an\n * element point wedged between block nodes, or beside a verse number that is a childless decorator\n * and so hosts no caret of its own. Wherever the caret sits in rendered text the browser's own line\n * movement is the right answer and this declines. That includes inside an editable verse marker's\n * glyph, which is rendered text the caret walks a character at a time exactly like the words beside\n * it: intercepting there turned a line move into a jump to wherever the next verse happened to be —\n * the next paragraph, or sideways along the same line when the next verse shared it.\n *\n * The remaining case is one screen location with two spellings. Lexical normalizes an element point\n * to text offset 0 when the next child is a `TextNode`, so a caret at offset 0 of the text after a\n * caret-less verse number IS that element point, and the jump still owns it. A verse marker\n * rendered as glyph text hosts its own caret, so offset 0 after it is an ordinary text position and\n * both spellings of that location decline alike.\n */\nfunction $shouldAttemptVerticalVerseNavigation(selection) {\n if (selection.anchor.type === \"element\")\n return true;\n if (selection.anchor.offset !== 0)\n return false;\n const previousNode = selection.anchor.getNode().getPreviousSibling();\n return $isSomeVerseNode(previousNode) && $isDecoratorNode(previousNode);\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport { deepEqual } from \"fast-equals\";\nimport { $getState, TextNode } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { $hasSameCharAttributes, $isCharNode, $isMarkerNode, $isSeparatorPrefixHostText, $syncDisplayRun, $syncNestedGlyphs, $syncOpenerSeparators, charIdState, CharNode, displayRunDescriptor, EMPTY_CHAR_PLACEHOLDER_TEXT, NBSP, } from \"shared\";\n/** Combine adjacent CharNodes with the same attributes. */\nexport function CharNodePlugin() {\n const [editor] = useLexicalComposerContext();\n useCharNode(editor);\n return null;\n}\nfunction useCharNode(editor) {\n useEffect(() => {\n if (!editor.hasNodes([CharNode])) {\n throw new Error(\"CharNodePlugin: CharNode not registered on editor!\");\n }\n return mergeRegister(editor.registerNodeTransform(CharNode, $charNodeTransform), \n // Self-healing nested glyphs: whenever a char span is dirtied (created, moved, merged,\n // unwrapped), re-derive its glyphs' `+` from tree position — see nestedGlyphs.utils.ts\n // (`shared`) for the full representation rules this enforces.\n editor.registerNodeTransform(CharNode, $syncNestedGlyphs), \n // Self-healing display separators: every opening char glyph is followed by its NBSP\n // separator (text prefix or standalone spacer) — see markerSeparators.utils.ts (`shared`).\n editor.registerNodeTransform(CharNode, $syncOpenerSeparators), \n // Self-healing attribute display run: re-derive the `|…` run from unknownAttributes\n // whenever a span is dirtied — heals remote collab updates (delta-apply only calls\n // setUnknownAttributes) and structure surgery. $syncDisplayRun (displayRunSync.utils.ts,\n // `shared`), driven here with the char descriptor from displayRunRegistry.ts (`shared`).\n editor.registerNodeTransform(CharNode, (node) => $syncDisplayRun(displayRunDescriptor(\"char\"), node)), editor.registerNodeTransform(TextNode, $charTextNodeTransform));\n }, [editor]);\n}\n/**\n * Whether `node` renders its own marker glyphs — the editable-marker views build every char span\n * with an opening `MarkerNode` (and, for a closed span, a matching closer), while the visible and\n * hidden views build none at all.\n *\n * Combining two glyph-bearing spans is not a normalization but a byte change: the survivor keeps\n * BOTH spans' glyphs among its children, so a merged pair displays `\\nd a\\nd*\\nd b\\nd*` while\n * being ONE span. Re-tokenizing those bytes — what every settle does — yields two spans again,\n * which this transform merges again, and the rebuild's fixed-point refusal never fires because\n * each side genuinely differs from the other. That is a live editor freeze, reachable from any\n * edit that leaves two same-attribute spans adjacent (deleting the `*` from a `\\va*` closer\n * re-tokenizes into exactly that shape). Where the glyphs are absent there are no bytes to\n * contradict, so the merge stays: adjacent same-attribute runs really are equivalent there, and\n * that is the case delta-apply and structure surgery rely on.\n *\n * The MIXED pairing — a glyph-less span beside a glyph-bearing one — must still merge, because\n * the marker-apply paths rely on it: `$wrapRunInCharNode` (usj-marker-action.utils.ts) wraps the\n * uncovered run in a deliberately glyph-less span and counts on this transform to reunite it with\n * the glyph-bearing neighbor whose identity it copied. But a plain child move would land the\n * content OUTSIDE the neighbor's glyph pair (`[\\nd, \"Lord\", \\nd*, \" God\"]` — bytes that\n * re-tokenize as content after the closed span, inside it), so the mixed branches below splice\n * the glyph-less content inside the pair instead: after the opening glyph when the partner\n * follows, before the closing glyph when it precedes.\n */\nfunction $rendersOwnGlyphs(node) {\n return node.getChildren().some($isMarkerNode);\n}\n/**\n * Merge glyph-less `node` into the same-attribute glyph-bearing `target` that FOLLOWS it. In\n * document order the merged content starts with `node`'s children, so they belong directly after\n * `target`'s opening glyph — and the NBSP display separator moves with the \"first content\" role:\n * the old first content's presentation NBSP (prefix or standalone spacer) is stripped here, and\n * `$syncOpenerSeparators` re-derives the separator for the new first content when `target` is\n * re-processed as a dirtied span (the sync only ever ADDS a missing separator; it never strips a\n * stale one, so the strip must happen in the same surgery that demotes the old host).\n *\n * @returns `false` (nothing touched) when `target`'s first child is not an opening glyph — a\n * mid-edit shape (e.g. a just-deleted opener) the marker-edit engine settles itself.\n */\nfunction $mergeIntoFollowingGlyphSpan(node, target) {\n const opener = target.getFirstChild();\n if (!$isMarkerNode(opener) || opener.getMarkerSyntax() !== \"opening\")\n return false;\n const oldFirstContent = opener.getNextSibling();\n if ($isSeparatorPrefixHostText(oldFirstContent)) {\n const text = oldFirstContent.getTextContent();\n if (text.startsWith(NBSP)) {\n if (text === NBSP)\n oldFirstContent.remove();\n else\n oldFirstContent.setTextContent(text.slice(NBSP.length));\n }\n }\n target.splice(1, 0, node.getChildren());\n node.remove();\n return true;\n}\n/**\n * Merge glyph-less `node` into the same-attribute glyph-bearing `target` that PRECEDES it. The\n * merged content ends with `node`'s children, so they go before `target`'s closing glyph when it\n * has one; an unclosed span (opening glyph only) takes them as a plain append. The separator\n * needs no attention in this direction — `target`'s first content keeps its role.\n */\nfunction $mergeIntoPrecedingGlyphSpan(node, target) {\n const closer = target.getLastChild();\n const children = node.getChildren();\n if ($isMarkerNode(closer) && closer.getMarkerSyntax() === \"closing\") {\n children.forEach((child) => closer.insertBefore(child));\n }\n else {\n target.append(...children);\n }\n node.remove();\n}\n/**\n * Combine adjacent CharNodes with the same attributes.\n * @param node - CharNode thats needs updating.\n * @param editor - LexicalEditor instance.\n */\nfunction $charNodeTransform(node) {\n if (!$isCharNode(node))\n return;\n if (node.isEmpty()) {\n node.remove();\n return;\n }\n // Glyph-bearing spans are the displayed bytes; see `$rendersOwnGlyphs`.\n if ($rendersOwnGlyphs(node))\n return;\n const style = node.getMarker();\n // `\\fp` (footnote-paragraph) spans are exempt from combining: each span IS a paragraph\n // break inside the note (Enter and a multi-line paste there create consecutive `\\fp`\n // spans), so adjacency is content structure — combining collapsed two footnote paragraphs\n // into one in the serialized USJ. Formatting chars keep combining: for them adjacent\n // same-attribute runs really are equivalent.\n if (style === \"fp\")\n return;\n const cid = $getState(node, charIdState);\n const unknownAttributes = node.getUnknownAttributes();\n const nextNode = node.getNextSibling();\n if ($isCharNode(nextNode) &&\n $hasSameCharAttributes({ style, cid }, nextNode) &&\n deepEqual(unknownAttributes, nextNode.getUnknownAttributes())) {\n // Combine with next CharNode since it has the same attributes. A glyph-bearing partner\n // survives the merge (its glyphs are the displayed bytes) and takes the content inside its\n // glyph pair; `node` is gone after that, so the previous-sibling pass below cannot run — the\n // re-run on the dirtied survivor's siblings picks up any further merge.\n if ($rendersOwnGlyphs(nextNode)) {\n if ($mergeIntoFollowingGlyphSpan(node, nextNode))\n return;\n }\n else {\n node.append(...nextNode.getChildren());\n nextNode.remove();\n }\n }\n const prevNode = node.getPreviousSibling();\n if ($isCharNode(prevNode) &&\n $hasSameCharAttributes({ style, cid }, prevNode) &&\n deepEqual(unknownAttributes, prevNode.getUnknownAttributes())) {\n // Combine with previous CharNode since it has the same attributes.\n if ($rendersOwnGlyphs(prevNode)) {\n $mergeIntoPrecedingGlyphSpan(node, prevNode);\n }\n else {\n prevNode.append(...node.getChildren());\n node.remove();\n }\n }\n}\n/**\n * Remove 'empty' placeholder in CharNode once other text content is added.\n * @param node - TextNode that might be a placeholder.\n */\nfunction $charTextNodeTransform(node) {\n const parent = node.getParent();\n if (!$isCharNode(parent) || parent.getChildrenSize() !== 1)\n return;\n const text = node.getTextContent();\n if (text.length > 1 && text.startsWith(EMPTY_CHAR_PLACEHOLDER_TEXT)) {\n node.setTextContent(text.slice(1));\n node.selectEnd();\n }\n}\n","import { mergeRegister } from \"@lexical/utils\";\nimport { $getSelection, COMMAND_PRIORITY_LOW, COPY_COMMAND, CUT_COMMAND, PASTE_COMMAND, } from \"lexical\";\nfunction cleanupText(text) {\n return text.replaceAll(\"\\t\", \" \");\n}\n/**\n * Whether the selection holds content a copy could put on the clipboard: one exists and covers\n * something. A collapsed caret — or no selection at all — does not.\n *\n * Reads the ACTIVE editor state, so it must be called inside an update or a command listener. That\n * is the whole point: the committed state is a microtask behind, so a selection made earlier in the\n * same tick is invisible to `editor.getEditorState().read()` — a `copy()` right after a\n * programmatic select would read \"nothing selected\" and silently do nothing.\n */\nexport function $hasCopyableSelection() {\n const selection = $getSelection();\n return !!selection && !selection.isCollapsed();\n}\n/**\n * Stops a copy or cut that has nothing to copy, wherever it was dispatched from.\n *\n * A copy with no clipboard event of its own — the keyboard shortcuts below, the context menu's\n * Cut/Copy, an editor-ref `copy()` call — has to have one synthesized before anything can be\n * written: `@lexical/clipboard` appends a hidden placeholder element to the editor, points the DOM\n * selection at it, and runs `document.execCommand(\"copy\")` to provoke a real clipboard event it can\n * fill in. With nothing selected, that filling step declines — and declines BEFORE suppressing the\n * browser's own copy — so the browser copies what it was pointed at, the placeholder, and the user\n * loses whatever the clipboard already held to a character that was never in the document.\n *\n * So the command is CLAIMED here and does nothing, which is what copying an empty selection means.\n * The guard lives on the command rather than in front of each dispatch for two reasons: a command\n * listener runs inside the update, where the selection is authoritative for both pending and\n * committed state, and every dispatcher is covered by the one registration.\n *\n * Registered at `COMMAND_PRIORITY_LOW` — above `@lexical/rich-text`'s own copy/cut fallback at\n * EDITOR, which is the thing that synthesizes the event, and below every feature handler, so a view\n * with its own clipboard payload (Standard view's USFM copy) still claims first and this never runs.\n */\nexport function registerEmptyCopyGuard(editor) {\n const $claimWhenNothingToCopy = () => !$hasCopyableSelection();\n return mergeRegister(editor.registerCommand(COPY_COMMAND, $claimWhenNothingToCopy, COMMAND_PRIORITY_LOW), editor.registerCommand(CUT_COMMAND, $claimWhenNothingToCopy, COMMAND_PRIORITY_LOW));\n}\n/** Copies the selection. Dispatched unconditionally; an empty selection copies nothing because\n * {@link registerEmptyCopyGuard} claims the command, not because the dispatch is withheld. */\nexport const copySelection = (editor) => {\n editor.dispatchCommand(COPY_COMMAND, null);\n};\n/** Cuts the selection, under the same guard as {@link copySelection} — a cut copies first, so it\n * reaches the identical synthesized-copy path. */\nexport const cutSelection = (editor) => {\n editor.dispatchCommand(CUT_COMMAND, null);\n};\nexport const pasteSelection = (editor) => {\n navigator.clipboard.read().then(async (items) => {\n const permission = await navigator.permissions.query({\n // @ts-expect-error These types are incorrect.\n name: \"clipboard-read\",\n });\n if (permission.state === \"denied\") {\n alert(\"Not allowed to paste from clipboard.\");\n return;\n }\n const data = new DataTransfer();\n const item = items[0];\n for (const type of item.types) {\n const dataString = await (await item.getType(type)).text();\n data.setData(type, cleanupText(dataString));\n }\n const event = new ClipboardEvent(\"paste\", {\n clipboardData: data,\n });\n editor.dispatchCommand(PASTE_COMMAND, event);\n });\n};\nexport const pasteSelectionAsPlainText = (editor) => {\n navigator.clipboard.read().then(async () => {\n const permission = await navigator.permissions.query({\n // @ts-expect-error These types are incorrect.\n name: \"clipboard-read\",\n });\n if (permission.state === \"denied\") {\n alert(\"Not allowed to paste from clipboard.\");\n return;\n }\n const data = new DataTransfer();\n const text = await navigator.clipboard.readText();\n data.setData(\"text/plain\", cleanupText(text));\n const event = new ClipboardEvent(\"paste\", {\n clipboardData: data,\n });\n editor.dispatchCommand(PASTE_COMMAND, event);\n });\n};\n","import { copySelection, cutSelection, pasteSelection, pasteSelectionAsPlainText, registerEmptyCopyGuard, } from \"./clipboard.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { IS_APPLE, mergeRegister } from \"@lexical/utils\";\nimport { useEffect } from \"react\";\nexport function ClipboardPlugin() {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n const onKeyDown = (event) => {\n const { key, shiftKey, metaKey, ctrlKey, altKey } = event;\n if (!(IS_APPLE ? metaKey : ctrlKey) || altKey)\n return;\n if (!shiftKey && key.toLowerCase() === \"c\") {\n event.preventDefault();\n copySelection(editor);\n }\n else if (!shiftKey && key.toLowerCase() === \"x\") {\n event.preventDefault();\n cutSelection(editor);\n }\n else if (key.toLowerCase() === \"v\") {\n event.preventDefault();\n if (shiftKey)\n pasteSelectionAsPlainText(editor);\n else\n pasteSelection(editor);\n }\n };\n return mergeRegister(\n // Every copy/cut this plugin's shortcuts synthesize — and every one the context menu or an\n // editor ref synthesizes against the same editor — passes through this guard.\n registerEmptyCopyGuard(editor), editor.registerRootListener((rootElement, prevRootElement) => {\n if (prevRootElement !== null) {\n prevRootElement.removeEventListener(\"keydown\", onKeyDown);\n }\n if (rootElement !== null) {\n rootElement.addEventListener(\"keydown\", onKeyDown);\n }\n }));\n }, [editor]);\n return null;\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport { COMMAND_PRIORITY_NORMAL, DROP_COMMAND, KEY_DOWN_COMMAND, PASTE_COMMAND } from \"lexical\";\nimport { useEffect } from \"react\";\n/**\n * This plugin prevents the backslash or forward slash key from being typed, or pasted or dragged.\n * @returns `null`. This plugin has no DOM presence.\n */\nexport function CommandMenuPlugin({ logger }) {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n return mergeRegister(\n // When the backslash or forward slash key is typed.\n editor.registerCommand(KEY_DOWN_COMMAND, (event) => {\n if (event.key !== \"\\\\\" && event.key !== \"/\")\n return false;\n event.preventDefault();\n return true;\n }, COMMAND_PRIORITY_NORMAL), \n // When the backslash or forward slash character is pasted into the editor.\n editor.registerCommand(PASTE_COMMAND, (event) => {\n const text = event.clipboardData?.getData(\"text/plain\");\n if (!text || (!text.includes(\"\\\\\") && !text.includes(\"/\")))\n return false;\n logger?.info(\"CommandMenuPlugin: paste containing backslash or forward slash ignored.\");\n event.preventDefault();\n return true;\n }, COMMAND_PRIORITY_NORMAL), \n // When the backslash or forward slash character is dragged into the editor.\n editor.registerCommand(DROP_COMMAND, (event) => {\n const text = event.dataTransfer?.getData(\"text/plain\");\n if (!text || (!text.includes(\"\\\\\") && !text.includes(\"/\")))\n return false;\n logger?.info(\"CommandMenuPlugin: drag containing backslash or forward slash ignored.\");\n event.preventDefault();\n return true;\n }, COMMAND_PRIORITY_NORMAL));\n }, [editor, logger]);\n return null;\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\n/**\n * Adapted from https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/ContextMenuPlugin/index.tsx\n */\nimport { copySelection, cutSelection, pasteSelection, pasteSelectionAsPlainText, registerEmptyCopyGuard, } from \"./clipboard.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport { isImmutableChapterElement } from \"shared\";\nfunction ContextMenuItem({ index, isSelected, onClick, onMouseEnter, option, }) {\n let className = \"item\";\n if (isSelected) {\n className += \" selected\";\n }\n if (option.isDisabled) {\n className += \" disabled\";\n }\n return (_jsx(\"li\", { tabIndex: -1, className: className, role: \"option\", \"aria-selected\": isSelected, \"aria-disabled\": option.isDisabled, id: \"typeahead-item-\" + index, onMouseEnter: onMouseEnter, onClick: option.isDisabled ? undefined : onClick, children: _jsx(\"span\", { className: \"text\", children: option.title }) }));\n}\nfunction ContextMenu({ options, selectedItemIndex, onOptionClick, onOptionMouseEnter, }) {\n return (_jsx(\"div\", { className: \"typeahead-popover\", children: _jsx(\"ul\", { children: options.map((option, i) => (_jsx(ContextMenuItem, { index: i, isSelected: selectedItemIndex === i, onClick: () => onOptionClick(option, i), onMouseEnter: () => onOptionMouseEnter(i), option: option }, option.key))) }) }));\n}\nlet optionKeyCounter = 0;\nexport class ContextMenuOption {\n key;\n title;\n onSelect;\n isDisabled;\n constructor(title, options) {\n this.key = `context-menu-option-${optionKeyCounter++}`;\n this.title = title;\n this.onSelect = options.onSelect.bind(this);\n this.isDisabled = options.isDisabled || false;\n }\n}\nexport function ContextMenuPlugin({ options: extraOptions, } = {}) {\n const [editor] = useLexicalComposerContext();\n const [isReadonly, setIsReadonly] = useState(() => !editor.isEditable());\n const [menuState, setMenuState] = useState({\n isOpen: false,\n x: 0,\n y: 0,\n });\n const [selectedIndex, setSelectedIndex] = useState(undefined);\n const options = useMemo(() => {\n const builtIn = [\n // Cut/Copy with nothing selected leave the clipboard alone rather than writing a placeholder\n // over it — `registerEmptyCopyGuard` (mounted below) claims the command, so no selection\n // check is needed here. They are not disabled in that case, because this option list is\n // built once per editor rather than per menu opening, so its `isDisabled` flags cannot track\n // the live selection.\n new ContextMenuOption(`Cut`, {\n onSelect: () => {\n cutSelection(editor);\n },\n isDisabled: isReadonly,\n }),\n new ContextMenuOption(`Copy`, {\n onSelect: () => {\n copySelection(editor);\n },\n }),\n new ContextMenuOption(`Paste`, {\n onSelect: () => {\n pasteSelection(editor);\n },\n isDisabled: isReadonly,\n }),\n new ContextMenuOption(`Paste as Plain Text`, {\n onSelect: () => {\n pasteSelectionAsPlainText(editor);\n },\n isDisabled: isReadonly,\n }),\n ];\n const extra = (extraOptions ?? []).map((opt) => new ContextMenuOption(opt.title, { onSelect: opt.onSelect, isDisabled: opt.isDisabled }));\n return [...builtIn, ...extra];\n }, [editor, isReadonly, extraOptions]);\n const closeMenu = useCallback(() => {\n setMenuState((prev) => ({ ...prev, isOpen: false }));\n setSelectedIndex(undefined);\n }, []);\n // This plugin is exported on its own, so a host can mount it without `ClipboardPlugin` — and its\n // Cut/Copy options would then hit the unguarded synthesized-copy path, overwriting whatever the\n // clipboard already held with `@lexical/clipboard`'s hidden placeholder character. Registering\n // the guard here keeps the plugin self-sufficient; a second registration alongside\n // `ClipboardPlugin`'s is harmless, since both listeners sit at the same priority and the first\n // one to claim an empty selection stops propagation before the other runs.\n useEffect(() => registerEmptyCopyGuard(editor), [editor]);\n // Register context menu event on editor root\n useEffect(() => {\n const handleContextMenu = (event) => {\n const target = event.target;\n if (editor.getRootElement() === target || isImmutableChapterElement(target)) {\n return;\n }\n event.preventDefault();\n setMenuState({ isOpen: true, x: event.clientX, y: event.clientY });\n setSelectedIndex(undefined);\n };\n return editor.registerRootListener((rootElement, prevRootElement) => {\n prevRootElement?.removeEventListener(\"contextmenu\", handleContextMenu);\n if (!rootElement)\n return;\n rootElement.addEventListener(\"contextmenu\", handleContextMenu);\n });\n }, [editor]);\n // Close menu on scroll\n useEffect(() => {\n if (!menuState.isOpen)\n return;\n const handleScroll = () => {\n closeMenu();\n };\n globalThis.addEventListener(\"scroll\", handleScroll, true);\n return () => globalThis.removeEventListener(\"scroll\", handleScroll, true);\n }, [menuState.isOpen, closeMenu]);\n // Close menu on click outside\n useEffect(() => {\n if (!menuState.isOpen)\n return;\n const handlePointerDown = () => {\n closeMenu();\n };\n document.addEventListener(\"pointerdown\", handlePointerDown);\n return () => document.removeEventListener(\"pointerdown\", handlePointerDown);\n }, [menuState.isOpen, closeMenu]);\n // Keyboard navigation and close on Escape\n useEffect(() => {\n if (!menuState.isOpen)\n return;\n const handleKeyDown = (event) => {\n if (event.key === \"Escape\") {\n closeMenu();\n }\n else if (event.key === \"ArrowDown\") {\n event.preventDefault();\n event.stopPropagation();\n setSelectedIndex((prev) => (prev === undefined ? 0 : (prev + 1) % options.length));\n }\n else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n event.stopPropagation();\n setSelectedIndex((prev) => prev === undefined ? options.length - 1 : (prev - 1 + options.length) % options.length);\n }\n else if (event.key === \"Enter\" && selectedIndex !== undefined) {\n event.preventDefault();\n event.stopPropagation();\n const option = options[selectedIndex];\n if (option && !option.isDisabled) {\n editor.update(() => {\n option.onSelect();\n });\n closeMenu();\n }\n }\n };\n // Use capture phase so this fires before Lexical's own keydown handler,\n // which would otherwise consume arrow keys and move the editor cursor.\n document.addEventListener(\"keydown\", handleKeyDown, true);\n return () => document.removeEventListener(\"keydown\", handleKeyDown, true);\n }, [menuState.isOpen, closeMenu, options, selectedIndex, editor]);\n useEffect(() => editor.registerEditableListener((editable) => {\n setIsReadonly(!editable);\n }), [editor]);\n const menuRef = useRef(null);\n // Clamp menu position to viewport bounds before first paint to prevent off-screen rendering.\n useLayoutEffect(() => {\n const menu = menuRef.current;\n if (!menu)\n return;\n const { width, height } = menu.getBoundingClientRect();\n const clampedLeft = Math.max(0, Math.min(menuState.x, globalThis.innerWidth - width));\n const clampedTop = Math.max(0, Math.min(menuState.y, globalThis.innerHeight - height));\n menu.style.left = `${clampedLeft}px`;\n menu.style.top = `${clampedTop}px`;\n menu.style.visibility = \"visible\";\n }, [menuState.isOpen, menuState.x, menuState.y]);\n if (!menuState.isOpen)\n return null;\n return ReactDOM.createPortal(_jsx(\"div\", { ref: menuRef, className: \"typeahead-popover auto-embed-menu\", style: {\n left: menuState.x,\n position: \"fixed\",\n top: menuState.y,\n userSelect: \"none\",\n visibility: \"hidden\",\n width: 200,\n zIndex: 9999,\n }, onPointerDown: (e) => e.stopPropagation(), children: _jsx(ContextMenu, { options: options, selectedItemIndex: selectedIndex, onOptionClick: (option) => {\n if (!option.isDisabled) {\n editor.update(() => {\n option.onSelect();\n });\n closeMenu();\n }\n }, onOptionMouseEnter: (index) => {\n setSelectedIndex(index);\n } }) }), document.body);\n}\n","import { $isImmutableNoteCallerNode } from \"../../nodes/usj\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $createRangeSelectionFromDom, $getNearestNodeFromDOMNode, $getSelection, $isDecoratorNode, $isRangeSelection, $setSelection, COMMAND_PRIORITY_CRITICAL, getDOMSelection, isDOMTextNode, isSelectionWithinEditor, SELECTION_CHANGE_COMMAND, } from \"lexical\";\nimport { useEffect, useRef } from \"react\";\n/**\n * Whether `point` is the DOM-ORDER START of `range` — which is what decides the direction a point\n * inside a decorator has to grow, and cannot be read off anchor/focus: a backward drag makes the\n * anchor the range's end. Both the node and the offset are compared, so a range whose two ends sit\n * in the SAME container is still told apart by its offsets.\n */\nfunction isRangeStart(range, point) {\n return range.startContainer === point.node && range.startOffset === point.offset;\n}\n/**\n * The edge of the decorator a COLLAPSED point is nearest to. A caret is a single position rather\n * than a direction, so the only thing to honour is which side of the glyph the user pointed at:\n * the first half of a text node goes before it, the rest after it. A point that is not in text\n * names no position within the glyph at all, so it takes the leading edge.\n */\nfunction nearestBoundary(point) {\n if (!isDOMTextNode(point.node))\n return \"before\";\n const length = point.node.nodeValue?.length ?? 0;\n // Doubled rather than halved so the comparison stays in integers for an odd-length glyph.\n return point.offset * 2 < length ? \"before\" : \"after\";\n}\n/**\n * Whether another rule already owns where a selection landing on this decorator belongs, so\n * normalizing it here would take the landing away from that rule.\n *\n * A note caller is the one such decorator. `TrailingNoteCaretGuardPlugin` answers a landing on the\n * caller of a collapsed note that ends its block by hosting a caret PAST the whole note, and it\n * recognizes that landing precisely by the selection being unresolved — so supplying a selection\n * for it silently disables the repair and leaves the caret in the note's hidden body, which is the\n * position that repair exists to avoid. Every boundary of the caller is inside the note anyway:\n * between the note's opening glyph and the caller, or between the caller and its hidden content.\n * Neither is a position a collapsed note can draw a caret at, so there is nothing here to win.\n */\nfunction $hasDedicatedSelectionOwner(node) {\n return $isImmutableNoteCallerNode(node);\n}\n/**\n * The DOM point at `boundary` of the decorator that CONTAINS `point`, or `undefined` when `point`\n * is not inside one.\n *\n * \"Inside\" means strictly inside: the decorator's own element with an offset — the boundary form\n * Lexical already resolves to an element point beside the decorator — is left alone, and so is a\n * point in any node the decorator does not contain.\n *\n * Mutating context only in the sense that it reads the active editor state; it changes nothing.\n */\nfunction $decoratorBoundary(editor, point, boundary) {\n const node = $getNearestNodeFromDOMNode(point.node);\n if (!$isDecoratorNode(node) || $hasDedicatedSelectionOwner(node))\n return undefined;\n const element = editor.getElementByKey(node.getKey());\n if (!element || element === point.node || !element.contains(point.node))\n return undefined;\n const parent = element.parentNode;\n if (!parent)\n return undefined;\n const index = Array.prototype.indexOf.call(parent.childNodes, element);\n if (index < 0)\n return undefined;\n return { node: parent, offset: boundary === \"before\" ? index : index + 1 };\n}\n/**\n * Hand Lexical the selection it refused to resolve, with each end that lands inside a decorator moved\n * to that decorator's own boundaries.\n *\n * The DOM selection is READ here and written only by Lexical's own reconciler pass, which\n * `isPointerDown` decides the timing of — see the plugin doc comment for the rule and why it turns on\n * the button.\n *\n * Mutating (sets the editor's selection): call inside `editor.update()` or a command handler.\n *\n * @param isPointerDown Whether a pointer button is held, i.e. whether a drag is in flight.\n * @returns Whether the browser's DOM selection has been left holding points Lexical cannot resolve,\n * so it still has to be materialized when the button comes up.\n */\nfunction $snapSelectionToDecoratorBoundaries(editor, isPointerDown) {\n // Only an UNRESOLVED arrival is this plugin's business. A DOM point inside a decorator always\n // nulls the WHOLE selection (see the plugin doc comment), so a selection Lexical did produce\n // cannot have an end inside one — and reading the DOM over it would clobber a caret that\n // something else placed deliberately, since a programmatic move leaves the DOM a commit behind.\n if ($getSelection())\n return false;\n const rootElement = editor.getRootElement();\n const domSelection = getDOMSelection(rootElement?.ownerDocument.defaultView ?? null);\n if (!domSelection || domSelection.rangeCount === 0)\n return false;\n const { anchorNode, anchorOffset, focusNode, focusOffset } = domSelection;\n if (!anchorNode || !focusNode)\n return false;\n if (!isSelectionWithinEditor(editor, anchorNode, focusNode))\n return false;\n const anchor = { node: anchorNode, offset: anchorOffset };\n const focus = { node: focusNode, offset: focusOffset };\n let snappedAnchor;\n let snappedFocus;\n if (domSelection.isCollapsed) {\n snappedAnchor = $decoratorBoundary(editor, anchor, nearestBoundary(anchor));\n snappedFocus = snappedAnchor;\n }\n else {\n // A range grows OUTWARD, so the decorator ends up wholly inside the selection rather than\n // clipped: whichever end leads in DOM order takes the leading edge and the other the trailing\n // one. That is what makes a drag begun on `\\fig ` carry the whole glyph, and a drag that runs\n // into `|src=\"…\"` carry the whole attribute run.\n const anchorLeads = isRangeStart(domSelection.getRangeAt(0), anchor);\n snappedAnchor = $decoratorBoundary(editor, anchor, anchorLeads ? \"before\" : \"after\");\n snappedFocus = $decoratorBoundary(editor, focus, anchorLeads ? \"after\" : \"before\");\n }\n if (!snappedAnchor && !snappedFocus)\n return false;\n const nextAnchor = snappedAnchor ?? anchor;\n const nextFocus = snappedFocus ?? focus;\n // The snapped ends reach Lexical as a selection-SHAPED value rather than by this plugin writing\n // them into the DOM, because mid-drag the browser's own points have to survive untouched (see the\n // plugin doc comment); the DOM write, when it happens, is Lexical's own reconciler pass. Everything\n // `$createRangeSelectionFromDom` reads is present — see {@link DomSelectionPoints}, which is what\n // makes the one cast honest rather than a widening of a partial object.\n const snapped = {\n anchorNode: nextAnchor.node,\n anchorOffset: nextAnchor.offset,\n focusNode: nextFocus.node,\n focusOffset: nextFocus.offset,\n };\n const selection = $createRangeSelectionFromDom(snapped, editor);\n if (!selection)\n return false;\n $setSelection(selection);\n // `dirty` is how a selection asks `$commitPendingUpdates` (lexical/LexicalUpdates) to sync itself\n // to the browser; with it clear, the commit's `updateDOMSelection` — the ONE place in the commit\n // path that touches the browser's selection, `removeAllRanges()` included — is not reached for an\n // update that dirties no nodes. So clearing it mid-drag is what leaves the browser's base alone,\n // and leaving it set everywhere else is what keeps the DOM in the resolvable boundary form. Order\n // matters: `$setSelection` sets the flag.\n //\n // `SKIP_DOM_SELECTION_TAG` expresses the same intent and is read in the same guard, but it is not\n // confined to one commit: `$commitPendingUpdates` only empties `editor._updateTags` when the update\n // dirtied nodes, and a selection repair dirties none — so the tag would survive into the next\n // commit and swallow the DOM write for the caret move after it.\n selection.dirty = !isPointerDown;\n return isPointerDown;\n}\n/**\n * Keeps a selection that lands inside a DECORATOR usable, by normalizing the offending end(s) to\n * the decorator's own boundaries.\n *\n * A decorator is atomic: no selection point exists inside one, and Lexical says so by refusing such\n * a point outright. `$internalResolveSelectionPoint` (lexical/LexicalSelection) resolves a DOM\n * point whose node sits INSIDE a decorator's element to `null` — the walk up from the DOM node\n * reaches the decorator, which is not a `TextNode`, and the text branch returns `null` — and\n * `$internalResolveSelectionPoints` then nulls the WHOLE selection, not just that end. On commit,\n * `updateDOMSelection` calls `domSelection.removeAllRanges()` because the previous selection was\n * inside the editor, so the visible selection is destroyed too. (A Lexical upgrade that changes\n * either function is the thing to re-read before trusting this plugin.)\n *\n * That matters because Standard view renders read-only USFM bytes as decorators whose glyph text is\n * an ordinary DOM text child of the decorator's own element: an `UnknownNode`'s `\\fig `, its\n * `|src=\"…\"` attribute run, and its `\\fig*` closer are each an `ImmutableTypedTextNode`. Those bytes\n * are selectable and copyable by design — that is what a read-only block is FOR\n * (`OpaqueBlockGuardPlugin`) — but pointing at them is exactly what produces the interior DOM point\n * Lexical will not resolve. The measured results: a drag begun on the `\\fig ` glyph left the editor\n * with no selection at all, so Ctrl+C reached only the empty-copy guard and the clipboard was never\n * written; and a drag from the caption into the attribute run stopped dead at the caption's end.\n *\n * So each offending end is moved to the decorator's boundary IN ITS PARENT — the one form of the\n * position Lexical does resolve, into an element point beside the decorator. A range grows outward,\n * so the glyph is wholly in or wholly out and never clipped; a caret takes the nearer side.\n *\n * WHERE those boundary points go turns on one thing: whether a pointer button is down.\n *\n * While it is down — a drag in flight — they go to the editor state ALONE, and the browser's DOM\n * selection is left exactly as the browser wrote it. Chromium extends a drag only from a base it\n * placed itself, so replacing that base with a scripted element point stops the drag dead: each\n * further mouse move only re-places a collapsed caret under the pointer, no range is ever formed, and\n * Ctrl+C reaches the empty-copy guard again — the very failure this repair exists to fix. (Chromium\n * also clamps a drag begun inside a `contenteditable=\"false\"` island to that island, which is not\n * ours to change either.) So mid-drag the editor state carries the boundary form that copy and every\n * plugin read, while the browser keeps the text position it is willing to extend from.\n *\n * On the release — and immediately for any arrival with NO button down, which is to say a keyboard\n * extend such as Shift+Arrow into a glyph, or a programmatic move — the boundary form is written to\n * the DOM through Lexical's ordinary reconciler pass. That is the hazard this rule closes rather than\n * a cosmetic touch: Lexical re-derives an update's selection FROM THE DOM whenever the update is not\n * attributable to a DOM event it trusts (`$internalCreateRangeSelection` in lexical/LexicalSelection\n * consults `window.event`), so a DOM selection left on raw interior points turns the next event-less\n * `editor.update()` — one opened from a timer, a microtask or a React effect — into a `null`\n * selection, and that commit's `removeAllRanges()` takes the user's visible selection with it.\n * Whenever no drag is in flight the DOM holds the boundary form, so any such re-derivation lands back\n * on the same selection.\n *\n * The release is a safe moment to materialize from: inside a `pointerup` listener `window.event.type`\n * is `\"pointerup\"`, which is not one of the types `$internalCreateRangeSelection` re-reads the DOM\n * for, so that update clones the snapped selection out of the editor state instead of re-resolving\n * the interior points it is there to replace.\n *\n * Runs at `COMMAND_PRIORITY_CRITICAL` so the caret guards that read the selection see the repaired\n * one rather than none, and never claims the command — a selection change is nobody's to own.\n * Lexical dispatches `SELECTION_CHANGE_COMMAND` even for a selection it resolved to `null`, and at\n * that moment the DOM selection is still intact, which is what makes the repair possible at all.\n *\n * Not gated on view options: decorators exist in every view. The one decorator it steps around is\n * the note caller, whose landings another rule already owns — see\n * {@link $hasDedicatedSelectionOwner}.\n *\n * @returns Always `null`; this plugin renders no UI.\n */\nexport function DecoratorBoundarySelectionPlugin() {\n const [editor] = useLexicalComposerContext();\n const isPointerDown = useRef(false);\n const isMaterializePending = useRef(false);\n useEffect(() => {\n // Only the PRIMARY button starts a drag Chromium will extend a selection from, and only a\n // press that starts one may set this flag: a secondary-button press can open the platform's\n // NATIVE context menu (`ContextMenuPlugin` deliberately lets it through for some targets),\n // which on Windows and Linux grabs the mouse so the page never sees the matching `pointerup`.\n // The flag would then stay set with no drag in flight, and every later arrival — a click, a\n // Shift+Arrow extend — would be treated as one, leaving the DOM selection stranded on points\n // Lexical cannot resolve. The button is duck-typed rather than read through `instanceof\n // PointerEvent`: not every host defines that global (jsdom before 27 has none), and a press that\n // carries no button at all still counts as a primary one.\n const markDown = (event) => {\n if (\"button\" in event && event.button !== 0)\n return;\n isPointerDown.current = true;\n };\n // BOTH ends are listened for on the DOCUMENT in the capture phase, and the release on\n // `pointercancel` as well as `pointerup`: a drag that starts in the editor can finish anywhere,\n // and a flag that fails to clear would leave every later keyboard move reading as a drag and the\n // DOM selection stranded on points Lexical cannot resolve. The press has to be heard on the\n // document rather than the root for the mirror-image reason — Chromium places a caret INSIDE\n // the editor for a press that lands in the container just outside the contenteditable root, so\n // a root-scoped listener misses the start of a real drag and the first snap is materialized into\n // the DOM, which is exactly what stops a drag dead. A press with no editor selection behind it\n // costs nothing: the flag is only ever read while repairing a selection inside the editor.\n // (`NoteShellCaretGuardPlugin` registers all three on the document for the same reason.)\n //\n // `release` opens an `editor.update()` from a DOM listener, a deliberate exception to the rule\n // that mutations enter through commands (`NoteNodePlugin` and `ContextMenuPlugin` make the same\n // one). A command would not remove the dependency that makes the call safe: it must run\n // synchronously inside the trusted `pointerup`/`pointercancel` dispatch, with no update of this\n // editor already active. Lexical queues an update requested during another one, and the queued\n // callback runs after that dispatch has ended, where `window.event` no longer says `pointerup`\n // and the update re-derives its selection from the raw interior DOM points instead of cloning the\n // snapped one.\n const release = () => {\n isPointerDown.current = false;\n if (!isMaterializePending.current)\n return;\n isMaterializePending.current = false;\n // Re-dirty the snapped selection so Lexical's ordinary reconciler pass writes the boundary form\n // into the DOM, now that no drag needs the browser's own base any more.\n editor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection))\n selection.dirty = true;\n });\n };\n // One listener does both the wiring and the unwiring: Lexical calls it with the current root at\n // registration, with each replacement root on a swap, and with `(null, previous)` on teardown.\n return editor.registerRootListener((rootElement, prevRootElement) => {\n const previousDocument = prevRootElement?.ownerDocument;\n previousDocument?.removeEventListener(\"pointerdown\", markDown, true);\n previousDocument?.removeEventListener(\"pointerup\", release, true);\n previousDocument?.removeEventListener(\"pointercancel\", release, true);\n isPointerDown.current = false;\n isMaterializePending.current = false;\n const currentDocument = rootElement?.ownerDocument;\n currentDocument?.addEventListener(\"pointerdown\", markDown, true);\n currentDocument?.addEventListener(\"pointerup\", release, true);\n currentDocument?.addEventListener(\"pointercancel\", release, true);\n });\n }, [editor]);\n useEffect(() => {\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {\n if ($snapSelectionToDecoratorBoundaries(editor, isPointerDown.current))\n isMaterializePending.current = true;\n return false;\n }, COMMAND_PRIORITY_CRITICAL);\n }, [editor]);\n return null;\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { IS_APPLE } from \"@lexical/utils\";\nimport { COMMAND_PRIORITY_CRITICAL, KEY_DOWN_COMMAND } from \"lexical\";\nimport { useEffect } from \"react\";\n/**\n * Prevent undo and redo keyboard shortcuts while preserving command-based undo/redo.\n * @returns `null`. This plugin has no DOM presence.\n */\nexport function DisableHistoryShortcutsPlugin() {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n return editor.registerCommand(KEY_DOWN_COMMAND, (event) => {\n const { key, shiftKey, metaKey, ctrlKey, altKey } = event;\n if (!(IS_APPLE ? metaKey : ctrlKey) || altKey)\n return false;\n const normalizedKey = key.toLowerCase();\n const isUndo = normalizedKey === \"z\" && !shiftKey;\n const isRedo = normalizedKey === \"y\" || (normalizedKey === \"z\" && shiftKey);\n if (!isUndo && !isRedo)\n return false;\n event.preventDefault();\n return true;\n }, COMMAND_PRIORITY_CRITICAL);\n }, [editor]);\n return null;\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useLayoutEffect } from \"react\";\nexport function EditablePlugin({ isEditable }) {\n const [editor] = useLexicalComposerContext();\n useLayoutEffect(() => {\n editor.setEditable(isEditable);\n }, [editor, isEditable]);\n return null;\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport { $addUpdateTag, $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, SELECTION_CHANGE_COMMAND, TextNode, } from \"lexical\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { $caretHostAtBoundary, $createCursorPlaceholderNode, $isCursorPlaceholderOnlyText, $placeCaretAtBoundary, $removeCursorPlaceholder, CURSOR_CHANGE_TAG, CURSOR_PLACEHOLDER_CHAR, isCursorPlaceholderOnly, } from \"shared\";\n/** Whether `key` currently resolves to a bare cursor-host text node (only placeholder chars). */\nfunction $isPlaceholderHost(key) {\n return !!key && $isCursorPlaceholderOnlyText($getNodeByKey(key));\n}\n/**\n * The transient caret-host lifecycle, shared by every guard that needs one.\n *\n * Some caret positions in this editor can only be expressed as an ELEMENT point — a boundary with\n * no text node on it — because what surrounds them renders no text the browser can draw an\n * insertion point in: a childless decorator such as an immutable verse number, or a collapsed note\n * whose content is hidden. Lexical is perfectly happy with such a caret; the browser paints\n * nothing, so the user sees no cursor and the next keypress becomes the page's rather than the\n * editor's. The repair is to materialize a zero-width-space text node at that position and move the\n * caret into it.\n *\n * The repair is stated once, as \"put the caret at the boundary past this node, and give that\n * boundary something to render the caret in if nothing there already does\". Stating it that way\n * lets a guard drive it from an arrival the caret's own resting place cannot express — a click that\n * came down inside hidden content, or produced no caret at all — and get the same position and the\n * same single host as the arrival that does announce itself.\n *\n * The host is TRANSIENT, and this hook is what makes that true in one place rather than once per\n * guard: it is created only when the caret comes to rest at such a position, removed as soon as the\n * caret leaves or the editor blurs, and stripped the moment real text is typed into it. It never\n * accumulates and never appears in a document nobody put a caret into.\n *\n * Every mutation carries {@link CURSOR_CHANGE_TAG}, which is in `blackListedChangeTags` — so the\n * commit never reaches the host application's USJ-change handler and produces no delta op. The USJ\n * serializer drops a placeholder-only text node as well, and the collab coordinate systems give it\n * zero length, so a host cannot reach saved Scripture or a peer even if one somehow outlived a\n * commit. All placeholder handling is scoped to the tracked host by node key — a zero-width space\n * is legitimate content in some scripts (Thai/Khmer/Lao line breaks) and is never touched.\n *\n * Unlike the arrow-driven `CursorHandler` placeholder system (perf-react), this hosts a *resting*\n * caret.\n *\n * @param $caretHostAnchor - The guard's own rule for when a host is needed. Must be stable across\n * renders (declare it at module scope), since it is an effect dependency.\n * @returns The repair step itself, for a guard that has an arrival of its own to drive it from.\n */\nexport function useTransientCaretHost($caretHostAnchor) {\n const [editor] = useLexicalComposerContext();\n const hostKeyRef = useRef(undefined);\n // See CaretHostRepair. Deliberately free of `editor`: everything it touches is resolved from the\n // active editor state, so it is safe to hand to a caller that is already inside an update.\n const $repairCaret = useCallback((target) => {\n const selection = $getSelection();\n const anchorKey = $isRangeSelection(selection) && selection.isCollapsed() ? selection.anchor.key : undefined;\n const staleKey = hostKeyRef.current;\n // A tracked host that is no longer a bare placeholder (typed into) is no longer ours.\n const staleIsBareHost = $isPlaceholderHost(staleKey);\n if (staleKey && !staleIsBareHost)\n hostKeyRef.current = undefined;\n // The host the caret was just put into, which is therefore not stale whatever it was before.\n let occupiedKey;\n if (target) {\n // The repair is the same however the caret got here: put it at the boundary just past the\n // named node, materializing a host there only when nothing already renders one. Reusing an\n // existing host is what keeps a second arrival at the same position — a click, once an\n // earlier arrival has already put a host there — from stacking hosts.\n const parent = target.getParentOrThrow();\n const boundary = target.getIndexWithinParent() + 1;\n const existing = $caretHostAtBoundary(parent, boundary);\n if (existing) {\n // Track the adopted host too: every cleanup path (the stale-host pass below, blur,\n // unmount) acts solely on hostKeyRef, so a host reused from another instance — or one\n // this instance forgot after $stripPlaceholderOnEdit cleared the ref — would otherwise\n // never be removed when the caret leaves it.\n hostKeyRef.current = existing.getKey();\n occupiedKey = existing.getKey();\n }\n else {\n const host = $createCursorPlaceholderNode();\n target.insertAfter(host);\n hostKeyRef.current = host.getKey();\n occupiedKey = host.getKey();\n }\n $placeCaretAtBoundary(parent, boundary);\n }\n // A tracked host the caret has left, and has not just been put back into, should go.\n if (staleKey && staleIsBareHost && staleKey !== anchorKey && staleKey !== occupiedKey) {\n const stale = $getNodeByKey(staleKey);\n if ($isTextNode(stale))\n stale.remove();\n if (hostKeyRef.current === staleKey)\n hostKeyRef.current = undefined;\n }\n }, []);\n useEffect(() => {\n // Repair the caret's resting place and/or drop a stale host it has left. Runs from\n // SELECTION_CHANGE (a command, the sanctioned place to mutate), which Lexical dispatches\n // from WITHIN the update that applies the new selection — so `$getSelection()` must be read\n // DIRECTLY here, where it resolves against the pending state. Wrapping the reads in\n // `editor.getEditorState().read(...)` evaluated the PREVIOUS commit: the anchor was the\n // caret's old position, so no host was created where the caret just arrived, and on the\n // next move the stale-host pass treated the still-current host as abandoned and yanked the\n // caret back to the previous boundary. A nested `editor.update(...)` is queued rather than\n // run inline while an update is active, so the repair also runs directly, tagging the\n // active update instead. It converges: after the repair the caret sits in a host past the\n // anchor, so the next SELECTION_CHANGE finds nothing to repair and no stale host.\n const $syncCaretHost = () => {\n const target = $caretHostAnchor();\n const selection = $getSelection();\n const anchorKey = $isRangeSelection(selection) && selection.isCollapsed() ? selection.anchor.key : undefined;\n const staleKey = hostKeyRef.current;\n // Either a position to repair, or a tracked host the caret is no longer resting in.\n const hasWork = !!target || (!!staleKey && staleKey !== anchorKey);\n if (!hasWork)\n return;\n $addUpdateTag(CURSOR_CHANGE_TAG);\n $repairCaret(target);\n };\n /**\n * Strip the placeholder once real text is typed into *our* host, fixing the caret offset.\n * Scoped to the tracked host node by key so a legitimate ZWSP elsewhere (e.g. a Thai/Khmer\n * line-break in real text) is never touched.\n */\n const $stripPlaceholderOnEdit = (node) => {\n if (node.getKey() !== hostKeyRef.current)\n return;\n const text = node.getTextContent();\n // Still a bare host (nothing typed yet), or no placeholder to remove: leave it.\n if (isCursorPlaceholderOnly(text) || !text.includes(CURSOR_PLACEHOLDER_CHAR))\n return;\n const selection = $getSelection();\n const anchorOffset = $isRangeSelection(selection) &&\n selection.isCollapsed() &&\n selection.anchor.key === node.getKey()\n ? selection.anchor.offset\n : undefined;\n $removeCursorPlaceholder(node);\n hostKeyRef.current = undefined;\n if (anchorOffset !== undefined) {\n const removedBefore = text.slice(0, anchorOffset).split(CURSOR_PLACEHOLDER_CHAR).length - 1;\n const nextOffset = Math.max(0, anchorOffset - removedBefore);\n node.select(nextOffset, nextOffset);\n }\n };\n const unregister = mergeRegister(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {\n $syncCaretHost();\n return false;\n }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(BLUR_COMMAND, () => {\n const key = hostKeyRef.current;\n if (!key)\n return false;\n let isBareHost = false;\n editor.getEditorState().read(() => {\n isBareHost = $isPlaceholderHost(key);\n });\n if (isBareHost) {\n editor.update(() => {\n const host = $getNodeByKey(key);\n if ($isTextNode(host))\n host.remove();\n }, { tag: CURSOR_CHANGE_TAG });\n }\n hostKeyRef.current = undefined;\n return false;\n }, COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(TextNode, $stripPlaceholderOnEdit));\n // Drop the tracked key when the editor changes so it can't resolve into a different editor.\n return () => {\n unregister();\n hostKeyRef.current = undefined;\n };\n }, [editor, $caretHostAnchor, $repairCaret]);\n return $repairCaret;\n}\n","import { useTransientCaretHost } from \"./transientCaretHost\";\nimport { $getSelection, $isElementNode, $isRangeSelection } from \"lexical\";\nimport { $caretHostAtBoundary } from \"shared\";\nimport { $isSomeVerseNode } from \"../../nodes/usj\";\n/**\n * The verse marker after which an empty-verse caret host is needed, or `undefined`.\n *\n * A verse whose content has all been deleted collapses to just its marker — an\n * `ImmutableVerseNode` is a childless decorator, so the caret can only land as an element point\n * wedged between markers, which the browser renders with no visible caret. This detects that state:\n * a collapsed element-type caret sitting immediately after a verse marker that is followed by\n * nothing, or by another verse marker (i.e. no `TextNode` to host the caret).\n *\n * Returns `undefined` when a text node already follows the marker (real content, or an existing\n * placeholder host), or when the caret is not at such a boundary.\n *\n * Read-only: call inside `editor.getEditorState().read()`.\n */\nexport function $emptyVerseNeedingHost() {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return undefined;\n const { anchor } = selection;\n if (anchor.type !== \"element\")\n return undefined;\n const element = anchor.getNode();\n if (!$isElementNode(element))\n return undefined;\n const children = element.getChildren();\n const verse = children[anchor.offset - 1];\n if (!$isSomeVerseNode(verse))\n return undefined;\n // A text node already hosts the caret at this boundary (real content or an existing placeholder).\n if ($caretHostAtBoundary(element, anchor.offset))\n return undefined;\n const following = children[anchor.offset];\n // Nothing, or another verse marker, follows: this verse has no caret host.\n if (following === undefined || $isSomeVerseNode(following))\n return verse;\n return undefined;\n}\n/**\n * Keeps a visible caret in a verse whose text has been fully deleted.\n *\n * A verse number is rendered by a childless `ImmutableVerseNode` decorator, so once a verse has no\n * text the caret can only rest as an element point between decorators — which the browser draws\n * with no visible caret. This plugin drops a zero-width-space \"caret host\" text node into such an\n * empty verse and moves the caret into it, so the insertion point stays visible and typing lands in\n * the verse. {@link useTransientCaretHost} owns the host's lifetime — created on arrival, removed on\n * departure or blur, stripped the moment real text is typed — and the exclusions that keep it out of\n * saved Scripture and out of collaborative traffic; this file supplies only the rule for WHERE one\n * is needed. `TrailingNoteCaretGuardPlugin` supplies the other rule.\n *\n * Unlike the arrow-driven `CursorHandler` placeholder system (perf-react), this hosts a *resting*\n * caret and is aware of verse markers, so it fits the platform editor's immutable verse numbers.\n *\n * @returns Always `null`; this plugin renders no UI.\n */\nexport function EmptyVerseCaretGuardPlugin() {\n useTransientCaretHost($emptyVerseNeedingHost);\n return null;\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $addUpdateTag, CLEAR_HISTORY_COMMAND, SKIP_DOM_SELECTION_TAG } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { EXTERNAL_USJ_MUTATION_TAG } from \"shared\";\n/**\n * A plugin component that updates the state of the lexical editor when incoming Scripture changes.\n * @param scripture - Scripture data.\n * @param scriptureRef - Optional ref to scripture data. If provided, reads from ref at update time\n * to get the most current value (useful when options change triggers state updates).\n * @param nodeOptions - Options for each node.\n * @param editorAdaptor - Editor adaptor.\n * @param viewOptions - View options of the editor.\n * @param logger - Logger instance.\n * @returns null, i.e. no DOM elements.\n */\nexport function LoadStatePlugin({ scripture, scriptureRef, nodeOptions, editorAdaptor, viewOptions, logger, }) {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n editorAdaptor.initialize?.(nodeOptions, logger);\n }, [editorAdaptor, logger, nodeOptions]);\n useEffect(() => {\n // Read scripture from ref if available (to get latest value after state updates),\n // otherwise fall back to the prop value\n const currentScripture = scriptureRef?.current ?? scripture;\n editorAdaptor.reset?.();\n const serializedEditorState = editorAdaptor.serializeEditorState(currentScripture, viewOptions);\n if (serializedEditorState == null) {\n logger?.warn(\"LoadStatePlugin: serializedEditorState was null or undefined. Skipping editor update.\");\n return;\n }\n try {\n const editorState = editor.parseEditorState(serializedEditorState);\n // Use queueMicrotask to defer the editor update outside of React's lifecycle,\n // preventing flushSync warnings when this is triggered by a parent component update\n queueMicrotask(() => {\n // An external replace parses to a null selection; reconciling that against the\n // SHARED document selection clears/moves the caret of whatever DOES have focus — observed\n // live as the parent editor's PDP echo (~150-250ms after an edit) stealing DOM focus out\n // of the footnote-editor popover mid-typing. An editor without focus has no claim on the\n // DOM selection, so skip DOM-selection reconciliation entirely in that case. Evaluated at\n // apply time (inside the microtask), not schedule time, so a focus change in between is\n // honored. A focused editor keeps the existing behavior.\n const rootElement = editor.getRootElement();\n const activeElement = rootElement?.ownerDocument.activeElement;\n const editorHasFocus = rootElement != null &&\n activeElement != null &&\n (rootElement === activeElement || rootElement.contains(activeElement));\n editor.update(() => {\n if (!editorHasFocus)\n $addUpdateTag(SKIP_DOM_SELECTION_TAG);\n editor.setEditorState(editorState);\n editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);\n }, { tag: EXTERNAL_USJ_MUTATION_TAG });\n });\n }\n catch {\n logger?.error(\"LoadStatePlugin: error parsing or setting editor state.\");\n }\n }, [editor, editorAdaptor, logger, scripture, scriptureRef, viewOptions]);\n return null;\n}\n","import { $isImmutableNoteCallerNode, $isImmutableVerseNode, defaultCrossRefCallers, defaultNoteCallers, ImmutableNoteCallerNode, } from \"../../nodes/usj\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $findMatchingParent, mergeRegister } from \"@lexical/utils\";\nimport { $createRangeSelection, $createTextNode, $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, TextNode, } from \"lexical\";\nimport { useEffect, useRef } from \"react\";\nimport { $findFirstAncestorNoteNode, $getNoteCallerPreviewText, $isCharNode, $isMarkerNode, $isNoteNode, $isSomeParaNode, CharNode, EMPTY_CHAR_PLACEHOLDER_TEXT, GENERATOR_NOTE_CALLER, getEditableCallerText, NBSP, NoteNode, } from \"shared\";\n/**\n * This plugin is responsible for handling NoteNode and NoteNodeCaller interactions. It also\n * updates the counter style symbols for note callers when the node options change.\n * @param expandedNoteKeyRef - Ref to track the currently expanded note key, if any.\n * @param nodeOptions - Node options that includes the list of potential node callers.\n * @param viewOptions - View options of the editor.\n * @param logger - Logger to use, if any.\n * @returns\n */\nexport function NoteNodePlugin({ expandedNoteKeyRef, nodeOptions, viewOptions, logger, }) {\n const [editor] = useLexicalComposerContext();\n useNodeOptions(nodeOptions, logger);\n useNoteNode(editor, expandedNoteKeyRef, viewOptions, logger);\n return null;\n}\n/**\n * This hook is responsible for handling updates to `nodeOptions`.\n * @param nodeOptions - Node options that includes the list of potential note and cross-reference\n * callers.\n * @param logger - Logger to use, if any.\n */\nfunction useNodeOptions(nodeOptions, logger) {\n const previousNoteCallersRef = useRef(undefined);\n const previousCrossRefCallersRef = useRef(undefined);\n const nodeOptionsNoteCallers = nodeOptions.noteCallers;\n const nodeOptionsCrossRefCallers = nodeOptions.crossRefCallers;\n useEffect(() => {\n let noteCallers = nodeOptionsNoteCallers;\n if (!noteCallers || noteCallers.length <= 0)\n noteCallers = defaultNoteCallers;\n if (previousNoteCallersRef.current !== noteCallers) {\n previousNoteCallersRef.current = noteCallers;\n updateCounterStyleSymbols(\"note-callers\", noteCallers, logger);\n }\n }, [logger, nodeOptionsNoteCallers]);\n useEffect(() => {\n let crossRefCallers = nodeOptionsCrossRefCallers;\n if (!crossRefCallers || crossRefCallers.length <= 0)\n crossRefCallers = defaultCrossRefCallers;\n if (previousCrossRefCallersRef.current !== crossRefCallers) {\n previousCrossRefCallersRef.current = crossRefCallers;\n updateCounterStyleSymbols(\"cross-ref-callers\", crossRefCallers, logger);\n }\n }, [logger, nodeOptionsCrossRefCallers]);\n}\n/**\n * This hook is responsible for handling NoteNode and NoteNodeCaller interactions.\n * @param editor - The LexicalEditor instance used to access the DOM.\n * @param expandedNoteKeyRef - Ref to track the currently expanded note key, if any.\n * @param viewOptions - View options of the editor.\n * @param logger - Logger to use, if any.\n */\nfunction useNoteNode(editor, expandedNoteKeyRef, viewOptions, logger) {\n useEffect(() => {\n if (!editor.hasNodes([CharNode, NoteNode, ImmutableNoteCallerNode])) {\n throw new Error(\"NoteNodePlugin: CharNode, NoteNode or ImmutableNoteCallerNode not registered on editor!\");\n }\n const doubleClickListener = (event) => editor.update(() => $handleDoubleClick(event));\n return mergeRegister(\n // Remove NoteNode if it doesn't contain a caller node and ensure typed text goes before it.\n editor.registerNodeTransform(NoteNode, (node) => $noteNodeTransform(node, viewOptions)), \n // Update NoteNodeCaller preview text when NoteNode children text is changed.\n editor.registerNodeTransform(CharNode, $noteCharNodeTransform), editor.registerNodeTransform(TextNode, $noteTextNodeTransform), \n // Ensure NBSP after caller.\n editor.registerNodeTransform(ImmutableNoteCallerNode, $noteCallerNodeTransform), \n // Re-generate all note callers when a note is removed.\n editor.registerMutationListener(ImmutableNoteCallerNode, (nodeMutations, { prevEditorState }) => generateNoteCallersOnDestroy(nodeMutations, prevEditorState)), \n // Handle the cursor moving next to a NoteNode. NoteNode arrow key navigation when note is\n // after a verse node is handled in the ArrowNavigationPlugin.\n editor.registerCommand(SELECTION_CHANGE_COMMAND, () => $handleCursorNextToNoteNode(editor, expandedNoteKeyRef, viewOptions, logger), COMMAND_PRIORITY_LOW), \n // Handle double-click of a word immediately following a NoteNode (no space between).\n editor.registerRootListener((rootElement, prevRootElement) => {\n if (prevRootElement !== null) {\n prevRootElement.removeEventListener(\"dblclick\", doubleClickListener);\n }\n if (rootElement !== null) {\n rootElement.addEventListener(\"dblclick\", doubleClickListener);\n }\n }));\n }, [editor, expandedNoteKeyRef, logger, viewOptions]);\n}\n/**\n * Cleans up a NoteNode to ensure it is valid.\n *\n * @remarks Removes a NoteNode if it does not contain an ImmutableNoteCallerNode child when\n * `markerMode` is not 'editable'. This can happen during certain editing operations or data\n * inconsistencies. When editing notes we may intentionally exclude the caller by setting it empty.\n * Also if the first node is a TextNode move it before the NoteNode, i.e. the user typed when the\n * selection was at the beginning of a NoteNode.\n * @param node - The NoteNode to check.\n * @param viewOptions - The view options that includes the marker mode.\n */\nfunction $noteNodeTransform(node, viewOptions) {\n const nodeChildren = node.getChildren();\n const hasNoteCaller = nodeChildren.some((child) => $isImmutableNoteCallerNode(child));\n if (!hasNoteCaller && viewOptions?.markerMode !== \"editable\" && node.getCaller() !== \"\")\n node.remove();\n if (nodeChildren.length > 0) {\n const firstChild = nodeChildren[0];\n if ($isTextNode(firstChild) && !$isMarkerNode(firstChild)) {\n // Never eject the expanded editable caller text (` caller`, getEditableCallerText):\n // after the opening glyph is deleted it becomes the note's first child, and ejecting it\n // plants the caller word in the paragraph on every serialization round (live-observed as a\n // repeated `word~` spray). It must stay in place so MarkerEditPlugin's note-deletion\n // transform can recognize the damaged editable note and remove it whole. User-typed stray\n // text at the note's start (any other text) is still salvaged out.\n if (firstChild.getTextContent() !== getEditableCallerText(node.getCaller()))\n node.insertBefore(firstChild);\n }\n }\n}\n/**\n * Changes in NoteNode children text are updated in the NoteNodeCaller preview text.\n * Also ensure NBSP after each note top-level node.\n * @param node - CharNode thats needs its preview text updated.\n */\nfunction $noteCharNodeTransform(node) {\n const parent = node.getParentOrThrow();\n const children = parent.getChildren();\n const noteCaller = children.find((child) => $isImmutableNoteCallerNode(child));\n if (!$isCharNode(node) || !$isNoteNode(parent) || !noteCaller)\n return;\n const previewText = $getNoteCallerPreviewText(children);\n if (noteCaller.getPreviewText() !== previewText)\n noteCaller.setPreviewText(previewText);\n // Ensure NBSP after each note top-level CharNode\n const nextSibling = node.getNextSibling();\n if (!$isTextNode(nextSibling))\n node.insertAfter($createTextNode(NBSP));\n else if (nextSibling.getTextContent() !== NBSP)\n nextSibling.setTextContent(NBSP);\n}\n/**\n * Changes in NoteNode children text are updated in the NoteNodeCaller preview text.\n * Also ensure NBSP after each note top-level CharNode isn't modified.\n * Also remove 'empty' placeholder in CharNode inside NoteNode once other text content is added.\n * @param node - TextNode thats needs its preview text updated.\n */\nfunction $noteTextNodeTransform(node) {\n const noteNode = $findFirstAncestorNoteNode(node);\n const children = noteNode?.getChildren();\n const noteCaller = children?.find((child) => $isImmutableNoteCallerNode(child));\n if (!$isTextNode(node) || !$isNoteNode(noteNode) || !noteCaller || !children)\n return;\n const parent = node.getParent();\n if (!$isMarkerNode(node) && $isNoteNode(parent)) {\n if (node.getTextContent() !== NBSP) {\n node.setTextContent(NBSP);\n node.selectEnd();\n }\n }\n if ($isCharNode(parent) && parent.getChildrenSize() === 1) {\n const text = node.getTextContent();\n if (text.length > 1 && text.startsWith(EMPTY_CHAR_PLACEHOLDER_TEXT)) {\n node.setTextContent(text.slice(1));\n node.selectEnd();\n }\n }\n const previewText = $getNoteCallerPreviewText(children);\n if (noteCaller.getPreviewText() !== previewText)\n noteCaller.setPreviewText(previewText);\n}\n/**\n * Ensure NBSP after caller.\n * @param node - TextNode thats needs its preview text updated.\n */\nfunction $noteCallerNodeTransform(node) {\n if (!$isImmutableNoteCallerNode(node))\n return;\n const nextSibling = node.getNextSibling();\n if (!$isTextNode(nextSibling) || $isMarkerNode(nextSibling))\n node.insertAfter($createTextNode(NBSP));\n else if (nextSibling.getTextContent() !== NBSP)\n nextSibling.setTextContent(NBSP);\n}\n/**\n * When a NoteNode is destroyed, check if it was generated and force a CSS reflow.\n * @param nodeMutations - Map of node mutations.\n * @param prevEditorState - The previous EditorState.\n */\nfunction generateNoteCallersOnDestroy(nodeMutations, prevEditorState) {\n for (const [nodeKey, mutation] of nodeMutations) {\n if (mutation !== \"destroyed\")\n continue;\n const nodeWasGenerated = prevEditorState.read(() => {\n const node = $getNodeByKey(nodeKey);\n const parent = node?.getParent();\n return ($isImmutableNoteCallerNode(node) &&\n $isNoteNode(parent) &&\n parent.getCaller() === GENERATOR_NOTE_CALLER);\n });\n const editorElement = document.querySelector(\".editor-input\");\n if (!nodeWasGenerated || !editorElement)\n continue;\n editorElement.classList.add(\"reset-counters\");\n // Force a reflow to ensure the counter reset is applied\n void editorElement.offsetHeight;\n editorElement.classList.remove(\"reset-counters\");\n }\n}\nfunction $handleCursorNextToNoteNode(editor, expandedNoteKeyRef, viewOptions, logger) {\n if (viewOptions?.noteMode !== \"expandInline\")\n return false;\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const anchor = selection.anchor;\n const node = anchor.getNode();\n // Case 1: caret moved away from a NoteNode → collapse it\n if (expandedNoteKeyRef.current) {\n const noteAncestor = $findMatchingParent(node, (n) => $isNoteNode(n));\n if (!noteAncestor) {\n const note = $getNodeByKey(expandedNoteKeyRef.current);\n if (note && !note.getIsCollapsed()) {\n logger?.debug(\"Cursor moved away from NoteNode, collapsing it\");\n $toggleNoteCollapseWithFallback(editor, expandedNoteKeyRef.current, logger);\n }\n expandedNoteKeyRef.current = undefined;\n }\n else {\n // Still inside a NoteNode → keep tracking it.\n if (expandedNoteKeyRef.current !== noteAncestor.getKey()) {\n // Update key since we moved to a different note.\n expandedNoteKeyRef.current = noteAncestor.getKey();\n }\n }\n }\n // Case 2: caret at start of a text node → check prev sibling\n if (anchor.offset === 0) {\n const prev = node.getPreviousSibling();\n if ($isNoteNode(prev)) {\n logger?.debug(\"Cursor is just after a NoteNode\");\n const noteKey = prev.getKey();\n if (prev.getIsCollapsed())\n expandedNoteKeyRef.current = noteKey;\n else\n expandedNoteKeyRef.current = undefined;\n $toggleNoteCollapseWithFallback(editor, noteKey, logger);\n }\n }\n // Case 3: caret at end of a text node → check next sibling\n if (anchor.offset === node.getTextContentSize()) {\n const next = node.getNextSibling();\n if ($isNoteNode(next)) {\n logger?.debug(\"Cursor is just before a NoteNode\");\n const noteKey = next.getKey();\n if (next.getIsCollapsed())\n expandedNoteKeyRef.current = noteKey;\n else\n expandedNoteKeyRef.current = undefined;\n $toggleNoteCollapseWithFallback(editor, noteKey, logger);\n }\n else if (!next) {\n const noteAncestor = $findMatchingParent(node, (n) => $isNoteNode(n));\n if (noteAncestor &&\n noteAncestor.getIsCollapsed() &&\n $isSomeParaNode(noteAncestor.getParent()) &&\n noteAncestor.is(noteAncestor.getParent()?.getLastChild())) {\n logger?.debug(\"Cursor is at end of note at end of para\");\n const noteKey = noteAncestor.getKey();\n expandedNoteKeyRef.current = noteKey;\n $toggleNoteCollapseWithFallback(editor, noteKey, logger);\n }\n }\n }\n // Case 4: caret between verse and note → toggle note\n if ($isSomeParaNode(node)) {\n const child = node.getChildAtIndex(anchor.offset);\n const prevChild = child?.getPreviousSibling();\n if ($isImmutableVerseNode(prevChild) && $isNoteNode(child)) {\n logger?.debug(\"Cursor is between verse and NoteNode\");\n const noteKey = child.getKey();\n if (child.getIsCollapsed())\n expandedNoteKeyRef.current = noteKey;\n else\n expandedNoteKeyRef.current = undefined;\n $toggleNoteCollapseWithFallback(editor, noteKey, logger);\n }\n }\n return false;\n}\n/**\n * Toggle the collapse state of a NoteNode, with fallback to deferred update if read-only error\n * occurs. This handles the edge case where clicking next to a note immediately after editor load\n * triggers a read-only selection context.\n * @param editor - The LexicalEditor instance used to access the DOM.\n * @param noteKey - The key of the NoteNode to toggle.\n */\nfunction $toggleNoteCollapseWithFallback(editor, noteKey, logger) {\n const noteNode = $getNodeByKey(noteKey);\n try {\n // Try immediate update first (works in most cases)\n noteNode?.toggleIsCollapsed();\n }\n catch (error) {\n // If we get a read-only error, defer the update\n if (error instanceof Error && error.message.includes(\"read only\")) {\n logger?.warn(\"Fallback triggered after stabilization - edge case\");\n setTimeout(() => {\n editor.update(() => {\n noteNode?.toggleIsCollapsed();\n });\n }, 0);\n }\n else {\n throw error; // Re-throw if it's not the expected read-only error\n }\n }\n}\n/**\n * Ensure that when double-clicking on a word after a note node (no space between) that it only\n * selects that word and does not include the note in the selection.\n * @param event - The MouseEvent triggered by the double-click interaction\n */\nfunction $handleDoubleClick(event) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return;\n const anchor = selection.anchor;\n const focus = selection.focus;\n const anchorNode = anchor.getNode();\n const focusNode = focus.getNode();\n if ($isNoteNode(anchorNode) && $isTextNode(focusNode)) {\n event.preventDefault();\n // Create new selection only including the TextNode\n const newSelection = $createRangeSelection();\n newSelection.anchor.set(focusNode.getKey(), 0, \"text\");\n newSelection.focus.set(focusNode.getKey(), focus.offset, \"text\");\n $setSelection(newSelection);\n }\n}\nfunction updateCounterStyleSymbols(counterStyleName, newSymbols, logger) {\n // Loop through all stylesheets\n for (const styleSheet of document.styleSheets) {\n try {\n const cssRules = styleSheet.cssRules || styleSheet.rules;\n // Loop through all CSS rules in the current stylesheet\n for (const rule of cssRules) {\n if (isCounterStyleRuleLike(rule, counterStyleName)) {\n // Create the symbols string (space-separated symbols)\n const symbolsValue = newSymbols.map((symbol) => `\"${symbol}\"`).join(\" \");\n // Set the new symbols\n rule.symbols = symbolsValue;\n return;\n }\n }\n }\n catch {\n // Skip cross-origin stylesheets that can't be accessed\n continue;\n }\n }\n // If the counter-style wasn't found, you could create it\n logger?.warn(`Editor: counter style \"${counterStyleName}\" not found.`);\n}\nfunction isCounterStyleRuleLike(rule, counterStyleName) {\n return (\n // This check could be simpler but as is also works for test mocks.\n typeof rule === \"object\" &&\n rule !== null &&\n \"name\" in rule &&\n rule.name === counterStyleName &&\n \"symbols\" in rule &&\n typeof rule.symbols === \"string\");\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $addUpdateTag, $getPreviousSelection, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_EDITOR, SELECTION_CHANGE_COMMAND, } from \"lexical\";\nimport { useEffect, useRef } from \"react\";\nimport { $isMarkerNode, $isNoteNode, $noteEditableCallerNode, $placeCaretAtBoundary, CURSOR_CHANGE_TAG, } from \"shared\";\n/**\n * An expanded note's SHELL — its leading opening glyph(s) and its editable caller, the `\\f + ` a\n * reader sees — as the nodes that carry it, or an empty list when this note has no protected shell.\n *\n * Read off the nodes' MODE rather than the view options, the same way every other note rule is read\n * off the tree: the adaptor puts exactly these nodes in `token` mode when the host governs the\n * marker and the caller through its own UI (`ViewOptions.isNoteShellEditable: false`), so a view\n * that leaves the shell editable builds `normal` nodes and every rule here is structurally a no-op\n * for it. Nothing has to stay in sync with a flag.\n */\nfunction $noteShellNodes(note) {\n if (note.getIsCollapsed() !== false)\n return [];\n const shell = [];\n for (const child of note.getChildren()) {\n if (!$isMarkerNode(child) || child.getMarkerSyntax() !== \"opening\")\n break;\n shell.push(child);\n }\n const caller = $noteEditableCallerNode(note);\n if (caller)\n shell.push(caller);\n // Protected only when the adaptor marked it so. A partly-token shell is not a shape the adaptor\n // builds; requiring ALL of it keeps this from half-applying to one it did not.\n return shell.length > 0 && shell.every((node) => $isTextNode(node) && node.getMode() === \"token\")\n ? shell\n : [];\n}\n/** The note whose protected shell `node` belongs to, or `undefined`. */\nfunction $shellOwner(node) {\n const note = node.getParent();\n if (!$isNoteNode(note))\n return undefined;\n return $noteShellNodes(note).some((shellNode) => shellNode.is(node)) ? note : undefined;\n}\n/**\n * The boundary index just past `note`'s shell — the start of the note's own content, and the only\n * caret position at the shell's trailing edge that is inside the note.\n */\nfunction $contentStartIndex(note) {\n const shell = $noteShellNodes(note);\n const last = shell[shell.length - 1];\n return last ? last.getIndexWithinParent() + 1 : 0;\n}\n/** `note`'s own child that contains `node`, or `undefined` when `node` is not inside `note`. */\nfunction $noteChildContaining(note, node) {\n for (let cursor = node; cursor; cursor = cursor.getParent())\n if (note.is(cursor.getParent()))\n return cursor;\n return undefined;\n}\n/**\n * Whether the caret reached the shell from the note's CONTENT side — which is what a leftward move\n * out of the note looks like, and the one case where pushing it forward again would trap it.\n *\n * Taken from the PREVIOUS selection because the shell is crossed whole in either direction and the\n * landing point alone cannot say which way the user was going. Anything else — a rightward move\n * from before the note, no previous selection at all — reads as travelling forward, which is also\n * the safe default: forward lands in editable content.\n *\n * Only asked of a KEYBOARD move. A pointer is not travelling anywhere — it names a destination\n * outright — so the previous caret says nothing about the user's intent, and reading it as a\n * direction sends a click away from the note it landed in. That is not hypothetical: a popover\n * that focuses its editor with no selection parks the caret at the document end, which for a\n * document holding one note is that note's own closing glyph — so the FIRST click on the shell\n * read as \"coming from the content side\" and threw the caret past the whole note.\n */\nfunction $arrivedFromContentSide(note) {\n const previous = $getPreviousSelection();\n if (!$isRangeSelection(previous))\n return false;\n const { anchor } = previous;\n const node = anchor.getNode();\n if (note.is(node))\n return anchor.offset >= $contentStartIndex(note);\n const child = $noteChildContaining(note, node);\n return child !== undefined && child.getIndexWithinParent() >= $contentStartIndex(note);\n}\n/**\n * The note whose shell `point` rests in ILLEGITIMATELY, or `undefined`.\n *\n * Exactly one offset in the whole shell is a caret position: the trailing edge of its last node.\n * Lexical's `token` mode redirects an insertion at a token node's boundary to a sibling, and only\n * there does it pick the right one — a fresh node after the caller, which is the start of the\n * note's content. At the shell's LEADING edge it inserts a text node inside the note before the\n * opening glyph (a `NoteNode` does not refuse text before it), and at the seam between the glyph\n * and the caller it inserts BETWEEN them. Every other offset is strictly inside a token node,\n * where an insertion replaces that node outright.\n *\n * So the trailing edge is where this guard puts the caret, and the one place it leaves alone —\n * which is also what stops it from correcting its own correction.\n */\nfunction $shellAt(point) {\n if (point.type !== \"text\")\n return undefined;\n const node = point.getNode();\n const note = $shellOwner(node);\n if (!note)\n return undefined;\n return $isShellTrailingEdge(note, node, point.offset) ? undefined : note;\n}\n/** Whether `point` is at the shell's trailing edge — the caret position just past `\\f + `. */\nfunction $isShellTrailingEdge(note, node, offset) {\n const shell = $noteShellNodes(note);\n const last = shell[shell.length - 1];\n return last !== undefined && last.is(node) && offset === last.getTextContentSize();\n}\n/** Collapse the caret to the shell's trailing edge, the start of the note's own content. */\nfunction $placeAtShellTrailingEdge(note) {\n const shell = $noteShellNodes(note);\n const last = shell[shell.length - 1];\n if ($isTextNode(last))\n last.select(last.getTextContentSize(), last.getTextContentSize());\n else\n $placeCaretAtBoundary(note, $contentStartIndex(note));\n}\n/**\n * Move a caret that has come to rest inside an expanded note's protected shell to the nearest\n * position outside it: the start of the note's own content, or — for a KEYBOARD move coming back\n * out of that content — the position before the whole note, so the shell can be crossed leftward\n * instead of trapping the caret against it.\n *\n * `isPointerGesture` says the caret was placed by a pointer, which is a destination rather than a\n * direction: such a caret always goes to the content, the position the user was pointing into.\n *\n * Returns `true` when the selection was corrected.\n *\n * Exported for direct unit testing; production reaches it through\n * {@link NoteShellCaretGuardPlugin}.\n *\n * Mutating (moves the selection): call inside `editor.update()` or a command handler.\n */\nexport function $guardCaretOutOfNoteShell(isPointerGesture = false) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection))\n return false;\n if (!selection.isCollapsed())\n return $expandSelectionPastShell(selection.anchor, selection.focus);\n const note = $shellAt(selection.anchor);\n if (!note)\n return false;\n if (!isPointerGesture && $arrivedFromContentSide(note)) {\n const parent = note.getParent();\n if (!parent)\n return false;\n $placeCaretAtBoundary(parent, note.getIndexWithinParent());\n }\n else {\n $placeAtShellTrailingEdge(note);\n }\n return true;\n}\n/**\n * Push a RANGE's endpoints out of any shell they land in, away from the other endpoint, so the\n * shell ends up wholly inside the selection or wholly outside it.\n *\n * A range that stops partway through the shell is the other way a keystroke reaches it: replacing\n * such a selection edits the shell node the range clipped. Growing the range instead makes the\n * shell behave as the single unit it is drawn as — the same treatment `token` mode gives deletion.\n */\nfunction $expandSelectionPastShell(anchor, focus) {\n const anchorNote = $shellAt(anchor);\n const focusNote = $shellAt(focus);\n if (!anchorNote && !focusNote)\n return false;\n // Which endpoint leads is the range's own direction; each offending one moves to the shell edge\n // that is farther from the other, which is what grows rather than shrinks the selection.\n const anchorLeads = anchor.isBefore(focus);\n if (anchorNote)\n $movePointPastShell(anchor, anchorNote, anchorLeads);\n if (focusNote)\n $movePointPastShell(focus, focusNote, !anchorLeads);\n return true;\n}\n/** Move `point` to the shell's leading edge (`toStart`) or to the start of the note's content. */\nfunction $movePointPastShell(point, note, toStart) {\n const parent = note.getParent();\n if (toStart && parent)\n point.set(parent.getKey(), note.getIndexWithinParent(), \"element\");\n else\n point.set(note.getKey(), $contentStartIndex(note), \"element\");\n}\n/**\n * Keeps the caret out of an expanded note's shell — the opening glyph and caller a host governs\n * through its own UI rather than as text (`ViewOptions.isNoteShellEditable: false`; Paratext 10's\n * footnote editor has a dropdown for each, as does Paratext 9).\n *\n * Rendering those nodes in Lexical's `token` mode is what makes them atomic to the operations that\n * ASK a node whether it can be split, but it does not keep a caret from landing among their\n * characters, and a caret that does land there is not inert: an insertion with the caret strictly\n * inside a token node replaces the WHOLE node with the typed character. The measured results are a\n * caller replaced by the keystroke — which then leaks into the note's content on save — and, for\n * the opening glyph, a note destroyed outright, since a note that has lost its opener is unwrapped\n * as deletion damage. Both read on screen as an edit that was accepted and then quietly reverted.\n *\n * So the caret is corrected the moment it comes to rest there, in the same update, before anything\n * can be typed. It lands at the start of the note's content — where the note IS editable, and where\n * a `\\cat` category run belongs. The one exception is a KEYBOARD move coming back out of that\n * content: that one lands before the whole note, so the shell is crossed in a single hop rather\n * than trapping the caret against it.\n *\n * A pointer is held to a destination, never a direction. It is read from the pointer being DOWN\n * when the selection lands, which is the order a click delivers (`pointerdown`, then the selection\n * change, then `pointerup`) — the click event itself arrives too late to answer in the same update,\n * and correcting twice would let other selection listeners see the wrong position in between.\n *\n * Not gated on view options: the rule reads the shell's own node mode, so it is structurally a\n * no-op in the views that build an editable shell (the main editor's Markers view expands notes\n * precisely so the whole note can be edited as text).\n *\n * @returns Always `null`; this plugin renders no UI.\n */\nexport function NoteShellCaretGuardPlugin() {\n const [editor] = useLexicalComposerContext();\n const isPointerDown = useRef(false);\n useEffect(() => {\n const markDown = () => {\n isPointerDown.current = true;\n };\n const markUp = () => {\n isPointerDown.current = false;\n };\n // Listened for on the DOCUMENT in the capture phase, and released on `pointercancel` as well\n // as `pointerup`: a drag that starts in the editor can finish anywhere, and a pointer flag\n // that fails to clear would make every later keyboard move read as a click.\n return editor.registerRootListener((rootElement, prevRootElement) => {\n const previous = prevRootElement?.ownerDocument;\n previous?.removeEventListener(\"pointerdown\", markDown, true);\n previous?.removeEventListener(\"pointerup\", markUp, true);\n previous?.removeEventListener(\"pointercancel\", markUp, true);\n isPointerDown.current = false;\n const current = rootElement?.ownerDocument;\n current?.addEventListener(\"pointerdown\", markDown, true);\n current?.addEventListener(\"pointerup\", markUp, true);\n current?.addEventListener(\"pointercancel\", markUp, true);\n });\n }, [editor]);\n useEffect(() => {\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {\n // Command handlers already run inside an update, so the tag joins that commit rather than\n // opening a new one. Nothing here changes content, so tagging it costs nothing already\n // excluded from saved Scripture and collaborative traffic.\n if ($guardCaretOutOfNoteShell(isPointerDown.current))\n $addUpdateTag(CURSOR_CHANGE_TAG);\n return false;\n }, COMMAND_PRIORITY_EDITOR);\n }, [editor]);\n return null;\n}\n","import { $getUsjSelectionFromEditor } from \"./annotation/selection.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND } from \"lexical\";\nimport { useEffect } from \"react\";\nexport function OnSelectionChangePlugin({ onChange, }) {\n const [editor] = useLexicalComposerContext();\n useEffect(() => editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {\n // Called as a bare `$` function, NOT via `editor.read()` or a committed-state read:\n // command listeners always run inside the active update, so `$getSelection()` sees the\n // CURRENT (pending) selection. `editor.read()` here would force-flush an in-flight\n // `editor.update()` mid-dispatch (`$commitPendingUpdates` runs unconditionally) — the\n // enabler of the frozen-commit crash class (see OnSelectionChangePlugin.test.tsx) —\n // while reading the last committed state instead would report every ordinary selection\n // change one interaction late, because Lexical dispatches SELECTION_CHANGE from inside\n // a not-yet-committed update on the normal DOM path too.\n const usjSelection = $getUsjSelectionFromEditor();\n onChange?.(usjSelection);\n return false;\n }, COMMAND_PRIORITY_LOW), [editor, onChange]);\n return null;\n}\n","import { $removeLeadingSpace, wasNodeCreated } from \"../../nodes/usj/node-react.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $getNodeByKey } from \"lexical\";\nimport { useEffect } from \"react\";\nimport { $isParaNode, ParaNode } from \"shared\";\nexport function ParaNodePlugin() {\n const [editor] = useLexicalComposerContext();\n useParaNode(editor);\n return null;\n}\nfunction useParaNode(editor) {\n useEffect(() => {\n if (!editor.hasNodes([ParaNode])) {\n throw new Error(\"ParaNodePlugin: ParaNode not registered on editor!\");\n }\n // Update ParaNode to remove leading space and to not contain text if it is a 'b' marker.\n return editor.registerNodeTransform(ParaNode, (node) => $paraNodeTransform(node, editor));\n }, [editor]);\n}\n/**\n * Remove any leading space if the node was just created, e.g. by hitting the enter key in the\n * middle of a paragraph. Update ParaNode to not contain text if it is a 'b' marker (blank line).\n * However, if the 'b' ParaNode already contained text, it can be modified (backwards compatible\n * with PT9 & USFM v3.0).\n * @param node - ParaNode thats needs updating.\n */\nfunction $paraNodeTransform(node, editor) {\n if (wasNodeCreated(editor, node.getKey()))\n $removeLeadingSpace(node.getFirstChild());\n if (!$isParaNode(node) || node.getMarker() !== \"b\" || node.isEmpty())\n return;\n const prevEditorState = editor.getEditorState();\n const wasEmpty = prevEditorState.read(() => {\n const prevNode = $getNodeByKey(node.getKey());\n return $isParaNode(prevNode) && (prevNode?.isEmpty() ?? false);\n });\n if (!wasEmpty)\n return;\n node.clear();\n}\n","import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $findMatchingParent, mergeRegister } from \"@lexical/utils\";\nimport { CAN_UNDO_COMMAND, COMMAND_PRIORITY_CRITICAL, CAN_REDO_COMMAND, SELECTION_CHANGE_COMMAND, $getSelection, $isRangeSelection, $isRootOrShadowRoot, } from \"lexical\";\nimport { useRef, useEffect, useState, useCallback } from \"react\";\nimport { $getCommonAncestorCompatible, $isBookNode, $isParaNode, $isImmutableChapterNode, $isVerseBlockNode, } from \"shared\";\nimport { $isReactNodeWithMarker } from \"../../nodes/usj/node-react.utils\";\n/** Plugin to track state and update parent component state */\nexport function StateChangePlugin({ onStateChange }) {\n const [editor] = useLexicalComposerContext();\n const [activeEditor, setActiveEditor] = useState(editor);\n const canUndoRef = useRef(false);\n const canRedoRef = useRef(false);\n const blockMarkerRef = useRef(undefined);\n const contextMarkerRef = useRef(undefined);\n const $updateState = useCallback(() => {\n const selection = $getSelection();\n let contextMarker;\n if ($isRangeSelection(selection)) {\n const anchorNode = selection.anchor.getNode();\n const focusNode = selection.focus.getNode();\n let node = anchorNode.getKey() === \"root\"\n ? anchorNode\n : $findMatchingParent(anchorNode, (e) => {\n const parent = e.getParent();\n return parent !== null && $isRootOrShadowRoot(parent);\n });\n if (node === null) {\n node = anchorNode.getTopLevelElementOrThrow();\n }\n // In the block verse layout the top-level element is the verse block, which carries no\n // marker of its own. The block marker the host wants is still the paragraph inside it that\n // holds the caret, so resolve through the block. `$isParaNode`, not `$isSomeParaNode`: an\n // implied paragraph has no marker to report, and the reporting guard below is the same\n // predicate - resolving to a node that guard then rejects would emit no state change at all.\n if ($isVerseBlockNode(node))\n node = $findMatchingParent(anchorNode, $isParaNode) ?? node;\n const nodeKey = node.getKey();\n const elementDOM = activeEditor.getElementByKey(nodeKey);\n const contextNode = $getCommonAncestorCompatible(anchorNode, focusNode);\n if (contextNode && $isReactNodeWithMarker(contextNode)) {\n contextMarker = contextNode.getMarker();\n }\n if (elementDOM !== null &&\n ($isParaNode(node) || $isBookNode(node) || $isImmutableChapterNode(node))) {\n blockMarkerRef.current = node.getMarker();\n contextMarkerRef.current = contextMarker;\n onStateChange?.({\n canUndo: canUndoRef.current,\n canRedo: canRedoRef.current,\n blockMarker: blockMarkerRef.current,\n contextMarker,\n });\n return;\n }\n }\n contextMarkerRef.current = contextMarker;\n }, [activeEditor, onStateChange]);\n useEffect(() => {\n return editor.registerCommand(SELECTION_CHANGE_COMMAND, (_payload, newEditor) => {\n $updateState();\n setActiveEditor(newEditor);\n return false;\n }, COMMAND_PRIORITY_CRITICAL);\n }, [editor, $updateState]);\n useEffect(() => {\n return mergeRegister(activeEditor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n $updateState();\n });\n }), activeEditor.registerCommand(CAN_UNDO_COMMAND, (payload) => {\n canUndoRef.current = payload;\n onStateChange?.({\n canUndo: canUndoRef.current,\n canRedo: canRedoRef.current,\n blockMarker: blockMarkerRef.current,\n contextMarker: contextMarkerRef.current,\n });\n return false;\n }, COMMAND_PRIORITY_CRITICAL), activeEditor.registerCommand(CAN_REDO_COMMAND, (payload) => {\n canRedoRef.current = payload;\n onStateChange?.({\n canUndo: canUndoRef.current,\n canRedo: canRedoRef.current,\n blockMarker: blockMarkerRef.current,\n contextMarker: contextMarkerRef.current,\n });\n return false;\n }, COMMAND_PRIORITY_CRITICAL));\n }, [$updateState, activeEditor, onStateChange]);\n return null;\n}\n","import { $isSomeVerseNode } from \"../../nodes/usj\";\nimport { $advancePastParaPrefixes } from \"./ParaMarkerPrefixCursorGuardPlugin\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport { $createTextNode, $isElementNode, $isNodeSelection, $isRangeSelection, $isTextNode, } from \"lexical\";\nimport { $isSomeChapterNode, $isSomeParaNode } from \"shared\";\n/**\n * Maps a keydown to the structural edit it would cause, or undefined for non-editing keys.\n * Deliberately does NOT early-return on Alt/Ctrl/Meta for Backspace/Delete — Alt/Cmd+Backspace\n * (delete-word / delete-line) are destructive and must be classified as deletions.\n */\nexport function keyDownToIntent(event) {\n if (event.key === \"Enter\" && !event.shiftKey)\n return \"insertParagraph\";\n if (event.key === \"Backspace\")\n return \"deleteBackward\";\n if (event.key === \"Delete\")\n return \"deleteForward\";\n // Printable character with no command modifier. Alt allowed (special chars on some layouts).\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey)\n return \"insertText\";\n return undefined;\n}\n/** Returns the paragraph (ParaNode or ImpliedParaNode) that contains `node`, if any. */\nfunction $getParaAncestor(node) {\n if (!node)\n return undefined;\n if ($isSomeParaNode(node))\n return node;\n const para = $findMatchingParent(node, (n) => $isSomeParaNode(n));\n return para ?? undefined;\n}\n/** True when the selection covers more than one paragraph block. */\nexport function $selectionSpansBlockBoundary(selection) {\n if (!$isRangeSelection(selection))\n return false;\n const paraKeys = new Set();\n for (const node of selection.getNodes()) {\n const para = $getParaAncestor(node);\n if (para)\n paraKeys.add(para.getKey());\n }\n return paraKeys.size > 1;\n}\n/**\n * True when the selection includes any verse marker node. A collapsed caret at an element-type\n * point never counts — that is adjacency, not containment (see {@link $adjacentVerseMarker}).\n */\nexport function $selectionContainsVerseMarker(selection) {\n // A collapsed range covers no nodes, so a non-empty getNodes() for one is Lexical emulating \"the\n // descendant at the caret\" — that is adjacency, not containment, and must not block editing.\n // An element-type point is the only collapsed point that can reach that emulation: it is what\n // Lexical falls back to when there is no TextNode to host the caret, as in an empty verse (a verse\n // marker holds no text of its own, and ImmutableVerseNode is a childless decorator leaf, so an\n // empty verse has no text node at all).\n // Text-type points are excluded deliberately: the mutable VerseNode IS a TextNode, so a caret\n // inside one is genuine containment — editing there would rewrite the verse number, which must\n // stay blocked. Widening this to every collapsed caret reopens exactly that.\n // Adjacency for deletes is decided separately by $adjacentVerseMarker, which reads child indices\n // rather than relying on this emulation.\n if ($isRangeSelection(selection) &&\n selection.isCollapsed() &&\n selection.anchor.type === \"element\")\n return false;\n if (!$isRangeSelection(selection) && !$isNodeSelection(selection))\n return false;\n return selection.getNodes().some((n) => $isSomeVerseNode(n));\n}\n/** True when a collapsed caret sits at the very start of its paragraph. */\nexport function $caretAtParaStart(selection) {\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const { anchor } = selection;\n const node = anchor.getNode();\n const para = $getParaAncestor(node);\n if (!para)\n return false;\n if (anchor.offset !== 0)\n return false;\n // No content between the caret and the paragraph start.\n let current = node;\n while (current && current.getKey() !== para.getKey()) {\n if (current.getPreviousSibling())\n return false;\n current = current.getParent();\n }\n return true;\n}\n/** True when a collapsed caret sits at the very end of its paragraph. */\nexport function $caretAtParaEnd(selection) {\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n const { anchor } = selection;\n const node = anchor.getNode();\n const para = $getParaAncestor(node);\n if (!para)\n return false;\n if ($isElementNode(node)) {\n if (anchor.offset !== node.getChildrenSize())\n return false;\n }\n else if (anchor.offset !== node.getTextContentSize()) {\n return false;\n }\n let current = node;\n while (current && current.getKey() !== para.getKey()) {\n if (current.getNextSibling())\n return false;\n current = current.getParent();\n }\n return true;\n}\n/**\n * True when the node immediately before/after a collapsed caret is a verse marker.\n *\n * @param selection - The current selection; only a collapsed RangeSelection can be adjacent.\n * @param direction - `\"backward\"` checks the node before the caret; `\"forward\"` the node after.\n * @returns Whether a verse marker sits immediately in that direction.\n */\nexport function $caretAdjacentToVerseMarker(selection, direction) {\n return !!$adjacentVerseMarker(selection, direction);\n}\n/**\n * The verse marker immediately before/after a collapsed caret, or undefined.\n * Node-returning sibling of `$caretAdjacentToVerseMarker`.\n *\n * @param selection - The current selection; only a collapsed RangeSelection can be adjacent.\n * @param direction - `\"backward\"` looks before the caret; `\"forward\"` looks after it.\n * @returns The adjacent verse marker node, or undefined when none is adjacent.\n */\nexport function $adjacentVerseMarker(selection, direction) {\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return undefined;\n const { anchor } = selection;\n const node = anchor.getNode();\n if (anchor.type === \"element\" && $isElementNode(node)) {\n const children = node.getChildren();\n const idx = direction === \"backward\" ? anchor.offset - 1 : anchor.offset;\n if (idx < 0)\n return undefined;\n const candidate = children[idx];\n return $isSomeVerseNode(candidate) ? candidate : undefined;\n }\n if (direction === \"backward\") {\n if (anchor.offset !== 0)\n return undefined;\n const prev = node.getPreviousSibling();\n return $isSomeVerseNode(prev) ? prev : undefined;\n }\n if (anchor.offset !== node.getTextContentSize())\n return undefined;\n const next = node.getNextSibling();\n return $isSomeVerseNode(next) ? next : undefined;\n}\n/** True when the paragraph holding the caret has a preceding block sibling. */\nfunction $hasNeighborBlock(selection, direction) {\n if (!$isRangeSelection(selection))\n return false;\n const para = $getParaAncestor(selection.anchor.getNode());\n if (!para)\n return false;\n return !!(direction === \"backward\" ? para.getPreviousSibling() : para.getNextSibling());\n}\n/**\n * Rule 1 (all input vectors): block when the given selection spans a block boundary or\n * touches a verse marker. Used by paste/cut/drop/IME guards.\n */\nexport function $shouldBlockSelectionReplacement(selection) {\n return $selectionContainsVerseMarker(selection) || $selectionSpansBlockBoundary(selection);\n}\n/**\n * Full keyboard decision: combines Rule 1 with collapsed-caret structural rules.\n *\n * @param selection - The current selection to evaluate.\n * @param intent - The structural edit the keystroke would cause (see {@link keyDownToIntent}).\n * @returns Whether the edit should be blocked in a structure-protected document.\n */\nexport function $shouldBlockStructuralEdit(selection, intent) {\n if ($selectionContainsVerseMarker(selection) || $selectionSpansBlockBoundary(selection)) {\n return true;\n }\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return false;\n switch (intent) {\n case \"insertParagraph\":\n return true;\n case \"deleteBackward\":\n return (($caretAtParaStart(selection) && $hasNeighborBlock(selection, \"backward\")) ||\n $caretAdjacentToVerseMarker(selection, \"backward\"));\n case \"deleteForward\":\n return (($caretAtParaEnd(selection) && $hasNeighborBlock(selection, \"forward\")) ||\n $caretAdjacentToVerseMarker(selection, \"forward\"));\n case \"insertText\":\n return false;\n }\n}\n/**\n * The marker/section a delete keystroke would remove at a structural boundary, or undefined\n * when the keystroke is ordinary editing. Mirror of `$shouldBlockStructuralEdit`'s boundary\n * conditions, but resolves the target node instead of returning a boolean.\n *\n * Backward: the adjacent verse, else the current paragraph (when a previous block exists).\n * Forward: the adjacent verse, else the NEXT paragraph (when a next block exists) — the block\n * whose marker the merge removes.\n */\nexport function $structuralDeleteTarget(selection, intent) {\n if (!$isRangeSelection(selection) || !selection.isCollapsed())\n return undefined;\n if (intent === \"deleteBackward\") {\n const verse = $adjacentVerseMarker(selection, \"backward\");\n if (verse)\n return { kind: \"verse\", node: verse };\n if ($caretAtParaStart(selection) && $hasNeighborBlock(selection, \"backward\")) {\n const para = $getParaAncestor(selection.anchor.getNode());\n if ($isSomeParaNode(para))\n return { kind: \"para\", node: para };\n }\n return undefined;\n }\n if (intent === \"deleteForward\") {\n const verse = $adjacentVerseMarker(selection, \"forward\");\n if (verse)\n return { kind: \"verse\", node: verse };\n if ($caretAtParaEnd(selection) && $hasNeighborBlock(selection, \"forward\")) {\n const para = $getParaAncestor(selection.anchor.getNode());\n const next = para?.getNextSibling();\n if ($isSomeParaNode(next))\n return { kind: \"para\", node: next };\n }\n return undefined;\n }\n return undefined;\n}\n/** True when the live selection still encodes the armed target. */\nexport function $isArmedSelection(selection, armed) {\n if (!selection)\n return false;\n if (armed.kind === \"verse\") {\n return $isNodeSelection(selection) && selection.has(armed.key);\n }\n if (armed.kind === \"selection\") {\n if (!$isRangeSelection(selection) || selection.isCollapsed())\n return false;\n if (!armed.anchor || !armed.focus)\n return false;\n const { anchor, focus } = selection;\n return (anchor.key === armed.anchor.key &&\n anchor.offset === armed.anchor.offset &&\n anchor.type === armed.anchor.type &&\n focus.key === armed.focus.key &&\n focus.offset === armed.focus.offset &&\n focus.type === armed.focus.type);\n }\n if (!$isRangeSelection(selection) || selection.isCollapsed())\n return false;\n const anchorPara = $getParaAncestor(selection.anchor.getNode());\n const focusPara = $getParaAncestor(selection.focus.getNode());\n return (!!anchorPara &&\n anchorPara.getKey() === armed.key &&\n !!focusPara &&\n focusPara.getKey() === armed.key);\n}\n/** Collapses the caret to the end of `node` (end of its text for a TextNode). */\nexport function $placeCaretAtEnd(node) {\n if ($isTextNode(node)) {\n const size = node.getTextContentSize();\n node.select(size, size);\n }\n else if ($isElementNode(node)) {\n node.selectEnd();\n }\n else {\n node.selectNext(0, 0);\n }\n}\n/**\n * Merge-into-previous semantics for a paragraph delete: move `para`'s children into its\n * previous paragraph sibling (which keeps ITS marker), remove `para` (dropping its marker),\n * and place the caret at the junction. Text is never lost. Only paragraphs merge into\n * paragraphs (ParaNode/ImpliedParaNode either way); any other previous sibling is a no-op.\n * Caller guarantees a previous element sibling exists (checked via `$hasNeighborBlock`).\n *\n * @param para - The paragraph whose marker is being removed by merging it into its predecessor.\n */\nexport function $mergeParaIntoPrevious(para) {\n const prev = para.getPreviousSibling();\n if (!$isSomeParaNode(prev))\n return;\n const junction = prev.getLastChild();\n const moved = para.getChildren();\n prev.append(...moved);\n para.remove();\n // When `prev` had content, the junction is the end of its last child; when it was empty the\n // junction is its start — so the caret lands where the two paragraphs joined, not at the end.\n if (junction)\n $placeCaretAtEnd(junction);\n else if (!$advancePastParaPrefixes(prev))\n prev.selectStart();\n}\n/**\n * Flattens one pasted/dropped node for a structure-protected document: verse and chapter\n * markers vanish (leaf markers), paragraph wrappers are removed but their children kept,\n * and all other (inline) nodes pass through unchanged.\n */\nfunction $flattenForProtectedStructure(node) {\n if ($isSomeVerseNode(node) || $isSomeChapterNode(node))\n return [];\n if ($isSomeParaNode(node))\n return node.getChildren().flatMap($flattenForProtectedStructure);\n return [node];\n}\n/**\n * Sanitizes a flat array of pasted/dropped nodes for a structure-protected document by\n * stripping structural markers (paragraph breaks, verse markers, chapter markers) while\n * preserving text, inline character formatting, and notes. A removed top-level paragraph\n * boundary is replaced with a single space so words from adjacent paragraphs do not fuse;\n * nested paragraphs are flattened without inserting a separator.\n *\n * @param nodes - The top-level nodes produced from the payload (e.g. via `$generateNodesFromDOM`).\n * @returns A new flat array of inline nodes safe to insert without altering document structure.\n */\nexport function $sanitizeNodesForProtectedStructure(nodes) {\n const result = [];\n for (const node of nodes) {\n const flattened = $flattenForProtectedStructure(node);\n if (flattened.length === 0)\n continue;\n if ($isSomeParaNode(node) && result.length > 0)\n result.push($createTextNode(\" \"));\n result.push(...flattened);\n }\n return result;\n}\n","/*! @license DOMPurify 3.4.13 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.13/LICENSE */\n\nfunction _arrayLikeToArray(r, a) {\n (null == a || a > r.length) && (a = r.length);\n for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];\n return n;\n}\nfunction _arrayWithHoles(r) {\n if (Array.isArray(r)) return r;\n}\nfunction _iterableToArrayLimit(r, l) {\n var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"];\n if (null != t) {\n var e,\n n,\n i,\n u,\n a = [],\n f = true,\n o = false;\n try {\n if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);\n } catch (r) {\n o = true, n = r;\n } finally {\n try {\n if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;\n } finally {\n if (o) throw n;\n }\n }\n return a;\n }\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\nfunction _slicedToArray(r, e) {\n return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();\n}\nfunction _unsupportedIterableToArray(r, a) {\n if (r) {\n if (\"string\" == typeof r) return _arrayLikeToArray(r, a);\n var t = {}.toString.call(r).slice(8, -1);\n return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;\n }\n}\n\nconst entries = Object.entries,\n setPrototypeOf = Object.setPrototypeOf,\n isFrozen = Object.isFrozen,\n getPrototypeOf = Object.getPrototypeOf,\n getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nlet freeze = Object.freeze,\n seal = Object.seal,\n create = Object.create; // eslint-disable-line import/no-mutable-exports\nlet _ref = typeof Reflect !== 'undefined' && Reflect,\n apply = _ref.apply,\n construct = _ref.construct;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!apply) {\n apply = function apply(func, thisArg) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!construct) {\n construct = function construct(Func) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n return new Func(...args);\n };\n}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst arrayIsArray = Array.isArray;\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst numberToString = unapply(Number.prototype.toString);\nconst booleanToString = unapply(Boolean.prototype.toString);\nconst bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);\nconst symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst objectToString = unapply(Object.prototype.toString);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst typeErrorCreate = unconstruct(TypeError);\n/**\n * Creates a new function that calls the given function with a specified thisArg and arguments.\n *\n * @param func - The function to be wrapped and called.\n * @returns A new function that calls the given function with a specified thisArg and arguments.\n */\nfunction unapply(func) {\n return function (thisArg) {\n if (thisArg instanceof RegExp) {\n thisArg.lastIndex = 0;\n }\n for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n args[_key3 - 1] = arguments[_key3];\n }\n return apply(func, thisArg, args);\n };\n}\n/**\n * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n *\n * @param func - The constructor function to be wrapped and called.\n * @returns A new function that constructs an instance of the given constructor function with the provided arguments.\n */\nfunction unconstruct(Func) {\n return function () {\n for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n args[_key4] = arguments[_key4];\n }\n return construct(Func, args);\n };\n}\n/**\n * Add properties to a lookup table\n *\n * @param set - The set to which elements will be added.\n * @param array - The array containing elements to be added to the set.\n * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n * @returns The modified set with added elements.\n */\nfunction addToSet(set, array) {\n let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n if (!arrayIsArray(array)) {\n return set;\n }\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n element = lcElement;\n }\n }\n set[element] = true;\n }\n return set;\n}\n/**\n * Clean up an array to harden against CSPP\n *\n * @param array - The array to be cleaned.\n * @returns The cleaned version of the array\n */\nfunction cleanArray(array) {\n for (let index = 0; index < array.length; index++) {\n const isPropertyExist = objectHasOwnProperty(array, index);\n if (!isPropertyExist) {\n array[index] = null;\n }\n }\n return array;\n}\n/**\n * Shallow clone an object\n *\n * @param object - The object to be cloned.\n * @returns A new object that copies the original.\n */\nfunction clone(object) {\n const newObject = create(null);\n for (const _ref2 of entries(object)) {\n var _ref3 = _slicedToArray(_ref2, 2);\n const property = _ref3[0];\n const value = _ref3[1];\n const isPropertyExist = objectHasOwnProperty(object, property);\n if (isPropertyExist) {\n if (arrayIsArray(value)) {\n newObject[property] = cleanArray(value);\n } else if (value && typeof value === 'object' && value.constructor === Object) {\n newObject[property] = clone(value);\n } else {\n newObject[property] = value;\n }\n }\n }\n return newObject;\n}\n/**\n * Convert non-node values into strings without depending on direct property access.\n *\n * @param value - The value to stringify.\n * @returns A string representation of the provided value.\n */\nfunction stringifyValue(value) {\n switch (typeof value) {\n case 'string':\n {\n return value;\n }\n case 'number':\n {\n return numberToString(value);\n }\n case 'boolean':\n {\n return booleanToString(value);\n }\n case 'bigint':\n {\n return bigintToString ? bigintToString(value) : '0';\n }\n case 'symbol':\n {\n return symbolToString ? symbolToString(value) : 'Symbol()';\n }\n case 'undefined':\n {\n return objectToString(value);\n }\n case 'function':\n case 'object':\n {\n if (value === null) {\n return objectToString(value);\n }\n const valueAsRecord = value;\n const valueToString = lookupGetter(valueAsRecord, 'toString');\n if (typeof valueToString === 'function') {\n const stringified = valueToString(valueAsRecord);\n return typeof stringified === 'string' ? stringified : objectToString(stringified);\n }\n return objectToString(value);\n }\n default:\n {\n return objectToString(value);\n }\n }\n}\n/**\n * This method automatically checks if the prop is function or getter and behaves accordingly.\n *\n * @param object - The object to look up the getter function in its prototype chain.\n * @param prop - The property name for which to find the getter function.\n * @returns The getter function found in the prototype chain or a fallback function.\n */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n object = getPrototypeOf(object);\n }\n function fallbackValue() {\n return null;\n }\n return fallbackValue;\n}\nfunction isRegex(value) {\n try {\n regExpTest(value, '');\n return true;\n } catch (_unused) {\n return false;\n }\n}\n\nconst html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\nconst svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nconst svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\nconst mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);\nconst svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dominant-baseline', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-orientation', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\nconst mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\nconst MUSTACHE_EXPR = seal(/{{[\\w\\W]*|^[\\w\\W]*}}/g);\nconst ERB_EXPR = seal(/<%[\\w\\W]*|^[\\w\\W]*%>/g);\nconst TMPLIT_EXPR = seal(/\\${[\\w\\W]*/g);\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n// Markup-significant character probes used by _sanitizeElements.\n// Shared module-level instances are safe despite the sticky /g flags:\n// unapply() resets lastIndex for RegExp receivers before every call.\nconst ELEMENT_MARKUP_PROBE = seal(/<[/\\w!]/g);\nconst COMMENT_MARKUP_PROBE = seal(/<[/\\w]/g);\nconst FALLBACK_TAG_CLOSE = seal(/<\\/no(script|embed|frames)/i);\nconst SELF_CLOSING_TAG = seal(/\\/>/i);\n\n// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\nconst NODE_TYPE = {\n element: 1,\n attribute: 2,\n text: 3,\n cdataSection: 4,\n entityReference: 5,\n // Deprecated\n entityNode: 6,\n // Deprecated\n processingInstruction: 7,\n comment: 8,\n document: 9,\n documentType: 10,\n documentFragment: 11,\n notation: 12 // Deprecated\n};\nconst getGlobal = function getGlobal() {\n return typeof window === 'undefined' ? null : window;\n};\n/**\n * Creates a no-op policy for internal use only.\n * Don't export this function outside this module!\n * @param trustedTypes The policy factory.\n * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n * @return The policy created (or null, if Trusted Types\n * are not supported or creating the policy failed).\n */\nconst _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {\n if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {\n return null;\n }\n // Allow the callers to control the unique policy name\n // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n // Policy creation with duplicate names throws in Trusted Types.\n let suffix = null;\n const ATTR_NAME = 'data-tt-policy-suffix';\n if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n suffix = purifyHostElement.getAttribute(ATTR_NAME);\n }\n const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n try {\n return trustedTypes.createPolicy(policyName, {\n createHTML(html) {\n return html;\n },\n createScriptURL(scriptUrl) {\n return scriptUrl;\n }\n });\n } catch (_) {\n // Policy creation failed (most likely another DOMPurify script has\n // already run). Skip creating the policy, as this will only cause errors\n // if TT are enforced.\n console.warn('TrustedTypes policy ' + policyName + ' could not be created.');\n return null;\n }\n};\nconst _createHooksMap = function _createHooksMap() {\n return {\n afterSanitizeAttributes: [],\n afterSanitizeElements: [],\n afterSanitizeShadowDOM: [],\n beforeSanitizeAttributes: [],\n beforeSanitizeElements: [],\n beforeSanitizeShadowDOM: [],\n uponSanitizeAttribute: [],\n uponSanitizeElement: [],\n uponSanitizeShadowNode: []\n };\n};\n/**\n * Resolve a set-valued configuration option: a fresh set built from\n * cfg[key] when it is an own array property (seeded with a clone of\n * options.base when given, case-normalized via options.transform),\n * the fallback set otherwise.\n *\n * @param cfg the cloned, prototype-free configuration object\n * @param key the configuration property to read\n * @param fallback the set to use when the option is absent or not an array\n * @param options transform and optional base set to merge into\n * @returns the resolved set\n */\nconst _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {\n return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;\n};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.4.13';\n DOMPurify.removed = [];\n if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {\n // Not running in a browser, provide a factory function\n // so that you can pass your own Window\n DOMPurify.isSupported = false;\n return DOMPurify;\n }\n let document = window.document;\n const originalDocument = document;\n const currentScript = originalDocument.currentScript;\n window.DocumentFragment;\n const HTMLTemplateElement = window.HTMLTemplateElement,\n Node = window.Node,\n Element = window.Element,\n NodeFilter = window.NodeFilter,\n _window$NamedNodeMap = window.NamedNodeMap;\n _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;\n window.HTMLFormElement;\n const DOMParser = window.DOMParser,\n trustedTypes = window.trustedTypes;\n const ElementPrototype = Element.prototype;\n const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n const remove = lookupGetter(ElementPrototype, 'remove');\n const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');\n const getAttributes = lookupGetter(ElementPrototype, 'attributes');\n const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;\n const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;\n const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, 'ownerDocument') : null;\n // As per issue #47, the web-components registry is inherited by a\n // new document created via createHTMLDocument. As per the spec\n // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n // a new empty registry is used when creating a template contents owner\n // document, so we use that as our parent document to ensure nothing\n // is inherited.\n if (typeof HTMLTemplateElement === 'function') {\n const template = document.createElement('template');\n if (template.content && template.content.ownerDocument) {\n document = template.content.ownerDocument;\n }\n }\n let trustedTypesPolicy;\n let emptyHTML = '';\n // The instance's own internal Trusted Types policy. Unlike a caller-supplied\n // `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws\n // on duplicate policy names — and is the only policy allowed to persist\n // across configurations and survive `clearConfig()`.\n let defaultTrustedTypesPolicy;\n let defaultTrustedTypesPolicyResolved = false;\n // Tracks whether we are already inside a call to the configured Trusted Types\n // policy (`createHTML` or `createScriptURL`). If a supplied policy callback\n // itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would\n // re-enter the policy and recurse until the stack overflows. We detect that\n // re-entry and throw a clear, actionable error instead. The guard is shared\n // across both callbacks, because either one re-entering `sanitize` triggers\n // the same unbounded recursion.\n let IN_TRUSTED_TYPES_POLICY = 0;\n const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {\n if (IN_TRUSTED_TYPES_POLICY > 0) {\n throw typeErrorCreate('A configured TRUSTED_TYPES_POLICY callback (createHTML or ' + 'createScriptURL) must not call DOMPurify.sanitize, as that causes ' + 'infinite recursion. Do not pass a policy whose callbacks wrap ' + 'DOMPurify as TRUSTED_TYPES_POLICY; see the \"DOMPurify and Trusted ' + 'Types\" section of the README.');\n }\n };\n const _createTrustedHTML = function _createTrustedHTML(html) {\n _assertNotInTrustedTypesPolicy();\n IN_TRUSTED_TYPES_POLICY++;\n try {\n return trustedTypesPolicy.createHTML(html);\n } finally {\n IN_TRUSTED_TYPES_POLICY--;\n }\n };\n const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {\n _assertNotInTrustedTypesPolicy();\n IN_TRUSTED_TYPES_POLICY++;\n try {\n return trustedTypesPolicy.createScriptURL(scriptUrl);\n } finally {\n IN_TRUSTED_TYPES_POLICY--;\n }\n };\n // Lazily resolve (and cache) the instance's internal default policy.\n // Resolution is attempted at most once: a successful `createPolicy` cannot be\n // repeated (Trusted Types throws on duplicate names), and a failed or\n // unsupported attempt must not be retried on every parse.\n const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {\n if (!defaultTrustedTypesPolicyResolved) {\n defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);\n defaultTrustedTypesPolicyResolved = true;\n }\n return defaultTrustedTypesPolicy;\n };\n const _document = document,\n implementation = _document.implementation,\n createNodeIterator = _document.createNodeIterator,\n createDocumentFragment = _document.createDocumentFragment,\n getElementsByTagName = _document.getElementsByTagName;\n const importNode = originalDocument.importNode;\n let hooks = _createHooksMap();\n /**\n * Expose whether this browser supports running the full DOMPurify.\n */\n DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;\n const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,\n ERB_EXPR$1 = ERB_EXPR,\n TMPLIT_EXPR$1 = TMPLIT_EXPR,\n DATA_ATTR$1 = DATA_ATTR,\n ARIA_ATTR$1 = ARIA_ATTR,\n IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,\n ATTR_WHITESPACE$1 = ATTR_WHITESPACE,\n CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;\n let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;\n /**\n * We consider the elements and attributes below to be safe. Ideally\n * don't add any new ones but feel free to remove unwanted ones.\n */\n /* allowed element names */\n let ALLOWED_TAGS = null;\n const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);\n /* Allowed attribute names */\n let ALLOWED_ATTR = null;\n const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);\n /*\n * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.\n * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n */\n let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {\n tagNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n allowCustomizedBuiltInElements: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: false\n }\n }));\n /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n let FORBID_TAGS = null;\n /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n let FORBID_ATTR = null;\n /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\n /* Decide if ARIA attributes are okay */\n let ALLOW_ARIA_ATTR = true;\n /* Decide if custom data attributes are okay */\n let ALLOW_DATA_ATTR = true;\n /* Decide if unknown protocols are okay */\n let ALLOW_UNKNOWN_PROTOCOLS = false;\n /* Decide if self-closing tags in attributes are allowed.\n * Usually removed due to a mXSS issue in jQuery 3.0 */\n let ALLOW_SELF_CLOSE_IN_ATTR = true;\n /* Output should be safe for common template engines.\n * This means, DOMPurify removes data attributes, mustaches and ERB\n */\n let SAFE_FOR_TEMPLATES = false;\n /* Output should be safe even for XML used within HTML and alike.\n * This means, DOMPurify removes comments when containing risky content.\n */\n let SAFE_FOR_XML = true;\n /* Decide if document with ... should be returned */\n let WHOLE_DOCUMENT = false;\n /* Track whether config is already set on this instance of DOMPurify. */\n let SET_CONFIG = false;\n /* Pristine allowlist bindings captured at setConfig() time. On the\n * persistent-config path sanitize() restores the sets from these before\n * the per-walk hook clone-guard, so a hook's in-call widening cannot\n * carry across calls. Null until setConfig() is called; reset by\n * clearConfig(). */\n let SET_CONFIG_ALLOWED_TAGS = null;\n let SET_CONFIG_ALLOWED_ATTR = null;\n /* Decide if all elements (e.g. style, script) must be children of\n * document.body. By default, browsers might move them to document.head */\n let FORCE_BODY = false;\n /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported).\n * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n */\n let RETURN_DOM = false;\n /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported) */\n let RETURN_DOM_FRAGMENT = false;\n /* Try to return a Trusted Type object instead of a string, return a string in\n * case Trusted Types are not supported */\n let RETURN_TRUSTED_TYPE = false;\n /* Output should be free from DOM clobbering attacks?\n * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n */\n let SANITIZE_DOM = true;\n /* Achieve full DOM Clobbering protection by isolating the namespace of named\n * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n *\n * HTML/DOM spec rules that enable DOM Clobbering:\n * - Named Access on Window (§7.3.3)\n * - DOM Tree Accessors (§3.1.5)\n * - Form Element Parent-Child Relations (§4.10.3)\n * - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n * - HTMLCollection (§4.2.10.2)\n *\n * Namespace isolation is implemented by prefixing `id` and `name` attributes\n * with a constant string, i.e., `user-content-`\n */\n let SANITIZE_NAMED_PROPS = false;\n const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n /* Keep element content when removing element? */\n let KEEP_CONTENT = true;\n /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n * of importing it into a new Document and returning a sanitized copy */\n let IN_PLACE = false;\n /* Allow usage of profiles like html, svg and mathMl */\n let USE_PROFILES = {};\n /* Tags to ignore content of when KEEP_CONTENT is true */\n let FORBID_CONTENTS = null;\n const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',\n // mirrors the selected