diff --git a/docs/clipboard-semantics.md b/docs/clipboard-semantics.md
new file mode 100644
index 00000000..a68ec3dc
--- /dev/null
+++ b/docs/clipboard-semantics.md
@@ -0,0 +1,264 @@
+# Standard-View Clipboard USFM Semantics (PT-4201)
+
+## Overview
+
+This document specifies the copy/paste behavior for USFM content in Platform.Bible's Standard-view scripture editor. These semantics ensure that clipboard operations preserve USFM fidelity, allowing users to copy from the Standard view, paste into external editors, and vice versa while maintaining marker integrity and positional accuracy.
+
+---
+
+## Semantics Decisions (S1–S7)
+
+### S1. Copy (Standard view) — `text/plain` is valid USFM of the selection
+
+Already ~true because markers are real text nodes; this plan closes the exceptions:
+
+- **Note callers:** `\f` + caller + content + `\f*` with the note's USJ `caller` value (`+`, `-`, or literal) — currently the caller is silently dropped (and rewrites to `+` on re-paste). This deliberately _exceeds_ P9, where plain-text copy carries only the caller glyph and full notes survive only in CF_HTML. Rationale: in P10 there is no CF_HTML fidelity carrier for external apps, and the ticket requires "copy from Standard → paste into a plain-text editor: markers present and correctly placed".
+
+- **Collapsed notes** contribute their full (visually hidden) `\f …\f*` bytes. "What you copy" > "what you see", for fidelity. This is documented, not changed.
+
+- **Display-NBSP handling, and ONE set of bytes for both readable flavors:** `text/plain` inverts EVERY display-NBSP to a plain space and leaves a data-NBSP as the `~` the display already shows. `text/html` carries those SAME bytes — the selection's USFM, HTML-escaped, one `
…
` per `\n`-separated line (`usfmToClipboardHtml`, `whitespaceDisplay.plugin.utils.ts`). Lexical's own DOM export (`$getHtmlContent`) is deliberately NOT the html carrier, because it is lossy in two independent ways: `ImmutableNoteCallerNode.exportDOM` carries a collapsed note's caller as a `data-caller` ATTRIBUTE with no text at all, and `UnknownNode.exportDOM` returns `{element: null}` for every kind, which stops `@lexical/html`'s node walk before the construct's own display children — so an export-derived html reached a consumer with the note caller missing and every figure/sidebar/periph/ref/optbreak absent. Paratext 9 is that consumer in practice: its paste reads an incoming fragment's TEXT as USFM, so a P10→P9 note paste arrived with an empty caller. The block-per-line shape mirrors Lexical's own `TextNode` export, so an html consumer that re-imports through `$generateNodesFromDOM` (`StructureKeyboardPlugin`'s DROP sanitizer, and any foreign consumer that rebuilds nodes from html) still sees one block per line; `white-space: pre-wrap` is what keeps an edge space and a run of spaces alive through such a re-import, which is why neither flavor needs to carry an NBSP the other one does not. Consequence: no NBSP reaches either readable flavor, and the two decode to the same document text — a consumer's flavor choice cannot change what the user pasted. `application/x-lexical-editor` stays unnormalized, so a paste back into a Standard-view editor round-trips the display form exactly. A genuine data-NBSP is a literal `~` on screen (the USFM byte form PT9 also shows and copies), so it ships as `~` in every flavor.
+
+- **Byte fidelity around note-internal markers:** Copied text must not contain spaces the source USFM lacks. Live repro (2026-08-07): copying `\x - \xo 1:3: \xo*\xt 2Cor 4:6\xt*\x*Den` produced `… \xo* \xt 2Cor 4:6\xt* \x*Den` — phantom spaces after each closing marker (display-separator NBSPs leaking through the blanket NBSP→space mapping). The copy walker must be source-faithful, mirroring the serialization inverse, not a blanket `replaceAll`.
+
+- **An attribute the display does not show is still copied, whenever the file has bytes for it:** the same rule the note-caller bullet above states, applied wherever the display and the file disagree. A COLLAPSED note deliberately renders no `\cat` run (`createNote`, `usj-editor.adaptor.ts` — the category displays only in the editable EXPANDED layout, mirroring `\va`/`\vp`), so the copy walker contributes the file's own `\cat …\cat*` span itself, placed where the file has it (`\f + \cat People\cat*\fr 1:1 …`, with no trailing space — a space there re-tokenizes into a stray text child inside the note). A spanning TABLE CELL was a display gap rather than a view decision: USFM tables carry no pipe attributes at all, so a cell's width lives in its MARKER NAME (`\thc3-4`) and the tokenizer trims it off into `colspan` on the way in — the cell's display glyph now re-encodes it (`tableCellMarkerWithSpan`, shared with the `UnknownNode` byte builder), so what is shown, copied, and re-tokenized is the cell the file describes rather than a one-column cell. Both were found by sweeping the repo's real-world fixture (`2sa.usj.ts`) through the copy→paste round trip; both are pinned on the copied bytes in `clipboardCopyFidelity.test.tsx`.
+
+- **Multi-block selections:** One `\n` between blocks; a selection starting mid-paragraph omits that paragraph's own `\p ` glyph (matches P9's rendered-text copy). Pinned, not changed.
+
+- **Copying nothing leaves the clipboard untouched, and a read-only construct is copied by selecting it from outside:** a copy triggered by anything other than a real browser clipboard event (the Ctrl+C/Ctrl+X key handling in `ClipboardPlugin`, the context menu's Cut/Copy items, `EditorRef.copy()`/`.cut()`) dispatches `COPY_COMMAND`/`CUT_COMMAND` with a `null` payload, and a null payload has to be turned into a real clipboard event before anything can be written: `@lexical/clipboard`'s `copyToClipboard` appends a hidden placeholder element to the editor root, points the DOM selection at it, and runs `document.execCommand("copy")` to provoke one. Its own handler for that synthesized event declines an empty Lexical selection _before_ calling `preventDefault`, so the browser's default copy then runs against the DOM selection it was handed — the placeholder — and the user's real clipboard contents are replaced by a character that was never in the document. Live report (2026-08-26): trying to copy the marker text of an uneditable construct (`\fig`) yielded one stray character. The construct only makes it _likelier_: `UnknownNode` renders `contentEditable="false"` and its marker/attribute glyphs are `ImmutableTypedTextNode` decorators with `isKeyboardSelectable() === false`, so a click or drag inside one leaves the caret in the prose beside it instead of selecting anything — but a plain collapsed caret anywhere reaches the same path, in every view mode, and cut does too. **Rule:** a copy with nothing selected writes nothing at all. Enforced at two layers, both of which decide INSIDE the update where the selection is authoritative — a check in front of the dispatch would read the last COMMITTED selection, which Lexical is a microtask behind, and would silently no-op a `copy()` made in the same tick as the selection it is copying. (1) `registerEmptyCopyGuard` (`clipboard.utils.ts`, shared-react), a `COPY_COMMAND`/`CUT_COMMAND` listener `ClipboardPlugin` registers at `COMMAND_PRIORITY_LOW` — above `@lexical/rich-text`'s EDITOR fallback, which is what synthesizes the event, and below every feature handler. One registration covers every leg (the keyboard shortcuts, the context menu's Cut/Copy, `EditorRef.copy()`/`.cut()`), since all of them dispatch the same command against the same editor. (2) `$handleCopyForStandardView` CLAIMS a null-payload dispatch it cannot build data for, rather than declining it into that fallback. A real clipboard event is still declined by layer 2 at a collapsed caret (the browser already writes nothing, and declining leaves it un-`preventDefault`ed). A NodeSelection is claimed by neither layer — it has real content, and Lexical's own path copies it correctly. **How a read-only construct IS copied:** by a selection that CONTAINS it. Such a selection — measured at the Lexical level as two ELEMENT points on the parent paragraph either side of the block — reaches every one of the block's display glyphs through the same walker as ordinary prose, so it copies the construct's real USFM bytes; no node-selection state over these constructs exists to recover, and none was invented. **Unverified assumption:** that a real drag or click over a `contentEditable=false` block is what a browser resolves INTO that selection shape (on the reasoning that it will not place a selection endpoint inside one). jsdom cannot observe DOM-range → Lexical selection resolution — the same boundary S6 already records for the WI-2 quirk — so this needs a real-browser check before it is treated as fact. If a browser instead leaves the caret collapsed beside the block, the leak is still closed (nothing is written) but the user gets nothing rather than the figure, which would be a selection-affordance gap to file separately. Pinned in `whitespaceDisplay.plugin.utils.test.tsx` (`"copying an empty selection leaves the clipboard alone"`, including the node-selection carve-out, plus the figure-alone copy), `CommandMenuPlugin.gate.test.tsx` (the same rule in a hidden-marker view where no Standard-view handler is registered at all, and through `EditorRef.copy()`/`.cut()`), and shared-react's `ClipboardPlugin.test.tsx` (the shortcuts, and the same-tick select-then-copy case) and `ContextMenuPlugin.test.tsx` (the menu leg).
+
+- **Optbreak (`\optbreak`, displayed as the literal token `//`) is a non-backslash marker token the copy walker must respect like any other content:** `$selectionToUsfmText` contributes an optbreak's `//` via its `ImmutableTypedTextNode` display child (the `$isDecoratorNode` branch), with no padding added on either side — the significant flanking spaces PT9 preserves byte-for-byte around an optbreak come entirely from the surrounding plain-text nodes, never from the walker itself, and since every TextNode's NBSP is inverted to a plain space unconditionally (the same rule as any other content), `text/plain` never carries an NBSP adjacent to `//`. Pinned in `optbreakClipboardFidelity.test.tsx`'s "copy characterization" describe (2026-08-13).
+
+- **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.
+
+### 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.
+
+`UnknownNode.excludeFromCopy` returned `destination !== "clone"`, excluding every `UnknownNode` unconditionally from the lexical-JSON flavor. An excluded node is not dropped silently — `$appendNodesToJSON` hoists its own children into the parent's list in its place. For an optbreak, whose only child is the `//` `ImmutableTypedTextNode` display token (a content-free `DecoratorNode` with no meaning once separated from its owning `UnknownNode`), that stranded a loose decorator on paste: `$parseSerializedNode` reconstructed the bare decorator, not a recognized optbreak, and nothing re-tokenizes a decorator's text — the discretionary line break silently vanished. Live report (TJ, 2026-08-13, most likely a pre-branch or parent-branch build): copying a selection containing an optbreak put `//` on the clipboard correctly, but pasting that same clipboard back did not restore it.
+
+Fixed by narrowing `UnknownNode.excludeFromCopy` (`UnknownNode.ts`) to leave a CHILD-BEARING "optbreak" out of the exclusion (a genuinely childless live optbreak — an emptied husk mid-settle — still falls back to excluded, matching `text/plain`'s "nothing here" for that shape), plus an `isSelected` override closing a SECOND, independent carrier-disagreement gap measured while verifying the fix: a selection whose ending boundary resolves to an ELEMENT-type point ON the construct at offset 0 (touching the wrapper without covering any of its content) still marked a CHILD-BEARING node "selected" under Lexical's default `isSelected`, producing an entry in the copy that `text/plain` has nothing corresponding to — `excludeFromCopy` alone cannot see this (it has no visibility into which children a given selection will include), so the override closes it at its actual source, mirroring `$selectionToUsfmText`'s own `getNodes()`-based walk so both carriers agree. The override tests child MEMBERSHIP in `getNodes()`, deliberately, and NOT the narrower "does the selection cover a child's content" — the two differ for exactly one shape, and the narrower rule is the worse of the two. That boundary reaches the children two different ways depending on what the first child IS: a construct whose display bytes lead (a figure's `\fig ` glyph, an optbreak's `//`) starts with a DECORATOR, so the element point stays one, no child is in `getNodes()`, and both carriers agree; a construct with no display bytes (a `ref`, whose container USFM never carried) starts with a real `TextNode`, and Lexical normalizes the same point into a TEXT point at that child's offset 0 — the child is then in `getNodes()` contributing zero characters, so `text/plain` emits nothing while this predicate still answers true. Answering false there was measured, and it is a SILENT STRUCTURAL LOSS rather than the agreement it looks like: excluding the wrapper makes `$appendNodesToJSON` hoist its children in its place, `createUnknown` stamps `mode:"token"` on every text child, and `$sliceSelectedTextNodeContent` refuses to slice a token node — so the copy carries the construct's characters with the node and its attributes gone (`[verse, "See ", {"type":"text","mode":"token","text":"Genesis 1:1"}]`, no `ref`, no `loc`), which is precisely the convincing-lie hazard this pair exists to prevent. Membership keeps the construct whole (`[verse, "See ", {"type":"unknown","tag":"ref","unknownAttributes":{"loc":"GEN 1:1"},"children":["Genesis 1:1"]}]`), making the lexical flavor a SUPERSET of `text/plain` at that one boundary. That residual is what the flavor-omission rule below closes, at the layer that can see it: a boundary ON a construct is a selection reaching INTO one, so no lexical flavor is written for it at all and there is nothing left to disagree. Closing it WITHIN the JSON generator would still need a lever Lexical does not offer — `exportNodeToJSON` requires every ElementNode's `exportJSON()` to return a `children` array, so a node cannot say "drop me AND my children". Both rules key on the node's own CHILD COUNT rather than on its kind, so they cover every `UnknownNode` construct alike — the kinds share one display-decorator shape (`unknownDisplayParts` builds each kind's marker/attribute bytes as the same content-free `ImmutableTypedTextNode`s), and so shared one hoisting failure. Pinned end-to-end for optbreak (all three real-world payload shapes: plain-only, plain+html without the lexical flavor, and the full sync/native payload carrying it; plus both childless-shape edges — a genuine husk, and the boundary-selection case) in `optbreakClipboardFidelity.test.tsx`, per kind for figure/sidebar/periph/ref across the same three shapes in `unknownClipboardFidelity.test.tsx` — whose own `"a construct's own boundary"` describe pins what each of those boundaries now puts on the clipboard — text carriers only, for a decorator-led kind (`figure`) and a text-led one (`ref`) alike, with the selection at the construct's start and extended over its first child — rather than leaving the shared predicate inferred from optbreak alone. Every one of those assertions reads the construct's own CONTENT BYTES, never just its tag — a tag-only assertion cannot see a copy that dropped the wrapper while hoisting its characters out, which is exactly how the lossy variant passed review once — plus direct unit pins on `excludeFromCopy` in `UnknownNode.test.ts`. Tables are NOT part of this: `table`/`table:row`/`table:cell` are their own `ImmutableTable*` nodes now, never `UnknownNode`s, so they never took the exclusion at all — measured, and pinned in the same per-kind sweep.
+
+**The internal flavor is written only for a selection that can carry a construct WHOLE:** `$getStandardViewClipboardData` (`whitespaceDisplay.plugin.utils.ts`) omits `application/x-lexical-editor` outright whenever an END of the selection lies inside an opaque construct (`$selectionReachesIntoOpaqueBlock`, `OpaqueBlockGuardPlugin.tsx` — a caret parked in one, or a range that reaches in and stops partway through). The flavor exists to carry a construct WHOLE, and a selection cutting through one cannot be carried whole: `createUnknown` (`usj-editor.adaptor.ts`) builds a construct's text children in token mode, and `$sliceSelectedTextNodeContent` (`@lexical/selection`) refuses to slice a token-mode TextNode, so it carries the node's full text or none of it. A figure caption selected from its third character to its seventh therefore serialized as the COMPLETE figure — wrapper, `file`, `size`, and the caption's whole text — while `text/plain`/`text/html` carried the four selected characters; a native paste of that clipboard inserted a second figure where the user asked for four letters, and a save persisted the invented bytes with no error. The text flavors have no such floor: they are exactly the selected bytes, and Tier 2 re-tokenizes them back into whatever construct those bytes actually spell (an unclosed `\fig` becomes a char span, not a figure). So a cut-through selection ships those two and nothing else, and the carriers agree by there being only one kind of carrier; a selection whose ends are both OUTSIDE still ships the flavor and still takes the node-tree fast path.
+
+A consequence recorded rather than traded away: a `ref` holds exactly one child, so a boundary at its own END covers its content whole yet is still INSIDE it, and the copy loses the `ref` wrapper and its `loc` (USFM has no bytes for the container). The rule keys on where the selection's ENDS are, not on how much of the construct they enclose. That shape is not what a drag produces — `UnknownNode` renders `contentEditable="false"`, so a browser resolves a drag ending "at the ref" to a point beside it rather than inside — and the loss is asserted, not hidden, in `unknownClipboardFidelity.test.tsx`'s `"a construct's own boundary"` describe. The rule itself is pinned in `partialConstructClipboard.test.tsx`: both cut-through shapes (caption-interior, and prose-into-caption), the whole-construct control that must keep the fast path, and the async-clipboard shape a real Ctrl+V delivers.
+
+Which paste paths the flavor reaches is worth stating, because it is why this over-carry never showed up in hand testing: the keyboard Ctrl+V and the context-menu Paste both go through `pasteSelection` (`clipboard.utils.ts`, shared-react), which rebuilds a `DataTransfer` from `navigator.clipboard.read()` — an API that exposes only sanctioned MIME types, so the private flavor is never in it and those pastes always take the text path. The flavor reaches a paste only through a genuine NATIVE paste event (an OS/Electron Edit-menu "Paste", a browser-delivered `paste`) or a programmatic `PASTE_COMMAND` carrying one. Which paste path a host uses is not something this editor controls, so the rule is enforced at the COPY.
+
+Otherwise unchanged, EXCEPT: a paste whose selection touches an attribute display run (a char span's `|attrs`
+list, a milestone's attribute run, or a verse's `\va`/`\vp` value) never takes this path, even when
+the clipboard carries the same-namespace flavor — attribute-context paste always claims plain-text
+insertion instead (see S3's "Paste inside an attribute display run" subsection below). This closes a
+gap the fact recorded in S3 ("the private Lexical flavor is dead on Ctrl+V") does not cover: that
+fact is about the RECONSTRUCTED `DataTransfer` a programmatic/reconstructed paste dispatch uses
+(`clipboard.utils.ts`'s `pasteSelection`), not about a genuine LIVE NATIVE `paste` event, whose
+`clipboardData` is the browser's own and can still carry the flavor for a same-page/session copy —
+this is the same "live native paste event that still has it" case S2's own node-tree fast path is
+written to keep for ordinary content.
+
+### S3. Paste (Standard view, external) — plain text IS the fidelity carrier
+
+Any paste without a same-namespace Lexical payload is treated as a USFM text fragment: take `text/plain` (fall back to text derived from `text/html` with block boundaries → `\n`), normalize NBSPs **positionally**, insert as text, let Tier 2 re-tokenize markers.
+
+- **One extraction layer for every paste claim:** `getPastePayload` (`whitespaceDisplay.plugin.utils.ts`) is the single place a `PASTE_COMMAND` payload is read. It performs the jsdom-safe clipboard duck-check, decodes `text/html` through `htmlPasteText`, normalizes `\r\n` and bare `\r` to `\n` BEFORE any caller tests for a line break, and reports whether the clipboard carries this editor's own `application/x-lexical-editor` flavor. The carrier rule it applies is three-way: a **Paratext 9** clipboard's html is decoded to USFM and WINS over that clipboard's own `text/plain` (see "Paste from Paratext 9" below); every other source's `text/plain` wins whenever it carries anything; a clipboard with only `text/html` falls back to that html's decoded text. Four handlers race on `PASTE_COMMAND` — the in-note `\fp` claim at CRITICAL, this Standard-view claim and the character-stack line replay at HIGH, the paragraph-split arm at LOW — and they must agree byte-for-byte on what was pasted, so none of them reads the clipboard itself. What each claim DOES with `isInternal` still differs by design (the in-note claim covers internal pastes; the Standard-view claim declines them unless the selection touches an attribute run).
+
+- **Paste from Paratext 9 — the html carrier is decoded, because P9's own `text/plain` is strictly less faithful:** P9 writes `text/plain` as the selection's VISIBLE text and keeps the USFM its own paste reads in `CF_HTML`, as escaped `` comments inside spans it marks `exclude` (see "Paratext 9 Reference Behavior" below for the exact shape). A collapsed note therefore renders — and copies as plain text — as its caller GLYPH alone (`a`, or `*` for a `-` caller), so reading the plain carrier pasted one stray character where a footnote belonged. `paratext9HtmlToUsfm` (`paratext9Clipboard.utils.ts`) implements P9's reverse XSLT (`Standard-Reverse.xslt`) over the DOM — `text()` bytes, `usfm:` comments unescaped with newlines flattened to spaces, `exclude`/`include` modes, `div`/`tr`/`br` newlines, `span[class="attribute"]`, and the `usfmopen`/`usfmclosed` class encoding with its `nested` `+` prefix — and every NBSP becomes a plain space, which is P9's OWN copy rule (its stylesheets emit display NBSPs after every opening marker, and a user-supplied non-breaking space reaches the clipboard as the `~` byte). Newline runs are then collapsed and trimmed exactly as `htmlPasteText` does, so a P9 paste splits into lines like any other multi-line paste. The decoder's SIGNATURE is what keeps this from becoming a general "prefer html" rule: it answers `undefined` unless the fragment carries a `usfm:` comment or an element with both a `usfm_` class and `usfmopen`/`usfmclosed`. Two deliberate consequences of that narrowness: a P9 STANDARD-view fragment with no note in it matches neither clause and keeps using its `text/plain` (P9 renders every marker there as literal text, so the two carriers already agree); and P10's own html never matches, since no P10 node emits `usfmopen`/`usfmclosed` and P10 writes no `usfm:` comments — even though P10's own DOM export does stamp `usfm_` classes, which is why the class clause requires both halves. Not implemented: P9's ruby-gloss reconstruction (`span[@id^='ruby:']`), which needs project ruby settings, and the `@colspan` re-encoding P9 applies to a `usfmopen` table cell.
+
+- **A multi-line payload is replayed line by line through `INSERT_PARAGRAPH_COMMAND`**, not through a bare `selection.insertParagraph()` per newline. Going through the command is what makes a paste landing inside a character-style stack close and reopen that stack at every line break, exactly as Enter does — a raw split tears the span, dropping its closing marker and leaving every line after the first outside the reopened style. The selection is removed up front rather than relying on the first `insertText` to replace it, so a payload whose first line is empty (a leading newline) cannot split a still-selected range. `MarkerEditContext.splitExpected` is armed before the first line goes in — the command's own handler arms it for each dispatch, but the first line is inserted before any dispatch runs — so every fresh paragraph the paste creates gets the marker prefix that keeps it from being read as marker-deleted and merged back. A line that ALREADY opens with its own paragraph-marker literal is the one exception: the prefix is not injected there (`$paraMarkerDeletionTransform`, `markerEditDeletion.utils.ts`), because the marker the user pasted is the paragraph's marker and a `\p ` in front of it would settle as a stray empty paragraph. `\b` is where that shows, since a blank-line marker carries neither content nor a separator. Foreign HTML formatting is dropped by design — Standard view is markers-as-text, exactly like P9's Standard view where the reformat pipeline re-tokenizes everything. This generalizes the existing NBSP-gated handler and kills both live-observed corruptions plus the latent doubled-glyph path (re-imported Standard-view HTML producing a `CharNode` via `data-marker` importDOM _and_ literal `\nd` text, since MarkerNode has no importDOM).
+
+- **A paste inserts what was pasted; it never edits the host paragraph's marker.** A whole-paragraph copy carries its paragraph's own `\p ` literal (S1), so pasting one at an existing paragraph's content start puts two paragraph-marker occurrences on the line, and Tier 2 splits it — leaving the host behind as an empty paragraph ahead of the pasted one. That is what Paratext 9 does with the same bytes (`NormalizeTokenUsfm`, `ParatextData/UsfmToken.cs`, emits a line break before every Paragraph token), and it is what typing the same bytes does here, so paste needs no rule of its own and has none. An earlier build deduplicated that pair by dropping the HOST's glyph, which silently retagged the user's paragraph — `\p` became `\q1` — and required a whole paste-provenance apparatus (a per-commit armed flag plus a set of pended node keys, to carry "this rebuild came from a paste" across a deferred settle) to keep it out of typed input. Both are gone.
+
+- **Positional NBSP normalization (replaces the blanket NBSP→`~`), three-part rule:** (1) a leading NBSP — string-start or right after a newline — is a structural separator with nothing in front of it to match against and becomes a space; (2) an NBSP immediately FOLLOWING a marker token (`\marker`, nested `\+marker`, either's closer, or a milestone's anonymous `\*` self-closer) is the required opener/closer separator and becomes a space; (3) an NBSP immediately PRECEDING a marker token is a structural spacer with no source counterpart and is DROPPED entirely (neither spaced nor kept as data) — `createNote` (`usj-editor.adaptor.ts`) appends a spacer after EVERY note child, not just the first, so one sits directly before `\ft`/`\f*` and every other child after the caller; a browser-hop/html-derived copy of a collapsed note therefore carries structural NBSPs on BOTH sides of its interior markers, not only after the opener. Every remaining NBSP is user data → `~`. This is P9's `PostprocessUsfm` model. Live repro that mandates it (2026-08-07): pasting P10's own copied footnote back produced `\f~ \fr~1:1 ~ \ft~Caller test.~ \f*` (every display-NBSP became `~`, breaking marker recognition); a browser-hop paste of `\nd …\nd*` produced `\nd~light … \nd*` with an unmatched pair. **Rule precedence when both position rules match the same NBSP:** a closed-children note shape (`\xo*` immediately followed by NBSP immediately followed by `\xt`) sits after a closing marker AND before the next opening marker at once; the after-marker rule wins and the NBSP becomes a space rather than being dropped, because `normalizePastedNbsp`'s `.replace` chain applies `AFTER_MARKER_NBSP` before `BEFORE_MARKER_NBSP`, consuming the NBSP into a space before the before-marker pass ever sees it. The tie-break is correct, not incidental: an after-closer NBSP in ordinary body text (`\nd*and`) is a real content space that dropping would silently lose, and the case is reachable only via a foreign NBSP-preserving carrier — P10's own `text/plain` copy (S1) never emits an NBSP in this position to begin with.
+
+- **Optbreak (`//`) is a non-backslash marker token the positional NBSP rule does not recognize (characterized, not a live gap):** `AFTER_MARKER_NBSP`/`BEFORE_MARKER_NBSP` (`whitespaceDisplay.plugin.utils.ts`) only match `\`-shaped tokens, so an NBSP adjacent to `//` is not treated as display whitespace and falls to the blanket `~` rule like any other interior NBSP. This is UNREACHABLE via P10's own copy, and DOUBLY so: (1) S1 above establishes `text/plain` never carries an NBSP next to `//` to begin with, and the plain payload is always preferred when present; (2) P10's own `text/html` is those same bytes (S1), so even with `text/plain` absent the decoded html carries the `//` with the same plain flanking spaces and nothing for the positional rule to mistake. Reachable only via a genuinely foreign clipboard source supplying its OWN `text/html` (never P10's) with no `text/plain` at all: a synthetic `before // after
`-shaped payload settles to `before~// after` — the optbreak token itself still recognizes correctly (the tokenizer's `//` split is a plain string `.split("//")`, unaffected by an adjacent `~`), and the NBSP itself is genuine data FROM that foreign source (not a byte P10 invented) — the `~` it becomes is the correct display form for real data-NBSP under the "every remaining NBSP is user data → `~`" rule above, just landing one position earlier than a marker-adjacent rule would recognize. Characterized (not extended/fixed — no live repro reaches it) in `optbreakClipboardFidelity.test.tsx`'s "copy characterization" describe (2026-08-13).
+
+- **The private Lexical flavor is dead on Ctrl+V — mechanically verified, not inferred:** `pasteSelection` (`clipboard.utils.ts`) rebuilds its `DataTransfer` from `navigator.clipboard.read()`, Chromium's async Clipboard-API read. That API, with no `unsanitized`/custom-format opt-in (none is used here), exposes only a fixed, sanctioned MIME allow-list (`text/plain`, `text/html`, and a short list of others) — `application/x-lexical-editor` is not one of them, so the rebuilt `DataTransfer` a real Ctrl+V dispatches can **never** contain it, by construction of the read API itself. This is a static fact about the code path (confirmed by reading `clipboard.utils.ts`), not an inference from the live tilde-corruption symptom — that symptom is merely consistent with it. Consequence: the USFM text carrier IS the internal path too — acceptable once S1's copy is byte-faithful — and the sync `ClipboardEvent` path keeps the node-tree fast path when the flavor is present (S2).
+
+- **Structure-protected documents (Simple interface mode) get the same BYTES and a different MECHANISM:** `$handlePasteForStandardView` handles a paste in a `structureProtectionMode: "protected"` document itself, applying every byte rule above — the `\c`/`\id` strip, the positional NBSP mapping, the Paratext 9 html decode, the attribute-context path. Protection changes exactly two things. (1) A selection `StructureKeyboardPlugin` refuses to replace — `$shouldBlockSelectionReplacement` (`structureKeyboard.utils.ts`): a range spanning a paragraph boundary, or one containing a verse marker, the mutable `VerseNode`'s own caret included — is DECLINED here, so that refusal keeps exactly one owner. Both plugins register `PASTE_COMMAND` at `COMMAND_PRIORITY_HIGH` and the marker engine mounts first, so claiming such a paste would starve the sanitizer's block. (2) A multi-line payload never splits a paragraph: each `\n` becomes a single space and the whole payload goes in as ONE `insertText`, with no `INSERT_PARAGRAPH_COMMAND` and no `splitExpected` arming — the same convention `$sanitizeNodesForProtectedStructure` uses for a boundary it removes.
+
+ Declining the whole paste instead — handing it to the sanitizer's `$sanitizeAndInsert`, which reads `text/html` and nothing else — made the protected mode STRICTLY LESS safe than the unprotected one on three counts: a pasted `\c 7` never reached the strip, so it created a second chapter node and poisoned every subsequent save (the exact corruption the strip exists for, reachable only under protection); NBSPs never reached the positional rule; and a P9 clipboard's `usfm:` comments were never decoded, so a P9 footnote arrived as the caller glyph its `text/plain` shows. It was never protecting this view from marker BYTES either: the marker engine has no protection gate, so a pasted or typed `\v`/`\p` literal tokenizes into a real marker in both modes, and `$sanitizeNodesForProtectedStructure` only strips verse/para NODES out of an html DOM import — a shape Standard view's own `text/html` no longer carries at all (S1). So protection in Standard view governs selection replacement and paragraph splitting; marker bytes are content here.
+
+ One residual belongs to the refusal rule rather than to this handler: a collapsed caret at the end of the editable `\v ` glyph counts as containing a verse marker, so a paste there is refused, while the same paste at offset 0 of the prose immediately after it — the same screen position — succeeds.
+
+- **Marker-bearing lines own their markers:** A pasted line starting with a paragraph-marker literal does NOT also get the host paragraph's cloned prefix (no doubled/empty paragraphs). Marker-free lines inherit the host marker. **Live-verified correct on c7e666fa (2026-08-07)** — `\p one\n\p two` and `tail\n\q1 line` both produced exactly the target structure.
+
+- **Pasted `\c` / `\id`:** Strip during paste normalization (approved). Live-verified harm (2026-08-07): pasting `\c 2` mid-chapter put a chapter node in the editor and poisoned the save loop — PDP rejects every save with "Multiple chapter markers present", the error surfaces only in the renderer log, disk and other editors silently stop updating. (P9 destroys pasted `\c` at save with a user-facing error.)
+
+- **Paste inside an attribute display run — paste ≡ typing:** a selection that TOUCHES attribute-display text at either end — a char span's bare `|attrs` list, a milestone's attribute run, or a verse's `\va`/`\vp` value — is inserted exactly as the SAME characters typed at the SAME caret (or over the same selection) would be: one `selection.insertText`, with each `\n` becoming a single space, per-newline, not run-collapsed (attribute values are single-line; there is no multi-line attribute byte shape to collapse INTO — `"a\n\nb"` pastes as `"a b"`, two spaces). A selection that only PARTLY sits inside attribute-display text (one end in, one end out) takes this same INSERTION MECHANISM — see below for the two corruption shapes that earns it. The existing attribute pend/settle machinery (`$textNodeTier2Transform`'s attribute-tagged early return, `$resolvePendingMarkers`) then re-tokenizes the displayed bytes back into node state identically whether they arrived by typing or paste.
+
+ **The two BODY-content paste rules are skipped only for a selection wholly inside ONE attribute node** — the `\c`/`\id` strip and the positional NBSP mapping, which are the only two places a paste deliberately diverges from typing the same bytes. What earns that carve-out is paste ≡ TYPING: the bytes land where the same keystrokes would land, and the attribute-context contract is that a paste in a value behaves as typing there does. It is NOT that the bytes never re-tokenize — measured, that is false. The `"attribute"` tag survives the insertion, but the caret-departure settle re-tokenizes the paragraph, so a pasted `\c 5` in a value DOES become a chapter marker, and a pasted interior NBSP is read as display whitespace and saved as a plain space. Both happen IDENTICALLY when the same bytes are TYPED at the same caret, which is why this is the typed-`\c` hole recorded as Deferred item 2 — inherited by the paste path, not introduced by it. Extending the strip to cover it would eat bytes out of an attribute value that were never a chapter token — the very regression the carve-out exists to prevent — and would break the equivalence. Both halves are pinned SETTLED, on concrete USJ, in `attributeContextPasteFidelity.test.tsx`. A selection that merely TOUCHES an attribute run does not have that property, and pretending it does was a real data-fidelity hole: removing such a range takes the bytes out of the run — and when the range covers a char span's closing glyph it deletes the closer, so the whole span re-tokenizes around whatever just arrived. Measured both ways round (a range starting in body text and reaching INTO the run, and one starting inside the run and reaching PAST the closer), pasting `\c 5` over such a selection produced a real chapter node mid-paragraph with the rest of the paragraph stranded outside it — the exact save-loop poisoning the strip exists to prevent — and a pasted interior NBSP was read as display whitespace and saved as a plain space instead of surviving as data. So a touching-but-not-wholly-inside selection gets body content's rules for the bytes, while keeping the attribute path's insertion mechanism. One deliberate residual comes with that split: a MULTI-LINE payload landing in body content this way still collapses per newline rather than splitting the paragraph, because splitting a paragraph whose char span the removal has just cut in half is the corruption this path exists to avoid.
+
+ TJ's live repro (filed 2026-08-11, against a pre-branch build): existing span `\nd asdf|who="hi"\nd*`, caret at the end of the `who="hi"` run, paste plain text `sid="things"` — the `who` attribute display and the closing `\nd*` glyph both vanished from the editor, the pasted text rendered outside the span, and the saved file diverged from the editor. `sid="things"` carries no NBSP; the pre-branch build's paste handler still had its OLD NBSP-gated form (see the "positional NBSP normalization" bullet above — that gate was generalized to claim every external paste, NBSP or not, on 2026-08-07), so the most plausible mechanism is that OLD gate declining an NBSP-free paste outright and falling through to Lexical's own default rich-paste node insertion — NOT a same-namespace `application/x-lexical-editor` flavor on the clipboard (this document's own "private Lexical flavor is dead on Ctrl+V" fact, above, still holds for the reconstructed-`DataTransfer` paste path this repro most likely used).
+
+ What matters for the current code is the SHAPE the corruption takes once ANY handler declines an attribute-context paste to Lexical's default rich-paste node insertion — reproduced directly here (`attributeContextPasteFidelity.test.tsx`'s "root cause" describe): it has no notion that an attribute run's text must stay inside its one tagged TextNode, and merges the run, the closing glyph, and even the FOLLOWING paragraph's sibling text into one plain node, destroying the attribute display and the closing marker and leaving the pasted bytes loose in body content. On the current code (a single-line, NBSP-free, flavor-free external paste is already safe since 2026-08-07's generalization), the confirmed regression classes this paste-≡-typing rule closes are: (1) a live native paste event that still carries a same-namespace `application/x-lexical-editor` flavor (S2's own documented case for when that reaches a real handler); (2) a multi-line plain-text payload, which the ordinary pipeline would split into real paragraphs via `INSERT_PARAGRAPH_COMMAND`; (3) a marker-bearing payload (`\c 5`) landing wholly INSIDE one attribute node, where the ordinary pipeline's `\c`/`\id` strip would eat bytes out of an attribute VALUE that were never a chapter token (bytes merely TOUCHING a run do get that strip — see the body-rules paragraph above); (4) a MIXED selection (one end in the run, one end out) combined with either (1) or (2) — the selection touches attribute context, so it must not reach either risky branch merely because its other end sits outside.
+
+ Pre-existing precedence, UNCHANGED by this rule: the CRITICAL-priority in-note multi-line `PASTE_COMMAND` claim (`MarkerEditPlugin.tsx`) still runs before the Standard-view external-paste handler and still wins for a multi-line payload whose selection touches EXPANDED note content — an attribute run that happens to sit inside an expanded note's content is reached by this rule only when that in-note claim itself declines. See `$handlePasteForStandardView`'s doc comment (`whitespaceDisplay.plugin.utils.ts`) for the full mechanism.
+
+### S4. Paste-as-plain-text (Ctrl+Shift+V / context menu) — narrows the payload to `text/plain`
+
+Under S3 this is semantically identical to a normal external paste in Standard view. There is NO "paste literally, don't tokenize" mode; P9 has none either (no Paste Special exists in P9). Live-confirmed identical 2026-08-07. Documented + pinned as equivalence, no new UI. TJ note: the equivalence is Standard-view-scoped — other views may legitimately differentiate the two commands later; out of scope here.
+
+### S5. Hidden-marker views (`formatted`, `paragraph-structure`)
+
+- **Copy-out is prose** (no marker text) — existing, gets a gate test protecting Standard-view handlers from leaking there.
+
+- **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)
+
+Including the empty case: a cut with nothing selected writes nothing and removes nothing, by the same rule and the same two enforcement layers as copy (see S1's "Copying nothing leaves the clipboard untouched").
+
+The WI-2 filed quirk — a selection-delete ending exactly at a just-settled char-span boundary absorbing one adjacent character — gets a targeted regression pin. **Outcome (2026-08-07):** does NOT reproduce at the Lexical selection level. Pinned as regression armor for both `CUT_COMMAND` and a plain `removeText()`, each given a selection whose focus is an element point mirroring DOM `range.setEndAfter(spanElement)` — the exact boundary shape the live repro's programmatic DOM selection used. Both variants produced byte-exact, correctly-scoped results, narrowing the live repro's root cause to the DOM Range → Lexical selection-resolution layer (`applyDOMRange`/`$internalResolveSelectionPoints`), which is not exercised by a Lexical-level `RangeSelection` and so remains covered only by the E2E selection steps.
+
+### S7. Undo — every paste, including rebuilds it triggers, is one undo step
+
+This holds for BOTH paste shapes, and the earlier qualification recorded here no longer applies.
+
+- A paste whose rebuild happens SYNCHRONOUSLY, within the paste's own commit (a terminated marker, or a marker-bearing line's Tier 2 rebuild via `armPasteRebuildDedup`) is one commit and one undo step, as it always was.
+- A paste whose literal text PENDS and only re-tokenizes on a LATER caret departure (e.g. a bare `//` with no terminator) is still TWO commits — the insertion and the departure-triggered settle — but only ONE undo step, because a settle is never its own history entry: it merges into the entry holding the edit it completes. A single press therefore lands on the true pre-paste state rather than stranding the user on a half-settled literal they never typed. Which carrier the clipboard happened to provide (plain payload vs. the same-namespace Lexical flavor) is invisible to undo.
+
+The multi-line `INSERT_PARAGRAPH_COMMAND` replay does not change this either: every dispatch and every `insertText` runs inside the ONE `editor.update()` the `PASTE_COMMAND` dispatch is already in, so a three-line paste is still a single history entry.
+
+Measured, not assumed: pinned for optbreak in `optbreakClipboardFidelity.test.tsx`'s "undo after a PLAIN-payload paste is ONE step" and "undo after the lexical-flavor paste restores the pre-paste (empty host) USJ in one step" pins, and for the synchronous shape in `markerPasteFidelity.test.tsx`'s `undoAndSettle` pins.
+
+---
+
+## Paratext 9 Reference Behavior
+
+Understanding P9's clipboard model provides context for P10's design decisions:
+
+- **Data formats:** P9 writes three clipboard formats (`CopySelectionToClipboard`, `HtmlEditor/FirefoxHtmlEditor/IHtmlEditorCopyPaste.cs`): (1) `text/plain`, the selection's VISIBLE DOM text with every NBSP replaced by a space; in Standard view markers are rendered as literal text, so plain-text copy includes them; (2) `CF_HTML` (or `text/html`), the fidelity carrier — the selected range's serialized DOM fragment wrapped by `HtmlAdaptorUtils.FormatAsHtml`'s `…`; (3) a private `ParatextLanguageId` format for app-internal use.
+
+- **The exact html shape P9's Standard view copies**, and what P10 does with it: a paragraph is `\p …
` (the separator NBSP is a sibling of the glyph span, not inside it), a verse `\v 1 `, a char style `\nd …\nd* `, an attribute list `|src="…" `, and a COLLAPSED NOTE `a ` — the note's real bytes exist ONLY in that `usfm:` comment, the visible `a` is the rendered caller glyph (a `-` caller renders as `*`), and the `exclude`class is what tells P9's own reverse XSLT to drop the glyph while still reading the comment. Comment escaping is`XsltExtensions.EscapeComment`: every character except `a-zA-Z`becomes`%`plus four uppercase hex digits, because an html comment may contain neither`--`nor`>`. **P10 decodes this** (`paratext9HtmlToUsfm`, S3's "Paste from Paratext 9" above), so a P9→P10 footnote paste lands as a real note instead of the caller glyph its `text/plain`carries. P9's FORMATTED views encode markers in`usfmopen`/`usfmclosed` classes instead of as literal text; the decoder implements that rule too, but no copy from those views has been verified against it.
+
+- **Paste workflow:** P9's paste is HTML-first and unsanitized. All normalization happens in a subsequent reformat pipeline: `CleanHtml` → Unicode NFC/NFD per project settings → reverse XSLT → `PostprocessUsfm` (removing FEFF marks; applying NBSP policy — token-leading NBSP → space, interior kept per `AllowInvisibleChars` setting) → `UsfmToken.NormalizeUsfm` (full re-tokenization: whitespace collapse, newlines inserted before paragraph/verse markers, RTL mark handling).
+
+- **Paste Special:** P9 has no Paste Special command. Both normal paste and plain-text paste ride the same normalization path.
+
+- **Structural errors:** P9 destroys pasted `\c` markers at save time with a user-facing error message: "You cannot put a \c marker in the middle of a chapter".
+
+- **Unknown markers:** Pasted unknown USFM markers are kept and flagged red in the UI, never stripped.
+
+---
+
+## Known Accepted Asymmetries vs Paratext 9
+
+These differences are documented and intentional; they are not implemented as workarounds:
+
+1. **P9→P10 paste from P9's Formatted view is unverified:** P9's Formatted-view copy encodes markers in `CF_HTML` CSS classes (`usfmopen`/`usfmclosed`) rather than as literal text. `paratext9HtmlToUsfm` (`paratext9Clipboard.utils.ts`) DOES implement that rule — including the `nested` `+` prefix — and its `usfm_` + `usfmopen` class pair is one of the decoder's two signatures, so such a fragment is decoded rather than pasted as prose. But no copy taken from a running P9 Formatted view has been measured against it: the rule is implemented from `Standard-Reverse.xslt` and pinned on synthetic fragments only, and the `@colspan` re-encoding P9 applies to a Formatted/Preview table cell is not implemented at all. P9's Standard-view copy, by contrast, pastes into P10 through both carriers — its plain text for everything P9 renders as literal marker text, its html for the notes that ride only there (S3's "Paste from Paratext 9").
+
+2. **P10 formatted-view copy loses note callers in plain text:** P9 Standard-view copy preserved the caller glyph in plain text (though full notes survived only in `CF_HTML`). P10's formatted-view copy produces prose-only output, so note callers do not appear in plain text. This is acceptable — prose copy stays clean and users can switch to Standard view for marker-aware copy.
+
+---
+
+## Known Lossy Constructs — Copy→Paste Round Trip
+
+Found by a corpus-style sweep (`clipboardCorpusRoundTrip.test.tsx`): for every fixture in the
+shared USJ round-trip corpus (`corpus-data.ts`) and for the repo's one real-world fixture
+(`2sa.usj.ts`), select the chapter's content, copy, paste into a fresh editor holding the same
+chapter header, and compare the resulting USJ to the source. Three of the remaining failures are
+INHERENT — the plain-text `text/plain` carrier (S3) has no bytes capable of representing the
+construct or the attribute, so no paste-side fix is possible without a different carrier. One is
+ACCEPTED normalization that matches Paratext 9's own behavior, not a bug at all. Items 1–2 and 4
+stay in the sweep as `it.skip`, not deleted, so a future engine change un-skips one automatically
+instead of the gap going unnoticed — which is how the sidebar, table, milestone and `\periph`
+entries that used to sit here left the list. Item 3 is not a skip: it is a single attribute inside a
+fixture that otherwise sweeps clean, so the sweep asserts it as a NAMED carve-out — both its
+presence in the source and its absence after the paste — rather than skipping 143 items' worth of
+coverage over it.
+
+1. **Cross-reference `[` target wrapper (inherent):** USJ's `ref` element is a wrapper USFM
+ itself never carried (`unknownUsfm.utils.ts`'s own doc comment: "USJ invented this container,
+ USFM never carried it... only its child text renders"). Source content
+ `["See ", {type:"ref", loc:"GEN 1:1", content:["Genesis 1:1"]}, " for details."]` copies as
+ plain `"See Genesis 1:1 for details."` with no marker bytes anywhere marking the wrapper's
+ extent; paste re-tokenizes it as ordinary prose (`["See Genesis 1:1 for details."]`, the `ref`
+ wrapper gone). A raw USFM export of this same fixture has the identical gap — not specific to
+ clipboard mechanics.
+
+2. **`closed="false"` char span followed by more paragraph content (inherent):** a `closed="false"`
+ span has, by definition, no closing marker byte anywhere in its own USFM. When such a span is
+ not the last thing in its paragraph (`Tell the ]Lord plainly.`), the
+ copied text (`\nd Lord plainly.`) carries no byte marking where the span's content ends and the
+ trailing prose resumes, so paste has nothing to stop at "Lord" on — it swallows the rest of the
+ paragraph into the span (`{marker:"nd", content:["Lord plainly."]}`, the top-level `" plainly."`
+ string gone). The sibling `"unclosed note (closed=false)"` fixture, whose unclosed span IS the
+ last thing in its paragraph, has no such trailing content to lose and round-trips clean —
+ confirming the ambiguity is specifically about trailing content after an implicit close, not
+ `closed="false"` itself.
+
+3. **A verse's derived `sid` (inherent):** USJ/USX carry `sid="2SA 1:1"` on a verse; USFM carries
+ `\v 1` and nothing else — the identifier is DERIVED from book + chapter + verse when
+ ParatextData produces USX, so there are no bytes anywhere for the plain-text carrier to copy and
+ nothing for paste to rebuild one from. Measured rather than assumed: a plain load→save of the
+ 2SA fixture keeps all 26 of its verse sids, so the loss belongs to the carrier and not to the
+ adaptors. The CHAPTER's `sid` survives the same round trip only because the sweep seeds the
+ target editor with the chapter header. Asserted by name in the sweep's
+ `"real-world multi-construct fixture (2sa)"` describe, on both sides (the source really carries
+ them; the paste carries none), so the carve-out can neither be vacuous nor outlive the gap.
+
+4. **Paragraph-leading space swallowed on paste (ACCEPTED normalization — matches Paratext 9, not a
+ bug):** isolated with a minimal non-corpus repro: pasting the literal text `"\p X"` (marker, its
+ own required separator, and a SECOND, real content-leading space) into a fresh empty `"\p"` host
+ produces `"\p X"` — one space, not two. The mechanism is `consumeSeparator()`
+ (`usfmFragmentToUsj.ts`), whose own comment states exactly this: "Consume the separator
+ whitespace after an opening marker (PT9 skips it) — all leading whitespace, not just a single
+ space." That mirrors Paratext 9's own `NormalizeUsfm` re-tokenization pass (see "Paratext 9
+ Reference Behavior" above: "whitespace collapse, newlines inserted before paragraph/verse
+ markers"), which likewise collapses a whitespace run after a marker during its own paste reformat
+ pipeline — P10 doing the same is parity with P9, not a divergence from it. Corpus symptom:
+ copying ` Leading space precedes this text. ` (source content
+ `" Leading space precedes this text."`) round-trips through paste to
+ `"Leading space precedes this text."` — the leading space is gone, the same as it would be in
+ P9. Kept in the sweep's skip list (the byte-level comparison genuinely differs from the source)
+ but is NOT a fix candidate.
+
+---
+
+## Deferred / Out of Scope
+
+The following items are recorded as deferred and not addressed in this plan:
+
+1. **Pre-existing upstream CommandMenuPlugin `/`-swallow:** The paste gate in hidden-marker views inspects only `text/plain` (`event.clipboardData?.getData("text/plain")`) and blocks the whole paste — no partial insertion — whenever that string contains `\` or `/`, so it also eats URLs, dates, and common phrases like "and/or" outright; the only feedback is a `logger?.info(...)` call, which never reaches the user (no toast, no visible indication the paste was dropped). The check is `text/plain`-only, so a clipboard payload carrying **just** `text/html` (no `text/plain` key at all) skips the gate entirely — confirmed by exercising the paste path directly (2026-08-07): an html-only, backslash-bearing payload reaches Lexical's own HTML-import fallback instead of being blocked, unlike the byte-identical `text/plain` payload. This behavior originates in the `CommandMenuPlugin` on `origin/main` and predates the standard-view branches. As of 2026-08-07, this is not fixed here per TJ's guidance; it belongs to a separate upstream cleanup task.
+
+2. **Typing `\c` mid-chapter poisons the save loop:** If a user manually types `\c 2` in the middle of a chapter, it corrupts the editor state. The PDP error "Multiple chapter markers present" surfaces only in the renderer log, and disk/other editors silently stop updating. This is a data-fidelity issue tracked separately (WI-10's data-fidelity audit) and is not addressed by paste normalization.
+
+3. **Structural marker application on paste in hidden-marker views:** When a user pastes USFM markers into a hidden-marker (formatted/paragraph-structure) view, there is no logic to apply those markers as structural elements. This is by design — hidden-marker views are not marker-aware and should reject or convert marker input. Implementation is out of scope.
+
+4. **P9-style CF_HTML class parsing — now implemented, not yet verified live:** P9's Formatted-view copy encodes markers in `CF_HTML` CSS classes (`usfmopen`/`usfmclosed`) rather than as literal text, and P10 used to ignore them. `paratext9HtmlToUsfm` (`paratext9Clipboard.utils.ts`) implements the rule, so such a fragment decodes to USFM instead of arriving as prose. What remains open is verification and one sub-rule, both recorded as accepted asymmetry #1: no copy from a running P9 Formatted view has been measured against the decoder, and the `@colspan` re-encoding P9 applies to a Formatted/Preview table cell is not implemented.
+
+5. **Custom `text/usfm` MIME type / "Copy as USFM" command:** A dedicated `text/usfm` MIME type or separate "Copy as USFM" command was considered and rejected. Once `text/plain` IS USFM (S1), a separate command adds no value; the existing single-copy-format approach is sufficient.
+
+6. **Popover expanded-note `isStandardView` gating issue:** An issue with note-expansion behavior in popovers is tracked separately and is not addressed in the current plan.
+
+7. **Formatted-view copy includes CSS-hidden collapsed-note content as plain prose:** hidden-marker views (S5) hide a collapsed note's content — `\ft` text and the rest — from view with CSS; the content stays in the DOM/editor state rather than being excluded from it. A prose copy in these views is not scoped to only the visibly-rendered text, so the hidden collapsed-note body is included in the copied prose alongside the surrounding paragraph's own text. This is a pre-existing consequence of how hidden-marker views render (CSS-hide, not exclude) rather than something introduced by this plan, and is out of scope here.
+
+8. **A foreign clipboard that kept an NBSP only in `text/html` loses it (accepted trade-off in the paste carrier choice):** outside the Paratext 9 clause (S3), `getPastePayload` resolves a paste to `text/plain` whenever the clipboard carries any, falling back to the decoded `text/html` only when it does not. A stronger rule is possible and genuinely better for FOREIGN sources — choose the carrier the NBSP survived in (`plainText.includes(NBSP) ? plainText : htmlText`) — because some sources collapse ` ` to a plain space in their `text/plain` while keeping the real NBSP in their `text/html`; with the presence rule, `{"text/plain": "3 000", "text/html": "3 000
"}` inserts `3 000` where the stronger rule would insert `3~000`. What the presence rule stands on is that preferring html buys NOTHING on P10's own copy: both of its readable flavors are the same USFM bytes (S1), neither carries an NBSP at all (display ones invert to plain spaces; a genuine data NBSP displays and copies as `~`), so "the plain text has no NBSP" is true of every P10 copy while the html has no NBSP to recover from. Foreign html-only sources are unaffected either way — they already reach the html fallback, since the rule is about which carrier wins when BOTH are present. So the remaining choice is between a rule that helps a foreign clipboard's data-NBSP and one that is simple and cannot mis-trigger; the simple one stands and the foreign-source loss is accepted. Closing it properly means a per-carrier reconciliation (take the plain text's words and the html's NBSP positions) rather than a choice between the two — more machinery than the gap justifies today.
+
+9. **A hidden-marker view's copy drops a CONTENT-LESS opaque construct entirely:** `UnknownNode.excludeFromCopy` keeps a construct in the `application/x-lexical-editor` flavor only while it has children of its own, which is exactly when `text/plain` has bytes for it — the two carriers agreeing is the point of the rule. In an editable-marker view a construct's own marker and attribute display bytes ARE children, so only a genuinely emptied husk is childless. In a HIDDEN-marker view `createUnknown` (`usj-editor.adaptor.ts`) builds no display children at all, so a construct with no content — a caption-less `figure`, an empty `ref`, every optbreak — has zero children and is dropped from both carriers; an internal (lexical-flavor) copy→paste inside such a view therefore loses the node and every attribute on it. This is consistent with S5 (a hidden-marker view copies the prose it shows, and it shows nothing for a construct with no content) rather than a regression in the copy rule, and closing it would mean deciding that a marker-free view copies marker-only constructs anyway — a view-semantics question, not a clipboard one. Recorded rather than guessed at.
+
+10. **CLOSED — a selection ending exactly at a TEXT-LED construct's own start carried it in the lexical flavor but not in `text/plain`:** closed by the flavor-omission rule in S2 — a boundary ON a construct is a selection reaching INTO one, and a copy that reaches into a construct writes the two text carriers only, so there is no longer a second carrier to disagree. The measurement below stands as the reason the narrower `isSelected` predicate was NOT the answer, and as the reason the rule had to live at the copy rather than inside the JSON generator. `UnknownNode.isSelected` counts a construct selected when one of its own children is in `selection.getNodes()`. For every construct whose display bytes lead (figure, sidebar, periph, optbreak) that boundary is an element point that reaches no child, and the carriers agree. A `ref` has no display bytes of its own, so its first child is a real `TextNode` and Lexical normalizes the same boundary into a TEXT point at that child's offset 0 — the child is in `getNodes()` while contributing zero characters, so `text/plain` correctly emits none of it while the `application/x-lexical-editor` flavor carries the whole `ref` (its `loc` attribute and child text included). The narrower "covers a child's content" predicate closes the disagreement and was rejected on measurement: excluding the wrapper makes `$appendNodesToJSON` HOIST its children in its place, and `createUnknown` stamps `mode:"token"` on every text child so `$sliceSelectedTextNodeContent` never trims the zero-width one — the copy then carries `Genesis 1:1` with the `ref` node and its `loc` silently gone, trading a visible superset for an invisible structural loss. Closing this properly needs a way for a node to exclude itself AND its children from the JSON generator; `exportNodeToJSON` requires every ElementNode's `exportJSON()` to return a `children` array, so there is no such lever without a custom node class for construct content — which is why the rule ended up at the copy instead. What each boundary puts on the clipboard is pinned (not merely characterized) in `unknownClipboardFidelity.test.tsx`'s `"a construct's own boundary"` describe, on content bytes, so neither the lossy hoisting variant nor the over-carrying one can come back unnoticed.
+
+## Test Mapping (S1–S7)
+
+| Semantic | Pinning Test |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **S1** Copy (Standard view) — `text/plain` is valid USFM | `packages/platform/src/editor/markerEdit/clipboardCopyFidelity.test.tsx` — `"note caller fidelity"`, `"phantom-space live-repro pins (2026-08-07)"`, `"multi-paragraph selections"`, `"AttributeRunNode traversal"`, `"attributes a construct carries in bytes the display had to reconstruct"` (a collapsed note's `\cat` run; a spanning table cell's span suffix) describes; `packages/platform/src/editor/markerEdit/whitespaceDisplay.plugin.utils.test.tsx` — `"clipboard normalization"` and `"copy across an UnknownNode (figure)"` describes (display-NBSP→space, `~` data-NBSP preserved, full-USFM figure byte display, in BOTH readable flavors) |
+| **S2** Paste (Standard view, internal) — Lexical node tree reconstruction | `packages/platform/src/editor/markerEdit/whitespaceDisplay.plugin.utils.test.tsx` — `"declines internal pastes (a same-namespace \`application/x-lexical-editor\` payload is present)"`and`"keeps current behavior (declines) when an \`application/x-lexical-editor\` payload is present, even if \`text/html\` carries NBSP"`(external-paste handler steps aside so the Lexical fast path owns the node-tree reconstruction);`packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx`equivalence pins exercise the resulting tokenized tree shape end-to-end;`packages/platform/src/editor/markerEdit/partialConstructClipboard.test.tsx` — the flavor is written only for a selection that can carry a construct whole |
+| **S3** Paste (Standard view, external) — plain text as fidelity carrier | `packages/platform/src/editor/markerEdit/whitespaceDisplay.plugin.utils.test.tsx` — `"paste normalization ($handlePasteForStandardView)"` describe, including its `"positional NBSP normalization"`, `"multi-line paste interplay (splitExpected arming)"`, `"structure protection"`, and `"tilde-corruption regression (2026-08-07 live repro)"` sub-describes; `packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx` — `"multi-line marker-bearing paste semantics (live-verified 2026-08-07)"` and `"\\c/\\id strip on paste"` describes; `packages/platform/src/editor/markerEdit/protectedFigurePaste.test.tsx` — paste in a structure-protected Standard view (the figure built from plain text, from the editor's own html, and from html alone; a legacy DOM-export html inserting prose without a figure; the verse-glyph selection refusal; the `\c`/`\id` strip in both modes; a P9 `usfm:` comment decoded into a note) and `whitespaceDisplay.plugin.utils.test.tsx`'s `"structure protection"` describe (the byte rules a protected paste keeps, no paragraph split, the declined block-spanning selection, and the `Editor`→plugin wiring measured as a difference between the two modes) |
+| **S3 (attribute-context paste)** Paste inside a char/milestone/verse attribute display run — paste ≡ typing | `packages/platform/src/editor/markerEdit/attributeContextPasteFidelity.test.tsx` — `"typed characterization (baseline)"`, `"paste ≡ typed (TJ's repro shape)"` (undo pinned), `"root cause: a native paste event carrying a same-namespace application/x-lexical-editor flavor must not corrupt the run"` (a confirmed regression class, not the literal pre-branch mechanism — see the S3 subsection above), `"leading-space payload"`, `"replace-selection paste inside the attribute value"` (undo pinned), `"multi-line payload collapses to a single space, per newline"` (undo pinned), `"marker-bearing payload"`, `"CUT of a selection inside the attribute value"`, `"mixed selection"` (now claims the attribute path — both the flavor and multi-line corruption shapes pinned), `"mixed selection: the pasted bytes are BODY content, not attribute-value bytes"` (the `\\c` strip and the positional NBSP rule applying to a touching selection from either end, the wholly-inside boundary that keeps value-byte semantics, and a plain payload still settling identically to the same character typed over the same range), `"milestone attribute run paste"` (including the same touching-selection rule on a second run kind), and `"verse \\va run paste"` describes |
+| **S4** Paste-as-plain-text (Ctrl+Shift+V) — equivalence to external paste | `packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx` — `"paste-as-plain-text equivalence (S4): no literal mode, plain always wins"` describe |
+| **S5** Hidden-marker views — prose copy, paste gate preservation | `packages/platform/src/editor/CommandMenuPlugin.gate.test.tsx` — `"CommandMenuPlugin editable-mode gate"` and `"MarkerEditPlugin's Standard-view clipboard handlers do not leak into Formatted view"` describes |
+| **S6** Cut = copy + `removeText()` — WI-2 regression pin | `packages/platform/src/editor/markerEdit/markerEditDeletion.utils.test.tsx` — `"selection-delete at a settled char-span boundary (WI-2 filed)"` (does not reproduce at the Lexical level; pinned as regression armor); `packages/platform/src/editor/markerEdit/clipboardCopyFidelity.test.tsx` — `"cut = copy + removeText"` describe pins the byte-fidelity half of the semantic (cut's clipboard bytes match copy's, and the source is removed) |
+| **S7** Undo — paste and rebuilds as single step | `packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx` — the `undoAndSettle`-based pins (multi-line marker-bearing paste, `\c`/`\id` strip, no-op `\c` paste) each asserting one `UNDO_COMMAND` dispatch restores the exact pre-paste USJ; `packages/platform/src/editor/markerEdit/optbreakClipboardFidelity.test.tsx` — the two one-step undo pins covering the pend-then-departure-settle shape on both carriers |
+| **S3 (peripheral divisions)** `\periph` re-tokenizes back into a `periph` division — its title, its attribute list, and the blocks it contains | `libs/shared/src/converters/usfm/usfmFragmentToUsj.test.ts` — `"usfmFragmentToUsjContent — peripheral divisions (\\periph)"` describe: the marker line split into `alt` plus attributes, the line-per-marker spelling reading identically, following blocks taken into the division, one division ending at the next, a chapter-free contentless division, a nested sidebar, the unparseable-attribute degrade to a literal paragraph, and note context opening no division at all |
+| **Corpus sweep** Copy→paste round trip across the shared USJ fixture corpus AND the repo's real-world fixture | `packages/platform/src/editor/markerEdit/clipboardCorpusRoundTrip.test.tsx` — `"corpus copy/paste round trip (Standard view)"` describe; three known-lossy fixtures kept as `it.skip` (see "Known Lossy Constructs" above); the corpus's own `"optional line break (optbreak)"`, `"sidebar (esb)"`, `"figure (USFM 3 attributes)"` and `"table with header and cells"` fixtures are swept clean, not skipped. Its `"real-world multi-construct fixture (2sa)"` describe sweeps `libs/test-data/src/data/2sa.usj.ts` (143 top-level items, every supported construct in the shapes a real project produced) to full USJ equality, with the one inherent verse-`sid` loss asserted by name on both sides. The whole file runs in about a second |
+| **Optbreak (`//`) clipboard fidelity** Copy characterization (`text/plain`/`text/html`), paste round trip across all three real-world payload shapes (plain-only, plain+html, plain+html+lexical), undo (one step for both the lexical-flavor path and a plain-payload paste that pends until departure — S7), cut | `packages/platform/src/editor/markerEdit/optbreakClipboardFidelity.test.tsx`; the S2 fix itself lives in `libs/shared/src/nodes/features/UnknownNode.ts` (`excludeFromCopy`) |
+| **Per-kind opaque constructs** Copy→paste round trip for figure/sidebar/periph/ref (and the `ImmutableTable*` kinds that used to be `UnknownNode`s) across all three payload shapes, plus the shared `isSelected` boundary rule on more than one kind | `packages/platform/src/editor/markerEdit/unknownClipboardFidelity.test.tsx` — one describe per kind. Figure, sidebar, periph and table round-trip on all three shapes; `ref` round-trips on the lexical flavor and has its plain-carrier LOSS asserted by name (USFM has no bytes at all for a `ref` wrapper). The periph describe also pins WHY the copy stays on one line, by pasting the line-per-marker spelling and asserting the division comes back empty with its paragraph beside it. Mounts the full `Editor`, not `MarkerEditPlugin` alone. Its `"a construct's own start boundary"` describe pins the `isSelected` rule for a decorator-led kind (`figure`, excluded from both carriers) and a text-led one (`ref`, kept whole in the lexical flavor — Deferred item 10), asserting content BYTES rather than tags |
+| **S3 (deferred paste rebuild)** A pasted line whose Tier-2 rebuild PENDS settles to the same document the identical TYPED bytes do | `packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx` — `"a DEFERRED settle treats pasted and typed bytes identically"` describe: a paste whose bytes eject content out of a milestone (so the rebuild waits for departure) is asserted with the pre-departure literal in place, so the pin cannot pass through the immediate-rebuild path, and its sibling drives the same bytes by typing and expects the identical USJ. The no-invented-marker rule for a multi-line paste is pinned in the same file's `"a multi-line paste never invents a `\p` for a line that already carries its own marker"` |
+| **S1 (`text/html` flavor)** The rich-text carrier ships the same USFM bytes as `text/plain` | `packages/platform/src/editor/markerEdit/whitespaceDisplay.plugin.utils.test.tsx` — `"text/html flavor — the same USFM bytes as text/plain"` describe (no NBSP in either flavor, `~` untouched in both, a space run and a fragment-edge space surviving the html carrier, and both flavors decoding to the same document text), including its `"usfmToClipboardHtml mechanics"` unit pins (block per line, empty line, entity escaping); `packages/platform/src/editor/markerEdit/clipboardCopyFidelity.test.tsx` — `"text/html carries the same USFM bytes as text/plain"` describe (a collapsed note's `+`/`-`/literal caller, a cross-reference's `-` caller present in the html, no `data-caller`/node-class residue, a two-paragraph selection's line break, `<`/`>`/`&` escaping, and an html-ONLY paste of the editor's own copy round-tripping the note caller); `packages/platform/src/editor/markerEdit/optbreakClipboardFidelity.test.tsx` — the `"copy characterization"` describe's `text/html` pin (the `//` is present and decodes to the plain carrier's bytes) |
+| **S1/S6 (empty copy)** A copy or cut with nothing selected writes nothing; a read-only construct is copied by a selection containing it | `packages/platform/src/editor/markerEdit/whitespaceDisplay.plugin.utils.test.tsx` — `"copying an empty selection leaves the clipboard alone"` describe (collapsed-caret copy, cut, and the caret-beside-a-figure shape from the live report), plus `"copies the figure's own bytes when the selection covers the figure alone"`; `packages/platform/src/editor/CommandMenuPlugin.gate.test.tsx` — `"copying an empty selection leaves the clipboard alone in a hidden-marker view"` (the rule holds where no Standard-view handler is registered); `libs/shared-react/src/plugins/usj/ClipboardPlugin.test.tsx` — the Ctrl+C/Ctrl+X key handling itself, with the paste keys pinned unaffected |
+| **S3 (opaque construct materialized by a paste)** A pasted `\fig …\fig*` keeps its caption INSIDE the figure — the construct's own content is not ejected as paragraph prose | `packages/platform/src/editor/markerEdit/figurePasteFidelity.test.tsx` — mid-paragraph and end-of-paragraph USJ pins plus a display-byte-order pin whose oracle is a real LOAD of the same document; the fix itself lives in `libs/shared-react/src/plugins/usj/TextSpacingPlugin.tsx` (`$textNodeInUnknownTransform` ejects only into a wrapper that predates the update), with its boundary pinned in that plugin's own suite. These pins mount the full `Editor`: the ejection lives in a sibling plugin, so a `MarkerEditPlugin`-only harness reports the same paste clean |
+| **S3 (Paratext 9 html)** A P9 clipboard's html is decoded to USFM and wins over its own `text/plain` | `packages/platform/src/editor/markerEdit/paratext9Clipboard.utils.test.ts` — three describes: the note shapes P9's plain text cannot carry (`+`/`-`/literal caller, `%XXXX` unescaping of a `--` pair and non-ASCII bytes, a folded `\cat` run), Standard-Reverse's structural rules (`exclude` with a nested `include`, `div`/`br` newlines, `usfmopen`/`usfmclosed` with the `nested` `+` prefix, `span.attribute`, NBSP→space, newline collapsing, and a note inside a real verse+paragraph fragment), and the negatives that keep the decoder off every other clipboard (Word, Google Docs, this editor's own copy built by the real producer, a note-free P9 Standard-view fragment, and a CF_HTML wrapper comment); `packages/platform/src/editor/markerEdit/markerPasteFidelity.test.tsx` — `"Paratext 9 clipboard html (P9→P10 paste)"` describe: a real `PASTE_COMMAND` carrying `text/plain: "a"` plus P9's html materializes the footnote with its caller and body and leaves the glyph out of the document, and the same for a `-` caller cross-reference |
+| **E2E** Real-browser clipboard round trip (Standard view) through the real OS clipboard | `e2e-tests/tests/isolated/scripture-editor/clipboard-usfm-round-trip.spec.ts` (paranext-core repo) |
+
+---
diff --git a/libs/shared-react/src/plugins/usj/ClipboardPlugin.test.tsx b/libs/shared-react/src/plugins/usj/ClipboardPlugin.test.tsx
new file mode 100644
index 00000000..cc061543
--- /dev/null
+++ b/libs/shared-react/src/plugins/usj/ClipboardPlugin.test.tsx
@@ -0,0 +1,188 @@
+import { ClipboardPlugin } from "./ClipboardPlugin";
+import { copySelection } from "./clipboard.utils";
+import { baseTestEnvironment } from "./react-test.utils";
+import { act } from "@testing-library/react";
+import {
+ $createNodeSelection,
+ $createTextNode,
+ $getRoot,
+ $setSelection,
+ LexicalEditor,
+ TextNode,
+} from "lexical";
+import { $createImmutableTypedTextNode, $createParaNode } from "shared";
+
+/**
+ * `document.execCommand("copy")` is how a clipboard write reaches the browser when there is no real
+ * clipboard event to fill in: `@lexical/clipboard` points the DOM selection at a hidden placeholder
+ * element it appends to the editor and runs it to provoke one. jsdom implements no `execCommand` at
+ * all, so these tests install a spy in its place — **called means a write reached the browser, not
+ * called means the clipboard is untouched**. That is the observable throughout; the placeholder's
+ * own content belongs to `@lexical/clipboard` and is never asserted on here.
+ */
+let execCommand: ReturnType;
+
+beforeEach(() => {
+ execCommand = vi.fn(() => true);
+ Object.defineProperty(document, "execCommand", {
+ configurable: true,
+ writable: true,
+ value: execCommand,
+ });
+ // `pasteSelection`/`pasteSelectionAsPlainText` read the async clipboard API, which jsdom also
+ // does not implement. A read that never settles is enough for these tests: they assert only that
+ // the paste keys still reach it, not what a paste does with what it finds.
+ vi.stubGlobal("navigator", {
+ ...navigator,
+ clipboard: { read: vi.fn(() => new Promise(() => undefined)) },
+ });
+});
+
+afterEach(async () => {
+ // `@lexical/clipboard` keeps a MODULE-level timer handle while it waits for the clipboard event
+ // its `execCommand` call should provoke, and refuses to start another copy until that handle
+ // clears. A test that reaches the real copy path therefore silences the `execCommand` assertion
+ // in the NEXT test unless the window is drained here — which would make a failure show up only in
+ // whichever test happens to run first. The window is `EVENT_LATENCY`, 50ms.
+ await new Promise((resolve) => setTimeout(resolve, 60));
+ Reflect.deleteProperty(document, "execCommand");
+ vi.unstubAllGlobals();
+});
+
+/** An editor holding one paragraph of text, with the clipboard key handling under test mounted. */
+async function clipboardEnvironment(): Promise<{ editor: LexicalEditor; text: TextNode }> {
+ let text: TextNode | undefined;
+ const { editor } = await baseTestEnvironment(
+ () => {
+ text = $createTextNode("In the beginning");
+ $getRoot().append($createParaNode("p").append(text));
+ },
+ ,
+ );
+ if (!text) throw new Error("expected the initial text node to exist");
+ return { editor, text };
+}
+
+/** Presses a clipboard shortcut on the editor's root element, where the plugin listens. */
+async function pressShortcut(
+ editor: LexicalEditor,
+ key: string,
+ shiftKey = false,
+): Promise {
+ const rootElement = editor.getRootElement();
+ if (!rootElement) throw new Error("editor has no root element to press a key on");
+ const event = new KeyboardEvent("keydown", {
+ key,
+ ctrlKey: true,
+ shiftKey,
+ bubbles: true,
+ cancelable: true,
+ });
+ await act(async () => {
+ rootElement.dispatchEvent(event);
+ });
+ return event;
+}
+
+describe("ClipboardPlugin — copy/cut with nothing selected", () => {
+ it("leaves the clipboard untouched on Ctrl+C at a collapsed caret", async () => {
+ const { editor, text } = await clipboardEnvironment();
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ await pressShortcut(editor, "c");
+
+ expect(execCommand).not.toHaveBeenCalled();
+ });
+
+ it("leaves the clipboard untouched on Ctrl+X at a collapsed caret, and removes nothing", async () => {
+ const { editor, text } = await clipboardEnvironment();
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ await pressShortcut(editor, "x");
+
+ expect(execCommand).not.toHaveBeenCalled();
+ editor.getEditorState().read(() => expect(text.getTextContent()).toBe("In the beginning"));
+ });
+});
+
+describe("ClipboardPlugin — copy/cut with a selection", () => {
+ it("copies on Ctrl+C", async () => {
+ const { editor, text } = await clipboardEnvironment();
+ await act(async () => editor.update(() => text.select(0, text.getTextContentSize())));
+
+ await pressShortcut(editor, "c");
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+
+ it("cuts on Ctrl+X", async () => {
+ const { editor, text } = await clipboardEnvironment();
+ await act(async () => editor.update(() => text.select(0, text.getTextContentSize())));
+
+ await pressShortcut(editor, "x");
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+
+ it("copies a node selection — the guard is about having nothing to copy, not about ranges", async () => {
+ // A node selection covers real content and is not collapsed, so it copies like any other. The
+ // guard tests "is there anything here", NOT "is this a range": narrowing it to range selections
+ // would silently swallow this copy.
+ const { editor } = await clipboardEnvironment();
+ await act(async () =>
+ editor.update(() => {
+ const decorator = $createImmutableTypedTextNode("marker", "\\p");
+ $getRoot().getFirstChild()?.insertBefore?.($createParaNode("p").append(decorator));
+ const nodeSelection = $createNodeSelection();
+ nodeSelection.add(decorator.getKey());
+ $setSelection(nodeSelection);
+ }),
+ );
+
+ await pressShortcut(editor, "c");
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+});
+
+describe("ClipboardPlugin — the guard reads the live selection, not the committed one", () => {
+ // Lexical commits on a microtask, so the last COMMITTED selection lags a selection made earlier
+ // in the same synchronous tick. A guard that read the committed state would see "nothing
+ // selected" here and silently copy nothing — and this is the ordinary shape of the public
+ // `EditorRef.copy()` path: select something programmatically, then copy it.
+ it("copies a selection made earlier in the same synchronous tick", async () => {
+ const { editor, text } = await clipboardEnvironment();
+
+ await act(async () => {
+ editor.update(() => text.select(0, text.getTextContentSize()));
+ copySelection(editor);
+ });
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+
+ it("copies a selection made inside the same editor.update() as the copy call", async () => {
+ const { editor, text } = await clipboardEnvironment();
+
+ await act(async () =>
+ editor.update(() => {
+ text.select(0, text.getTextContentSize());
+ copySelection(editor);
+ }),
+ );
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+});
+
+describe("ClipboardPlugin — paste keys are unaffected", () => {
+ it("claims Ctrl+V and Ctrl+Shift+V regardless of the selection", async () => {
+ const { editor, text } = await clipboardEnvironment();
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ // A collapsed caret is exactly where a paste belongs, so the empty-selection rule copy and cut
+ // now follow must not reach these.
+ expect((await pressShortcut(editor, "v")).defaultPrevented).toBe(true);
+ expect((await pressShortcut(editor, "v", true)).defaultPrevented).toBe(true);
+ });
+});
diff --git a/libs/shared-react/src/plugins/usj/ClipboardPlugin.tsx b/libs/shared-react/src/plugins/usj/ClipboardPlugin.tsx
index 9158db69..cbcca185 100644
--- a/libs/shared-react/src/plugins/usj/ClipboardPlugin.tsx
+++ b/libs/shared-react/src/plugins/usj/ClipboardPlugin.tsx
@@ -1,7 +1,12 @@
-import { pasteSelection, pasteSelectionAsPlainText } from "./clipboard.utils";
+import {
+ copySelection,
+ cutSelection,
+ pasteSelection,
+ pasteSelectionAsPlainText,
+ registerEmptyCopyGuard,
+} from "./clipboard.utils";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
-import { IS_APPLE } from "@lexical/utils";
-import { COPY_COMMAND, CUT_COMMAND } from "lexical";
+import { IS_APPLE, mergeRegister } from "@lexical/utils";
import { useEffect } from "react";
export function ClipboardPlugin(): null {
@@ -14,10 +19,10 @@ export function ClipboardPlugin(): null {
if (!shiftKey && key.toLowerCase() === "c") {
event.preventDefault();
- editor.dispatchCommand(COPY_COMMAND, null);
+ copySelection(editor);
} else if (!shiftKey && key.toLowerCase() === "x") {
event.preventDefault();
- editor.dispatchCommand(CUT_COMMAND, null);
+ cutSelection(editor);
} else if (key.toLowerCase() === "v") {
event.preventDefault();
if (shiftKey) pasteSelectionAsPlainText(editor);
@@ -25,15 +30,20 @@ export function ClipboardPlugin(): null {
}
};
- return editor.registerRootListener(
- (rootElement: HTMLElement | null, prevRootElement: HTMLElement | null) => {
- if (prevRootElement !== null) {
- prevRootElement.removeEventListener("keydown", onKeyDown);
- }
- if (rootElement !== null) {
- rootElement.addEventListener("keydown", onKeyDown);
- }
- },
+ return mergeRegister(
+ // 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.
+ registerEmptyCopyGuard(editor),
+ editor.registerRootListener(
+ (rootElement: HTMLElement | null, prevRootElement: HTMLElement | null) => {
+ if (prevRootElement !== null) {
+ prevRootElement.removeEventListener("keydown", onKeyDown);
+ }
+ if (rootElement !== null) {
+ rootElement.addEventListener("keydown", onKeyDown);
+ }
+ },
+ ),
);
}, [editor]);
diff --git a/libs/shared-react/src/plugins/usj/ContextMenuPlugin.test.tsx b/libs/shared-react/src/plugins/usj/ContextMenuPlugin.test.tsx
new file mode 100644
index 00000000..0c0093bb
--- /dev/null
+++ b/libs/shared-react/src/plugins/usj/ContextMenuPlugin.test.tsx
@@ -0,0 +1,132 @@
+import { ClipboardPlugin } from "./ClipboardPlugin";
+import { ContextMenuPlugin } from "./ContextMenuPlugin";
+import { baseTestEnvironment } from "./react-test.utils";
+import { act } from "@testing-library/react";
+import { $createTextNode, $getRoot, LexicalEditor, TextNode } from "lexical";
+import { $createParaNode } from "shared";
+
+/**
+ * The context menu's Cut/Copy dispatch the same commands the keyboard shortcuts do, so they are
+ * covered by the same empty-copy guard (`registerEmptyCopyGuard`). This pins that the leg really
+ * does go through it, rather than dispatching around it — both in the shape the shipped editors
+ * mount (alongside `ClipboardPlugin`, which registers the guard too) and with `ContextMenuPlugin`
+ * mounted alone, which a host consuming the plugin on its own is free to do.
+ *
+ * Note where `onSelect` runs: inside `editor.update()` (see the plugin's Enter handler). That is
+ * why the guard has to live on the COMMAND and read the live selection — a check in front of the
+ * dispatch reading the last committed state would be both stale and, via `editor.read()`, unsafe
+ * to call from there.
+ *
+ * `document.execCommand("copy")` is the observable, as everywhere else in this suite: called means
+ * a clipboard write reached the browser, not called means the clipboard is untouched.
+ */
+let execCommand: ReturnType;
+
+beforeEach(() => {
+ execCommand = vi.fn(() => true);
+ Object.defineProperty(document, "execCommand", {
+ configurable: true,
+ writable: true,
+ value: execCommand,
+ });
+});
+
+afterEach(async () => {
+ // Drain `@lexical/clipboard`'s module-level `EVENT_LATENCY` (50ms) handle, which otherwise makes
+ // a test that reached the real copy path silence the next test's assertion.
+ await new Promise((resolve) => setTimeout(resolve, 60));
+ Reflect.deleteProperty(document, "execCommand");
+});
+
+async function contextMenuEnvironment(
+ withClipboardPlugin = true,
+): Promise<{ editor: LexicalEditor; text: TextNode }> {
+ let text: TextNode | undefined;
+ const { editor } = await baseTestEnvironment(
+ () => {
+ text = $createTextNode("In the beginning");
+ $getRoot().append($createParaNode("p").append(text));
+ },
+ <>
+ {withClipboardPlugin && }
+
+ >,
+ );
+ if (!text) throw new Error("expected the initial text node to exist");
+ return { editor, text };
+}
+
+/**
+ * Opens the context menu over the editor's content and activates the option at `index` the way a
+ * keyboard user would (the plugin's own arrow/Enter handling), rather than by reaching for the
+ * rendered menu's markup. Built-in order: Cut, Copy, Paste, Paste as Plain Text.
+ */
+async function chooseContextMenuOption(editor: LexicalEditor, index: number): Promise {
+ const rootElement = editor.getRootElement();
+ const target = rootElement?.firstElementChild;
+ if (!target) throw new Error("expected the editor to have rendered content to right-click");
+ await act(async () => {
+ target.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, cancelable: true }));
+ });
+ for (let step = 0; step <= index; step++) {
+ await act(async () => {
+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
+ });
+ }
+ await act(async () => {
+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
+ });
+}
+
+const COPY_OPTION = 1;
+const CUT_OPTION = 0;
+
+describe("ContextMenuPlugin — Cut/Copy go through the empty-copy guard", () => {
+ it("Copy with a collapsed caret leaves the clipboard untouched", async () => {
+ const { editor, text } = await contextMenuEnvironment();
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ await chooseContextMenuOption(editor, COPY_OPTION);
+
+ expect(execCommand).not.toHaveBeenCalled();
+ });
+
+ it("Cut with a collapsed caret leaves the clipboard untouched and removes nothing", async () => {
+ const { editor, text } = await contextMenuEnvironment();
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ await chooseContextMenuOption(editor, CUT_OPTION);
+
+ expect(execCommand).not.toHaveBeenCalled();
+ editor.getEditorState().read(() => expect(text.getTextContent()).toBe("In the beginning"));
+ });
+
+ it("Copy with a selection copies — the menu leg is really wired to the command", async () => {
+ const { editor, text } = await contextMenuEnvironment();
+ await act(async () => editor.update(() => text.select(0, text.getTextContentSize())));
+
+ await chooseContextMenuOption(editor, COPY_OPTION);
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+});
+
+describe("ContextMenuPlugin mounted without ClipboardPlugin", () => {
+ it("Copy with a collapsed caret still leaves the clipboard untouched — the plugin carries its own guard", async () => {
+ const { editor, text } = await contextMenuEnvironment(false);
+ await act(async () => editor.update(() => text.select(3, 3)));
+
+ await chooseContextMenuOption(editor, COPY_OPTION);
+
+ expect(execCommand).not.toHaveBeenCalled();
+ });
+
+ it("Copy with a selection still copies — the standalone guard does not over-claim", async () => {
+ const { editor, text } = await contextMenuEnvironment(false);
+ await act(async () => editor.update(() => text.select(0, text.getTextContentSize())));
+
+ await chooseContextMenuOption(editor, COPY_OPTION);
+
+ expect(execCommand).toHaveBeenCalledWith("copy");
+ });
+});
diff --git a/libs/shared-react/src/plugins/usj/ContextMenuPlugin.tsx b/libs/shared-react/src/plugins/usj/ContextMenuPlugin.tsx
index 7d8efd25..75adb0ad 100644
--- a/libs/shared-react/src/plugins/usj/ContextMenuPlugin.tsx
+++ b/libs/shared-react/src/plugins/usj/ContextMenuPlugin.tsx
@@ -2,9 +2,14 @@
* Adapted from https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/ContextMenuPlugin/index.tsx
*/
-import { pasteSelection, pasteSelectionAsPlainText } from "./clipboard.utils";
+import {
+ copySelection,
+ cutSelection,
+ pasteSelection,
+ pasteSelectionAsPlainText,
+ registerEmptyCopyGuard,
+} from "./clipboard.utils";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
-import { COPY_COMMAND, CUT_COMMAND } from "lexical";
import {
ReactElement,
useCallback,
@@ -134,15 +139,20 @@ export function ContextMenuPlugin({
const options = useMemo(() => {
const builtIn = [
+ // 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 ContextMenuOption(`Cut`, {
onSelect: () => {
- editor.dispatchCommand(CUT_COMMAND, null);
+ cutSelection(editor);
},
isDisabled: isReadonly,
}),
new ContextMenuOption(`Copy`, {
onSelect: () => {
- editor.dispatchCommand(COPY_COMMAND, null);
+ copySelection(editor);
},
}),
new ContextMenuOption(`Paste`, {
@@ -170,6 +180,14 @@ export function ContextMenuPlugin({
setSelectedIndex(undefined);
}, []);
+ // This plugin is exported on its own, so a host can mount it without `ClipboardPlugin` — and its
+ // Cut/Copy options would then hit the unguarded synthesized-copy path, overwriting whatever the
+ // clipboard already held with `@lexical/clipboard`'s hidden placeholder character. Registering
+ // the guard here keeps the plugin self-sufficient; a second registration alongside
+ // `ClipboardPlugin`'s is harmless, since both listeners sit at the same priority and the first
+ // one to claim an empty selection stops propagation before the other runs.
+ useEffect(() => registerEmptyCopyGuard(editor), [editor]);
+
// Register context menu event on editor root
useEffect(() => {
const handleContextMenu = (event: MouseEvent) => {
diff --git a/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.test.tsx b/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.test.tsx
new file mode 100644
index 00000000..b2a766bc
--- /dev/null
+++ b/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.test.tsx
@@ -0,0 +1,150 @@
+/**
+ * The two edges of {@link DecoratorBoundarySelectionPlugin}'s scope, which the figure pins in
+ * `decoratorBoundarySelection.test.tsx` (platform) cannot reach: the DOM point form Lexical resolves
+ * by a route of its own, and the one decorator whose landings belong to another rule.
+ */
+
+import { $createImmutableNoteCallerNode } from "../../nodes/usj";
+import { DecoratorBoundarySelectionPlugin } from "./DecoratorBoundarySelectionPlugin";
+import { baseTestEnvironment } from "./react-test.utils";
+import { TrailingNoteCaretGuardPlugin } from "./TrailingNoteCaretGuardPlugin";
+import { act } from "@testing-library/react";
+import {
+ $createTextNode,
+ $getRoot,
+ $getSelection,
+ $isRangeSelection,
+ $isTextNode,
+ LexicalEditor,
+} from "lexical";
+import {
+ $createCharNode,
+ $createImmutableTypedTextNode,
+ $createMarkerNode,
+ $createMarkerTrailingSeparator,
+ $createNoteNode,
+ $createParaNode,
+ $createUnknownNode,
+ $isParaNode,
+ CURSOR_PLACEHOLDER_CHAR,
+ ParaNode,
+} from "shared";
+import { ReactNode } from "react";
+
+/** Flush the macrotask jsdom fires its native `selectionchange` on, plus the microtasks the caret
+ * guards defer their work by. */
+async function flushSelectionChange(): Promise {
+ await act(async () => {
+ await new Promise((resolve) => {
+ setTimeout(resolve, 0);
+ });
+ });
+}
+
+/** Point the DOM selection at one position and let the native `selectionchange` carry it into
+ * Lexical, the way a click does. */
+async function putDomCaret(node: Node, offset: number): Promise {
+ await flushSelectionChange();
+ const domSelection = document.getSelection();
+ if (!domSelection) throw new Error("no DOM selection");
+ await act(async () => {
+ domSelection.setBaseAndExtent(node, offset, node, offset);
+ });
+ await flushSelectionChange();
+}
+
+/** The document's one paragraph. */
+function readParagraph(editor: LexicalEditor): ParaNode {
+ return editor.getEditorState().read(() => {
+ const para = $getRoot().getChildren().find($isParaNode);
+ if (!para) throw new Error("no paragraph in the tree");
+ return para;
+ });
+}
+
+describe("a DOM point that IS a decorator's own element", () => {
+ /** `\p before \fig cap\fig*` — a read-only construct whose opening and closing USFM bytes are
+ * `ImmutableTypedTextNode` decorators, the shape Standard view renders. */
+ async function figureEnvironment(): Promise<{ editor: LexicalEditor }> {
+ return baseTestEnvironment(
+ () => {
+ const figure = $createUnknownNode("figure", "fig").append(
+ $createImmutableTypedTextNode("marker", "\\fig "),
+ $createTextNode("cap"),
+ $createImmutableTypedTextNode("marker", "\\fig*"),
+ );
+ $getRoot().append($createParaNode("p").append($createTextNode("before "), figure));
+ },
+ ,
+ );
+ }
+
+ it("is left to Lexical, whose own answer for that form the caret guards depend on", async () => {
+ // The plugin's job is the form Lexical REFUSES to resolve — a point strictly INSIDE a
+ // decorator's element. A point that IS the element resolves by a different route: Lexical makes
+ // an element point beside the decorator out of it, and then deliberately nulls a selection whose
+ // BOTH ends came from decorator DOM, which is the arrival `TrailingNoteCaretGuardPlugin` reads.
+ // Answering it here would take that arrival away from the rules that can already see it.
+ const { editor } = await figureEnvironment();
+ const openerDom = editor.getRootElement()?.querySelector('[data-text-type="marker"]');
+ if (!openerDom) throw new Error("the opener glyph did not render");
+
+ await putDomCaret(openerDom, 0);
+
+ editor.getEditorState().read(() => {
+ expect($getSelection()).toBeNull();
+ });
+ });
+});
+
+describe("a landing on a collapsed note's caller", () => {
+ /** `\p before |note|` — a collapsed note nothing renders past, the shape
+ * `TrailingNoteCaretGuardPlugin` hosts a caret for. */
+ async function noteEnvironment(children: ReactNode): Promise<{ editor: LexicalEditor }> {
+ return baseTestEnvironment(() => {
+ const note = $createNoteNode("f", "+").append(
+ $createMarkerNode("f", "opening"),
+ $createImmutableNoteCallerNode("+", "note preview"),
+ $createMarkerTrailingSeparator(),
+ $createCharNode("ft").append(
+ $createMarkerNode("ft", "opening"),
+ $createTextNode("note body"),
+ ),
+ $createMarkerNode("f", "closing"),
+ );
+ $getRoot().append($createParaNode("p").append($createTextNode("before "), note));
+ }, children);
+ }
+
+ /** The caller's rendered `` — the innermost DOM a hit test on the caller can land in, and
+ * so a position a click at the blank end of that line produces. */
+ function callerButton(editor: LexicalEditor): Element {
+ const button = editor.getRootElement()?.querySelector(".immutable-note-caller button");
+ if (!button) throw new Error("the note caller did not render");
+ return button;
+ }
+
+ it("goes to the trailing-note guard, which needs the arrival to stay unresolved", async () => {
+ // Snapping here would hand the note guard a selection, and that guard stands down whenever there
+ // is one — leaving the caret between the note's opening glyph and its caller, inside content a
+ // collapsed note does not render at all. The host past the note is the repair that must survive.
+ const { editor } = await noteEnvironment(
+ <>
+
+
+ >,
+ );
+ const para = readParagraph(editor);
+
+ await putDomCaret(callerButton(editor), 0);
+
+ editor.getEditorState().read(() => {
+ const host = para.getLastChild();
+ if (!$isTextNode(host)) throw new Error("expected a text host past the note");
+ expect(host.getTextContent()).toBe(CURSOR_PLACEHOLDER_CHAR);
+ const selection = $getSelection();
+ if (!$isRangeSelection(selection)) throw new Error("expected a range selection");
+ expect(selection.anchor.key).toBe(host.getKey());
+ });
+ });
+});
diff --git a/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.tsx b/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.tsx
new file mode 100644
index 00000000..df10da63
--- /dev/null
+++ b/libs/shared-react/src/plugins/usj/DecoratorBoundarySelectionPlugin.tsx
@@ -0,0 +1,319 @@
+import { $isImmutableNoteCallerNode } from "../../nodes/usj";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import {
+ $createRangeSelectionFromDom,
+ $getNearestNodeFromDOMNode,
+ $getSelection,
+ $isDecoratorNode,
+ $isRangeSelection,
+ $setSelection,
+ COMMAND_PRIORITY_CRITICAL,
+ getDOMSelection,
+ isDOMTextNode,
+ isSelectionWithinEditor,
+ LexicalEditor,
+ LexicalNode,
+ SELECTION_CHANGE_COMMAND,
+} from "lexical";
+import { useEffect, useRef } from "react";
+
+/** One end of a DOM selection — the container node plus the offset within it. */
+interface DomPoint {
+ node: Node;
+ offset: number;
+}
+
+/** Which edge of a decorator a point is being moved to, expressed in its PARENT's child list. */
+type Boundary = "before" | "after";
+
+/**
+ * The whole of a `Selection` that `$createRangeSelectionFromDom` looks at, given no previous Lexical
+ * selection to reuse: `$internalCreateRangeSelection` (lexical/LexicalSelection) takes the four point
+ * properties off it and hands them to `$internalResolveSelectionPoints`, and touches no other member
+ * — the `isSelectionWithinEditor` shortcut beside them needs a previous RANGE selection and so cannot
+ * be reached from that entry point. A plain object carrying the snapped ends is therefore a complete
+ * stand-in, which is what lets the boundary points reach Lexical without the DOM selection being
+ * written.
+ */
+type DomSelectionPoints = Pick<
+ Selection,
+ "anchorNode" | "anchorOffset" | "focusNode" | "focusOffset"
+>;
+
+/**
+ * Whether `point` is the DOM-ORDER START of `range` — which is what decides the direction a point
+ * inside a decorator has to grow, and cannot be read off anchor/focus: a backward drag makes the
+ * anchor the range's end. Both the node and the offset are compared, so a range whose two ends sit
+ * in the SAME container is still told apart by its offsets.
+ */
+function isRangeStart(range: Range, point: DomPoint): boolean {
+ return range.startContainer === point.node && range.startOffset === point.offset;
+}
+
+/**
+ * The edge of the decorator a COLLAPSED point is nearest to. A caret is a single position rather
+ * than a direction, so the only thing to honour is which side of the glyph the user pointed at:
+ * the first half of a text node goes before it, the rest after it. A point that is not in text
+ * names no position within the glyph at all, so it takes the leading edge.
+ */
+function nearestBoundary(point: DomPoint): Boundary {
+ if (!isDOMTextNode(point.node)) return "before";
+ const length = point.node.nodeValue?.length ?? 0;
+ // Doubled rather than halved so the comparison stays in integers for an odd-length glyph.
+ return point.offset * 2 < length ? "before" : "after";
+}
+
+/**
+ * Whether another rule already owns where a selection landing on this decorator belongs, so
+ * normalizing it here would take the landing away from that rule.
+ *
+ * A note caller is the one such decorator. `TrailingNoteCaretGuardPlugin` answers a landing on the
+ * caller of a collapsed note that ends its block by hosting a caret PAST the whole note, and it
+ * recognizes that landing precisely by the selection being unresolved — so supplying a selection
+ * for it silently disables the repair and leaves the caret in the note's hidden body, which is the
+ * position that repair exists to avoid. Every boundary of the caller is inside the note anyway:
+ * between the note's opening glyph and the caller, or between the caller and its hidden content.
+ * Neither is a position a collapsed note can draw a caret at, so there is nothing here to win.
+ */
+function $hasDedicatedSelectionOwner(node: LexicalNode): boolean {
+ return $isImmutableNoteCallerNode(node);
+}
+
+/**
+ * The DOM point at `boundary` of the decorator that CONTAINS `point`, or `undefined` when `point`
+ * is not inside one.
+ *
+ * "Inside" means strictly inside: the decorator's own element with an offset — the boundary form
+ * Lexical already resolves to an element point beside the decorator — is left alone, and so is a
+ * point in any node the decorator does not contain.
+ *
+ * Mutating context only in the sense that it reads the active editor state; it changes nothing.
+ */
+function $decoratorBoundary(
+ editor: LexicalEditor,
+ point: DomPoint,
+ boundary: Boundary,
+): DomPoint | undefined {
+ const node = $getNearestNodeFromDOMNode(point.node);
+ if (!$isDecoratorNode(node) || $hasDedicatedSelectionOwner(node)) return undefined;
+ const element = editor.getElementByKey(node.getKey());
+ if (!element || element === point.node || !element.contains(point.node)) return undefined;
+ const parent = element.parentNode;
+ if (!parent) return undefined;
+ const index = Array.prototype.indexOf.call(parent.childNodes, element);
+ if (index < 0) return undefined;
+ return { node: parent, offset: boundary === "before" ? index : index + 1 };
+}
+
+/**
+ * Hand Lexical the selection it refused to resolve, with each end that lands inside a decorator moved
+ * to that decorator's own boundaries.
+ *
+ * The DOM selection is READ here and written only by Lexical's own reconciler pass, which
+ * `isPointerDown` decides the timing of — see the plugin doc comment for the rule and why it turns on
+ * the button.
+ *
+ * Mutating (sets the editor's selection): call inside `editor.update()` or a command handler.
+ *
+ * @param isPointerDown Whether a pointer button is held, i.e. whether a drag is in flight.
+ * @returns Whether the browser's DOM selection has been left holding points Lexical cannot resolve,
+ * so it still has to be materialized when the button comes up.
+ */
+function $snapSelectionToDecoratorBoundaries(
+ editor: LexicalEditor,
+ isPointerDown: boolean,
+): boolean {
+ // Only an UNRESOLVED arrival is this plugin's business. A DOM point inside a decorator always
+ // nulls the WHOLE selection (see the plugin doc comment), so a selection Lexical did produce
+ // cannot have an end inside one — and reading the DOM over it would clobber a caret that
+ // something else placed deliberately, since a programmatic move leaves the DOM a commit behind.
+ if ($getSelection()) return false;
+
+ const rootElement = editor.getRootElement();
+ const domSelection = getDOMSelection(rootElement?.ownerDocument.defaultView ?? null);
+ if (!domSelection || domSelection.rangeCount === 0) return false;
+ const { anchorNode, anchorOffset, focusNode, focusOffset } = domSelection;
+ if (!anchorNode || !focusNode) return false;
+ if (!isSelectionWithinEditor(editor, anchorNode, focusNode)) return false;
+
+ const anchor: DomPoint = { node: anchorNode, offset: anchorOffset };
+ const focus: DomPoint = { node: focusNode, offset: focusOffset };
+ let snappedAnchor: DomPoint | undefined;
+ let snappedFocus: DomPoint | undefined;
+ if (domSelection.isCollapsed) {
+ snappedAnchor = $decoratorBoundary(editor, anchor, nearestBoundary(anchor));
+ snappedFocus = snappedAnchor;
+ } else {
+ // A range grows OUTWARD, so the decorator ends up wholly inside the selection rather than
+ // clipped: whichever end leads in DOM order takes the leading edge and the other the trailing
+ // one. That is what makes a drag begun on `\fig ` carry the whole glyph, and a drag that runs
+ // into `|src="…"` carry the whole attribute run.
+ const anchorLeads = isRangeStart(domSelection.getRangeAt(0), anchor);
+ snappedAnchor = $decoratorBoundary(editor, anchor, anchorLeads ? "before" : "after");
+ snappedFocus = $decoratorBoundary(editor, focus, anchorLeads ? "after" : "before");
+ }
+ if (!snappedAnchor && !snappedFocus) return false;
+
+ const nextAnchor = snappedAnchor ?? anchor;
+ const nextFocus = snappedFocus ?? focus;
+ // The snapped ends reach Lexical as a selection-SHAPED value rather than by this plugin writing
+ // them into the DOM, because mid-drag the browser's own points have to survive untouched (see the
+ // plugin doc comment); the DOM write, when it happens, is Lexical's own reconciler pass. Everything
+ // `$createRangeSelectionFromDom` reads is present — see {@link DomSelectionPoints}, which is what
+ // makes the one cast honest rather than a widening of a partial object.
+ const snapped: DomSelectionPoints = {
+ anchorNode: nextAnchor.node,
+ anchorOffset: nextAnchor.offset,
+ focusNode: nextFocus.node,
+ focusOffset: nextFocus.offset,
+ };
+ const selection = $createRangeSelectionFromDom(snapped as Selection, editor);
+ if (!selection) return false;
+ $setSelection(selection);
+ // `dirty` is how a selection asks `$commitPendingUpdates` (lexical/LexicalUpdates) to sync itself
+ // to the browser; with it clear, the commit's `updateDOMSelection` — the ONE place in the commit
+ // path that touches the browser's selection, `removeAllRanges()` included — is not reached for an
+ // update that dirties no nodes. So clearing it mid-drag is what leaves the browser's base alone,
+ // and leaving it set everywhere else is what keeps the DOM in the resolvable boundary form. Order
+ // matters: `$setSelection` sets the flag.
+ //
+ // `SKIP_DOM_SELECTION_TAG` expresses the same intent and is read in the same guard, but it is not
+ // confined to one commit: `$commitPendingUpdates` only empties `editor._updateTags` when the update
+ // dirtied nodes, and a selection repair dirties none — so the tag would survive into the next
+ // commit and swallow the DOM write for the caret move after it.
+ selection.dirty = !isPointerDown;
+ return isPointerDown;
+}
+
+/**
+ * Keeps a selection that lands inside a DECORATOR usable, by normalizing the offending end(s) to
+ * the decorator's own boundaries.
+ *
+ * A decorator is atomic: no selection point exists inside one, and Lexical says so by refusing such
+ * a point outright. `$internalResolveSelectionPoint` (lexical/LexicalSelection) resolves a DOM
+ * point whose node sits INSIDE a decorator's element to `null` — the walk up from the DOM node
+ * reaches the decorator, which is not a `TextNode`, and the text branch returns `null` — and
+ * `$internalResolveSelectionPoints` then nulls the WHOLE selection, not just that end. On commit,
+ * `updateDOMSelection` calls `domSelection.removeAllRanges()` because the previous selection was
+ * inside the editor, so the visible selection is destroyed too. (A Lexical upgrade that changes
+ * either function is the thing to re-read before trusting this plugin.)
+ *
+ * That matters because Standard view renders read-only USFM bytes as decorators whose glyph text is
+ * an ordinary DOM text child of the decorator's own element: an `UnknownNode`'s `\fig `, its
+ * `|src="…"` attribute run, and its `\fig*` closer are each an `ImmutableTypedTextNode`. Those bytes
+ * are selectable and copyable by design — that is what a read-only block is FOR
+ * (`OpaqueBlockGuardPlugin`) — but pointing at them is exactly what produces the interior DOM point
+ * Lexical will not resolve. The measured results: a drag begun on the `\fig ` glyph left the editor
+ * with no selection at all, so Ctrl+C reached only the empty-copy guard and the clipboard was never
+ * written; and a drag from the caption into the attribute run stopped dead at the caption's end.
+ *
+ * So each offending end is moved to the decorator's boundary IN ITS PARENT — the one form of the
+ * position Lexical does resolve, into an element point beside the decorator. A range grows outward,
+ * so the glyph is wholly in or wholly out and never clipped; a caret takes the nearer side.
+ *
+ * WHERE those boundary points go turns on one thing: whether a pointer button is down.
+ *
+ * While it is down — a drag in flight — they go to the editor state ALONE, and the browser's DOM
+ * selection is left exactly as the browser wrote it. Chromium extends a drag only from a base it
+ * placed itself, so replacing that base with a scripted element point stops the drag dead: each
+ * further mouse move only re-places a collapsed caret under the pointer, no range is ever formed, and
+ * Ctrl+C reaches the empty-copy guard again — the very failure this repair exists to fix. (Chromium
+ * also clamps a drag begun inside a `contenteditable="false"` island to that island, which is not
+ * ours to change either.) So mid-drag the editor state carries the boundary form that copy and every
+ * plugin read, while the browser keeps the text position it is willing to extend from.
+ *
+ * On the release — and immediately for any arrival with NO button down, which is to say a keyboard
+ * extend such as Shift+Arrow into a glyph, or a programmatic move — the boundary form is written to
+ * the DOM through Lexical's ordinary reconciler pass. That is the hazard this rule closes rather than
+ * a cosmetic touch: Lexical re-derives an update's selection FROM THE DOM whenever the update is not
+ * attributable to a DOM event it trusts (`$internalCreateRangeSelection` in lexical/LexicalSelection
+ * consults `window.event`), so a DOM selection left on raw interior points turns the next event-less
+ * `editor.update()` — one opened from a timer, a microtask or a React effect — into a `null`
+ * selection, and that commit's `removeAllRanges()` takes the user's visible selection with it.
+ * Whenever no drag is in flight the DOM holds the boundary form, so any such re-derivation lands back
+ * on the same selection.
+ *
+ * The release is a safe moment to materialize from: inside a `pointerup` listener `window.event.type`
+ * is `"pointerup"`, which is not one of the types `$internalCreateRangeSelection` re-reads the DOM
+ * for, so that update clones the snapped selection out of the editor state instead of re-resolving
+ * the interior points it is there to replace.
+ *
+ * Runs at `COMMAND_PRIORITY_CRITICAL` so the caret guards that read the selection see the repaired
+ * one rather than none, and never claims the command — a selection change is nobody's to own.
+ * Lexical dispatches `SELECTION_CHANGE_COMMAND` even for a selection it resolved to `null`, and at
+ * that moment the DOM selection is still intact, which is what makes the repair possible at all.
+ *
+ * Not gated on view options: decorators exist in every view. The one decorator it steps around is
+ * the note caller, whose landings another rule already owns — see
+ * {@link $hasDedicatedSelectionOwner}.
+ *
+ * @returns Always `null`; this plugin renders no UI.
+ */
+export function DecoratorBoundarySelectionPlugin(): null {
+ const [editor] = useLexicalComposerContext();
+ const isPointerDown = useRef(false);
+ const isMaterializePending = useRef(false);
+
+ useEffect(() => {
+ // Only the PRIMARY button starts a drag Chromium will extend a selection from, and only a
+ // press that starts one may set this flag: a secondary-button press can open the platform's
+ // NATIVE context menu (`ContextMenuPlugin` deliberately lets it through for some targets),
+ // which on Windows and Linux grabs the mouse so the page never sees the matching `pointerup`.
+ // The flag would then stay set with no drag in flight, and every later arrival — a click, a
+ // Shift+Arrow extend — would be treated as one, leaving the DOM selection stranded on points
+ // Lexical cannot resolve.
+ const markDown = (event: Event) => {
+ if (event instanceof PointerEvent && event.button !== 0) return;
+ isPointerDown.current = true;
+ };
+ // BOTH ends are listened for on the DOCUMENT in the capture phase, and the release on
+ // `pointercancel` as well as `pointerup`: a drag that starts in the editor can finish anywhere,
+ // and a flag that fails to clear would leave every later keyboard move reading as a drag and the
+ // DOM selection stranded on points Lexical cannot resolve. The press has to be heard on the
+ // document rather than the root for the mirror-image reason — Chromium places a caret INSIDE
+ // the editor for a press that lands in the container just outside the contenteditable root, so
+ // a root-scoped listener misses the start of a real drag and the first snap is materialized into
+ // the DOM, which is exactly what stops a drag dead. A press with no editor selection behind it
+ // costs nothing: the flag is only ever read while repairing a selection inside the editor.
+ // (`NoteShellCaretGuardPlugin` registers all three on the document for the same reason.)
+ const release = () => {
+ isPointerDown.current = false;
+ if (!isMaterializePending.current) return;
+ isMaterializePending.current = false;
+ // Re-dirty the snapped selection so Lexical's ordinary reconciler pass writes the boundary form
+ // into the DOM, now that no drag needs the browser's own base any more.
+ editor.update(() => {
+ const selection = $getSelection();
+ if ($isRangeSelection(selection)) selection.dirty = true;
+ });
+ };
+ // One listener does both the wiring and the unwiring: Lexical calls it with the current root at
+ // registration, with each replacement root on a swap, and with `(null, previous)` on teardown.
+ return editor.registerRootListener((rootElement, prevRootElement) => {
+ const previousDocument = prevRootElement?.ownerDocument;
+ previousDocument?.removeEventListener("pointerdown", markDown, true);
+ previousDocument?.removeEventListener("pointerup", release, true);
+ previousDocument?.removeEventListener("pointercancel", release, true);
+ isPointerDown.current = false;
+ isMaterializePending.current = false;
+ const currentDocument = rootElement?.ownerDocument;
+ currentDocument?.addEventListener("pointerdown", markDown, true);
+ currentDocument?.addEventListener("pointerup", release, true);
+ currentDocument?.addEventListener("pointercancel", release, true);
+ });
+ }, [editor]);
+
+ useEffect(() => {
+ return editor.registerCommand(
+ SELECTION_CHANGE_COMMAND,
+ () => {
+ if ($snapSelectionToDecoratorBoundaries(editor, isPointerDown.current))
+ isMaterializePending.current = true;
+ return false;
+ },
+ COMMAND_PRIORITY_CRITICAL,
+ );
+ }, [editor]);
+
+ return null;
+}
diff --git a/libs/shared-react/src/plugins/usj/TextSpacingPlugin.test.tsx b/libs/shared-react/src/plugins/usj/TextSpacingPlugin.test.tsx
index c3dbebdc..cd1f36c5 100644
--- a/libs/shared-react/src/plugins/usj/TextSpacingPlugin.test.tsx
+++ b/libs/shared-react/src/plugins/usj/TextSpacingPlugin.test.tsx
@@ -461,6 +461,34 @@ describe("TextSpacingPlugin", () => {
});
});
+ it("should keep the content of an UnknownNode created WITH its content in one update — only a later intrusion is ejected", async () => {
+ // A construct that arrives whole (a Tier-2 rebuild materializing a pasted `\fig …\fig*`, a
+ // document load, a collab insert, an undo restore) creates the wrapper and its content
+ // children in the SAME update, so that content is indistinguishable from typed text by
+ // newness alone. Ejecting it strands a figure's caption outside its own box, both on screen
+ // and in the exported USJ.
+ const { editor } = await testEnvironment(() => {
+ $getRoot().append($createParaNode());
+ });
+
+ await act(async () => {
+ editor.update(() => {
+ const para = $getRoot().getFirstChild();
+ if (!$isParaNode(para)) throw new Error("Expected a ParaNode");
+ para.append($createUnknownNode("figure", "fig").append($createTextNode("a caption")));
+ });
+ });
+
+ editor.getEditorState().read(() => {
+ const para = $getRoot().getFirstChild();
+ if (!$isParaNode(para)) throw new Error("Expected a ParaNode");
+ expect(para.getChildren()).toHaveLength(1);
+ const unknown = para.getFirstChild();
+ if (!$isUnknownNode(unknown)) throw new Error("Expected an UnknownNode");
+ expect(unknown.getTextContent()).toBe("a caption");
+ });
+ });
+
it("should insert a space before a verse if preceded by a CharNode", async () => {
const { editor } = await testEnvironment(() => {
$getRoot().append(
diff --git a/libs/shared-react/src/plugins/usj/TextSpacingPlugin.tsx b/libs/shared-react/src/plugins/usj/TextSpacingPlugin.tsx
index 9a49f8df..1a22d5e5 100644
--- a/libs/shared-react/src/plugins/usj/TextSpacingPlugin.tsx
+++ b/libs/shared-react/src/plugins/usj/TextSpacingPlugin.tsx
@@ -176,7 +176,8 @@ function $textNodeTrailingSpaceTransform(node: TextNode): void {
}
/**
- * Moves a TextNode after its parent if the parent is an UnknownNode.
+ * Moves a TextNode out of an UnknownNode when it was planted there by an edit, so a read-only
+ * opaque block never gains prose of its own.
* @param node - The TextNode to check.
* @param editor - The LexicalEditor instance.
*/
@@ -184,9 +185,15 @@ function $textNodeInUnknownTransform(node: TextNode, editor: LexicalEditor): voi
const unknownNode = node.getParent();
if (!$isUnknownNode(unknownNode) || !node.isAttached()) return;
- // If a text node is created inside an UnknownNode (e.g., by typing), move it after the
- // UnknownNode.
- if (wasNodeCreated(editor, node.getKey())) unknownNode.insertAfter(node);
+ // Only text planted inside a PRE-EXISTING opaque block is an intrusion (e.g. typing into a
+ // figure). A wrapper that appeared in this SAME update brought its own content with it — a
+ // Tier-2 rebuild materializing a pasted `\fig caption|src="…"\fig*`, a document load, a collab
+ // insert, an undo restore — and every one of those creates the wrapper and its content children
+ // together. Ejecting there strands the construct's own text outside it: a figure's caption
+ // lands after the `\fig*` glyph as ordinary paragraph prose, and the exported USJ keeps a
+ // `figure` object with no content at all.
+ if (wasNodeCreated(editor, node.getKey()) && !wasNodeCreated(editor, unknownNode.getKey()))
+ unknownNode.insertAfter(node);
}
/** Transform for a verse node (handles non-TextNode predecessors) */
diff --git a/libs/shared-react/src/plugins/usj/clipboard.utils.ts b/libs/shared-react/src/plugins/usj/clipboard.utils.ts
index e5b451e5..45544b7d 100644
--- a/libs/shared-react/src/plugins/usj/clipboard.utils.ts
+++ b/libs/shared-react/src/plugins/usj/clipboard.utils.ts
@@ -1,9 +1,71 @@
-import { LexicalEditor, PASTE_COMMAND } from "lexical";
+import { mergeRegister } from "@lexical/utils";
+import {
+ $getSelection,
+ COMMAND_PRIORITY_LOW,
+ COPY_COMMAND,
+ CUT_COMMAND,
+ LexicalEditor,
+ PASTE_COMMAND,
+} from "lexical";
function cleanupText(text: string): string {
return text.replaceAll("\t", " ");
}
+/**
+ * Whether the selection holds content a copy could put on the clipboard: one exists and covers
+ * something. A collapsed caret — or no selection at all — does not.
+ *
+ * Reads the ACTIVE editor state, so it must be called inside an update or a command listener. That
+ * is the whole point: the committed state is a microtask behind, so a selection made earlier in the
+ * same tick is invisible to `editor.getEditorState().read()` — a `copy()` right after a
+ * programmatic select would read "nothing selected" and silently do nothing.
+ */
+export function $hasCopyableSelection(): boolean {
+ const selection = $getSelection();
+ return !!selection && !selection.isCollapsed();
+}
+
+/**
+ * Stops a copy or cut that has nothing to copy, wherever it was dispatched from.
+ *
+ * A copy with no clipboard event of its own — the keyboard shortcuts below, the context menu's
+ * Cut/Copy, an editor-ref `copy()` call — has to have one synthesized before anything can be
+ * written: `@lexical/clipboard` appends a hidden placeholder element to the editor, points the DOM
+ * selection at it, and runs `document.execCommand("copy")` to provoke a real clipboard event it can
+ * fill in. With nothing selected, that filling step declines — and declines BEFORE suppressing the
+ * browser's own copy — so the browser copies what it was pointed at, the placeholder, and the user
+ * loses whatever the clipboard already held to a character that was never in the document.
+ *
+ * So the command is CLAIMED here and does nothing, which is what copying an empty selection means.
+ * The guard lives on the command rather than in front of each dispatch for two reasons: a command
+ * listener runs inside the update, where the selection is authoritative for both pending and
+ * committed state, and every dispatcher is covered by the one registration.
+ *
+ * Registered at `COMMAND_PRIORITY_LOW` — above `@lexical/rich-text`'s own copy/cut fallback at
+ * EDITOR, which is the thing that synthesizes the event, and below every feature handler, so a view
+ * with its own clipboard payload (Standard view's USFM copy) still claims first and this never runs.
+ */
+export function registerEmptyCopyGuard(editor: LexicalEditor): () => void {
+ const $claimWhenNothingToCopy = () => !$hasCopyableSelection();
+ return mergeRegister(
+ editor.registerCommand(COPY_COMMAND, $claimWhenNothingToCopy, COMMAND_PRIORITY_LOW),
+ editor.registerCommand(CUT_COMMAND, $claimWhenNothingToCopy, COMMAND_PRIORITY_LOW),
+ );
+}
+
+/** Copies the selection. Dispatched unconditionally; an empty selection copies nothing because
+ * {@link registerEmptyCopyGuard} claims the command, not because the dispatch is withheld. */
+export const copySelection = (editor: LexicalEditor) => {
+ editor.dispatchCommand(COPY_COMMAND, null);
+};
+
+/** Cuts the selection, under the same guard as {@link copySelection} — a cut copies first, so it
+ * reaches the identical synthesized-copy path. */
+export const cutSelection = (editor: LexicalEditor) => {
+ editor.dispatchCommand(CUT_COMMAND, null);
+};
+
export const pasteSelection = (editor: LexicalEditor) => {
navigator.clipboard.read().then(async (items) => {
const permission = await navigator.permissions.query({
diff --git a/libs/shared-react/src/plugins/usj/index.ts b/libs/shared-react/src/plugins/usj/index.ts
index 0f0f9e67..e98e4be9 100644
--- a/libs/shared-react/src/plugins/usj/index.ts
+++ b/libs/shared-react/src/plugins/usj/index.ts
@@ -7,6 +7,7 @@ export * from "./clipboard.utils";
export * from "./ClipboardPlugin";
export * from "./CommandMenuPlugin";
export * from "./ContextMenuPlugin";
+export * from "./DecoratorBoundarySelectionPlugin";
export * from "./DisableHistoryShortcutsPlugin";
export * from "./EditablePlugin";
export * from "./EmptyVerseCaretGuardPlugin";
@@ -19,6 +20,7 @@ export * from "./ParaMarkerPrefixCursorGuardPlugin";
export * from "./ParaNodePlugin";
export * from "./StateChangePlugin";
export * from "./structure-protection.model";
+export * from "./structureKeyboard.utils";
export * from "./StructureKeyboardPlugin";
export * from "./text-direction.model";
export * from "./TextDirectionPlugin";
diff --git a/libs/shared/src/converters/usfm/usfmFragmentToUsj.test.ts b/libs/shared/src/converters/usfm/usfmFragmentToUsj.test.ts
index fbb9e5b2..5d4ced03 100644
--- a/libs/shared/src/converters/usfm/usfmFragmentToUsj.test.ts
+++ b/libs/shared/src/converters/usfm/usfmFragmentToUsj.test.ts
@@ -5,6 +5,7 @@ import {
regularizeSpaces,
} from "./usfmFragmentToUsj.js";
import { NBSP } from "../../nodes/usj/node-constants.js";
+import { defaultStyleInfo } from "../../utils/usfm/defaultStyleInfo.js";
import { createMarkerLookup, StyleInfo } from "../../utils/usfm/styleInfo.js";
describe("usfmFragmentToUsjContent — core", () => {
@@ -1504,6 +1505,99 @@ describe("stylesheet-first classification", () => {
});
});
+describe("cell markers under a real stylesheet, which classifies them as Character", () => {
+ // usfm.sty declares every `\th…`/`\tc…` as a Character style, so with a project sheet in play a
+ // cell marker reaches assembly as a `charOpen` token, not a paragraph one — the shape the app
+ // actually runs. ParatextData derives a cell from the marker NAME either way, and the alignment
+ // comes from the name's infix (`thc3` → center, `thr5` → end), so the two token kinds must land
+ // on the same cell.
+ const sheetLookup = createMarkerLookup(defaultStyleInfo);
+
+ it("assembles align-infix cells that arrive as character tokens", () => {
+ expect(
+ usfmFragmentToUsjContent("\\tr \\thc3 middle\\thr5 right", { getMarker: sheetLookup }),
+ ).toEqual([
+ {
+ type: "table",
+ content: [
+ {
+ type: "table:row",
+ marker: "tr",
+ content: [
+ { type: "table:cell", marker: "thc3", align: "center", content: ["middle"] },
+ { type: "table:cell", marker: "thr5", align: "end", content: ["right"] },
+ ],
+ },
+ ],
+ },
+ ]);
+ });
+
+ it("a RANGED cell marker is not in the sheet at all, so it still arrives as a paragraph token", () => {
+ // usfm.sty declares `\tcr1`, never `\tcr1-4` — a span is spelled by ParatextData's range rule,
+ // not by a stylesheet entry — so a ranged cell is an UNKNOWN marker even under a project sheet
+ // and reaches assembly through the paragraph arm. Colspan assembly is therefore untouched by
+ // the character-token arm above; both arms build the cell through the same `pushTableCell`.
+ expect(usfmFragmentToUsjContent("\\tr \\tcr1-4 wide", { getMarker: sheetLookup })).toEqual([
+ {
+ type: "table",
+ content: [
+ {
+ type: "table:row",
+ marker: "tr",
+ content: [
+ { type: "table:cell", marker: "tcr1", align: "end", colspan: "4", content: ["wide"] },
+ ],
+ },
+ ],
+ },
+ ]);
+ });
+
+ it("a cell-named character marker with NO open row stays an ordinary char span", () => {
+ // The cell arm is guarded on an open `\tr`; only that opens a row. Without the guard, a span
+ // that merely shares a cell marker's name would be assembled into a table that does not exist.
+ expect(
+ usfmFragmentToUsjContent("\\p before \\thc3 middle", { getMarker: sheetLookup }),
+ ).toEqual([
+ {
+ type: "para",
+ marker: "p",
+ content: [
+ "before ",
+ { type: "char", marker: "thc3", content: ["middle"], closed: "false" },
+ ],
+ },
+ ]);
+ });
+
+ it("a NESTED cell-named marker inside a cell is a char span, not a second cell", () => {
+ expect(
+ usfmFragmentToUsjContent("\\tr \\thc3 middle \\+tc1 inner\\+tc1*", {
+ getMarker: sheetLookup,
+ }),
+ ).toEqual([
+ {
+ type: "table",
+ content: [
+ {
+ type: "table:row",
+ marker: "tr",
+ content: [
+ {
+ type: "table:cell",
+ marker: "thc3",
+ align: "center",
+ content: ["middle ", { type: "char", marker: "tc1", content: ["inner"] }],
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+ });
+});
+
describe("PT9 unknown-marker handling", () => {
it("unknown marker in body context becomes a paragraph (UsfmParser.DetermineUnknownTokenType)", () => {
const content = usfmFragmentToUsjContent("\\p before \\zfoo after");
@@ -1847,3 +1941,168 @@ describe("usfmFragmentToUsjContent — figures parse without a project styleshee
]);
});
});
+
+describe("usfmFragmentToUsjContent — peripheral divisions (\\periph)", () => {
+ const PERIPH_USJ = {
+ type: "periph",
+ alt: "Title Page",
+ id: "title",
+ content: [{ type: "para", marker: "mt1", content: ["The Title"] }],
+ };
+
+ it("assembles a periph division, splitting its marker-line text into alt and attributes", () => {
+ expect(usfmFragmentToUsjContent('\\periph Title Page|id="title"\\mt1 The Title')).toEqual([
+ PERIPH_USJ,
+ ]);
+ });
+
+ it("reads the line-per-marker spelling identically — the next marker delimits the attribute run, not the line break", () => {
+ // A USFM writer puts `\periph` on its own line, but the newline is ordinary whitespace to a
+ // tokenizer: the marker line's text ends where the next `\marker` begins either way. That is
+ // what lets an opaque construct copy out on ONE line and still parse (`$startsBlockLine`,
+ // whitespaceDisplay.plugin.utils.ts) — and it is why a periph's content can be reassembled at
+ // all, since a Tier-2 rebuild only ever re-tokenizes a single paragraph's bytes.
+ expect(usfmFragmentToUsjContent('\\periph Title Page|id="title"\n\\mt1 The Title')).toEqual([
+ PERIPH_USJ,
+ ]);
+ });
+
+ it("takes every following block into the division — periph has no closing marker", () => {
+ expect(
+ usfmFragmentToUsjContent('\\periph Title Page|id="title"\\mt1 The Title\\p Body.'),
+ ).toEqual([
+ {
+ type: "periph",
+ alt: "Title Page",
+ id: "title",
+ content: [
+ { type: "para", marker: "mt1", content: ["The Title"] },
+ { type: "para", marker: "p", content: ["Body."] },
+ ],
+ },
+ ]);
+ });
+
+ it("ends one division at the next, since peripheral divisions never nest", () => {
+ expect(usfmFragmentToUsjContent("\\periph One\\p a\\periph Two\\p b")).toEqual([
+ { type: "periph", alt: "One", content: [{ type: "para", marker: "p", content: ["a"] }] },
+ { type: "periph", alt: "Two", content: [{ type: "para", marker: "p", content: ["b"] }] },
+ ]);
+ });
+
+ it("keeps a marker-line with no attributes as pure alt text, its structural line break included", () => {
+ expect(usfmFragmentToUsjContent("\\periph Title Page\n\\mt1 The Title")).toEqual([
+ {
+ type: "periph",
+ alt: "Title Page",
+ content: [{ type: "para", marker: "mt1", content: ["The Title"] }],
+ },
+ ]);
+ });
+
+ it("keeps an attributes-only marker-line free of an empty alt", () => {
+ expect(usfmFragmentToUsjContent('\\periph |id="title"\\mt1 The Title')).toEqual([
+ {
+ type: "periph",
+ id: "title",
+ content: [{ type: "para", marker: "mt1", content: ["The Title"] }],
+ },
+ ]);
+ });
+
+ it("emits a contentless division for a marker line with nothing after it", () => {
+ expect(usfmFragmentToUsjContent('\\periph Title Page|id="title"')).toEqual([
+ { type: "periph", alt: "Title Page", id: "title" },
+ ]);
+ });
+
+ it("nests a sidebar inside the open division rather than beside it", () => {
+ expect(
+ usfmFragmentToUsjContent("\\periph One\\esb \\cat History\\cat*\\p in sidebar\\esbe"),
+ ).toEqual([
+ {
+ type: "periph",
+ alt: "One",
+ content: [
+ {
+ type: "sidebar",
+ marker: "esb",
+ category: "History",
+ content: [{ type: "para", marker: "p", content: ["in sidebar"] }],
+ },
+ ],
+ },
+ ]);
+ });
+
+ it("refuses an attribute list that does not parse, keeping every byte as the division's title", () => {
+ // Same refusal as every other attribute list here: `|id=""` is not a reading Paratext agrees
+ // with, so the bytes stay literal text where the author can see and fix them. The DIVISION
+ // survives the refusal — `\\periph` has no closing marker, so its extent is a property of the
+ // marker and not of its attributes, and Paratext 9 likewise opens `` for any `\\periph`
+ // token whose attributes failed to parse. The following blocks stay INSIDE it.
+ expect(usfmFragmentToUsjContent('\\periph Title Page|id=""\\mt1 The Title')).toEqual([
+ {
+ type: "periph",
+ alt: 'Title Page|id=""',
+ content: [{ type: "para", marker: "mt1", content: ["The Title"] }],
+ },
+ ]);
+ });
+
+ it("refuses a marker line that spells its title twice, keeping every byte literal", () => {
+ // `alt` is the division TITLE, which periph spells as marker-line text rather than a pipe pair
+ // (`unknownUsfm.utils.ts` renders it that way, so the editor can never produce this shape) — a
+ // line carrying both spellings has two conflicting readings and no lossless one, so it refuses
+ // the list like any other ambiguous attribute list rather than silently dropping a title. The
+ // division survives here too, for the same reason.
+ expect(usfmFragmentToUsjContent('\\periph Title|alt="Z"\\mt1 X')).toEqual([
+ {
+ type: "periph",
+ alt: 'Title|alt="Z"',
+ content: [{ type: "para", marker: "mt1", content: ["X"] }],
+ },
+ ]);
+ });
+
+ it("keeps a bare trailing pipe inside the division rather than un-nesting it mid-keystroke", () => {
+ // The shape a user passes THROUGH while typing `|id="cover"` onto a periph line: in Standard
+ // view the marker line re-tokenizes live, so a refusal that dropped the division would un-nest
+ // every block under it on the `|` keystroke and re-nest them several keystrokes later.
+ expect(usfmFragmentToUsjContent("\\periph Title|\\mt1 X")).toEqual([
+ {
+ type: "periph",
+ alt: "Title|",
+ content: [{ type: "para", marker: "mt1", content: ["X"] }],
+ },
+ ]);
+ });
+
+ it("reads a titleless marker line's `alt` pair as the division title", () => {
+ // No collision, so nothing is ambiguous and the pair is the only spelling present.
+ expect(usfmFragmentToUsjContent('\\periph |alt="Z" id="t"\\mt1 X')).toEqual([
+ {
+ type: "periph",
+ alt: "Z",
+ id: "t",
+ content: [{ type: "para", marker: "mt1", content: ["X"] }],
+ },
+ ]);
+ });
+
+ it("opens no division inside note content, where peripheral divisions do not occur", () => {
+ expect(
+ usfmFragmentToUsjContent("\\ft text \\periph Title\\mt1 The Title", { isNoteContext: true }),
+ ).toEqual([
+ {
+ type: "para",
+ marker: "p",
+ content: [
+ { type: "char", marker: "ft", content: ["text "], closed: "false" },
+ { type: "char", marker: "periph", content: ["Title"], closed: "false" },
+ ],
+ },
+ { type: "para", marker: "mt1", content: ["The Title"] },
+ ]);
+ });
+});
diff --git a/libs/shared/src/converters/usfm/usfmFragmentToUsj.ts b/libs/shared/src/converters/usfm/usfmFragmentToUsj.ts
index ac3cb05d..c5bde001 100644
--- a/libs/shared/src/converters/usfm/usfmFragmentToUsj.ts
+++ b/libs/shared/src/converters/usfm/usfmFragmentToUsj.ts
@@ -28,17 +28,21 @@
* element. A stray `\*` with no milestone to close is NOT literal either — it
* becomes an unmatched element (see above).
*
- * Figures, tables, and sidebars assemble to their faithful USJ shapes at the
- * assembly level, marker-name driven (they are parser-level structures in
- * ParatextData, independent of stylesheet classification): `\fig …\fig*` folds
- * to an inline `figure` object (USFM's `src` attribute renamed to USX/USJ's
- * `file`), `\tr` plus `t[hc][rc]#(-#)` cell markers build `table` →
- * `table:row` → `table:cell` with name-derived `align`/`colspan`, and
+ * Figures, tables, sidebars, and peripheral divisions assemble to their faithful
+ * USJ shapes at the assembly level, marker-name driven (they are parser-level
+ * structures in ParatextData, independent of stylesheet classification):
+ * `\fig …\fig*` folds to an inline `figure` object (USFM's `src` attribute
+ * renamed to USX/USJ's `file`), `\tr` plus `t[hc][rc]#(-#)` cell markers build
+ * `table` → `table:row` → `table:cell` with name-derived `align`/`colspan`,
* `\esb`…`\esbe` wraps the following blocks in a `sidebar` (`\cat` directly
- * after `\esb` folds to its `category`). Anything off the clean shapes —
- * nested markup or positional (USFM 2.0) attributes in a figure, a missing
- * `\fig*`, a cell marker with no open row — degrades to the plain char/para
- * output the marker classification produces on its own.
+ * after `\esb` folds to its `category`), and `\periph` opens a `periph`
+ * division that takes every following block until the next `\periph`, a
+ * chapter, or the fragment end — its marker line splitting at the first `|`
+ * into the division title (`alt`) and an ordinary attribute list. Anything off
+ * the clean shapes — nested markup or positional (USFM 2.0) attributes in a
+ * figure, a missing `\fig*`, a cell marker with no open row, a periph
+ * attribute list that does not parse — degrades to the plain char/para output
+ * the marker classification produces on its own.
*
* Input is USFM text: `~` means NBSP; U+FFFC sentinels (atomic-node placeholders
* from the Tier 2 fragment builder) ride through as ordinary text characters.
@@ -69,6 +73,10 @@ const FIGURE_MARKER = "fig";
const TABLE_ROW_MARKER = "tr";
const SIDEBAR_MARKER = "esb";
const SIDEBAR_END_MARKER = "esbe";
+const PERIPH_MARKER = "periph";
+/** The USJ property a periph's marker-line title lands on — the same name a pipe pair would use,
+ * which is why a line carrying both spellings is refused rather than resolved. */
+const PERIPH_TITLE_ATTRIBUTE = "alt";
/**
* Table cell marker names: `t` + header/cell (`h`/`c`) + optional alignment infix (`r`/`c`) +
@@ -814,10 +822,20 @@ export function usfmFragmentToUsjContent(
// tables). Implicit close (fragment end or a chapter token) marks it closed="false" —
// ParatextData auto-closes sidebars at the chapter boundary.
let sidebar: ClosableMarkerObject | undefined;
+ // Current open peripheral division: `\periph` opens one and takes every following block into
+ // it. Nothing closes a division but the next `\periph`, a chapter boundary, or the end of the
+ // fragment — USFM gives a division marker no closing bytes at all, so unlike a sidebar there is
+ // no terminated/implicit distinction to record. The marker LINE's own text (the division title
+ // and its attribute list) is captured first; see `periphCapture`.
+ let periph: MarkerObject | undefined;
- /** Where top-level blocks (paragraphs, tables) land: an open sidebar's content, else the
- * fragment result. */
- const blockTarget = (): MarkerContent[] => (sidebar ? getContent(sidebar) : result);
+ /** Where top-level blocks (paragraphs, tables) land: an open sidebar's content, else an open
+ * peripheral division's content, else the fragment result. */
+ const blockTarget = (): MarkerContent[] => {
+ if (sidebar) return getContent(sidebar);
+ if (periph) return getContent(periph);
+ return result;
+ };
// True between a chapter token and the next opened block: loose content there sits at the
// DOCUMENT ROOT in ParatextData's output, not in an implied paragraph — text typed after
@@ -882,6 +900,28 @@ export function usfmFragmentToUsjContent(
table = undefined;
tableRow = undefined;
};
+ /**
+ * Open a cell in the current row and make it the content container: text, char spans, notes and
+ * verses then flow into it through the ordinary `para`-based container logic. Shared by both
+ * token kinds a cell marker can arrive as — a stylesheet that knows `\th1`/`\tc1` classifies
+ * them as Character (usfm.sty does), one that does not delivers them as unknown paragraph
+ * markers, and ParatextData derives a cell from the NAME either way.
+ */
+ const pushTableCell = (row: MarkerObject, marker: string, cellMatch: RegExpExecArray) => {
+ closeCharStack();
+ const [, alignInfix, spanStart, spanEnd] = cellMatch;
+ // The cell keeps only the starting column in its marker (`thc3-4` → `thc3`); the span width
+ // becomes `colspan`, a string of columns spanned (`thc3-4` → "2").
+ const cell: TableCellObject = {
+ type: "table:cell",
+ marker: spanEnd ? marker.slice(0, marker.indexOf("-")) : marker,
+ align: TABLE_CELL_ALIGN_BY_INFIX[alignInfix],
+ content: [],
+ };
+ if (spanEnd) cell.colspan = String(Number(spanEnd) + 1 - Number(spanStart));
+ getContent(row).push(cell);
+ para = cell;
+ };
const closeSidebar = (terminated: boolean) => {
if (!sidebar) return;
// Only `\esbe` terminates a sidebar explicitly; an implicit close (fragment end or a
@@ -890,6 +930,11 @@ export function usfmFragmentToUsjContent(
sidebar = undefined;
};
+ /** End the open division. Nothing to record: a division has no closing bytes to be missing. */
+ const closePeriph = () => {
+ periph = undefined;
+ };
+
// ---- attribute-marker folding state (see ATTRIBUTE_MARKERS) ----
// The most recent chapter/verse/note object, still "receptive": an adjacent attribute
// marker folds onto it as an attribute. Any real content clears it.
@@ -958,6 +1003,67 @@ export function usfmFragmentToUsjContent(
attrCapture = undefined;
};
+ // ---- peripheral-division capture state ----
+ // The text of a `\periph` marker line, collected between the marker and the next marker token.
+ // `\periph` has no closing bytes, so the next marker is the only thing that ends the line —
+ // the same delimiter whether a USFM writer put the division on its own line or a copy laid the
+ // whole construct out on one (a line break is ordinary whitespace to a tokenizer). That is what
+ // lets a Tier-2 rebuild, which only ever re-tokenizes a single paragraph's bytes, reassemble a
+ // whole division from one fragment.
+ let periphCapture: { value: string } | undefined;
+ /**
+ * Close the captured marker line and open the division it describes: everything before the
+ * line's first `|` is the division title (USX/USJ's `alt`), the rest an ordinary named-attribute
+ * list.
+ *
+ * A list that does not parse is REFUSED, and so is one that spells the TITLE a second time
+ * (`alt`, alongside a non-empty marker-line title): the two spellings are the same USJ property,
+ * there is no reading that keeps both, and picking one would silently destroy bytes the author
+ * can still see. `alt` on a TITLELESS line collides with nothing and is read normally. The editor
+ * can never author the colliding shape — periph's display renders `alt` AS the marker-line title
+ * (`unknownUsfm.utils.ts`), never as a pipe pair.
+ *
+ * Refusing the list does NOT refuse the division. `\periph` opens a peripheral division for its
+ * own sake — it has no closing marker and nothing else delimits it, so the division's extent is
+ * a property of the marker, not of its attributes. Paratext 9 reads it the same way: a failed
+ * `SetAttributes` leaves the marker-line text alone (`ParatextData/UsfmToken.cs`) and the USX
+ * writer still opens `` for any `\periph` token, with an empty `id`
+ * (`ParatextData/UsxUsfmParserSink.cs`). Demoting the division to an ordinary paragraph would
+ * re-parent every block it contained up to the root instead. The refused bytes survive as the
+ * division's own title, where the author can still see and fix them, and they serialize back
+ * byte-for-byte (`alt` renders as literal marker content, not a pipe pair).
+ *
+ * @param atBlockBoundary - Whether the token that ended the line starts a new block (or the
+ * fragment ended). The line's trailing line break is structural there, the same rule the text
+ * case applies: the line ends where the next block marker begins, and ParatextData emits no
+ * content for it.
+ */
+ const finishPeriphLine = (atBlockBoundary: boolean) => {
+ if (!periphCapture) return;
+ let { value } = periphCapture;
+ periphCapture = undefined;
+ if (atBlockBoundary && value.endsWith("\n")) value = value.slice(0, -1);
+ const pipeIndex = value.indexOf("|");
+ const parsed =
+ pipeIndex >= 0 ? parseAttributeText(value.slice(pipeIndex + 1), PERIPH_MARKER) : undefined;
+ const parsedTitle = pipeIndex >= 0 ? value.slice(0, pipeIndex) : value;
+ const refused =
+ pipeIndex >= 0 && (!parsed || (!!parsedTitle && !!parsed[PERIPH_TITLE_ATTRIBUTE]));
+ const attributes = refused ? undefined : parsed;
+ const title = refused ? value : parsedTitle;
+ const opened: MarkerObject = {
+ type: "periph",
+ ...(title ? { [PERIPH_TITLE_ATTRIBUTE]: toUsjText(title) } : {}),
+ ...attributes,
+ };
+ opened.content = [];
+ // Landed BEFORE `periph` is set, for the same reason the sidebar assembly does it: an already
+ // open division would otherwise take the new one into its own content.
+ blockTarget().push(opened);
+ periph = opened;
+ para = undefined;
+ };
+
// ---- figure capture state ----
// A `\fig …\fig*` span being collected for faithful `figure` emission (ParatextData turns
// the span into `{ type: "figure", … }`). Only a clean span folds: plain-text content with
@@ -1089,6 +1195,20 @@ export function usfmFragmentToUsjContent(
}
}
+ if (periphCapture) {
+ // The marker line runs to the next marker token. An optbreak rejoins as the literal `//`
+ // the author typed, for the same reason the figure capture rejoins one: `//` inside an
+ // attribute value (a URL, say) is plain value bytes to ParatextData, which strips the
+ // attribute segment out at tokenize-time before its `//` pass ever runs.
+ if (token.kind === "text" || token.kind === "optbreak") {
+ periphCapture.value += token.kind === "text" ? token.text : "//";
+ continue;
+ }
+ finishPeriphLine(token.kind === "para" || token.kind === "chapter");
+ tokenIndex--;
+ continue;
+ }
+
if (figCapture) {
if (token.kind === "text" || token.kind === "optbreak") {
// The tokenizer splits `//` into optbreak tokens spec-blind — including inside what
@@ -1240,21 +1360,7 @@ export function usfmFragmentToUsjContent(
// isRecognizedTableCell) is an unknown marker that ENDS the table (and the next
// `\tr` starts a fresh one).
if (cellMatch && isRecognizedTableCell(cellMatch)) {
- closeCharStack();
- const [, alignInfix, spanStart, spanEnd] = cellMatch;
- // The cell keeps only the starting column in its marker (`thc3-4` → `thc3`);
- // the span width becomes `colspan`, a string of columns spanned (`thc3-4` → "2").
- const cell: TableCellObject = {
- type: "table:cell",
- marker: spanEnd ? token.marker.slice(0, token.marker.indexOf("-")) : token.marker,
- align: TABLE_CELL_ALIGN_BY_INFIX[alignInfix],
- content: [],
- };
- if (spanEnd) cell.colspan = String(Number(spanEnd) + 1 - Number(spanStart));
- getContent(tableRow).push(cell);
- // The cell becomes the current content container: text, char spans, notes, and
- // verses flow into it through the ordinary `para`-based container logic.
- para = cell;
+ pushTableCell(tableRow, token.marker, cellMatch);
break;
}
}
@@ -1268,8 +1374,15 @@ export function usfmFragmentToUsjContent(
closeNote(false);
// Sidebars never nest: an unterminated previous sidebar closes implicitly.
closeSidebar(false);
- sidebar = { type: "sidebar", marker: SIDEBAR_MARKER, content: [] };
- result.push(sidebar);
+ // Landed BEFORE `sidebar` is set: `blockTarget()` answers "the open sidebar's content"
+ // whenever one is open, so assigning first would nest the new sidebar inside itself.
+ const opened: ClosableMarkerObject = {
+ type: "sidebar",
+ marker: SIDEBAR_MARKER,
+ content: [],
+ };
+ blockTarget().push(opened);
+ sidebar = opened;
para = undefined;
attrTarget = sidebar; // receptive to \cat (directly after \esb only)
atChapterRootScope = false;
@@ -1284,6 +1397,19 @@ export function usfmFragmentToUsjContent(
para = undefined;
break;
}
+ // ---- peripheral-division assembly ----
+ if (!isNoteContext && token.marker === PERIPH_MARKER) {
+ closeCharStack();
+ closeNote(false);
+ // A division ends every block structure open inside it, including a sidebar the author
+ // never terminated; divisions themselves never nest.
+ closeSidebar(false);
+ closePeriph();
+ periphCapture = { value: "" };
+ para = undefined;
+ atChapterRootScope = false;
+ break;
+ }
startParagraph(token.marker);
break;
}
@@ -1304,9 +1430,11 @@ export function usfmFragmentToUsjContent(
closeCharStack();
closeNote(false);
// A chapter boundary ends any open table and implicitly closes an open sidebar
- // (ParatextData auto-closes sidebars at the end of the chapter).
+ // (ParatextData auto-closes sidebars at the end of the chapter). A peripheral division
+ // ends there too: chapters are book body, which is never division content.
endTable();
closeSidebar(false);
+ closePeriph();
para = undefined;
const chapter: MarkerObject = {
type: "chapter",
@@ -1331,6 +1459,20 @@ export function usfmFragmentToUsjContent(
break;
}
case "charOpen": {
+ // A cell marker inside an OPEN row is a cell, whichever token kind the stylesheet's
+ // classification made it: usfm.sty declares `\th1`/`\tc1`/… as Character styles, so with a
+ // real project sheet they arrive here rather than as paragraph tokens, and without this a
+ // table copied out of the editor pastes back as loose char spans inside empty rows.
+ // Guarded on an open row — only `\tr` opens one — so a char span that merely shares a
+ // cell marker's name outside a table keeps its ordinary resolution, and a NESTED span
+ // (`\+tc1`) is never a cell.
+ if (!note && !isNoteContext && tableRow && !token.isNested) {
+ const cellMatch = TABLE_CELL_MARKER_REGEX.exec(token.marker);
+ if (cellMatch && isRecognizedTableCell(cellMatch)) {
+ pushTableCell(tableRow, token.marker, cellMatch);
+ break;
+ }
+ }
// A new non-nested char marker auto-closes open char styles (PT9) — but never
// across an open note's boundary: the frames enclosing the note stay open.
if (!token.isNested) {
@@ -1384,6 +1526,9 @@ export function usfmFragmentToUsjContent(
break;
}
}
+ // Fragment ended on a `\periph` marker line: the division has its title and attributes but no
+ // content, which is a complete division — a periph never had a closer to be missing.
+ if (periphCapture) finishPeriphLine(true);
// Fragment ended mid-figure: no `\fig*` closer — degrade to the plain char/para span.
if (figCapture) materializeFigCapture();
if (attrCapture) {
diff --git a/libs/shared/src/nodes/features/UnknownNode.test.ts b/libs/shared/src/nodes/features/UnknownNode.test.ts
index 3656b114..798da4e6 100644
--- a/libs/shared/src/nodes/features/UnknownNode.test.ts
+++ b/libs/shared/src/nodes/features/UnknownNode.test.ts
@@ -1,7 +1,9 @@
+import { $createImmutableTypedTextNode, ImmutableTypedTextNode } from "./ImmutableTypedTextNode.js";
import { $createParaNode, ParaNode } from "../usj/ParaNode.js";
import { createBasicTestEnvironment } from "../usj/test.utils.js";
import { $createUnknownNode, UnknownNode } from "./UnknownNode.js";
import {
+ $createNodeSelection,
$createPoint,
$createRangeSelection,
$createTextNode,
@@ -341,4 +343,162 @@ describe("UnknownNode", () => {
);
});
});
+
+ // The predicate behind the same-namespace `application/x-lexical-editor` copy path:
+ // @lexical/clipboard's `$appendNodesToJSON` computes node exclusion from
+ // `excludeFromCopy('html')` for EVERY copy-out format it handles, not only
+ // actual `text/html` generation (see `excludeFromCopy`'s own doc comment). A direct unit pin on
+ // the predicate here — independent of the integration-level clipboard pins in
+ // `optbreakClipboardFidelity.test.tsx` and `unknownClipboardFidelity.test.tsx` — catches a
+ // regression to this method locally, without needing the full editor/selection/clipboard-event
+ // machinery those pins exercise.
+ describe("excludeFromCopy()", () => {
+ it("does NOT exclude a CHILD-BEARING optbreak — what lets a copied optbreak survive the lexical-flavor paste path", () => {
+ const { editor } = createBasicTestEnvironment([UnknownNode, ImmutableTypedTextNode]);
+ editor.update(() => {
+ const optbreak = $createUnknownNode("optbreak");
+ optbreak.append($createImmutableTypedTextNode("marker", "//"));
+ expect(optbreak.excludeFromCopy("html")).toBe(false);
+ });
+ });
+
+ it('excludes a CHILDLESS optbreak — a genuinely empty live husk stays excluded, matching text/plain\'s own "nothing here" for that shape', () => {
+ const { editor } = createBasicTestEnvironment([UnknownNode]);
+ editor.update(() => {
+ const optbreak = $createUnknownNode("optbreak");
+ expect(optbreak.getChildrenSize()).toBe(0);
+ expect(optbreak.excludeFromCopy("html")).toBe(true);
+ });
+ });
+
+ it("does NOT exclude a child-bearing figure either — the rule is the node's own child count, not its kind, because every kind's display bytes are the same content-free decorators that get stranded by a hoist", () => {
+ const { editor } = createBasicTestEnvironment([UnknownNode, ImmutableTypedTextNode]);
+ editor.update(() => {
+ const figure = $createUnknownNode("figure", "fig");
+ figure.append($createImmutableTypedTextNode("marker", "\\fig "));
+ expect(figure.excludeFromCopy("html")).toBe(false);
+ });
+ });
+
+ it("excludes a CHILDLESS figure — nothing of it would survive the copy anyway, and a childless placeholder in the lexical flavor has no counterpart in text/plain", () => {
+ const { editor } = createBasicTestEnvironment([UnknownNode]);
+ editor.update(() => {
+ const figure = $createUnknownNode("figure", "fig");
+ expect(figure.excludeFromCopy("html")).toBe(true);
+ });
+ });
+ });
+
+ // The override answers on CHILD membership, which is the right test for the range selections the
+ // clipboard pins exercise but blind to a `NodeSelection` — that marks a node by its OWN key and
+ // never puts the children in `getNodes()` at all. Paired with `excludeFromCopy` above, a false
+ // answer there would make `$appendNodesToJSON` hoist the (also unselected) children, so a
+ // construct selected outright would copy as nothing.
+ describe("isSelected()", () => {
+ function $figureInDocument() {
+ const figure = $createUnknownNode("figure", "fig");
+ figure.append($createImmutableTypedTextNode("marker", "\\fig "));
+ $getRoot().append($createParaNode("p").append(figure));
+ return figure;
+ }
+
+ it("answers true for a node a NodeSelection holds by its own key, whose children are NOT in the selection", () => {
+ const { editor } = createBasicTestEnvironment([
+ UnknownNode,
+ ImmutableTypedTextNode,
+ ParaNode,
+ ]);
+ editor.update(() => {
+ const figure = $figureInDocument();
+ const selection = $createNodeSelection();
+ selection.add(figure.getKey());
+ $setSelection(selection);
+
+ expect(selection.getNodes().some((node) => node.is(figure.getFirstChild()))).toBe(false);
+ expect(figure.isSelected()).toBe(true);
+ });
+ });
+
+ it("answers false for a NodeSelection holding some other node — the key test does not over-claim", () => {
+ const { editor } = createBasicTestEnvironment([
+ UnknownNode,
+ ImmutableTypedTextNode,
+ ParaNode,
+ ]);
+ editor.update(() => {
+ const figure = $figureInDocument();
+ const other = $createTextNode("elsewhere");
+ $getRoot().append($createParaNode("p").append(other));
+ const selection = $createNodeSelection();
+ selection.add(other.getKey());
+ $setSelection(selection);
+
+ expect(figure.isSelected()).toBe(false);
+ });
+ });
+
+ // The RANGE branch — the shape the override exists for, and the one Lexical's default gets
+ // wrong. A copy walks `$appendNodesToJSON` (`@lexical/clipboard`), which asks `isSelected()`
+ // for `shouldInclude`: a true answer at a boundary that covers none of the node's content
+ // serializes a CHILDLESS placeholder into `application/x-lexical-editor` for a node that HAS
+ // children, disagreeing with `text/plain`, whose walker emits nothing there.
+ //
+ // Asserted on the predicate rather than through a Standard-view copy, deliberately. That path
+ // cannot see this: a boundary ON the construct is also a selection reaching INTO an opaque
+ // block, so `$getStandardViewClipboardData` omits the internal flavor outright
+ // (`optbreakClipboardFidelity.test.tsx`, `unknownClipboardFidelity.test.tsx`) and every
+ // assertion about its contents would hold vacuously against an empty string. The flavor IS
+ // written by Lexical's own copy in the views that do not register that handler, which is where
+ // this predicate does its work.
+ function $figureAfterTextInDocument() {
+ const before = $createTextNode("before ");
+ const figure = $createUnknownNode("figure", "fig");
+ figure.append($createImmutableTypedTextNode("marker", "\\fig "));
+ $getRoot().append($createParaNode("p").append(before, figure));
+ return { before, figure };
+ }
+
+ it("answers false for a RANGE ending at an ELEMENT point ON the node, which covers none of its children", () => {
+ const { editor } = createBasicTestEnvironment([
+ UnknownNode,
+ ImmutableTypedTextNode,
+ ParaNode,
+ ]);
+ editor.update(() => {
+ const { before, figure } = $figureAfterTextInDocument();
+ const selection = $createRangeSelection();
+ selection.anchor = $createPoint(before.getKey(), 0, "text");
+ selection.focus = $createPoint(figure.getKey(), 0, "element");
+ $setSelection(selection);
+
+ // Falsifiable: the node IS in `getNodes()`, so the inherited `ElementNode.isSelected` —
+ // key membership in exactly that list — answers true here. Only the child-membership
+ // override answers false.
+ const selectedNodes = selection.getNodes();
+ expect(selectedNodes.some((node) => node.is(figure))).toBe(true);
+ expect(selectedNodes.some((node) => node.is(figure.getFirstChild()))).toBe(false);
+ expect(figure.isSelected(selection)).toBe(false);
+ });
+ });
+
+ it("answers true for a RANGE that reaches over one of the node's own children", () => {
+ const { editor } = createBasicTestEnvironment([
+ UnknownNode,
+ ImmutableTypedTextNode,
+ ParaNode,
+ ]);
+ editor.update(() => {
+ const { before, figure } = $figureAfterTextInDocument();
+ const selection = $createRangeSelection();
+ selection.anchor = $createPoint(before.getKey(), 0, "text");
+ // One past the `\fig ` glyph: the child itself is now inside the range, so both carriers
+ // have bytes for it and the construct must ride along whole.
+ selection.focus = $createPoint(figure.getKey(), 1, "element");
+ $setSelection(selection);
+
+ expect(selection.getNodes().some((node) => node.is(figure.getFirstChild()))).toBe(true);
+ expect(figure.isSelected(selection)).toBe(true);
+ });
+ });
+ });
});
diff --git a/libs/shared/src/nodes/features/UnknownNode.ts b/libs/shared/src/nodes/features/UnknownNode.ts
index 2ccbd6f5..584650a9 100644
--- a/libs/shared/src/nodes/features/UnknownNode.ts
+++ b/libs/shared/src/nodes/features/UnknownNode.ts
@@ -2,6 +2,9 @@ import { UnknownAttributes } from "../usj/node-constants.js";
import { MarkerObject } from "@eten-tech-foundation/scripture-utilities";
import {
$applyNodeReplacement,
+ $getSelection,
+ $isNodeSelection,
+ BaseSelection,
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
@@ -205,8 +208,94 @@ export class UnknownNode extends ElementNode {
return false;
}
+ // A CHILD-BEARING `UnknownNode` of any kind stays IN the copy. This affects ONLY the
+ // `application/x-lexical-editor` (lexical-JSON) flavor, for two independent reasons: an
+ // editable-marker view's own `text/html` is not a DOM export at all (Standard view renders the
+ // copy walker's USFM bytes — `usfmToClipboardHtml`, platform's whitespaceDisplay.plugin.utils.ts),
+ // and where Lexical's exporter DOES run, `$appendNodesToHTML` (`@lexical/html`) computes this same
+ // `excludeFromCopy('html')` value but returns early on `exportDOM()`'s unconditional
+ // `{element: null}` BEFORE ever consulting it. `'clone'` is never passed by any Lexical-shipped
+ // code path in the installed version, so an unconditional `destination !== "clone"` would
+ // exclude every `UnknownNode` from the lexical-JSON flavor outright.
+ //
+ // Excluding a node does not drop it silently: `$appendNodesToJSON` HOISTS the excluded node's own
+ // children into its parent's list in its place. Every kind's marker and attribute bytes are
+ // content-free `ImmutableTypedTextNode` display decorators (`unknownDisplayParts` builds them
+ // identically for all of them), so hoisting strands decorators that still `decorate()` their own
+ // literal text as loose siblings with no owning wrapper: the pasted document RENDERS the
+ // construct's full USFM bytes while its USJ has lost the node and every attribute on it — a
+ // convincing display over missing data, which a save then persists with no error.
+ //
+ // The `getChildrenSize() > 0` guard excludes a node with no children of its OWN — of any kind,
+ // and for whatever reason it has none — because that is exactly when `text/plain` also emits
+ // nothing for it, and the two carriers must agree. Three shapes reach it, and only the first is
+ // a husk: an optbreak whose `//` display child was deleted (`markerEditTier1.utils.ts`'s
+ // husk-removal check recognizes it by that same zero-child shape); a content-less construct in
+ // an editable-marker view, where the marker/attribute display bytes `createUnknown`
+ // (`usj-editor.adaptor.ts`) prepends are themselves children, so only a kind with no display
+ // bytes at all can be childless; and, in a HIDDEN-marker view, ANY content-less construct — a
+ // caption-less `figure`, an empty `ref`, every optbreak — because `createUnknown` builds no
+ // display children there at all. The last is prose-copy semantics rather than a husk: a
+ // hidden-marker view copies what it shows, and it shows no bytes for a construct with no
+ // content.
+ //
+ // The guard does NOT, on its own, cover a DIFFERENT carrier-agreement gap: a selection whose
+ // ending boundary resolves to an ELEMENT-type point ON this node at offset 0 (touching the
+ // wrapper without covering any of its content) still marks a CHILD-BEARING node "selected" under
+ // Lexical's default `isSelected` (key-membership in `selection.getNodes()`, unaffected by this
+ // node's own child count), so `$appendNodesToJSON` would serialize a CHILDLESS placeholder for a
+ // node that has children, disagreeing with `text/plain` (`$selectionToUsfmText`, which walks that
+ // same `getNodes()` list and correctly emits nothing for this boundary). The `isSelected`
+ // override below closes that second gap at its actual source, since `excludeFromCopy` has no
+ // visibility into which of a node's children a given selection will include — for every
+ // construct whose display bytes lead, which is all of them but `ref`; see that override for the
+ // one shape it deliberately does not close, and why.
override excludeFromCopy(destination: "clone" | "html"): boolean {
- return destination !== "clone";
+ return this.getChildrenSize() > 0 ? false : destination !== "clone";
+ }
+
+ // An `UnknownNode` is only meaningfully "selected" (and so only copy-included, per the
+ // `excludeFromCopy` guard above) when at least one of its own children is — mirrors
+ // `$selectionToUsfmText`'s copy walker so both clipboard carriers agree at the same selection
+ // boundary, the same way Lexical's own base `isSelected` (`LexicalNode.prototype.isSelected`)
+ // already special-cases an inline DECORATOR node sitting as a parent's last child at an
+ // exactly-there boundary point, for the identical reason. Without this, a selection ending
+ // exactly at this node's own start (an ElementNode touch-boundary that covers none of its
+ // content) still counts the WRAPPER as selected via the default `ElementNode.isSelected`
+ // (key-membership in `selection.getNodes()`), while no child is — producing a childless entry in
+ // the `application/x-lexical-editor` copy with nothing corresponding to it in `text/plain`.
+ //
+ // Child MEMBERSHIP is the test, deliberately, and not the narrower "does the selection cover a
+ // child's CONTENT". The two differ for exactly one shape, because that boundary reaches the
+ // children two ways depending on what the first child IS. A construct whose display bytes lead
+ // (a `figure`'s `\fig ` glyph, an optbreak's `//`) starts with a DECORATOR: the element-type
+ // point stays one, no child is in `getNodes()`, and both carriers agree. A construct with no
+ // display bytes (a `ref`, whose container USFM never carried) starts with a real `TextNode`, and
+ // Lexical normalizes that same point into a TEXT point at the child's offset 0 — the child is in
+ // `getNodes()` contributing zero characters, so `text/plain` emits nothing for it while this
+ // predicate still answers true and the construct rides along in the lexical flavor.
+ //
+ // Answering false there is WORSE, not better, and measurably so. Excluding the wrapper does not
+ // drop it quietly: `$appendNodesToJSON` HOISTS its children in its place, and `createUnknown`
+ // stamps `mode:"token"` on every text child, which `$sliceSelectedTextNodeContent` refuses to
+ // slice — so the zero-width child keeps its full text, the emptied-text reset never fires, and
+ // the copy ends up carrying the construct's CHARACTERS with the wrapper and its attributes
+ // silently gone. That is the convincing-lie hazard this whole pair exists to prevent. Membership
+ // keeps the construct whole, so the lexical flavor is a SUPERSET of `text/plain` at that one
+ // boundary rather than a structural loss. That residual stands deliberately: closing it needs a
+ // lever Lexical does not offer — `exportNodeToJSON` requires every ElementNode's `exportJSON()`
+ // to return a `children` array, so a node cannot say "drop me AND my children".
+ override isSelected(selection?: BaseSelection | null): boolean {
+ const targetSelection = selection ?? $getSelection();
+ if (!targetSelection) return false;
+ // A `NodeSelection` marks a node selected by its OWN key, never by its children's, so the
+ // child-membership test below cannot see one and the base predicate has to answer first.
+ // Answering false there does not drop the construct quietly — `$appendNodesToJSON` HOISTS its
+ // (also unselected) children in its place — so a node the user selected outright would copy as
+ // nothing at all, the same convincing-lie hazard the range case above exists to prevent.
+ if ($isNodeSelection(targetSelection) && super.isSelected(targetSelection)) return true;
+ const selectedNodes = targetSelection.getNodes();
+ return this.getChildren().some((child) => selectedNodes.some((node) => node.is(child)));
}
}
diff --git a/libs/shared/src/nodes/features/unknownUsfm.utils.ts b/libs/shared/src/nodes/features/unknownUsfm.utils.ts
index 8880f420..c275ac51 100644
--- a/libs/shared/src/nodes/features/unknownUsfm.utils.ts
+++ b/libs/shared/src/nodes/features/unknownUsfm.utils.ts
@@ -128,7 +128,9 @@ function renameFigureFileToSrc(attributes: UnknownAttributes): UnknownAttributes
}
/**
- * The cell's opening marker with its span suffix re-encoded from `colspan`. The tokenizer
+ * The cell's opening marker with its span suffix re-encoded from `colspan`. Exported because a
+ * cell's display bytes are built by `ImmutableTableCellNode`'s own adaptor path (the kind stopped
+ * being an `UnknownNode`), and both builders must spell a spanning cell the same way. The tokenizer
* (usfmFragmentToUsj.ts, table-cell assembly) splits a spanning cell marker apart on the way in —
* `\thc3-4` becomes marker `thc3` (span suffix trimmed off after the start column) plus colspan
* `"2"` (the COUNT of columns spanned, end − start + 1) — so rendering the stored marker bare
@@ -137,7 +139,7 @@ function renameFigureFileToSrc(attributes: UnknownAttributes): UnknownAttributes
* marker with no trailing start column to count from yields the bare marker rather than a garbage
* suffix.
*/
-function tableCellMarkerWithSpan(
+export function tableCellMarkerWithSpan(
marker: string | undefined,
colspan: string | undefined,
): string | undefined {
diff --git a/libs/shared/src/nodes/usj/AttributeRunNode.ts b/libs/shared/src/nodes/usj/AttributeRunNode.ts
index 462d6a0f..ddcc6a08 100644
--- a/libs/shared/src/nodes/usj/AttributeRunNode.ts
+++ b/libs/shared/src/nodes/usj/AttributeRunNode.ts
@@ -150,10 +150,12 @@ export class AttributeRunNode extends ElementNode {
}
override exportDOM(): DOMExportOutput {
- // A DocumentFragment rather than null: @lexical/html's $appendNodesToHTML treats a null
- // element as "skip this subtree" and never walks the children, so the run's glyphs AND its
- // value text (the "2" of `\va 2\va*`) vanished from the text/html clipboard flavor while
- // getTextContent() kept them on text/plain — and most rich paste targets prefer HTML. The
+ // A DocumentFragment rather than null: @lexical/html's $appendNodesToHTML treats a null element
+ // as "skip this subtree" and never walks the children, which would drop the run's glyphs AND its
+ // value text (the "2" of `\va 2\va*`) from any DOM-export html while getTextContent() keeps them
+ // on text/plain. That export is what the HIDDEN-marker views' copy still ships, and what any
+ // consumer re-importing html through $generateNodesFromDOM reads; an editable-marker view's own
+ // `text/html` is the copy walker's USFM bytes instead and never reaches this method. The
// fragment exports the children while still contributing no wrapper markup of its own.
return { element: document.createDocumentFragment() };
}
diff --git a/libs/test-data/src/data/2sa.lexical.editable.ts b/libs/test-data/src/data/2sa.lexical.editable.ts
index 09c65c18..41afcf71 100644
--- a/libs/test-data/src/data/2sa.lexical.editable.ts
+++ b/libs/test-data/src/data/2sa.lexical.editable.ts
@@ -9695,7 +9695,7 @@ export const lexicalEditable2Sa: SerializedEditorState = {
children: [
{
type: "marker",
- marker: "thc3",
+ marker: "thc3-4",
markerSyntax: "opening",
text: "",
detail: 0,
@@ -9944,7 +9944,7 @@ export const lexicalEditable2Sa: SerializedEditorState = {
children: [
{
type: "marker",
- marker: "thr4",
+ marker: "thr4-5",
markerSyntax: "opening",
text: "",
detail: 0,
@@ -10021,7 +10021,7 @@ export const lexicalEditable2Sa: SerializedEditorState = {
children: [
{
type: "marker",
- marker: "tcr1",
+ marker: "tcr1-4",
markerSyntax: "opening",
text: "",
detail: 0,
diff --git a/libs/test-data/src/data/2sa.lexical.visible.ts b/libs/test-data/src/data/2sa.lexical.visible.ts
index 3b68c856..55c3148a 100644
--- a/libs/test-data/src/data/2sa.lexical.visible.ts
+++ b/libs/test-data/src/data/2sa.lexical.visible.ts
@@ -6393,7 +6393,7 @@ export const lexicalVisible2Sa: SerializedEditorState = {
children: [
{
type: "immutable-typed-text",
- text: "\\thc3 ",
+ text: "\\thc3-4 ",
textType: "marker",
version: 1,
},
@@ -6540,7 +6540,7 @@ export const lexicalVisible2Sa: SerializedEditorState = {
children: [
{
type: "immutable-typed-text",
- text: "\\thr4 ",
+ text: "\\thr4-5 ",
textType: "marker",
version: 1,
},
@@ -6583,7 +6583,7 @@ export const lexicalVisible2Sa: SerializedEditorState = {
children: [
{
type: "immutable-typed-text",
- text: "\\tcr1 ",
+ text: "\\tcr1-4 ",
textType: "marker",
version: 1,
},
diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md
index d7e1ea79..2a8559f8 100644
--- a/packages/platform/CHANGELOG.md
+++ b/packages/platform/CHANGELOG.md
@@ -32,6 +32,40 @@ refused. The public surface grew substantially; nothing was removed.
### Changed
+- **`EditorRef.copy()` and `EditorRef.cut()` with nothing selected now leave the clipboard alone.**
+ Previously either one, called at a collapsed caret or with no selection, still wrote to the system
+ clipboard — it put a lone `#` there, because `@lexical/clipboard` synthesizes a copy event by
+ appending a hidden placeholder element and declines to fill it in before suppressing the browser's
+ own copy. A host calling `copy()` speculatively therefore destroyed whatever the user had on the
+ clipboard. The signatures are unchanged, so this arrives with no compile-time signal: a host that
+ worked around the old behavior (clearing the clipboard first, or reading it back and treating `#`
+ as empty) should drop that workaround.
+- **Standard view's `text/html` clipboard flavor now carries the same USFM bytes as `text/plain`.** It was
+ Lexical's DOM export, which is lossy in two independent ways: `ImmutableNoteCallerNode.exportDOM` puts a
+ collapsed note's caller in a `data-caller` attribute with no text, and `UnknownNode.exportDOM` returns a
+ null element for every kind, which stops the html walk before the construct's own display children. A
+ consumer that reads the fragment's text as USFM — Paratext 9 does — therefore received notes with an empty
+ caller and no figures, sidebars, peripherals, refs or optbreaks at all. The flavor is now the selection's
+ USFM, HTML-escaped, one `…
` per line, so both readable
+ flavors decode to one document. `application/x-lexical-editor` is unchanged, so an internal paste keeps its
+ node-tree fast path. A host that parsed the old export-shaped html (reading `data-caller`, `data-marker` or
+ node class names out of it) must read the USFM text instead.
+- **A structure-protected editor's pastes now get the same byte normalization as an unprotected one.**
+ With `structureProtectionMode: "protected"` the Standard-view paste handler used to decline outright,
+ handing every paste to `StructureKeyboardPlugin`'s html sanitizer — which reads `text/html` only. That
+ made the protected mode strictly less safe than the unprotected one: a pasted `\c 7` was never
+ stripped, so it created a second chapter node and every later save failed in the data provider; NBSPs
+ were never normalized positionally; and a Paratext 9 clipboard's note was never decoded. The handler
+ now owns a protected paste too. Two things still differ under protection: a selection
+ `StructureKeyboardPlugin` refuses to replace (a range spanning a paragraph boundary, or containing a
+ verse marker) is declined so that refusal keeps one owner, and a multi-line payload's newlines become
+ single spaces instead of paragraph splits, so a protected document never gains a block from a paste.
+- **A Paratext 9 clipboard's `text/html` is now decoded to USFM on paste, and wins over that clipboard's own
+ `text/plain`.** P9 writes `text/plain` as the selection's visible text and keeps the USFM its own paste
+ reads in `CF_HTML`, as escaped `` comments, so pasting a P9 footnote inserted the caller glyph
+ alone and lost the note. Every other source's `text/plain` still wins whenever present: the decoder
+ recognizes P9's html by signature (a `usfm:` comment, or an element carrying both a `usfm_` class and
+ `usfmopen`/`usfmclosed`) and declines everything else, this editor's own html included.
- `EditorRef.insertMarker` returns `string | undefined` (was `void`) — the created node's key.
- `NoteCallerOnClick` takes a 7th parameter, `getNoteIndex: () => number | undefined`.
- **Marker menu descriptions no longer carry the `(basic)` token.** `usfm.sty` marks commonly-used
@@ -42,6 +76,17 @@ refused. The public surface grew substantially; nothing was removed.
- `getUsj()` returns the settled document in editable marker modes. When nothing is pending and no
transient input is declared it short-circuits to the previous behavior, so the other view modes are
unaffected.
+- **A Standard-view copy whose selection cuts through an opaque construct — a figure, sidebar,
+ periph, ref, table or optbreak — no longer writes the private `application/x-lexical-editor`
+ flavor.** That flavor carries a construct WHOLE and cannot carry part of one: a construct's text is
+ token-mode, which `@lexical/selection` refuses to slice, so a caption selected from its third
+ character to its seventh went on the clipboard as a COMPLETE figure — wrapper, attributes and the
+ whole caption — while the two readable flavors carried the four selected characters. Pasting that
+ through a native paste event inserted a second figure, and a save persisted it. `text/plain` and
+ `text/html` are unchanged and still carry exactly the selected bytes. A host that reads the private
+ flavor off the clipboard must handle its absence for such a selection; a host pasting through
+ `navigator.clipboard.read()` (the editor's own Ctrl+V and context-menu Paste) sees no change, since
+ that API never exposed the flavor.
### Fixed
diff --git a/packages/platform/dist/index.d.ts b/packages/platform/dist/index.d.ts
index 56dd407c..f73b7303 100644
--- a/packages/platform/dist/index.d.ts
+++ b/packages/platform/dist/index.d.ts
@@ -349,12 +349,18 @@ export declare interface EditorRef {
/** Redo the last undone action. */
redo(): void;
/**
- * Cut the selected text.
+ * Cut the selected text. With nothing selected it does nothing and the clipboard keeps whatever
+ * it already held — see {@link EditorRef.copy}.
* @throws Will throw an error if the editor is in readonly mode or uses the block verse layout
* (`ViewOptions.verseLayout: "block"`), which is read-only by construction.
*/
cut(): void;
- /** Copy the selected text. */
+ /**
+ * Copy the selected text. With nothing selected — no selection, or a collapsed caret — it does
+ * nothing and the clipboard keeps whatever it already held, rather than receiving a placeholder
+ * character the editor never contained. A selection made programmatically immediately before this
+ * call still copies: the guard reads the live selection, not the last committed one.
+ */
copy(): void;
/**
* Paste text at the current cursor position.
diff --git a/packages/platform/dist/index.js b/packages/platform/dist/index.js
index 8090ca4a..1ba185c2 100644
--- a/packages/platform/dist/index.js
+++ b/packages/platform/dist/index.js
@@ -1,34 +1,34 @@
-import { jsx as C, jsxs as Te, Fragment as dn } from "react/jsx-runtime";
-import { forwardRef as vn, useState as de, useRef as X, useCallback as ge, useEffect as K, useMemo as Fe, memo as Mm, createContext as Vd, useContext as Wd, Children as Em, isValidElement as Am, cloneElement as Pm, useImperativeHandle as cc, useLayoutEffect as rs } from "react";
-import { assertSafeKey as Ve, isValidBookCode as Nm, MARKER_OBJECT_PROPS as Om, USJ_VERSION as pr, USJ_TYPE as hr, isUsjTextContentLocation as wm, indexesFromUsjJsonPath as Hd, isUsjAttributeKeyLocation as qm, isUsjAttributeMarkerLocation as Rm, isUsjClosingAttributeMarkerLocation as $m, isUsjMarkerLocation as Im, isUsjClosingMarkerLocation as Lm, isUsjPropertyValueLocation as Dm, getUsjDocumentLocationTypeName as Um, usjJsonPathFromIndexes as en, EMPTY_USJ as Gd } from "@eten-tech-foundation/scripture-utilities";
-import { $applyNodeReplacement as Ke, $parseSerializedNode as to, DecoratorNode as ns, ElementNode as Jt, isHTMLElement as Sn, createState as ro, $getState as re, $setState as mt, $isRangeSelection as N, $isElementNode as D, $isTextNode as M, ParagraphNode as lc, TextNode as ze, $createTextNode as he, $getCommonAncestor as Fm, $getSelection as R, $isLineBreakNode as no, NODE_STATE_KEY as is, $getEditor as ss, $hasUpdateTag as zm, $getNodeByKey as ne, $getRoot as Ue, $createRangeSelection as uc, $createPoint as zl, $getCharacterOffsets as Jd, KEY_DOWN_COMMAND as Tr, COMMAND_PRIORITY_HIGH as Ie, HISTORY_MERGE_TAG as Yd, CLICK_COMMAND as io, COMMAND_PRIORITY_EDITOR as fn, isDOMNode as Xd, $getNearestNodeFromDOMNode as os, CONTROLLED_TEXT_INSERTION_COMMAND as dc, PASTE_COMMAND as dr, COMMAND_PRIORITY_CRITICAL as fr, CUT_COMMAND as pn, DROP_COMMAND as fc, DELETE_CHARACTER_COMMAND as Km, DELETE_WORD_COMMAND as jm, DELETE_LINE_COMMAND as Bm, $isDecoratorNode as Qd, COPY_COMMAND as so, COMMAND_PRIORITY_NORMAL as Hn, SELECTION_CHANGE_COMMAND as gr, BLUR_COMMAND as pc, $addUpdateTag as Dr, SKIP_DOM_SELECTION_TAG as Vm, CLEAR_HISTORY_COMMAND as Wm, COMMAND_PRIORITY_LOW as Rt, $setSelection as Ui, $getPreviousSelection as Hm, $isRootOrShadowRoot as Gm, CAN_UNDO_COMMAND as Jm, CAN_REDO_COMMAND as Ym, $isNodeSelection as Zd, DRAGSTART_COMMAND as Xm, $createNodeSelection as ef, getDOMSelectionFromTarget as Qm, $onUpdate as Zm, KEY_ENTER_COMMAND as tf, LineBreakNode as rf, $copyNode as ey, FOCUS_COMMAND as ty, $isRootNode as ry, KEY_ESCAPE_COMMAND as nf, INSERT_PARAGRAPH_COMMAND as As, createCommand as sf, HISTORIC_TAG as hc, UNDO_COMMAND as of, REDO_COMMAND as af, CLEAR_EDITOR_COMMAND as ny } from "lexical";
-import { addClassNamesToElement as Ln, removeClassNamesFromElement as Ko, $findMatchingParent as nt, $dfsIterator as cf, $dfs as ii, mergeRegister as Xe, registerNestedElementResolver as lf, $unwrapNode as ba, IS_APPLE as Ps } from "@lexical/utils";
-import { useLexicalNodeSelection as iy } from "@lexical/react/useLexicalNodeSelection";
-import { deepEqual as Pt } from "fast-equals";
-import Ai from "quill-delta";
-import { useLexicalComposerContext as le } from "@lexical/react/LexicalComposerContext";
-import { copyToClipboard as sy, $getHtmlContent as oy, $getLexicalContent as ay } from "@lexical/clipboard";
-import { TreeView as cy } from "@lexical/react/LexicalTreeView";
-import * as ly from "react-dom";
-import { createPortal as un } from "react-dom";
-import { LexicalComposer as uf } from "@lexical/react/LexicalComposer";
-import { ContentEditable as df } from "@lexical/react/LexicalContentEditable";
-import { EditorRefPlugin as ff } from "@lexical/react/LexicalEditorRefPlugin";
-import { LexicalErrorBoundary as pf } from "@lexical/react/LexicalErrorBoundary";
-import { HistoryPlugin as hf } from "@lexical/react/LexicalHistoryPlugin";
-import { RichTextPlugin as uy } from "@lexical/react/LexicalRichTextPlugin";
-import { $setBlocksType as dy, createDOMRange as fy, createRectsFromDOMRange as py } from "@lexical/selection";
-import { autoUpdate as hy, computePosition as gy, shift as my, flip as yy } from "@floating-ui/dom";
-import { $generateNodesFromDOM as by } from "@lexical/html";
-import { AutoFocusPlugin as ky } from "@lexical/react/LexicalAutoFocusPlugin";
-import { ClearEditorPlugin as Ty } from "@lexical/react/LexicalClearEditorPlugin";
-import { useCollaborationContext as gf, LexicalCollaboration as xy } from "@lexical/react/LexicalCollaborationContext";
-import { OnChangePlugin as _y } from "@lexical/react/LexicalOnChangePlugin";
-import { PlainTextPlugin as Cy } from "@lexical/react/LexicalPlainTextPlugin";
-import { $rootTextContent as vy, $isRootTextContentEmpty as Sy } from "@lexical/text";
-import { TOGGLE_CONNECT_COMMAND as My } from "@lexical/yjs";
-import { Array as Kl, Map as jl, YArrayEvent as Ey } from "yjs";
-const jo = (e) => Ke(to(e)), Ay = {
+import { jsx as C, jsxs as Te, Fragment as gn } from "react/jsx-runtime";
+import { forwardRef as Mn, useState as de, useRef as Z, useCallback as he, useEffect as z, useMemo as Fe, memo as Bm, createContext as lf, useContext as uf, Children as Vm, isValidElement as Wm, cloneElement as Hm, useImperativeHandle as dc, useLayoutEffect as cs } from "react";
+import { assertSafeKey as Ve, isValidBookCode as Gm, MARKER_OBJECT_PROPS as Jm, USJ_VERSION as br, USJ_TYPE as kr, isUsjTextContentLocation as Ym, indexesFromUsjJsonPath as df, isUsjAttributeKeyLocation as Xm, isUsjAttributeMarkerLocation as Qm, isUsjClosingAttributeMarkerLocation as Zm, isUsjMarkerLocation as ey, isUsjClosingMarkerLocation as ty, isUsjPropertyValueLocation as ry, getUsjDocumentLocationTypeName as ny, usjJsonPathFromIndexes as on, EMPTY_USJ as ff } from "@eten-tech-foundation/scripture-utilities";
+import { $applyNodeReplacement as je, $parseSerializedNode as so, DecoratorNode as ls, ElementNode as Qt, isHTMLElement as En, createState as oo, $getState as ne, $setState as yt, $isRangeSelection as N, $isElementNode as F, $isTextNode as v, $getSelection as R, $isNodeSelection as fc, ParagraphNode as pc, TextNode as Ke, $createTextNode as pe, $getCommonAncestor as iy, $isLineBreakNode as us, NODE_STATE_KEY as ds, $getEditor as Xn, $hasUpdateTag as sy, $getNodeByKey as se, $getRoot as Ue, $createRangeSelection as hc, $createPoint as eu, $getCharacterOffsets as gc, KEY_DOWN_COMMAND as Sr, COMMAND_PRIORITY_HIGH as Ie, HISTORY_MERGE_TAG as pf, CLICK_COMMAND as ao, COMMAND_PRIORITY_EDITOR as mn, isDOMNode as hf, $getNearestNodeFromDOMNode as ci, CONTROLLED_TEXT_INSERTION_COMMAND as mc, PASTE_COMMAND as yr, COMMAND_PRIORITY_CRITICAL as ar, CUT_COMMAND as Qn, DROP_COMMAND as yc, DELETE_CHARACTER_COMMAND as oy, DELETE_WORD_COMMAND as ay, DELETE_LINE_COMMAND as cy, $isDecoratorNode as co, COPY_COMMAND as bc, COMMAND_PRIORITY_LOW as kt, COMMAND_PRIORITY_NORMAL as Gn, SELECTION_CHANGE_COMMAND as ur, getDOMSelection as ly, isSelectionWithinEditor as uy, $createRangeSelectionFromDom as dy, $setSelection as Zn, isDOMTextNode as fy, BLUR_COMMAND as kc, $addUpdateTag as Kr, SKIP_DOM_SELECTION_TAG as py, CLEAR_HISTORY_COMMAND as hy, $getPreviousSelection as gy, $isRootOrShadowRoot as my, CAN_UNDO_COMMAND as yy, CAN_REDO_COMMAND as by, DRAGSTART_COMMAND as ky, $createNodeSelection as gf, getDOMSelectionFromTarget as Ty, $onUpdate as xy, KEY_ENTER_COMMAND as mf, LineBreakNode as yf, $copyNode as _y, FOCUS_COMMAND as Cy, $isRootNode as Sy, KEY_ESCAPE_COMMAND as bf, INSERT_PARAGRAPH_COMMAND as qs, createCommand as kf, HISTORIC_TAG as Tc, UNDO_COMMAND as Tf, REDO_COMMAND as xf, CLEAR_EDITOR_COMMAND as vy } from "lexical";
+import { addClassNamesToElement as Dn, removeClassNamesFromElement as Ko, $findMatchingParent as nt, $dfsIterator as _f, $dfs as li, mergeRegister as He, registerNestedElementResolver as Cf, $unwrapNode as ba, IS_APPLE as Rs } from "@lexical/utils";
+import { useLexicalNodeSelection as My } from "@lexical/react/useLexicalNodeSelection";
+import { deepEqual as wt } from "fast-equals";
+import wi from "quill-delta";
+import { useLexicalComposerContext as ce } from "@lexical/react/LexicalComposerContext";
+import { copyToClipboard as Ey, $getLexicalContent as Ay } from "@lexical/clipboard";
+import { TreeView as Py } from "@lexical/react/LexicalTreeView";
+import * as Ny from "react-dom";
+import { createPortal as hn } from "react-dom";
+import { LexicalComposer as Sf } from "@lexical/react/LexicalComposer";
+import { ContentEditable as vf } from "@lexical/react/LexicalContentEditable";
+import { EditorRefPlugin as Mf } from "@lexical/react/LexicalEditorRefPlugin";
+import { LexicalErrorBoundary as Ef } from "@lexical/react/LexicalErrorBoundary";
+import { HistoryPlugin as Af } from "@lexical/react/LexicalHistoryPlugin";
+import { RichTextPlugin as Oy } from "@lexical/react/LexicalRichTextPlugin";
+import { $setBlocksType as wy, createDOMRange as qy, createRectsFromDOMRange as Ry } from "@lexical/selection";
+import { autoUpdate as $y, computePosition as Iy, shift as Ly, flip as Dy } from "@floating-ui/dom";
+import { $generateNodesFromDOM as Uy } from "@lexical/html";
+import { AutoFocusPlugin as Fy } from "@lexical/react/LexicalAutoFocusPlugin";
+import { ClearEditorPlugin as zy } from "@lexical/react/LexicalClearEditorPlugin";
+import { useCollaborationContext as Pf, LexicalCollaboration as Ky } from "@lexical/react/LexicalCollaborationContext";
+import { OnChangePlugin as jy } from "@lexical/react/LexicalOnChangePlugin";
+import { PlainTextPlugin as By } from "@lexical/react/LexicalPlainTextPlugin";
+import { $rootTextContent as Vy, $isRootTextContentEmpty as Wy } from "@lexical/text";
+import { TOGGLE_CONNECT_COMMAND as Hy } from "@lexical/yjs";
+import { Array as tu, Map as ru, YArrayEvent as Gy } from "yjs";
+const jo = (e) => je(so(e)), Jy = {
c: ["number"],
ef: ["caller"],
efe: ["caller"],
@@ -39,16 +39,16 @@ const jo = (e) => Ke(to(e)), Ay = {
v: ["number"],
x: ["caller"]
};
-function mf(e) {
- return Ay[e];
+function Nf(e) {
+ return Jy[e];
}
-const w = " ", Ns = "", It = w, gc = `${w}|`, rr = "p", Os = "+", yf = "-", ws = "chapter", ka = "verse", Bl = "invalid", Py = "text-spacing", Ny = "formatted-font", Oy = "marker-", bf = "external-usj-mutation", kf = "selection-change", Ur = "cursor-change", Ta = "annotation-change", Fi = "delta-change", Tf = "marker-settle", wy = [
- bf,
- kf,
- Ur,
+const L = " ", $s = "", Dt = L, xc = `${L}|`, cr = "p", Bi = "+", Of = "-", Is = "chapter", ka = "verse", nu = "invalid", Yy = "text-spacing", Xy = "formatted-font", Qy = "marker-", wf = "external-usj-mutation", qf = "selection-change", jr = "cursor-change", Ta = "annotation-change", Vi = "delta-change", Rf = "marker-settle", Zy = [
+ wf,
+ qf,
+ jr,
Ta,
- Fi
-], hn = "zmsc-s", Gn = "zmsc-e", qy = [hn, Gn], Ry = [
+ Vi
+], yn = "zmsc-s", Jn = "zmsc-e", eb = [yn, Jn], tb = [
"ts-s",
"ts-e",
"t-s",
@@ -67,16 +67,16 @@ const w = " ", Ns = "", It = w, gc = `${w}|`, rr = "p", Os = "+", yf = "-",
"qt-s",
"qt-e",
// custom markers used for annotations
- hn,
- Gn
-], xf = 1, mc = [
+ yn,
+ Jn
+], $f = 1, _c = [
"type",
"marker",
"sid",
"eid",
"content"
-], $y = mc.filter((e) => e !== "sid" && e !== "eid");
-class Vt extends ns {
+], rb = _c.filter((e) => e !== "sid" && e !== "eid");
+class Gt extends ls {
__marker;
__sid;
__eid;
@@ -95,13 +95,13 @@ class Vt extends ns {
}
static clone(t) {
const { __marker: r, __sid: n, __eid: i, __unknownAttributes: s, __attributeOrder: o, __key: a } = t;
- return new Vt(r, n, i, s, a, o);
+ return new Gt(r, n, i, s, a, o);
}
static importJSON(t) {
- return Cf().updateFromJSON(t);
+ return Lf().updateFromJSON(t);
}
static isValidMarker(t, r) {
- return t !== void 0 && (Ry.includes(t) || t.startsWith("z") || (r?.includes(t) ?? !1));
+ return t !== void 0 && (tb.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 +185,7 @@ class Vt extends ns {
eid: this.getEid(),
unknownAttributes: this.getUnknownAttributes(),
attributeOrder: this.getAttributeOrder(),
- version: xf
+ version: $f
};
}
// Mutation
@@ -193,18 +193,18 @@ class Vt extends ns {
return !1;
}
}
-function _f(e) {
- return qy.includes(e);
+function If(e) {
+ return eb.includes(e);
}
-function Cf(e, t, r, n, i) {
- return Ke(new Vt(e, t, r, n, void 0, i));
+function Lf(e, t, r, n, i) {
+ return je(new Gt(e, t, r, n, void 0, i));
}
-function je(e) {
- return e instanceof Vt;
+function Be(e) {
+ return e instanceof Gt;
}
-const yc = "f", Iy = [
+const Cc = "f", nb = [
// Footnote
- yc,
+ Cc,
"fe",
"ef",
"efe",
@@ -212,45 +212,45 @@ const yc = "f", Iy = [
"x",
"ex"
];
-function Pi(e) {
+function qi(e) {
return e.startsWith("f") || e.startsWith("ef") ? "footnote" : "crossref";
}
-const Ly = [
+const ib = [
"type",
"marker",
"caller",
"category",
"content"
-], vf = 1;
-class Me extends Jt {
+], Df = 1;
+class Ee extends Qt {
__marker;
__caller;
__isCollapsed;
__category;
__unknownAttributes;
- constructor(t = yc, r, n = !0, i, s, o) {
- super(o), this.__marker = t, this.__caller = r ?? (Pi(t) === "crossref" ? yf : Os), this.__isCollapsed = n, this.__category = i, this.__unknownAttributes = s;
+ constructor(t = Cc, r, n = !0, i, s, o) {
+ super(o), this.__marker = t, this.__caller = r ?? (qi(t) === "crossref" ? Of : Bi), 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 Me(r, n, i, s, o, a);
+ return new Ee(r, n, i, s, o, a);
}
static importDOM() {
return {
- span: (t) => Uy(t) ? {
- conversion: Dy,
+ span: (t) => ob(t) ? {
+ conversion: sb,
priority: 1
} : null
};
}
static importJSON(t) {
- return bc().updateFromJSON(t);
+ return Sc().updateFromJSON(t);
}
static isValidMarker(t, r) {
- return t !== void 0 && (Iy.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 +304,14 @@ class Me extends Jt {
}
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", Pi(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", qi(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", Pi(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", qi(this.__marker))), t.__caller !== this.__caller && r.setAttribute("data-caller", this.__caller), !1);
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(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", Pi(this.getMarker()))), { element: r };
+ return r && En(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", qi(this.getMarker()))), { element: r };
}
exportJSON() {
return {
@@ -322,7 +322,7 @@ class Me extends Jt {
isCollapsed: this.getIsCollapsed(),
category: this.getCategory(),
unknownAttributes: this.getUnknownAttributes(),
- version: vf
+ version: Df
};
}
// Mutation
@@ -333,33 +333,33 @@ class Me extends Jt {
return !0;
}
}
-function Dy(e) {
+function sb(e) {
const t = e.getAttribute("data-marker") ?? "f", r = e.getAttribute("data-caller") ?? "", n = e.classList.contains("collapsed");
- return { node: bc(t, r, n) };
+ return { node: Sc(t, r, n) };
}
-function bc(e, t, r, n, i) {
- return Ke(new Me(e, t, r, n, i));
+function Sc(e, t, r, n, i) {
+ return je(new Ee(e, t, r, n, i));
}
-function Uy(e) {
+function ob(e) {
if (!e)
return !1;
const t = e.getAttribute("data-marker") ?? "";
- return Me.isValidMarker(t) && e.classList.contains(Me.getType());
+ return Ee.isValidMarker(t) && e.classList.contains(Ee.getType());
}
function j(e) {
- return e instanceof Me;
+ return e instanceof Ee;
}
-var k;
+var T;
(function(e) {
e.FileIdentification = "FileIdentification", e.Headers = "Headers", e.Remarks = "Remarks", e.Introduction = "Introduction", e.DivisionMarks = "DivisionMarks", e.Paragraphs = "Paragraphs", e.Poetry = "Poetry", e.TitlesHeadings = "TitlesHeadings", e.Tables = "Tables", e.CenterTables = "CenterTables", e.RightTables = "RightTables", e.Lists = "Lists", e.Footnotes = "Footnotes", e.CrossReferences = "CrossReferences", e.SpecialText = "SpecialText", e.CharacterStyling = "CharacterStyling", e.Breaks = "Breaks", e.SpecialFeatures = "SpecialFeatures", e.PeripheralReferences = "PeripheralReferences", e.PeripheralMaterials = "PeripheralMaterials", e.Uncategorized = "Uncategorized";
-})(k || (k = {}));
+})(T || (T = {}));
var b;
(function(e) {
e.Paragraph = "Paragraph", e.Character = "Character", e.Note = "Note", e.Milestone = "Milestone", e.Unknown = "Unknown";
})(b || (b = {}));
const xa = {
id: {
- category: k.FileIdentification,
+ category: T.FileIdentification,
type: b.Paragraph,
description: "File identification information (BOOKID, FILENAME, EDITOR, MODIFICATION DATE)",
hasEndMarker: !1,
@@ -409,14 +409,14 @@ const xa = {
}
},
usfm: {
- category: k.FileIdentification,
+ category: T.FileIdentification,
type: b.Paragraph,
description: "File markup version information",
hasEndMarker: !1,
children: void 0
},
ide: {
- category: k.FileIdentification,
+ category: T.FileIdentification,
type: b.Paragraph,
description: "File encoding information",
hasEndMarker: !1,
@@ -425,7 +425,7 @@ const xa = {
}
},
h: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Running header text for a book (basic)",
hasEndMarker: !1,
@@ -434,7 +434,7 @@ const xa = {
}
},
h1: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Running header text",
hasEndMarker: !1,
@@ -443,7 +443,7 @@ const xa = {
}
},
h2: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Running header text, left side of page",
hasEndMarker: !1,
@@ -452,7 +452,7 @@ const xa = {
}
},
h3: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Running header text, right side of page",
hasEndMarker: !1,
@@ -461,70 +461,70 @@ const xa = {
}
},
toc1: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Long table of contents text",
hasEndMarker: !1,
children: void 0
},
toc2: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Short table of contents text",
hasEndMarker: !1,
children: void 0
},
toc3: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Book Abbreviation",
hasEndMarker: !1,
children: void 0
},
toca1: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Alternative language long table of contents text",
hasEndMarker: !1,
children: void 0
},
toca2: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Alternative language short table of contents text",
hasEndMarker: !1,
children: void 0
},
toca3: {
- category: k.Headers,
+ category: T.Headers,
type: b.Paragraph,
description: "Alternative language book Abbreviation",
hasEndMarker: !1,
children: void 0
},
rem: {
- category: k.Remarks,
+ category: T.Remarks,
type: b.Paragraph,
description: "Comments and remarks",
hasEndMarker: !1,
children: void 0
},
sts: {
- category: k.Remarks,
+ category: T.Remarks,
type: b.Paragraph,
description: "Status of this file",
hasEndMarker: !1,
children: void 0
},
restore: {
- category: k.Remarks,
+ category: T.Remarks,
type: b.Paragraph,
description: "Project restore information",
hasEndMarker: !1,
children: void 0
},
imt: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title, level 1 (if single level) (basic)",
hasEndMarker: !1,
@@ -534,7 +534,7 @@ const xa = {
}
},
imt1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -544,7 +544,7 @@ const xa = {
}
},
imt2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title, level 2",
hasEndMarker: !1,
@@ -554,7 +554,7 @@ const xa = {
}
},
imt3: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title, level 3",
hasEndMarker: !1,
@@ -564,7 +564,7 @@ const xa = {
}
},
imt4: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title, level 4 (usually within parenthesis)",
hasEndMarker: !1,
@@ -574,7 +574,7 @@ const xa = {
}
},
imte: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title at introduction end, level 1 (if single level)",
hasEndMarker: !1,
@@ -584,7 +584,7 @@ const xa = {
}
},
imte1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title at introduction end, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -594,7 +594,7 @@ const xa = {
}
},
imte2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction major title at introduction end, level 2",
hasEndMarker: !1,
@@ -604,7 +604,7 @@ const xa = {
}
},
is: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction section heading, level 1 (if single level) (basic)",
hasEndMarker: !1,
@@ -615,7 +615,7 @@ const xa = {
}
},
is1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction section heading, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -625,7 +625,7 @@ const xa = {
}
},
is2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction section heading, level 2",
hasEndMarker: !1,
@@ -635,7 +635,7 @@ const xa = {
}
},
iot: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline title (basic)",
hasEndMarker: !1,
@@ -645,7 +645,7 @@ const xa = {
}
},
io: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline text, level 1 (if single level)",
hasEndMarker: !1,
@@ -671,7 +671,7 @@ const xa = {
}
},
io1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline text, level 1 (if multiple levels) (basic)",
hasEndMarker: !1,
@@ -697,7 +697,7 @@ const xa = {
}
},
io2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline text, level 2",
hasEndMarker: !1,
@@ -723,7 +723,7 @@ const xa = {
}
},
io3: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline text, level 3",
hasEndMarker: !1,
@@ -749,7 +749,7 @@ const xa = {
}
},
io4: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction outline text, level 4",
hasEndMarker: !1,
@@ -775,14 +775,14 @@ const xa = {
}
},
ior: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Character,
description: "Introduction references range for outline entry; for marking references separately",
hasEndMarker: !0,
children: void 0
},
ip: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph (basic)",
hasEndMarker: !1,
@@ -809,7 +809,7 @@ const xa = {
}
},
im: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph, with no first line indent (may occur after poetry)",
hasEndMarker: !1,
@@ -835,7 +835,7 @@ const xa = {
}
},
ipi: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph, indented, with first line indent",
hasEndMarker: !1,
@@ -861,7 +861,7 @@ const xa = {
}
},
imi: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph text, indented, with no first line indent",
hasEndMarker: !1,
@@ -887,7 +887,7 @@ const xa = {
}
},
ili: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "A list entry, level 1 (if single level)",
hasEndMarker: !1,
@@ -913,7 +913,7 @@ const xa = {
}
},
ili1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "A list entry, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -939,7 +939,7 @@ const xa = {
}
},
ili2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "A list entry, level 2",
hasEndMarker: !1,
@@ -965,7 +965,7 @@ const xa = {
}
},
ipq: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph, quote from the body text",
hasEndMarker: !1,
@@ -991,7 +991,7 @@ const xa = {
}
},
imq: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph, quote from the body text, with no first line indent",
hasEndMarker: !1,
@@ -1017,7 +1017,7 @@ const xa = {
}
},
ipr: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction prose paragraph, right aligned",
hasEndMarker: !1,
@@ -1043,7 +1043,7 @@ const xa = {
}
},
ib: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction blank line",
hasEndMarker: !1,
@@ -1052,7 +1052,7 @@ const xa = {
}
},
iq: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction poetry text, level 1 (if single level)",
hasEndMarker: !1,
@@ -1078,7 +1078,7 @@ const xa = {
}
},
iq1: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction poetry text, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -1104,7 +1104,7 @@ const xa = {
}
},
iq2: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction poetry text, level 2",
hasEndMarker: !1,
@@ -1130,7 +1130,7 @@ const xa = {
}
},
iq3: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction poetry text, level 3",
hasEndMarker: !1,
@@ -1156,7 +1156,7 @@ const xa = {
}
},
iex: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction explanatory or bridge text (e.g. explanation of missing book in Short Old Testament)",
hasEndMarker: !1,
@@ -1166,21 +1166,21 @@ const xa = {
}
},
iqt: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Character,
description: "For quoted scripture text appearing in the introduction",
hasEndMarker: !0,
children: void 0
},
ie: {
- category: k.Introduction,
+ category: T.Introduction,
type: b.Paragraph,
description: "Introduction ending marker",
hasEndMarker: !1,
children: void 0
},
c: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Paragraph,
description: "Chapter number (necessary for normal Paratext operation)",
hasEndMarker: !1,
@@ -1215,14 +1215,14 @@ const xa = {
}
},
ca: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Character,
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)",
hasEndMarker: !0,
children: void 0
},
cp: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Paragraph,
description: "Published chapter number (chapter string that should appear in the published text)",
hasEndMarker: !1,
@@ -1231,14 +1231,14 @@ const xa = {
}
},
cl: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Paragraph,
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.",
hasEndMarker: !1,
children: void 0
},
cd: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Paragraph,
description: "Chapter Description (Publishing option D, e.g. in Russian Bibles)",
hasEndMarker: !1,
@@ -1264,28 +1264,28 @@ const xa = {
}
},
v: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Character,
description: "A verse number (Necessary for normal paratext operation) (basic)",
hasEndMarker: !1,
children: void 0
},
va: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Character,
description: "Second (alternate) verse number (for coding dual numeration in Psalms; see also NRSV Exo 22.1-4)",
hasEndMarker: !0,
children: void 0
},
vp: {
- category: k.DivisionMarks,
+ category: T.DivisionMarks,
type: b.Character,
description: "Published verse marker (verse string that should appear in the published text)",
hasEndMarker: !0,
children: void 0
},
p: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, with first line indent (basic)",
hasEndMarker: !1,
@@ -1315,7 +1315,7 @@ const xa = {
}
},
m: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, with no first line indent (may occur after poetry) (basic)",
hasEndMarker: !1,
@@ -1345,7 +1345,7 @@ const xa = {
}
},
po: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Letter opening",
hasEndMarker: !1,
@@ -1374,7 +1374,7 @@ const xa = {
}
},
pr: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Text refrain (paragraph text, right aligned)",
hasEndMarker: !1,
@@ -1404,7 +1404,7 @@ const xa = {
}
},
cls: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Letter Closing",
hasEndMarker: !1,
@@ -1413,7 +1413,7 @@ const xa = {
}
},
pmo: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Embedded text opening",
hasEndMarker: !1,
@@ -1441,7 +1441,7 @@ const xa = {
}
},
pm: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Embedded text paragraph",
hasEndMarker: !1,
@@ -1469,7 +1469,7 @@ const xa = {
}
},
pmc: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Embedded text closing",
hasEndMarker: !1,
@@ -1497,7 +1497,7 @@ const xa = {
}
},
pmr: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Embedded text refrain (e.g. Then all the people shall say, 'Amen!')",
hasEndMarker: !1,
@@ -1525,7 +1525,7 @@ const xa = {
}
},
pi: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, level 1 indent (if single level), with first line indent; often used for discourse (basic)",
hasEndMarker: !1,
@@ -1554,7 +1554,7 @@ const xa = {
}
},
pi1: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, level 1 indent (if multiple levels), with first line indent; often used for discourse",
hasEndMarker: !1,
@@ -1583,7 +1583,7 @@ const xa = {
}
},
pi2: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, level 2 indent, with first line indent; often used for discourse",
hasEndMarker: !1,
@@ -1612,7 +1612,7 @@ const xa = {
}
},
pi3: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, level 3 indent, with first line indent; often used for discourse",
hasEndMarker: !1,
@@ -1641,7 +1641,7 @@ const xa = {
}
},
pc: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, centered (for Inscription)",
hasEndMarker: !1,
@@ -1670,7 +1670,7 @@ const xa = {
}
},
mi: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, indented, with no first line indent; often used for discourse",
hasEndMarker: !1,
@@ -1699,7 +1699,7 @@ const xa = {
}
},
nb: {
- category: k.Paragraphs,
+ category: T.Paragraphs,
type: b.Paragraph,
description: "Paragraph text, with no break from previous paragraph text (at chapter boundary) (basic)",
hasEndMarker: !1,
@@ -1728,7 +1728,7 @@ const xa = {
}
},
q: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, level 1 indent (if single level)",
hasEndMarker: !1,
@@ -1757,7 +1757,7 @@ const xa = {
}
},
q1: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, level 1 indent (if multiple levels) (basic)",
hasEndMarker: !1,
@@ -1786,7 +1786,7 @@ const xa = {
}
},
q2: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, level 2 indent (basic)",
hasEndMarker: !1,
@@ -1815,7 +1815,7 @@ const xa = {
}
},
q3: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, level 3 indent",
hasEndMarker: !1,
@@ -1844,7 +1844,7 @@ const xa = {
}
},
q4: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, level 4 indent",
hasEndMarker: !1,
@@ -1873,7 +1873,7 @@ const xa = {
}
},
qc: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, centered",
hasEndMarker: !1,
@@ -1902,7 +1902,7 @@ const xa = {
}
},
qr: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, Right Aligned",
hasEndMarker: !1,
@@ -1931,7 +1931,7 @@ const xa = {
}
},
qs: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Character,
description: "Poetry text, Selah",
hasEndMarker: !0,
@@ -1941,21 +1941,21 @@ const xa = {
}
},
qa: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, Acrostic marker/heading",
hasEndMarker: !1,
children: void 0
},
qac: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Character,
description: "Poetry text, Acrostic markup of the first character of a line of acrostic poetry",
hasEndMarker: !0,
children: void 0
},
qm: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, embedded, level 1 indent (if single level)",
hasEndMarker: !1,
@@ -1983,7 +1983,7 @@ const xa = {
}
},
qm1: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, embedded, level 1 indent (if multiple levels)",
hasEndMarker: !1,
@@ -2011,7 +2011,7 @@ const xa = {
}
},
qm2: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, embedded, level 2 indent",
hasEndMarker: !1,
@@ -2039,7 +2039,7 @@ const xa = {
}
},
qm3: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text, embedded, level 3 indent",
hasEndMarker: !1,
@@ -2067,7 +2067,7 @@ const xa = {
}
},
qd: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "A Hebrew musical performance annotation, similar in content to Hebrew descriptive title.",
hasEndMarker: !1,
@@ -2095,14 +2095,14 @@ const xa = {
}
},
b: {
- category: k.Poetry,
+ category: T.Poetry,
type: b.Paragraph,
description: "Poetry text stanza break (e.g. stanza break) (basic)",
hasEndMarker: !1,
children: void 0
},
mt: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "The main title of the book (if single level)",
hasEndMarker: !1,
@@ -2112,7 +2112,7 @@ const xa = {
}
},
mt1: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "The main title of the book (if multiple levels) (basic)",
hasEndMarker: !1,
@@ -2122,7 +2122,7 @@ const xa = {
}
},
mt2: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A secondary title usually occurring before the main title (basic)",
hasEndMarker: !1,
@@ -2132,7 +2132,7 @@ const xa = {
}
},
mt3: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A secondary title occurring after the main title",
hasEndMarker: !1,
@@ -2142,21 +2142,21 @@ const xa = {
}
},
mt4: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A small secondary title sometimes occurring within parentheses",
hasEndMarker: !1,
children: void 0
},
mte: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "The main title of the book repeated at the end of the book, level 1 (if single level)",
hasEndMarker: !1,
children: void 0
},
mte1: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "The main title of the book repeated at the end of the book, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -2165,14 +2165,14 @@ const xa = {
}
},
mte2: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A secondary title occurring before or after the 'ending' main title",
hasEndMarker: !1,
children: void 0
},
ms: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A major section division heading, level 1 (if single level) (basic)",
hasEndMarker: !1,
@@ -2199,7 +2199,7 @@ const xa = {
}
},
ms1: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A major section division heading, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -2226,7 +2226,7 @@ const xa = {
}
},
ms2: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A major section division heading, level 2",
hasEndMarker: !1,
@@ -2253,7 +2253,7 @@ const xa = {
}
},
ms3: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A major section division heading, level 3",
hasEndMarker: !1,
@@ -2263,14 +2263,14 @@ const xa = {
}
},
mr: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A major section division references range heading (basic)",
hasEndMarker: !1,
children: void 0
},
s: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section heading, level 1 (if single level) (basic)",
hasEndMarker: !1,
@@ -2297,7 +2297,7 @@ const xa = {
}
},
s1: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section heading, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -2324,7 +2324,7 @@ const xa = {
}
},
s2: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section heading, level 2 (e.g. Proverbs 22-24)",
hasEndMarker: !1,
@@ -2351,7 +2351,7 @@ const xa = {
}
},
s3: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section heading, level 3 (e.g. Genesis 'The First Day')",
hasEndMarker: !1,
@@ -2378,7 +2378,7 @@ const xa = {
}
},
s4: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section heading, level 4",
hasEndMarker: !1,
@@ -2404,21 +2404,21 @@ const xa = {
}
},
sr: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A section division references range heading",
hasEndMarker: !1,
children: void 0
},
r: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Parallel reference(s) (basic)",
hasEndMarker: !1,
children: void 0
},
sp: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A heading, to identify the speaker (e.g. Job)",
hasEndMarker: !1,
@@ -2445,7 +2445,7 @@ const xa = {
}
},
d: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "A Hebrew text heading, to provide description (e.g. Psalms)",
hasEndMarker: !1,
@@ -2471,42 +2471,42 @@ const xa = {
}
},
sd: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Vertical space used to divide the text into sections, level 1 (if single level)",
hasEndMarker: !1,
children: void 0
},
sd1: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Vertical space used to divide the text into sections, level 1 (if multiple levels)",
hasEndMarker: !1,
children: void 0
},
sd2: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Vertical space used to divide the text into sections, level 2",
hasEndMarker: !1,
children: void 0
},
sd3: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Vertical space used to divide the text into sections, level 3",
hasEndMarker: !1,
children: void 0
},
sd4: {
- category: k.TitlesHeadings,
+ category: T.TitlesHeadings,
type: b.Paragraph,
description: "Vertical space used to divide the text into sections, level 4",
hasEndMarker: !1,
children: void 0
},
lh: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "List header (introductory remark)",
hasEndMarker: !1,
@@ -2533,7 +2533,7 @@ const xa = {
}
},
li: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "A list entry, level 1 (if single level)",
hasEndMarker: !1,
@@ -2561,7 +2561,7 @@ const xa = {
}
},
li1: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "A list entry, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -2589,7 +2589,7 @@ const xa = {
}
},
li2: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "A list entry, level 2",
hasEndMarker: !1,
@@ -2617,7 +2617,7 @@ const xa = {
}
},
li3: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "A list entry, level 3",
hasEndMarker: !1,
@@ -2645,7 +2645,7 @@ const xa = {
}
},
li4: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "A list entry, level 4",
hasEndMarker: !1,
@@ -2673,7 +2673,7 @@ const xa = {
}
},
lf: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "List footer (concluding remark)",
hasEndMarker: !1,
@@ -2700,7 +2700,7 @@ const xa = {
}
},
lim: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "An embedded list entry, level 1 (if single level)",
hasEndMarker: !1,
@@ -2728,7 +2728,7 @@ const xa = {
}
},
lim1: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "An embedded list entry, level 1 (if multiple levels)",
hasEndMarker: !1,
@@ -2756,7 +2756,7 @@ const xa = {
}
},
lim2: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "An embedded list entry, level 2",
hasEndMarker: !1,
@@ -2784,7 +2784,7 @@ const xa = {
}
},
lim3: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "An embedded list item, level 3",
hasEndMarker: !1,
@@ -2812,7 +2812,7 @@ const xa = {
}
},
lim4: {
- category: k.Lists,
+ category: T.Lists,
type: b.Paragraph,
description: "An embedded list entry, level 4",
hasEndMarker: !1,
@@ -2840,63 +2840,63 @@ const xa = {
}
},
litl: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "List entry total text",
hasEndMarker: !0,
children: void 0
},
lik: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry key text",
hasEndMarker: !0,
children: void 0
},
liv: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 1 content (if single value)",
hasEndMarker: !0,
children: void 0
},
liv1: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 1 content (if multiple values)",
hasEndMarker: !0,
children: void 0
},
liv2: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 2 content",
hasEndMarker: !0,
children: void 0
},
liv3: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 3 content",
hasEndMarker: !0,
children: void 0
},
liv4: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 4 content",
hasEndMarker: !0,
children: void 0
},
liv5: {
- category: k.Lists,
+ category: T.Lists,
type: b.Character,
description: "Structured list entry value 5 content",
hasEndMarker: !0,
children: void 0
},
f: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Note,
description: "A Footnote text item (basic)",
hasEndMarker: !0,
@@ -2923,7 +2923,7 @@ const xa = {
}
},
fe: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Note,
description: "An Endnote text item",
hasEndMarker: !0,
@@ -2950,84 +2950,84 @@ const xa = {
}
},
fr: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "The origin reference for the footnote (basic)",
hasEndMarker: !0,
children: void 0
},
ft: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "Footnote text, Protocanon (basic)",
hasEndMarker: !0,
children: void 0
},
fk: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A footnote keyword (basic)",
hasEndMarker: !0,
children: void 0
},
fq: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A footnote scripture quote or alternate rendering (basic)",
hasEndMarker: !0,
children: void 0
},
fqa: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A footnote alternate rendering for a portion of scripture text",
hasEndMarker: !0,
children: void 0
},
fl: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A footnote label text item, for marking or 'labelling' the type or alternate translation being provided in the note.",
hasEndMarker: !0,
children: void 0
},
fw: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A footnote witness list, for distinguishing a list of sigla representing witnesses in critical editions.",
hasEndMarker: !0,
children: void 0
},
fp: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A Footnote additional paragraph marker",
hasEndMarker: !0,
children: void 0
},
fv: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "A verse number within the footnote text",
hasEndMarker: !0,
children: void 0
},
fdc: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "Footnote text, applies to Deuterocanon only",
hasEndMarker: !0,
children: void 0
},
fm: {
- category: k.Footnotes,
+ category: T.Footnotes,
type: b.Character,
description: "An additional footnote marker location for a previous footnote",
hasEndMarker: !0,
children: void 0
},
x: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Note,
description: "A list of cross references (basic)",
hasEndMarker: !0,
@@ -3037,237 +3037,237 @@ const xa = {
}
},
xo: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "The cross reference origin reference (basic)",
hasEndMarker: !0,
children: void 0
},
xop: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "Published cross reference origin reference (origin reference that should appear in the published text)",
hasEndMarker: !0,
children: void 0
},
xt: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "The cross reference target reference(s), protocanon only (basic)",
hasEndMarker: !0,
children: void 0
},
xta: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "Cross reference target references added text",
hasEndMarker: !0,
children: void 0
},
xk: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "A cross reference keyword",
hasEndMarker: !0,
children: void 0
},
xq: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "A cross-reference quotation from the scripture text",
hasEndMarker: !0,
children: void 0
},
xot: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "Cross-reference target reference(s), Old Testament only",
hasEndMarker: !0,
children: void 0
},
xnt: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "Cross-reference target reference(s), New Testament only",
hasEndMarker: !0,
children: void 0
},
xdc: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "Cross-reference target reference(s), Deuterocanon only",
hasEndMarker: !0,
children: void 0
},
rq: {
- category: k.CrossReferences,
+ category: T.CrossReferences,
type: b.Character,
description: "A cross-reference indicating the source text for the preceding quotation.",
hasEndMarker: !0,
children: void 0
},
qt: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For Old Testament quoted text appearing in the New Testament (basic)",
hasEndMarker: !0,
children: void 0
},
nd: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For name of deity (basic)",
hasEndMarker: !0,
children: void 0
},
tl: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For transliterated words",
hasEndMarker: !0,
children: void 0
},
dc: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "Deuterocanonical/LXX additions or insertions in the Protocanonical text",
hasEndMarker: !0,
children: void 0
},
bk: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For the quoted name of a book",
hasEndMarker: !0,
children: void 0
},
sig: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For the signature of the author of an Epistle",
hasEndMarker: !0,
children: void 0
},
pn: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For a proper name",
hasEndMarker: !0,
children: void 0
},
png: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For a geographic proper name",
hasEndMarker: !0,
children: void 0
},
addpn: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For chinese words to be dot underline & underline",
hasEndMarker: !0,
children: void 0
},
wj: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For marking the words of Jesus",
hasEndMarker: !0,
children: void 0
},
k: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For a keyword",
hasEndMarker: !0,
children: void 0
},
sls: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "To represent where the original text is in a secondary language or from an alternate text source",
hasEndMarker: !0,
children: void 0
},
ord: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For the text portion of an ordinal number",
hasEndMarker: !0,
children: void 0
},
add: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Character,
description: "For a translational addition to the text",
hasEndMarker: !0,
children: void 0
},
lit: {
- category: k.SpecialText,
+ category: T.SpecialText,
type: b.Paragraph,
description: "For a comment or note inserted for liturgical use",
hasEndMarker: !1,
children: void 0
},
no: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, use normal text",
hasEndMarker: !0,
children: void 0
},
it: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, use italic text",
hasEndMarker: !0,
children: void 0
},
bd: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, use bold text",
hasEndMarker: !0,
children: void 0
},
bdit: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, use bold + italic text",
hasEndMarker: !0,
children: void 0
},
em: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, use emphasized text style",
hasEndMarker: !0,
children: void 0
},
sc: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, for small capitalization text",
hasEndMarker: !0,
children: void 0
},
sup: {
- category: k.CharacterStyling,
+ category: T.CharacterStyling,
type: b.Character,
description: "A character style, for superscript text. Typically for use in critical edition footnotes.",
hasEndMarker: !0,
children: void 0
},
pb: {
- category: k.Breaks,
+ category: T.Breaks,
type: b.Paragraph,
description: "Page Break used for new reader portions and children's bibles where content is controlled by the page",
hasEndMarker: !1,
children: void 0
}
-}, tn = {
+}, an = {
DivisionMarks: { add: ["v", "c"], remove: [] },
Paragraphs: { add: ["p"], remove: [] },
Poetry: { add: ["q", "q1", "q2", "q3", "q4", "b"], remove: [] },
@@ -3294,14 +3294,14 @@ const xa = {
],
remove: []
}
-}, Vl = {
- p: { children: tn },
- q: { children: tn },
- q1: { children: tn },
- q2: { children: tn },
- q3: { children: tn },
- q4: { children: tn },
- b: { children: tn },
+}, iu = {
+ p: { children: an },
+ q: { children: an },
+ q1: { children: an },
+ q2: { children: an },
+ q3: { children: an },
+ q4: { children: an },
+ b: { children: an },
qm: {
children: {
Paragraphs: { add: ["p"], remove: [] }
@@ -3319,19 +3319,19 @@ const xa = {
// They are defined here as complete entries rather than hand-edited into the
// generated file, which would be silently lost on regeneration.
w: {
- category: k.SpecialFeatures,
+ category: T.SpecialFeatures,
type: b.Character,
description: "A wordlist/glossary/dictionary entry marker for study/analysis purposes",
hasEndMarker: !0
},
rb: {
- category: k.SpecialFeatures,
+ category: T.SpecialFeatures,
type: b.Character,
description: "A ruby glossing marker for study/analysis purposes",
hasEndMarker: !0
},
jmp: {
- category: k.SpecialFeatures,
+ category: T.SpecialFeatures,
type: b.Character,
description: "A hyperlink marker for study/analysis purposes",
hasEndMarker: !0
@@ -3341,14 +3341,14 @@ const xa = {
// resolves falls back to this table, reads `\fig` as an unknown marker, and breaks the figure
// into its own paragraph with the closer stranded as unmatched.
fig: {
- category: k.SpecialFeatures,
+ category: T.SpecialFeatures,
type: b.Character,
description: "Illustration [Columns to span, height, filename, caption text]",
hasEndMarker: !0
}
};
-function nr(e) {
- const t = Object.hasOwn(xa, e) ? xa[e] : void 0, r = Object.hasOwn(Vl, e) ? Vl[e] : void 0;
+function lr(e) {
+ const t = Object.hasOwn(xa, e) ? xa[e] : void 0, r = Object.hasOwn(iu, e) ? iu[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 +3373,16 @@ function nr(e) {
children: n
};
}
-const Sf = "v", Mf = "c", rn = "fig", Wl = "tr", _a = "esb", Ef = "esbe", Fy = /^t[hc]([rc]?)(\d+)(?:-(\d+))?$/, zy = {
+const Uf = "v", Ff = "c", cn = "fig", su = "tr", _a = "esb", zf = "esbe", ou = "periph", au = "alt", cu = /^t[hc]([rc]?)(\d+)(?:-(\d+))?$/, ab = {
"": "start",
c: "center",
r: "end"
};
-function Ky(e) {
+function lu(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 Hl(e) {
+function uu(e) {
const t = e.charCodeAt(0);
return t >= 9 && t <= 13 ? !0 : t === 32 || // SPACE
t === 133 || // NEXT LINE
@@ -3395,24 +3395,24 @@ function Hl(e) {
t === 8287 || // MEDIUM MATHEMATICAL SPACE
t === 8203;
}
-const jy = /[\u200D\u2003\u2002\u0020\u00A0\u202F\u2009\u200A\u3000\u200B\u200C\u2060\u200E\u200F]/;
-function By(e) {
+const cb = /[\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 === Ns && s + 1 < e.length && Hl(e[s + 1]) || (Hl(o) ? (r || (i = t.length, t += o), r = !0) : jy.test(o) && o === n || (t += o, r = !1, i = -1)), (o === `
+ o.charCodeAt(0) < 32 ? (r || (i = t.length, t += " "), r = !0) : !r && o === $s && s + 1 < e.length && uu(e[s + 1]) || (uu(o) ? (r || (i = t.length, t += o), r = !0) : cb.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 Vy(e) {
+function ub(e) {
if (e.length === 0)
return !0;
const t = e[0];
return t === "*" ? !1 : !!(t === "\\" || t === "|" || /[\s\u200B]/.test(t));
}
-function Wy(e, t) {
+function db(e, t) {
let r = t;
for (; r < e.length; ) {
const n = e[r];
@@ -3428,9 +3428,9 @@ function Wy(e, t) {
}
return { name: e.slice(t, r), next: r };
}
-const Hy = /^(?:qt[1-5]?|ts)-[se]$/;
-function oo(e) {
- return Hy.test(e) || _f(e);
+const fb = /^(?:qt[1-5]?|ts)-[se]$/;
+function vc(e) {
+ return fb.test(e) || If(e);
}
function Bo(e, t) {
let r = t;
@@ -3444,7 +3444,7 @@ function Bo(e, t) {
r++;
return { word: i, next: r };
}
-function Gy(e, t, r) {
+function pb(e, t, r) {
const n = [];
let i = 0, s;
const o = (c) => {
@@ -3460,10 +3460,10 @@ function Gy(e, t, r) {
for (; i < e.length; ) {
if (e[i] !== "\\") {
const g = e.indexOf("\\", i), y = g === -1 ? e.length : g;
- a(By(e.slice(i, y))), i = y;
+ a(lb(e.slice(i, y))), i = y;
continue;
}
- const c = i, { name: l, next: u } = Wy(e, i + 1);
+ const c = i, { name: l, next: u } = db(e, i + 1);
if (i = u, l === "") {
o(e.slice(c, i));
continue;
@@ -3480,60 +3480,60 @@ function Gy(e, t, r) {
for (; i < e.length && /[\s\u00A0\u200B]/.test(e[i]); )
i++;
};
- if (l === Sf) {
+ if (l === Uf) {
const { word: g, next: y } = Bo(e, i);
i = y, n.push({ kind: "verse", number: g });
continue;
}
- if (l === Mf) {
+ if (l === Ff) {
const { word: g, next: y } = Bo(e, i);
i = y, s = void 0, n.push({ kind: "chapter", number: g });
continue;
}
const f = l.startsWith("+"), p = f ? l.slice(1) : l, m = t(p)?.type;
- if (m === b.Note || m === void 0 && Me.isValidMarker(l)) {
+ if (m === b.Note || m === void 0 && Ee.isValidMarker(l)) {
const { word: g, next: y } = Bo(e, i);
i = y, s = l, n.push({ kind: "note", marker: l, caller: g || "+" });
continue;
}
- if (m === b.Milestone || m === void 0 && oo(l)) {
- const g = rb(e, c, l, i);
+ if (m === b.Milestone || m === void 0 && vc(l)) {
+ const g = xb(e, c, l, i);
if (g)
n.push(g.token), g.ejectedText && o(g.ejectedText), i = g.next;
else {
- const y = e.indexOf("\\", i), T = y === -1 ? e.length : y;
- o(e.slice(c, T)), i = T;
+ 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 })) : qs(p) ? (d(), qs(p)?.shape === "para" ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f })) : (d(), !(r || s !== void 0) || l === _a || l === Ef ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f }));
+ m === b.Paragraph ? (d(), n.push({ kind: "para", marker: l })) : m === b.Character ? (d(), n.push({ kind: "charOpen", marker: p, isNested: f })) : Ls(p) ? (d(), Ls(p)?.shape === "para" ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f })) : (d(), !(r || s !== void 0) || l === _a || l === zf ? n.push({ kind: "para", marker: l }) : n.push({ kind: "charOpen", marker: p, isNested: f }));
}
return n;
}
-const Gl = {
+const du = {
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 qs(e) {
- return Object.hasOwn(Gl, e) ? Gl[e] : void 0;
+function Ls(e) {
+ return Object.hasOwn(du, e) ? du[e] : void 0;
}
-function Jy(e) {
- return qs(e) !== void 0;
+function hb(e) {
+ return Ls(e) !== void 0;
}
-const Yy = /([-\w]+)\s*=\s*"(.*?)"/g, Xy = /[\s\u200B]*[\n\r][\s\u200B]*/g, Af = {
+const gb = /([-\w]+)\s*=\s*"(.*?)"/g, mb = /[\s\u200B]*[\n\r][\s\u200B]*/g, Kf = {
w: "lemma",
rb: "gloss",
xt: "link-href",
jmp: "link-href"
};
-function ao(e) {
- return Af[e];
+function lo(e) {
+ return Kf[e];
}
-const Qy = /* @__PURE__ */ new Set(["type", "marker", "content"]);
-function Zy(e, t) {
+const yb = /* @__PURE__ */ new Set(["type", "marker", "content"]);
+function bb(e, t) {
let r = 0;
for (const n of t) {
const i = n.index;
@@ -3543,26 +3543,26 @@ function Zy(e, t) {
}
return e.slice(r).trim() === "";
}
-function co(e, t, r = Af[t]) {
- const n = e.replace(Xy, " "), i = /* @__PURE__ */ Object.create(null), s = [...n.matchAll(Yy)];
+function Wi(e, t, r = Kf[t]) {
+ const n = e.replace(mb, " "), i = /* @__PURE__ */ Object.create(null), s = [...n.matchAll(gb)];
if (s.length > 0) {
- if (!Zy(n, s) || s.some((o) => o[2] === ""))
+ if (!bb(n, s) || s.some((o) => o[2] === ""))
return;
for (const [, o, a] of s)
- Qy.has(o) || (i[o] = a);
+ yb.has(o) || (i[o] = a);
return Object.keys(i).length > 0 ? i : void 0;
}
if (n.trim() && r)
return { [r]: n };
}
-function lo(e) {
+function uo(e) {
return e.endsWith("-e") ? "eid" : e.startsWith("qt") ? "who" : "sid";
}
-function eb(e) {
- const t = xr(e)[0], r = typeof t == "object" && "content" in t ? t.content : void 0;
+function kb(e) {
+ const t = vr(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 tb(e, t, r) {
+function Tb(e, t, r) {
let n = t;
for (; n < e.length && /[\s\u00A0\u200B]/.test(e[n]); )
n++;
@@ -3571,17 +3571,17 @@ function tb(e, t, r) {
const i = e.indexOf("\\", n);
if (i === -1 || e.slice(i, i + 2) !== "\\*")
return;
- const s = co(e.slice(n + 1, i), r, lo(r));
+ const s = Wi(e.slice(n + 1, i), r, uo(r));
if (s)
return { attributes: s, next: i + 2 };
}
-function rb(e, t, r, n) {
+function xb(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 = co(s.slice(o + 1), r, lo(r)), !a && s.slice(o + 1).trim() !== "")) {
+ if (o >= 0 && (a = Wi(s.slice(o + 1), r, uo(r)), !a && s.slice(o + 1).trim() !== "")) {
const u = s.slice(0, o);
return {
token: { kind: "milestone", marker: r },
@@ -3596,7 +3596,7 @@ function rb(e, t, r, n) {
next: i,
ejectedText: c.replace(/^[ \u00A0]/, "")
};
- const l = tb(e, i + 2, r);
+ const l = Tb(e, i + 2, r);
return l ? {
token: {
kind: "milestone",
@@ -3606,258 +3606,301 @@ function rb(e, t, r, n) {
next: l.next
} : { token: { kind: "milestone", marker: r, attributes: a }, next: i + 2 };
}
-function lr(e) {
+function sr(e) {
return e.replaceAll(`
-`, " ").replaceAll("~", w);
+`, " ").replaceAll("~", L);
}
-function nn(e) {
+function $r(e) {
return e.content || (e.content = []), e.content;
}
-function xr(e, t) {
+function vr(e, t) {
const r = [], n = t?.isNoteContext ?? !1;
let i, s;
const o = [];
- let a = 0, c, l, u;
- const d = () => u ? nn(u) : r;
- let f = !1;
- const p = () => {
+ let a = 0, c, l, u, d;
+ const f = () => u ? $r(u) : d ? $r(d) : r;
+ let p = !1;
+ const m = () => {
if (s)
- return o.length > a ? nn(o[o.length - 1].object) : nn(s);
+ return o.length > a ? $r(o[o.length - 1].object) : $r(s);
if (o.length > 0)
- return nn(o[o.length - 1].object);
+ return $r(o[o.length - 1].object);
if (!i) {
- if (f && !n)
- return d();
- i = { type: "para", marker: rr, content: [] }, d().push(i);
- }
- return nn(i);
- }, m = (Q) => {
- const U = p();
- typeof Q == "string" && typeof U[U.length - 1] == "string" ? U[U.length - 1] = U[U.length - 1] + Q : U.push(Q);
- }, g = (Q) => {
- for (let U = Q; U < o.length; U += 1) {
- const Z = o[U].object;
- Z.closed = "false";
- }
- }, y = () => {
- g(0), o.length = 0;
- }, T = (Q) => {
- s && (o.length > a && (g(a), o.length = a), a = 0, Q || (s.closed = "false"), s = void 0);
+ if (p && !n)
+ return f();
+ i = { type: "para", marker: cr, content: [] }, f().push(i);
+ }
+ return $r(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 J = o[E].object;
+ J.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 = () => {
c = void 0, l = void 0;
- }, v = (Q) => {
- u && (Q || (u.closed = "false"), u = void 0);
+ }, P = (te, E, J) => {
+ k();
+ const [, le, W, xe] = J, pt = {
+ type: "table:cell",
+ marker: xe ? E.slice(0, E.indexOf("-")) : E,
+ align: ab[le],
+ content: []
+ };
+ xe && (pt.colspan = String(Number(xe) + 1 - Number(W))), $r(te).push(pt), i = pt;
+ }, A = (te) => {
+ u && (te || (u.closed = "false"), u = void 0);
+ }, B = () => {
+ d = void 0;
};
- let E, A = "", x;
- const F = () => {
- A && m(lr(A)), A = "";
- }, L = (Q = !1) => {
- E?.type === "sidebar" ? A = "" : Q && A.endsWith(`
-`) && (A = A.slice(0, -1)), E = void 0, F();
- }, G = () => {
- if (!x)
+ let M, w = "", $;
+ const Y = () => {
+ w && g(sr(w)), w = "";
+ }, Q = (te = !1) => {
+ M?.type === "sidebar" ? w = "" : te && w.endsWith(`
+`) && (w = w.slice(0, -1)), M = void 0, Y();
+ }, Me = () => {
+ if (!$)
return;
- const Q = { type: "char", marker: x.marker, content: [] };
- x.value && (Q.content = [lr(x.value)]), p().push(Q), o.push({ object: Q }), x = void 0;
- }, V = (Q, U) => {
- f = !1, S(), y(), T(!1), i = { type: "para", marker: Q, content: [] }, U && (i.content = [lr(U)]), d().push(i);
- }, ae = () => {
- x && (V(x.marker, x.value), x = void 0);
+ const te = { type: "char", marker: $.marker, content: [] };
+ $.value && (te.content = [sr($.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 = [sr(E)]), f().push(i);
+ }, Oe = () => {
+ $ && (re($.marker, $.value), $ = void 0);
};
- let ce;
- const ie = () => {
- if (ce) {
- if (ce.shape === "para")
- V(rn, ce.value);
+ let be;
+ const er = (te) => {
+ if (!be)
+ return;
+ let { value: E } = be;
+ be = void 0, te && E.endsWith(`
+`) && (E = E.slice(0, -1));
+ const J = E.indexOf("|"), le = J >= 0 ? Wi(E.slice(J + 1), ou) : void 0, W = J >= 0 ? E.slice(0, J) : E, xe = J >= 0 && (!le || !!W && !!le[au]), pt = xe ? void 0 : le, zt = xe ? E : W, ht = {
+ type: "periph",
+ ...zt ? { [au]: sr(zt) } : {},
+ ...pt
+ };
+ ht.content = [], f().push(ht), d = ht, i = void 0;
+ };
+ let we;
+ const en = () => {
+ if (we) {
+ if (we.shape === "para")
+ re(cn, we.value);
else {
- const Q = { type: "char", marker: rn, content: [] };
- ce.value && (Q.content = [lr(ce.value)]), p().push(Q), o.push({ object: Q });
+ const te = { type: "char", marker: cn, content: [] };
+ we.value && (te.content = [sr(we.value)]), m().push(te), o.push({ object: te });
}
- ce = void 0;
- }
- }, ve = Gy(e, t?.getMarker ?? nr, n);
- for (let Q = 0; Q < ve.length; Q++) {
- const U = ve[Q];
- if (x) {
- if (U.kind === "text") {
- x.value += U.text;
+ we = void 0;
+ }
+ }, gr = pb(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;
continue;
}
- if (x.shape === "char" && U.kind === "end" && U.marker.replace(/^\+/, "") === x.marker) {
- if (x.value.trim() === "") {
- p().push({ type: "char", marker: x.marker, content: [] }), x = void 0, L();
+ if ($.shape === "char" && E.kind === "end" && E.marker.replace(/^\+/, "") === $.marker) {
+ if ($.value.trim() === "") {
+ m().push({ type: "char", marker: $.marker, content: [] }), $ = void 0, Q();
continue;
}
- Object.assign(x.target, {
- [x.attrName]: lr(x.value.trim())
+ Object.assign($.target, {
+ [$.attrName]: sr($.value.trim())
});
- const Z = x.marker;
- if (x = void 0, Z === "ca") {
- const Ee = ve[Q + 1];
- Ee?.kind === "text" && /^[\s\u200B]*$/.test(Ee.text) && Q++;
+ const J = $.marker;
+ if ($ = void 0, J === "ca") {
+ const le = gr[te + 1];
+ le?.kind === "text" && /^[\s\u200B]*$/.test(le.text) && te++;
}
continue;
}
- if (x.shape === "para" && (U.kind === "para" || U.kind === "chapter")) {
- const Z = x.value.replace(/[\s\u200B]+$/, "");
- Z === "" ? (V(x.marker), x = void 0) : (Object.assign(x.target, { [x.attrName]: lr(Z) }), x = void 0);
+ if ($.shape === "para" && (E.kind === "para" || E.kind === "chapter")) {
+ const J = $.value.replace(/[\s\u200B]+$/, "");
+ J === "" ? (re($.marker), $ = void 0) : (Object.assign($.target, { [$.attrName]: sr(J) }), $ = void 0);
} else {
- E = void 0, (U.kind === "para" || U.kind === "chapter") && x.value.endsWith(`
-`) && (x.value = x.value.slice(0, -1)), x.shape === "para" ? ae() : G(), Q--;
+ M = void 0, (E.kind === "para" || E.kind === "chapter") && $.value.endsWith(`
+`) && ($.value = $.value.slice(0, -1)), $.shape === "para" ? Oe() : Me(), te--;
+ continue;
+ }
+ }
+ if (be) {
+ if (E.kind === "text" || E.kind === "optbreak") {
+ be.value += E.kind === "text" ? E.text : "//";
continue;
}
+ er(E.kind === "para" || E.kind === "chapter"), te--;
+ continue;
}
- if (ce) {
- if (U.kind === "text" || U.kind === "optbreak") {
- ce.value += U.kind === "text" ? U.text : "//";
+ if (we) {
+ if (E.kind === "text" || E.kind === "optbreak") {
+ we.value += E.kind === "text" ? E.text : "//";
continue;
}
- if (U.kind === "end" && U.marker.replace(/^\+/, "") === rn) {
- const Z = ce.value.indexOf("|"), Ee = Z >= 0 ? co(ce.value.slice(Z + 1), rn) : void 0;
- if (Ee) {
- const we = {};
- for (const [_t, Jr] of Object.entries(Ee))
- we[_t === "src" ? "file" : _t] = Jr;
- const Yt = {
+ if (E.kind === "end" && E.marker.replace(/^\+/, "") === cn) {
+ const J = we.value.indexOf("|"), le = J >= 0 ? Wi(we.value.slice(J + 1), cn) : void 0;
+ if (le) {
+ const W = {};
+ for (const [zt, ht] of Object.entries(le))
+ W[zt === "src" ? "file" : zt] = ht;
+ const xe = {
type: "figure",
- marker: rn,
- ...we
- }, ee = ce.value.slice(0, Z);
- ee && (Yt.content = [lr(ee)]), m(Yt), ce = void 0;
+ marker: cn,
+ ...W
+ }, pt = we.value.slice(0, J);
+ pt && (xe.content = [sr(pt)]), g(xe), we = void 0;
continue;
}
}
- ie(), Q--;
+ en(), te--;
continue;
}
- if (E)
- if (U.kind === "text") {
- if (U.text.includes(`
-`) && /^[\s\u200B]*$/.test(U.text)) {
- A += U.text;
+ if (M)
+ if (E.kind === "text") {
+ if (E.text.includes(`
+`) && /^[\s\u200B]*$/.test(E.text)) {
+ w += E.text;
continue;
}
- L();
- } else if (U.kind === "charOpen" || U.kind === "para") {
- const Z = U.kind === "para" || !U.isNested ? qs(U.marker) : void 0;
- if (Z && Z.targetTypes.includes(E.type)) {
- A = "", x = {
- target: E,
- attrName: Z.attrName,
- marker: U.marker,
- shape: Z.shape,
+ Q();
+ } else if (E.kind === "charOpen" || E.kind === "para") {
+ const J = E.kind === "para" || !E.isNested ? Ls(E.marker) : void 0;
+ if (J && J.targetTypes.includes(M.type)) {
+ w = "", $ = {
+ target: M,
+ attrName: J.attrName,
+ marker: E.marker,
+ shape: J.shape,
value: ""
};
continue;
}
- L(U.kind === "para");
+ Q(E.kind === "para");
} else
- L(U.kind === "chapter");
- if (!s && !n && (U.kind === "charOpen" && !U.isNested && U.marker === rn || U.kind === "para" && U.marker === rn)) {
- y(), ce = { shape: U.kind === "charOpen" ? "char" : "para", value: "" };
+ Q(E.kind === "chapter");
+ if (!s && !n && (E.kind === "charOpen" && !E.isNested && E.marker === cn || E.kind === "para" && E.marker === cn)) {
+ k(), we = { shape: E.kind === "charOpen" ? "char" : "para", value: "" };
continue;
}
- switch (U.kind) {
+ switch (E.kind) {
case "text": {
- let Z = U.text;
- if (!s && Z.endsWith(`
+ let J = E.text;
+ if (!s && J.endsWith(`
`)) {
- const Ee = ve[Q + 1];
- (Ee === void 0 || Ee.kind === "para" || Ee.kind === "chapter") && (Z = Z.slice(0, -1));
+ const le = gr[te + 1];
+ (le === void 0 || le.kind === "para" || le.kind === "chapter") && (J = J.slice(0, -1));
}
- Z && m(lr(Z));
+ J && g(sr(J));
break;
}
case "para": {
- const Z = !s && !n;
- if (Z && U.marker === Wl) {
- y(), c || (c = { type: "table", content: [] }, d().push(c)), l = { type: "table:row", marker: Wl, content: [] }, nn(c).push(l), i = l, f = !1;
+ const J = !s && !n;
+ if (J && E.marker === su) {
+ k(), c || (c = { type: "table", content: [] }, f().push(c)), l = { type: "table:row", marker: su, content: [] }, $r(c).push(l), i = l, p = !1;
break;
}
- if (Z && l) {
- const Ee = Fy.exec(U.marker);
- if (Ee && Ky(Ee)) {
- y();
- const [, we, Yt, ee] = Ee, _t = {
- type: "table:cell",
- marker: ee ? U.marker.slice(0, U.marker.indexOf("-")) : U.marker,
- align: zy[we],
- content: []
- };
- ee && (_t.colspan = String(Number(ee) + 1 - Number(Yt))), nn(l).push(_t), i = _t;
+ if (J && l) {
+ const le = cu.exec(E.marker);
+ if (le && lu(le)) {
+ P(l, E.marker, le);
break;
}
}
- if (S(), !n && U.marker === _a) {
- y(), T(!1), v(!1), u = { type: "sidebar", marker: _a, content: [] }, r.push(u), i = void 0, E = u, f = !1;
+ if (S(), !n && E.marker === _a) {
+ k(), _(!1), A(!1);
+ const le = {
+ type: "sidebar",
+ marker: _a,
+ content: []
+ };
+ f().push(le), u = le, i = void 0, M = u, p = !1;
+ break;
+ }
+ if (E.marker === zf && u) {
+ k(), _(!1), A(!0), i = void 0;
break;
}
- if (U.marker === Ef && u) {
- y(), T(!1), v(!0), i = void 0;
+ if (!n && E.marker === ou) {
+ k(), _(!1), A(!1), B(), be = { value: "" }, i = void 0, p = !1;
break;
}
- V(U.marker);
+ re(E.marker);
break;
}
case "verse": {
- T(!1);
- const Z = { type: "verse", marker: Sf, number: U.number };
- m(Z), E = Z;
+ _(!1);
+ const J = { type: "verse", marker: Uf, number: E.number };
+ g(J), M = J;
break;
}
case "chapter": {
- y(), T(!1), S(), v(!1), i = void 0;
- const Z = {
+ k(), _(!1), S(), A(!1), B(), i = void 0;
+ const J = {
type: "chapter",
- marker: Mf,
- number: U.number
+ marker: Ff,
+ number: E.number
};
- r.push(Z), E = Z, f = !0;
+ r.push(J), M = J, p = !0;
break;
}
case "note": {
- T(!1);
- const Z = p();
- s = { type: "note", marker: U.marker, caller: U.caller, content: [] }, a = o.length, Z.push(s), E = s;
+ _(!1);
+ const J = m();
+ s = { type: "note", marker: E.marker, caller: E.caller, content: [] }, a = o.length, J.push(s), M = s;
break;
}
case "charOpen": {
- if (!U.isNested) {
- const we = s ? a : 0;
- g(we), o.length = we;
+ if (!s && !n && l && !E.isNested) {
+ const W = cu.exec(E.marker);
+ if (W && lu(W)) {
+ P(l, E.marker, W);
+ break;
+ }
}
- const Z = p(), Ee = { type: "char", marker: U.marker, content: [] };
- Z.push(Ee), o.push({ object: Ee });
+ if (!E.isNested) {
+ const W = s ? a : 0;
+ y(W), o.length = W;
+ }
+ const J = m(), le = { type: "char", marker: E.marker, content: [] };
+ J.push(le), o.push({ object: le });
break;
}
case "end": {
- const Z = U.marker.replace(/^\+/, ""), Ee = s ? a : 0, we = o.findLastIndex((Yt, ee) => ee >= Ee && Yt.object.marker === Z);
- we >= 0 ? (nb(o[we].object), g(we + 1), o.length = we) : s && s.marker === Z ? T(!0) : (g(Ee), o.length = Ee, m({ type: "unmatched", marker: `${U.marker}*` }));
+ const J = E.marker.replace(/^\+/, ""), le = s ? a : 0, W = o.findLastIndex((xe, pt) => pt >= le && xe.object.marker === J);
+ W >= 0 ? (_b(o[W].object), y(W + 1), o.length = W) : s && s.marker === J ? _(!0) : (y(le), o.length = le, g({ type: "unmatched", marker: `${E.marker}*` }));
break;
}
case "milestone":
- m({ type: "ms", marker: U.marker, ...U.attributes });
+ g({ type: "ms", marker: E.marker, ...E.attributes });
break;
case "optbreak":
- m({ type: "optbreak" });
+ g({ type: "optbreak" });
break;
}
}
- if (ce && ie(), x)
- if (x.shape === "para") {
- const Q = x.value.replace(/[\s\u200B]+$/, "");
- Q === "" ? V(x.marker) : Object.assign(x.target, { [x.attrName]: lr(Q) }), x = void 0;
+ if (be && er(!0), we && en(), $)
+ if ($.shape === "para") {
+ const te = $.value.replace(/[\s\u200B]+$/, "");
+ te === "" ? re($.marker) : Object.assign($.target, { [$.attrName]: sr(te) }), $ = void 0;
} else
- x.value.endsWith(`
-`) && (x.value = x.value.slice(0, -1)), G();
- y(), T(!1), v(!1);
- const Pe = (Q) => {
- for (const U of Q)
- typeof U != "string" && U.content && (Pe(U.content), U.content.length === 0 && delete U.content);
+ $.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);
};
- return Pe(r), r;
+ return vt(r), r;
}
-function nb(e) {
+function _b(e) {
const t = e.content;
if (!t || t.length === 0)
return;
@@ -3871,22 +3914,22 @@ function nb(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 = co(i.slice(s + 1), e.marker ?? "");
+ const i = t.slice(n).map((c) => typeof c == "string" ? c : "//").join(""), s = i.indexOf("|"), o = Wi(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 gn = ro("cid", {
+const bn = oo("cid", {
parse: (e) => typeof e == "string" ? e : void 0
-}), Fr = ro("segment", {
+}), Br = oo("segment", {
parse: (e) => typeof e == "string" ? e : void 0
-}), oe = ro("textType", {
+}), oe = oo("textType", {
parse: (e) => typeof e == "string" ? e : void 0
-}), sr = "marker-trailing-space", Pf = 1, ib = "marker", kc = ro("isGutterMarker", {
+}), fr = "marker-trailing-space", jf = 1, Cb = "marker", Mc = oo("isGutterMarker", {
parse: (e) => e === !0
});
-class _r extends ns {
+class Mr extends ls {
__textType;
__text;
constructor(t = "", r = "", n) {
@@ -3897,18 +3940,18 @@ class _r extends ns {
}
static clone(t) {
const { __textType: r, __text: n, __key: i } = t;
- return new _r(r, n, i);
+ return new Mr(r, n, i);
}
static importDOM() {
return {
- span: (t) => cb(t) ? {
- conversion: sb,
+ span: (t) => Eb(t) ? {
+ conversion: Sb,
priority: 1
} : null
};
}
static importJSON(t) {
- return mr().updateFromJSON(t);
+ return Tr().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setTextType(t.textType).setTextContent(t.text);
@@ -3940,7 +3983,7 @@ class _r extends ns {
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(r) && r.setAttribute("data-text-type", this.getTextType()), { element: r };
+ return r && En(r) && r.setAttribute("data-text-type", this.getTextType()), { element: r };
}
/**
* No decorator payload: the glyph bytes are rendered by {@link createDOM} instead.
@@ -3976,7 +4019,7 @@ class _r extends ns {
type: this.getType(),
textType: this.getTextType(),
text: this.getTextContent(),
- version: Pf
+ version: jf
};
}
// Mutation
@@ -3984,30 +4027,30 @@ class _r extends ns {
return !1;
}
}
-function sb(e) {
+function Sb(e) {
const t = e.getAttribute("data-text-type") ?? "", r = e.textContent ?? "";
- return { node: mr(t, r) };
+ return { node: Tr(t, r) };
}
-function mr(e, t) {
- return Ke(new _r(e, t));
+function Tr(e, t) {
+ return je(new Mr(e, t));
}
-function ob(e) {
- return mt(mr(ib, e), kc, !0);
+function vb(e) {
+ return yt(Tr(Cb, e), Mc, !0);
}
-function ab(e) {
- return Wt(e) && re(e, kc);
+function Mb(e) {
+ return Jt(e) && ne(e, Mc);
}
-function cb(e) {
+function Eb(e) {
return e?.tagName === "span";
}
-function Wt(e) {
- return e instanceof _r;
+function Jt(e) {
+ return e instanceof Mr;
}
-function Nf(e) {
- return e?.type === _r.getType();
+function Bf(e) {
+ return e?.type === Mr.getType();
}
-const Lr = "internal-comment", lb = [Lr], Of = Object.freeze({}), Ca = Object.freeze({}), va = Object.freeze({}), Sa = Object.freeze({}), Ma = Object.freeze({}), ub = 1, Dn = /* @__PURE__ */ new Map(), Ti = /* @__PURE__ */ new Map(), Un = /* @__PURE__ */ new Map(), Fn = /* @__PURE__ */ new Map();
-class Ze extends Jt {
+const zr = "internal-comment", Ab = [zr], Vf = Object.freeze({}), Ca = Object.freeze({}), Sa = Object.freeze({}), va = Object.freeze({}), Ma = Object.freeze({}), Pb = 1, Un = /* @__PURE__ */ new Map(), Si = /* @__PURE__ */ new Map(), Fn = /* @__PURE__ */ new Map(), zn = /* @__PURE__ */ new Map();
+class Ze extends Qt {
__typedIDs;
__typedOnClicks;
__typedOnRemoves;
@@ -4017,39 +4060,39 @@ class Ze extends Jt {
__domOnMouseEnterListener;
__domOnMouseLeaveListener;
__suppressOnRemoveCallbacks;
- constructor(t = Of, r, n, i, s, o) {
- super(o), this.__typedIDs = ks(t), this.__typedOnClicks = Vo(r), this.__typedOnRemoves = Wo(n), this.__typedOnMouseEnters = Ho(i), this.__typedOnMouseLeaves = Go(s), this.pruneTypedOnClicks(), this.pruneTypedOnRemoves(), this.pruneTypedOnMouseEnters(), this.pruneTypedOnMouseLeaves(), this.syncTypedOnClicksToRegistry(), this.syncTypedOnRemovesToRegistry(), this.syncTypedOnMouseEntersToRegistry(), this.syncTypedOnMouseLeavesToRegistry();
+ constructor(t = Vf, r, n, i, s, o) {
+ super(o), this.__typedIDs = Ss(t), this.__typedOnClicks = Vo(r), this.__typedOnRemoves = Wo(n), this.__typedOnMouseEnters = Ho(i), this.__typedOnMouseLeaves = Go(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 = ks(t.__typedIDs), n = Vo(t.__typedOnClicks), i = Wo(t.__typedOnRemoves), s = Ho(t.__typedOnMouseEnters), o = Go(t.__typedOnMouseLeaves);
+ const r = Ss(t.__typedIDs), n = Vo(t.__typedOnClicks), i = Wo(t.__typedOnRemoves), s = Ho(t.__typedOnMouseEnters), o = Go(t.__typedOnMouseLeaves);
return new Ze(r, n, i, s, o, t.__key);
}
static isReservedType(t) {
- return lb.includes(t);
+ return Ab.includes(t);
}
static importDOM() {
return null;
}
static importJSON(t) {
- return zi().updateFromJSON(t);
+ return Hi().updateFromJSON(t);
}
exportJSON() {
return {
...super.exportJSON(),
type: this.getType(),
typedIDs: this.getTypedIDs(),
- version: ub
+ version: Pb
};
}
createDOM(t, r) {
const n = document.createElement("mark");
for (const [a, c] of Object.entries(this.__typedIDs)) {
- Ln(n, sn(t.theme.typedMark, a)), c.length > 1 && Ln(n, sn(t.theme.typedMarkOverlap, a));
+ Dn(n, ln(t.theme.typedMark, a)), c.length > 1 && Dn(n, ln(t.theme.typedMarkOverlap, a));
for (const l of c)
- Ln(n, sn("annotationId", l));
+ Dn(n, ln("annotationId", l));
}
const i = this.getOrCreateDOMClickListener(r);
n.addEventListener("click", i);
@@ -4064,13 +4107,13 @@ class Ze extends Jt {
...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 = sn(n.theme.typedMark, s), d = sn(n.theme.typedMarkOverlap, s);
- c !== l && (c === 0 ? l === 1 && Ln(r, u) : l === 0 && Ko(r, u), c === 1 ? l === 2 && Ln(r, d) : l === 1 && Ko(r, d));
+ 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 && Dn(r, u) : l === 0 && Ko(r, u), c === 1 ? l === 2 && Dn(r, d) : l === 1 && Ko(r, d));
const f = new Set(o), p = new Set(a);
for (const m of o)
- p.has(m) || Ko(r, sn("annotationId", m));
+ p.has(m) || Ko(r, ln("annotationId", m));
for (const m of a)
- f.has(m) || Ln(r, sn("annotationId", m));
+ f.has(m) || Dn(r, ln("annotationId", m));
}
return !1;
}
@@ -4088,13 +4131,13 @@ class Ze extends Jt {
}
getTypedIDs() {
const t = this.getLatest();
- return _e(t) ? t.__typedIDs : {};
+ return Ce(t) ? t.__typedIDs : {};
}
setTypedIDs(t) {
- const r = this.getWritable(), n = ks(r.__typedIDs);
- r.__typedIDs = ks(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 = 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 i = r.mergeWithAdjacentTypedMarks();
- return i.hasNoIDsForEveryType() && i.getParent() !== null && Rs(i), i;
+ return i.hasNoIDsForEveryType() && i.getParent() !== null && Ds(i), i;
}
setTypedOnClicks(t) {
const r = this.getWritable();
@@ -4102,7 +4145,7 @@ class Ze extends Jt {
}
getTypedOnClicks() {
const t = this.getLatest();
- return _e(t) ? Dn.get(t.getKey()) ?? {} : {};
+ return Ce(t) ? Un.get(t.getKey()) ?? {} : {};
}
setTypedOnRemoves(t) {
const r = this.getWritable();
@@ -4110,7 +4153,7 @@ class Ze extends Jt {
}
getTypedOnRemoves() {
const t = this.getLatest();
- return _e(t) ? Ti.get(t.getKey()) ?? {} : {};
+ return Ce(t) ? Si.get(t.getKey()) ?? {} : {};
}
setTypedOnMouseEnters(t) {
const r = this.getWritable();
@@ -4118,7 +4161,7 @@ class Ze extends Jt {
}
getTypedOnMouseEnters() {
const t = this.getLatest();
- return _e(t) ? Un.get(t.getKey()) ?? {} : {};
+ return Ce(t) ? Fn.get(t.getKey()) ?? {} : {};
}
setTypedOnMouseLeaves(t) {
const r = this.getWritable();
@@ -4126,11 +4169,11 @@ class Ze extends Jt {
}
getTypedOnMouseLeaves() {
const t = this.getLatest();
- return _e(t) ? Fn.get(t.getKey()) ?? {} : {};
+ return Ce(t) ? zn.get(t.getKey()) ?? {} : {};
}
addID(t, r, n, i, s, o) {
const a = this.getWritable();
- if (!_e(a))
+ if (!Ce(a))
return;
Ve(t), Ve(r);
let c = a.__typedIDs[t];
@@ -4144,7 +4187,7 @@ class Ze extends Jt {
}
deleteID(t, r) {
const n = this.getWritable();
- if (!_e(n))
+ if (!Ce(n))
return;
const i = n.__typedIDs[t];
if (!i || i.length === 0)
@@ -4156,13 +4199,13 @@ class Ze extends Jt {
}
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 && Rs(s);
+ s.hasNoIDsForEveryType() && s.getParent() !== null && Ds(s);
}
hasNoIDsForEveryType() {
return Object.values(this.getTypedIDs()).every((t) => t === void 0 || t.length === 0);
}
insertNewAfter(t, r = !0) {
- const n = zi(this.__typedIDs, this.getTypedOnClicks());
+ const n = Hi(this.__typedIDs, this.getTypedOnClicks());
return this.insertAfter(n, r), n;
}
canInsertTextBefore() {
@@ -4188,7 +4231,7 @@ class Ze extends Jt {
}
remove(t) {
const r = this.getWritable(), n = this.getTypedIDs();
- r.__suppressOnRemoveCallbacks ? r.__suppressOnRemoveCallbacks = void 0 : r.dispatchOnRemoveForTypedIDs(n, "destroyed"), Dn.delete(r.getKey()), Ti.delete(r.getKey()), Un.delete(r.getKey()), Fn.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"), Un.delete(r.getKey()), Si.delete(r.getKey()), Fn.delete(r.getKey()), zn.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) => {
@@ -4196,7 +4239,7 @@ class Ze extends Jt {
}), this.__domOnClickListener;
}
handleDOMClick(t, r) {
- const n = Dn.get(this.getKey());
+ const n = Un.get(this.getKey());
if (!n)
return;
const i = [];
@@ -4215,7 +4258,7 @@ class Ze extends Jt {
}), this.__domOnMouseEnterListener;
}
handleDOMMouseEnter(t, r) {
- const n = Un.get(this.getKey());
+ const n = Fn.get(this.getKey());
if (!n)
return;
const i = [];
@@ -4234,7 +4277,7 @@ class Ze extends Jt {
}), this.__domOnMouseLeaveListener;
}
handleDOMMouseLeave(t, r) {
- const n = Fn.get(this.getKey());
+ const n = zn.get(this.getKey());
if (!n)
return;
const i = [];
@@ -4249,17 +4292,17 @@ class Ze extends Jt {
}
ensureOnClickMapMutable() {
if (this.__typedOnClicks === void 0 || this.__typedOnClicks === Ca) {
- const t = Dn.get(this.getKey());
+ const t = Un.get(this.getKey());
this.__typedOnClicks = t ?? {};
}
return this.__typedOnClicks;
}
syncTypedOnClicksToRegistry() {
if (!this.__typedOnClicks || Object.keys(this.__typedOnClicks).length === 0) {
- Dn.delete(this.getKey()), this.__typedOnClicks && Object.keys(this.__typedOnClicks).length === 0 && (this.__typedOnClicks = void 0);
+ Un.delete(this.getKey()), this.__typedOnClicks && Object.keys(this.__typedOnClicks).length === 0 && (this.__typedOnClicks = void 0);
return;
}
- Dn.set(this.getKey(), this.__typedOnClicks);
+ Un.set(this.getKey(), this.__typedOnClicks);
}
setOnClickFor(t, r, n) {
Ve(t), Ve(r);
@@ -4272,14 +4315,14 @@ class Ze extends Jt {
const n = this.__typedOnClicks[t];
if (!n)
return;
- const i = wr(n, r);
+ const i = Ir(n, r);
if (Object.keys(i).length > 0)
this.__typedOnClicks = {
...this.__typedOnClicks,
[t]: i
};
else {
- const o = wr(this.__typedOnClicks, t);
+ const o = Ir(this.__typedOnClicks, t);
this.__typedOnClicks = Object.keys(o).length > 0 ? o : void 0;
}
this.syncTypedOnClicksToRegistry();
@@ -4302,18 +4345,18 @@ class Ze extends Jt {
this.__typedOnClicks = Object.keys(t).length > 0 ? t : void 0, this.syncTypedOnClicksToRegistry();
}
ensureOnRemoveMapMutable() {
- if (this.__typedOnRemoves === void 0 || this.__typedOnRemoves === va) {
- const t = Ti.get(this.getKey());
+ if (this.__typedOnRemoves === void 0 || this.__typedOnRemoves === Sa) {
+ const t = Si.get(this.getKey());
this.__typedOnRemoves = t ?? {};
}
return this.__typedOnRemoves;
}
syncTypedOnRemovesToRegistry() {
if (!this.__typedOnRemoves || Object.keys(this.__typedOnRemoves).length === 0) {
- Ti.delete(this.getKey()), this.__typedOnRemoves && Object.keys(this.__typedOnRemoves).length === 0 && (this.__typedOnRemoves = void 0);
+ Si.delete(this.getKey()), this.__typedOnRemoves && Object.keys(this.__typedOnRemoves).length === 0 && (this.__typedOnRemoves = void 0);
return;
}
- Ti.set(this.getKey(), this.__typedOnRemoves);
+ Si.set(this.getKey(), this.__typedOnRemoves);
}
setOnRemoveFor(t, r, n) {
Ve(t), Ve(r);
@@ -4326,20 +4369,20 @@ class Ze extends Jt {
const n = this.__typedOnRemoves[t];
if (!n)
return;
- const i = wr(n, r);
+ const i = Ir(n, r);
if (Object.keys(i).length > 0)
this.__typedOnRemoves = {
...this.__typedOnRemoves,
[t]: i
};
else {
- const o = wr(this.__typedOnRemoves, t);
+ const o = Ir(this.__typedOnRemoves, t);
this.__typedOnRemoves = Object.keys(o).length > 0 ? o : void 0;
}
this.syncTypedOnRemovesToRegistry();
}
pruneTypedOnRemoves() {
- if (!this.__typedOnRemoves || this.__typedOnRemoves === va) {
+ if (!this.__typedOnRemoves || this.__typedOnRemoves === Sa) {
this.__typedOnRemoves = void 0, this.syncTypedOnRemovesToRegistry();
return;
}
@@ -4356,18 +4399,18 @@ class Ze extends Jt {
this.__typedOnRemoves = Object.keys(t).length > 0 ? t : void 0, this.syncTypedOnRemovesToRegistry();
}
ensureOnMouseEnterMapMutable() {
- if (this.__typedOnMouseEnters === void 0 || this.__typedOnMouseEnters === Sa) {
- const t = Un.get(this.getKey());
+ if (this.__typedOnMouseEnters === void 0 || this.__typedOnMouseEnters === va) {
+ const t = Fn.get(this.getKey());
this.__typedOnMouseEnters = t ?? {};
}
return this.__typedOnMouseEnters;
}
syncTypedOnMouseEntersToRegistry() {
if (!this.__typedOnMouseEnters || Object.keys(this.__typedOnMouseEnters).length === 0) {
- Un.delete(this.getKey()), this.__typedOnMouseEnters && Object.keys(this.__typedOnMouseEnters).length === 0 && (this.__typedOnMouseEnters = void 0);
+ Fn.delete(this.getKey()), this.__typedOnMouseEnters && Object.keys(this.__typedOnMouseEnters).length === 0 && (this.__typedOnMouseEnters = void 0);
return;
}
- Un.set(this.getKey(), this.__typedOnMouseEnters);
+ Fn.set(this.getKey(), this.__typedOnMouseEnters);
}
setOnMouseEnterFor(t, r, n) {
Ve(t), Ve(r);
@@ -4380,20 +4423,20 @@ class Ze extends Jt {
const n = this.__typedOnMouseEnters[t];
if (!n)
return;
- const i = wr(n, r);
+ const i = Ir(n, r);
if (Object.keys(i).length > 0)
this.__typedOnMouseEnters = {
...this.__typedOnMouseEnters,
[t]: i
};
else {
- const o = wr(this.__typedOnMouseEnters, t);
+ const o = Ir(this.__typedOnMouseEnters, t);
this.__typedOnMouseEnters = Object.keys(o).length > 0 ? o : void 0;
}
this.syncTypedOnMouseEntersToRegistry();
}
pruneTypedOnMouseEnters() {
- if (!this.__typedOnMouseEnters || this.__typedOnMouseEnters === Sa) {
+ if (!this.__typedOnMouseEnters || this.__typedOnMouseEnters === va) {
this.__typedOnMouseEnters = void 0, this.syncTypedOnMouseEntersToRegistry();
return;
}
@@ -4411,17 +4454,17 @@ class Ze extends Jt {
}
ensureOnMouseLeaveMapMutable() {
if (this.__typedOnMouseLeaves === void 0 || this.__typedOnMouseLeaves === Ma) {
- const t = Fn.get(this.getKey());
+ const t = zn.get(this.getKey());
this.__typedOnMouseLeaves = t ?? {};
}
return this.__typedOnMouseLeaves;
}
syncTypedOnMouseLeavesToRegistry() {
if (!this.__typedOnMouseLeaves || Object.keys(this.__typedOnMouseLeaves).length === 0) {
- Fn.delete(this.getKey()), this.__typedOnMouseLeaves && Object.keys(this.__typedOnMouseLeaves).length === 0 && (this.__typedOnMouseLeaves = void 0);
+ zn.delete(this.getKey()), this.__typedOnMouseLeaves && Object.keys(this.__typedOnMouseLeaves).length === 0 && (this.__typedOnMouseLeaves = void 0);
return;
}
- Fn.set(this.getKey(), this.__typedOnMouseLeaves);
+ zn.set(this.getKey(), this.__typedOnMouseLeaves);
}
setOnMouseLeaveFor(t, r, n) {
Ve(t), Ve(r);
@@ -4434,14 +4477,14 @@ class Ze extends Jt {
const n = this.__typedOnMouseLeaves[t];
if (!n)
return;
- const i = wr(n, r);
+ const i = Ir(n, r);
if (Object.keys(i).length > 0)
this.__typedOnMouseLeaves = {
...this.__typedOnMouseLeaves,
[t]: i
};
else {
- const o = wr(this.__typedOnMouseLeaves, t);
+ const o = Ir(this.__typedOnMouseLeaves, t);
this.__typedOnMouseLeaves = Object.keys(o).length > 0 ? o : void 0;
}
this.syncTypedOnMouseLeavesToRegistry();
@@ -4468,7 +4511,7 @@ class Ze extends Jt {
s && (s(t, r, n, this.getTextContent()), this.removeOnRemoveFor(t, r));
}
dispatchRemovedIDs(t, r, n) {
- const i = db(t, r);
+ const i = Nb(t, r);
if (i.length !== 0)
for (const [s, o] of i)
this.invokeOnRemove(s, o, n);
@@ -4483,10 +4526,10 @@ class Ze extends Jt {
if (this.hasNoIDsForEveryType())
return this;
let t = this.getPreviousSibling();
- for (; _e(t) && Yl(t.getTypedIDs(), this.getTypedIDs()); )
+ for (; Ce(t) && pu(t.getTypedIDs(), this.getTypedIDs()); )
this.mergeWithPreviousTypedMark(t), t = this.getPreviousSibling();
let r = this.getNextSibling();
- for (; _e(r) && Yl(this.getTypedIDs(), r.getTypedIDs()); )
+ for (; Ce(r) && pu(this.getTypedIDs(), r.getTypedIDs()); )
this.mergeWithNextTypedMark(r), r = this.getNextSibling();
return this;
}
@@ -4503,29 +4546,29 @@ class Ze extends Jt {
mergeOnClicksFrom(t) {
if (!t || Object.keys(t).length === 0)
return;
- const r = fb(this.getTypedOnClicks(), t);
+ const r = Ob(this.getTypedOnClicks(), t);
Object.keys(r).length !== 0 && this.setTypedOnClicks(r);
}
mergeOnRemovesFrom(t) {
if (!t || Object.keys(t).length === 0)
return;
- const r = pb(this.getTypedOnRemoves(), t);
+ const r = wb(this.getTypedOnRemoves(), t);
Object.keys(r).length !== 0 && this.setTypedOnRemoves(r);
}
mergeOnMouseEntersFrom(t) {
if (!t || Object.keys(t).length === 0)
return;
- const r = hb(this.getTypedOnMouseEnters(), t);
+ const r = qb(this.getTypedOnMouseEnters(), t);
Object.keys(r).length !== 0 && this.setTypedOnMouseEnters(r);
}
mergeOnMouseLeavesFrom(t) {
if (!t || Object.keys(t).length === 0)
return;
- const r = gb(this.getTypedOnMouseLeaves(), t);
+ const r = Rb(this.getTypedOnMouseLeaves(), t);
Object.keys(r).length !== 0 && this.setTypedOnMouseLeaves(r);
}
}
-function ks(e = Of) {
+function Ss(e = Vf) {
const t = {};
for (const [r, n] of Object.entries(e)) {
if (Ve(r), !Array.isArray(n)) {
@@ -4553,7 +4596,7 @@ function Vo(e) {
return Object.keys(t).length > 0 ? t : void 0;
}
function Wo(e) {
- if (!e || e === va)
+ if (!e || e === Sa)
return;
const t = {};
for (const [r, n] of Object.entries(e)) {
@@ -4566,7 +4609,7 @@ function Wo(e) {
return Object.keys(t).length > 0 ? t : void 0;
}
function Ho(e) {
- if (!e || e === Sa)
+ if (!e || e === va)
return;
const t = {};
for (const [r, n] of Object.entries(e)) {
@@ -4591,19 +4634,19 @@ function Go(e) {
}
return Object.keys(t).length > 0 ? t : void 0;
}
-function wr(e, t) {
+function Ir(e, t) {
const r = {};
for (const [n, i] of Object.entries(e))
n !== t && (r[n] = i);
return r;
}
-function Jl(e) {
+function fu(e) {
const t = {};
for (const [r, n] of Object.entries(e))
!n || n.length === 0 || (t[r] = [...n].sort());
return t;
}
-function db(e, t) {
+function Nb(e, t) {
const r = [];
for (const [n, i] of Object.entries(e)) {
const s = new Set(t[n] ?? []);
@@ -4612,8 +4655,8 @@ function db(e, t) {
}
return r;
}
-function Yl(e, t) {
- const r = Jl(e), n = Jl(t), i = Object.keys(r).sort(), s = Object.keys(n).sort();
+function pu(e, t) {
+ const r = fu(e), n = fu(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++) {
@@ -4629,7 +4672,7 @@ function Yl(e, t) {
}
return !0;
}
-function fb(e, t) {
+function Ob(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 = {};
@@ -4641,7 +4684,7 @@ function fb(e, t) {
}
return r;
}
-function pb(e, t) {
+function wb(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 = {};
@@ -4653,7 +4696,7 @@ function pb(e, t) {
}
return r;
}
-function hb(e, t) {
+function qb(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 = {};
@@ -4665,7 +4708,7 @@ function hb(e, t) {
}
return r;
}
-function gb(e, t) {
+function Rb(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 = {};
@@ -4677,73 +4720,73 @@ function gb(e, t) {
}
return r;
}
-function sn(e, t) {
+function ln(e, t) {
return `${e}-${t}`;
}
-function Xl(e) {
+function hu(e) {
return `external-${e}`;
}
-function zi(e, t, r, n, i) {
- return Ke(new Ze(e, t, r, n, i));
+function Hi(e, t, r, n, i) {
+ return je(new Ze(e, t, r, n, i));
}
-function _e(e) {
+function Ce(e) {
return e instanceof Ze;
}
-function wf(e) {
+function Wf(e) {
return e?.type === Ze.getType();
}
-function Rs(e) {
+function Ds(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 qf(e, t, r, n, i, s, o) {
+function Hf(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++) {
- const T = a[y];
- if (D(g) && g.isParentOf(T))
+ const k = a[y];
+ if (F(g) && g.isParentOf(k))
continue;
- const S = y === 0, v = y === u - 1;
- let E = null;
- if (M(T)) {
- const A = T.getTextContentSize(), x = S ? f : 0, F = v ? p : A;
- if (x === 0 && F === 0)
+ 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)
continue;
- const L = T.splitText(x, F);
- E = L.length > 1 && (L.length === 3 || S && !v || F === A) ? L[1] : L[0];
+ const w = k.splitText(B, M);
+ P = w.length > 1 && (w.length === 3 || _ && !S || M === A) ? w[1] : w[0];
} else {
- if (_e(T))
+ if (Ce(k))
continue;
- D(T) && T.isInline() && (E = T);
+ F(k) && k.isInline() && (P = k);
}
- if (E !== null) {
- if (E && E.is(m))
+ if (P !== null) {
+ if (P && P.is(m))
continue;
- const A = E.getParent();
- (A == null || !A.is(m)) && (g = void 0), m = A, g === void 0 && (g = zi(), g.addID(t, r, n, i, s, o), E.insertBefore(g)), g.append(E);
+ 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);
} else
m = void 0, g = void 0;
}
- t === Lr && D(g) && (d ? g.selectStart() : g.selectEnd());
+ t === zr && F(g) && (d ? g.selectStart() : g.selectEnd());
}
-function mb(e, t, r) {
+function $b(e, t, r) {
let n = e;
for (; n !== null; ) {
- if (_e(n))
+ if (Ce(n))
return n.getTypedIDs()[t];
- if (M(n) && r === n.getTextContentSize()) {
+ if (v(n) && r === n.getTextContentSize()) {
const i = n.getNextSibling();
- if (_e(i))
+ if (Ce(i))
return i.getTypedIDs()[t];
}
n = n.getParent();
}
}
-const yb = ["type", "marker", "content"], Ea = "unknown", Rf = 1, bb = /* @__PURE__ */ new Set(["optbreak", "ref"]);
-class Mn extends Jt {
+const Ib = ["type", "marker", "content"], Ea = "unknown", Gf = 1, Lb = /* @__PURE__ */ new Set(["optbreak", "ref"]);
+class An extends Qt {
__tag;
__marker;
__unknownAttributes;
@@ -4755,18 +4798,18 @@ class Mn extends Jt {
}
static clone(t) {
const { __tag: r, __marker: n, __unknownAttributes: i, __key: s } = t;
- return new Mn(r, n, i, s);
+ return new An(r, n, i, s);
}
static importDOM() {
return {
- [Ea]: (t) => Tb(t) ? {
- conversion: kb,
+ [Ea]: (t) => Ub(t) ? {
+ conversion: Db,
priority: 1
} : null
};
}
static importJSON(t) {
- return Tc().updateFromJSON(t);
+ return Ec().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setTag(t.tag).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes);
@@ -4787,7 +4830,7 @@ class Mn extends Jt {
* callers must not add or strip spaces next to them.
*/
isInlineTag() {
- return bb.has(this.getTag());
+ return Lb.has(this.getTag());
}
setMarker(t) {
if (this.__marker === t)
@@ -4827,7 +4870,7 @@ class Mn extends Jt {
tag: this.getTag(),
marker: this.getMarker(),
unknownAttributes: this.getUnknownAttributes(),
- version: Rf
+ version: Gf
};
}
// Mutation
@@ -4840,31 +4883,113 @@ class Mn extends Jt {
extractWithChild() {
return !1;
}
+ // A CHILD-BEARING `UnknownNode` of any kind stays IN the copy. This affects ONLY the
+ // `application/x-lexical-editor` (lexical-JSON) flavor, for two independent reasons: an
+ // editable-marker view's own `text/html` is not a DOM export at all (Standard view renders the
+ // copy walker's USFM bytes — `usfmToClipboardHtml`, platform's whitespaceDisplay.plugin.utils.ts),
+ // and where Lexical's exporter DOES run, `$appendNodesToHTML` (`@lexical/html`) computes this same
+ // `excludeFromCopy('html')` value but returns early on `exportDOM()`'s unconditional
+ // `{element: null}` BEFORE ever consulting it. `'clone'` is never passed by any Lexical-shipped
+ // code path in the installed version, so an unconditional `destination !== "clone"` would
+ // exclude every `UnknownNode` from the lexical-JSON flavor outright.
+ //
+ // Excluding a node does not drop it silently: `$appendNodesToJSON` HOISTS the excluded node's own
+ // children into its parent's list in its place. Every kind's marker and attribute bytes are
+ // content-free `ImmutableTypedTextNode` display decorators (`unknownDisplayParts` builds them
+ // identically for all of them), so hoisting strands decorators that still `decorate()` their own
+ // literal text as loose siblings with no owning wrapper: the pasted document RENDERS the
+ // construct's full USFM bytes while its USJ has lost the node and every attribute on it — a
+ // convincing display over missing data, which a save then persists with no error.
+ //
+ // The `getChildrenSize() > 0` guard excludes a node with no children of its OWN — of any kind,
+ // and for whatever reason it has none — because that is exactly when `text/plain` also emits
+ // nothing for it, and the two carriers must agree. Three shapes reach it, and only the first is
+ // a husk: an optbreak whose `//` display child was deleted (`markerEditTier1.utils.ts`'s
+ // husk-removal check recognizes it by that same zero-child shape); a content-less construct in
+ // an editable-marker view, where the marker/attribute display bytes `createUnknown`
+ // (`usj-editor.adaptor.ts`) prepends are themselves children, so only a kind with no display
+ // bytes at all can be childless; and, in a HIDDEN-marker view, ANY content-less construct — a
+ // caption-less `figure`, an empty `ref`, every optbreak — because `createUnknown` builds no
+ // display children there at all. The last is prose-copy semantics rather than a husk: a
+ // hidden-marker view copies what it shows, and it shows no bytes for a construct with no
+ // content.
+ //
+ // The guard does NOT, on its own, cover a DIFFERENT carrier-agreement gap: a selection whose
+ // ending boundary resolves to an ELEMENT-type point ON this node at offset 0 (touching the
+ // wrapper without covering any of its content) still marks a CHILD-BEARING node "selected" under
+ // Lexical's default `isSelected` (key-membership in `selection.getNodes()`, unaffected by this
+ // node's own child count), so `$appendNodesToJSON` would serialize a CHILDLESS placeholder for a
+ // node that has children, disagreeing with `text/plain` (`$selectionToUsfmText`, which walks that
+ // same `getNodes()` list and correctly emits nothing for this boundary). The `isSelected`
+ // override below closes that second gap at its actual source, since `excludeFromCopy` has no
+ // visibility into which of a node's children a given selection will include — for every
+ // construct whose display bytes lead, which is all of them but `ref`; see that override for the
+ // one shape it deliberately does not close, and why.
excludeFromCopy(t) {
- return t !== "clone";
+ return this.getChildrenSize() > 0 ? !1 : t !== "clone";
+ }
+ // An `UnknownNode` is only meaningfully "selected" (and so only copy-included, per the
+ // `excludeFromCopy` guard above) when at least one of its own children is — mirrors
+ // `$selectionToUsfmText`'s copy walker so both clipboard carriers agree at the same selection
+ // boundary, the same way Lexical's own base `isSelected` (`LexicalNode.prototype.isSelected`)
+ // already special-cases an inline DECORATOR node sitting as a parent's last child at an
+ // exactly-there boundary point, for the identical reason. Without this, a selection ending
+ // exactly at this node's own start (an ElementNode touch-boundary that covers none of its
+ // content) still counts the WRAPPER as selected via the default `ElementNode.isSelected`
+ // (key-membership in `selection.getNodes()`), while no child is — producing a childless entry in
+ // the `application/x-lexical-editor` copy with nothing corresponding to it in `text/plain`.
+ //
+ // Child MEMBERSHIP is the test, deliberately, and not the narrower "does the selection cover a
+ // child's CONTENT". The two differ for exactly one shape, because that boundary reaches the
+ // children two ways depending on what the first child IS. A construct whose display bytes lead
+ // (a `figure`'s `\fig ` glyph, an optbreak's `//`) starts with a DECORATOR: the element-type
+ // point stays one, no child is in `getNodes()`, and both carriers agree. A construct with no
+ // display bytes (a `ref`, whose container USFM never carried) starts with a real `TextNode`, and
+ // Lexical normalizes that same point into a TEXT point at the child's offset 0 — the child is in
+ // `getNodes()` contributing zero characters, so `text/plain` emits nothing for it while this
+ // predicate still answers true and the construct rides along in the lexical flavor.
+ //
+ // Answering false there is WORSE, not better, and measurably so. Excluding the wrapper does not
+ // drop it quietly: `$appendNodesToJSON` HOISTS its children in its place, and `createUnknown`
+ // stamps `mode:"token"` on every text child, which `$sliceSelectedTextNodeContent` refuses to
+ // slice — so the zero-width child keeps its full text, the emptied-text reset never fires, and
+ // the copy ends up carrying the construct's CHARACTERS with the wrapper and its attributes
+ // silently gone. That is the convincing-lie hazard this whole pair exists to prevent. Membership
+ // keeps the construct whole, so the lexical flavor is a SUPERSET of `text/plain` at that one
+ // boundary rather than a structural loss. That residual stands deliberately: closing it needs a
+ // lever Lexical does not offer — `exportNodeToJSON` requires every ElementNode's `exportJSON()`
+ // to return a `children` array, so a node cannot say "drop me AND my children".
+ isSelected(t) {
+ const r = t ?? R();
+ if (!r)
+ return !1;
+ if (fc(r) && super.isSelected(r))
+ return !0;
+ const n = r.getNodes();
+ return this.getChildren().some((i) => n.some((s) => s.is(i)));
}
}
-function kb(e) {
+function Db(e) {
const t = e.getAttribute("data-tag") ?? "", r = e.getAttribute("data-marker") ?? "";
- return { node: Tc(t, r) };
+ return { node: Ec(t, r) };
}
-function Tc(e, t, r) {
- return Ke(new Mn(e, t, r));
+function Ec(e, t, r) {
+ return je(new An(e, t, r));
}
-function Tb(e) {
+function Ub(e) {
return e?.tagName.toLowerCase() === Ea;
}
function Le(e) {
- return e instanceof Mn;
+ return e instanceof An;
}
-const Ki = "id", $f = 1, xb = [
+const Gi = "id", Jf = 1, Fb = [
"type",
"marker",
"code",
"content"
];
-class Lt extends Jt {
- __marker = Ki;
+class Ut extends Qt {
+ __marker = Gi;
__code;
__unknownAttributes;
constructor(t = "", r, n) {
@@ -4875,14 +5000,14 @@ class Lt extends Jt {
}
static clone(t) {
const { __code: r, __unknownAttributes: n, __key: i } = t;
- return new Lt(r, n, i);
+ return new Ut(r, n, i);
}
static importJSON(t) {
const { code: r } = t;
- return If(r).updateFromJSON(t);
+ return Yf(r).updateFromJSON(t);
}
static isValidBookCode(t) {
- return Nm(t);
+ return Gm(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setCode(t.code).setUnknownAttributes(t.unknownAttributes);
@@ -4924,20 +5049,20 @@ class Lt extends Jt {
marker: this.getMarker(),
code: this.getCode(),
unknownAttributes: this.getUnknownAttributes(),
- version: $f
+ version: Jf
};
}
}
-function If(e, t) {
- return Ke(new Lt(e, t));
+function Yf(e, t) {
+ return je(new Ut(e, t));
}
-function Tt(e) {
- return e instanceof Lt;
+function _t(e) {
+ return e instanceof Ut;
}
-function Lf(e) {
- return e?.type === Lt.getType();
+function Xf(e) {
+ return e?.type === Ut.getType();
}
-const $s = "c", Df = 1, _b = [
+const Us = "c", Qf = 1, zb = [
"type",
"marker",
"number",
@@ -4946,7 +5071,7 @@ const $s = "c", Df = 1, _b = [
"pubnumber",
"content"
];
-class Et extends Jt {
+class Nt extends Qt {
__marker;
__number;
__sid;
@@ -4954,17 +5079,17 @@ class Et extends Jt {
__pubnumber;
__unknownAttributes;
constructor(t = "", r, n, i, s, o) {
- super(o), this.__marker = $s, this.__number = t, this.__sid = r, this.__altnumber = n, this.__pubnumber = i, this.__unknownAttributes = s;
+ super(o), this.__marker = Us, 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 Et(r, n, i, s, o, a);
+ return new Nt(r, n, i, s, o, a);
}
static importJSON(t) {
- return Uf().updateFromJSON(t);
+ return Zf().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);
@@ -5023,7 +5148,7 @@ class Et extends Jt {
}
createDOM() {
const t = document.createElement("p");
- return t.setAttribute("data-marker", this.__marker), t.classList.add(ws, `usfm_${this.__marker}`), t.setAttribute("data-number", this.__number), t;
+ return t.setAttribute("data-marker", this.__marker), t.classList.add(Is, `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;
@@ -5038,20 +5163,20 @@ class Et extends Jt {
altnumber: this.getAltnumber(),
pubnumber: this.getPubnumber(),
unknownAttributes: this.getUnknownAttributes(),
- version: Df
+ version: Qf
};
}
}
-function Uf(e, t, r, n, i) {
- return Ke(new Et(e, t, r, n, i));
+function Zf(e, t, r, n, i) {
+ return je(new Nt(e, t, r, n, i));
}
function $e(e) {
- return e instanceof Et;
+ return e instanceof Nt;
}
-function Cb(e) {
- return e?.type === Et.getType();
+function Kb(e) {
+ return e?.type === Nt.getType();
}
-const Ff = [
+const ep = [
"fr",
"fq",
"fqa",
@@ -5064,7 +5189,7 @@ const Ff = [
"fm",
"fdc"
// Deprecated marker.
-], zf = [
+], tp = [
"xo",
"xop",
"xk",
@@ -5075,7 +5200,7 @@ const Ff = [
"xnt",
"xdc"
// Deprecated marker.
-], vb = [
+], jb = [
// Chapter & Verse
"ca",
"cp",
@@ -5130,10 +5255,10 @@ const Ff = [
"liv3",
"liv4",
"liv5",
- ...Ff,
- ...zf
-], Kf = 1, Sb = ["type", "marker", "content"];
-class ye extends Jt {
+ ...ep,
+ ...tp
+], rp = 1, Bb = ["type", "marker", "content"];
+class me extends Qt {
__marker;
__unknownAttributes;
constructor(t = "", r, n) {
@@ -5144,16 +5269,16 @@ class ye extends Jt {
}
static clone(t) {
const { __marker: r, __unknownAttributes: n, __key: i } = t;
- return new ye(r, n, i);
+ return new me(r, n, i);
}
static isValidMarker(t, r) {
- return t !== void 0 && (vb.includes(t) || (r?.includes(t) ?? !1));
+ return t !== void 0 && (jb.includes(t) || (r?.includes(t) ?? !1));
}
static isValidFootnoteMarker(t) {
- return t !== void 0 && Ff.includes(t);
+ return t !== void 0 && ep.includes(t);
}
static isValidCrossReferenceMarker(t) {
- return t !== void 0 && zf.includes(t);
+ return t !== void 0 && tp.includes(t);
}
/**
* Whether a character marker belongs to the note-content families - footnote or cross-reference.
@@ -5167,18 +5292,18 @@ class ye extends Jt {
* @returns `true` if the marker is a footnote or cross-reference marker, `false` otherwise.
*/
static isNoteContentMarker(t) {
- return ye.isValidFootnoteMarker(t) || ye.isValidCrossReferenceMarker(t);
+ return me.isValidFootnoteMarker(t) || me.isValidCrossReferenceMarker(t);
}
static importDOM() {
return {
- span: (t) => Eb(t) ? {
- conversion: Mb,
+ span: (t) => Wb(t) ? {
+ conversion: Vb,
priority: 1
} : null
};
}
static importJSON(t) {
- return yr().updateFromJSON(t);
+ return xr().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes);
@@ -5201,14 +5326,14 @@ class ye extends Jt {
}
createDOM(t) {
const r = document.createElement("span");
- return Ql(r, this.__marker, t), r.classList.add(this.__type), r;
+ return gu(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}`), Ql(r, this.__marker, n)), !1;
+ return t.__marker !== this.__marker && (r.classList.remove(`usfm_${t.__marker}`), gu(r, this.__marker, n)), !1;
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r };
+ return r && En(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r };
}
exportJSON() {
return {
@@ -5216,12 +5341,12 @@ class ye extends Jt {
type: this.getType(),
marker: this.getMarker(),
unknownAttributes: this.getUnknownAttributes(),
- version: Kf
+ version: rp
};
}
// Mutation
insertNewAfter(t, r) {
- const n = this.getUnknownAttributes()?.closed === "false", i = yr(this.getMarker(), n ? { closed: "false" } : void 0);
+ const n = this.getUnknownAttributes()?.closed === "false", i = xr(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() {
@@ -5231,30 +5356,30 @@ class ye extends Jt {
return !0;
}
}
-function Ql(e, t, r) {
+function gu(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 Mb(e) {
+function Vb(e) {
const t = e.getAttribute("data-marker") ?? "f";
- return { node: yr(t) };
+ return { node: xr(t) };
}
-function yr(e, t) {
- return Ke(new ye(e, t));
+function xr(e, t) {
+ return je(new me(e, t));
}
-function Eb(e) {
+function Wb(e) {
if (!e)
return !1;
const t = e.getAttribute("data-marker") ?? "";
- return ye.isValidMarker(t) && e.classList.contains(ye.getType());
+ return me.isValidMarker(t) && e.classList.contains(me.getType());
}
-function $(e) {
- return e instanceof ye;
+function D(e) {
+ return e instanceof me;
}
-function Ab(e) {
- return e?.type === ye.getType();
+function Hb(e) {
+ return e?.type === me.getType();
}
-const jf = 1, Pb = "c", Bf = "span";
-class or extends ns {
+const np = 1, Gb = "c", ip = "span";
+class pr extends ls {
__marker;
__number;
__showMarker;
@@ -5263,25 +5388,25 @@ class or extends ns {
__pubnumber;
__unknownAttributes;
constructor(t = "", r = !1, n, i, s, o, a) {
- super(a), this.__marker = Pb, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o;
+ super(a), this.__marker = Gb, 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 or(r, n, i, s, o, a, c);
+ return new pr(r, n, i, s, o, a, c);
}
static importDOM() {
return {
- span: (t) => Vf(t) ? {
- conversion: Nb,
+ span: (t) => sp(t) ? {
+ conversion: Jb,
priority: 1
} : null
};
}
static importJSON(t) {
- return xc().updateFromJSON(t);
+ return Ac().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);
@@ -5348,15 +5473,15 @@ class or extends ns {
return this.getLatest().__unknownAttributes;
}
createDOM() {
- const t = document.createElement(Bf);
- return t.setAttribute("data-marker", this.__marker), t.classList.add(ws, `usfm_${this.__marker}`), this.__showMarker && t.classList.add("marker"), t.setAttribute("data-number", this.__number), t;
+ const t = document.createElement(ip);
+ return t.setAttribute("data-marker", this.__marker), t.classList.add(Is, `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 && Sn(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(ws, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r };
+ return r && En(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(Is, `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.
@@ -5376,7 +5501,7 @@ class or extends ns {
* these bytes from `createDOM` instead, the way `ImmutableTypedTextNode` does.
*/
decorate() {
- return this.getShowMarker() ? $t(this.getMarker(), this.getNumber()) : this.getNumber();
+ return this.getShowMarker() ? Lt(this.getMarker(), this.getNumber()) : this.getNumber();
}
exportJSON() {
return {
@@ -5388,7 +5513,7 @@ class or extends ns {
altnumber: this.getAltnumber(),
pubnumber: this.getPubnumber(),
unknownAttributes: this.getUnknownAttributes(),
- version: jf
+ version: np
};
}
// Mutation
@@ -5399,59 +5524,59 @@ class or extends ns {
return !1;
}
}
-function Nb(e) {
+function Jb(e) {
const t = e.getAttribute("data-number") ?? "0";
- return { node: xc(t) };
+ return { node: Ac(t) };
}
-function xc(e, t, r, n, i, s) {
- return Ke(new or(e, t, r, n, i, s));
+function Ac(e, t, r, n, i, s) {
+ return je(new pr(e, t, r, n, i, s));
}
-function Vf(e) {
- return e ? e.classList.contains(ws) && e.tagName.toLowerCase() === Bf : !1;
+function sp(e) {
+ return e ? e.classList.contains(Is) && e.tagName.toLowerCase() === ip : !1;
}
-function as(e) {
- return e instanceof or;
+function fs(e) {
+ return e instanceof pr;
}
-function Ob(e) {
- return e?.type === or.getType();
+function Yb(e) {
+ return e?.type === pr.getType();
}
-const Wf = 1;
-class zr extends lc {
+const op = 1;
+class Vr extends pc {
static getType() {
return "implied-para";
}
static clone(t) {
- return new zr(t.__key);
+ return new Vr(t.__key);
}
static importJSON(t) {
- return jt().updateFromJSON(t);
+ return Wt().updateFromJSON(t);
}
getMarker() {
- return rr;
+ return cr;
}
exportJSON() {
return {
...super.exportJSON(),
type: this.getType(),
- version: Wf
+ version: op
};
}
// Mutation
insertNewAfter(t, r) {
- const n = jt();
+ const n = Wt();
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 jt() {
- return Ke(new zr());
+function Wt() {
+ return je(new Vr());
}
-function ir(e) {
- return e instanceof zr;
+function dr(e) {
+ return e instanceof Vr;
}
-function uo(e) {
- return e?.type === zr.getType();
+function fo(e) {
+ return e?.type === Vr.getType();
}
-const wb = [
+const Xb = [
// Identification
"ide",
"sts",
@@ -5529,7 +5654,7 @@ const wb = [
"sd3",
"sd4",
// Body Paragraphs
- rr,
+ cr,
"m",
"po",
"cls",
@@ -5584,11 +5709,11 @@ const wb = [
"lim4",
// Breaks - see https://docs.usfm.bible/usfm/3.1/char/breaks/pb.html
"pb"
-], Hf = 1, qb = ["type", "marker", "content"];
-class Qe extends lc {
+], ap = 1, Qb = ["type", "marker", "content"];
+class Qe extends pc {
__marker;
__unknownAttributes;
- constructor(t = rr, r, n) {
+ constructor(t = cr, r, n) {
super(n), this.__marker = t, this.__unknownAttributes = r;
}
static getType() {
@@ -5599,18 +5724,18 @@ class Qe extends lc {
return new Qe(r, n, i);
}
static isValidMarker(t, r) {
- return t !== void 0 && (wb.includes(t) || (r?.includes(t) ?? !1));
+ return t !== void 0 && (Xb.includes(t) || (r?.includes(t) ?? !1));
}
static importDOM() {
return {
p: () => ({
- conversion: Rb,
+ conversion: Zb,
priority: 1
})
};
}
static importJSON(t) {
- return ji().updateFromJSON(t);
+ return Ji().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setMarker(t.marker).setUnknownAttributes(t.unknownAttributes);
@@ -5641,7 +5766,7 @@ class Qe extends lc {
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r };
+ return r && En(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(this.getType(), `usfm_${this.getMarker()}`)), { element: r };
}
exportJSON() {
return {
@@ -5649,17 +5774,17 @@ class Qe extends lc {
type: this.getType(),
marker: this.getMarker(),
unknownAttributes: this.getUnknownAttributes(),
- version: Hf
+ version: ap
};
}
// Mutation
insertNewAfter(t, r) {
- const n = ji(this.getMarker());
+ const n = Ji(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 Rb(e) {
- const t = e.getAttribute("data-marker") ?? void 0, r = ji(t);
+function Zb(e) {
+ const t = e.getAttribute("data-marker") ?? void 0, r = Ji(t);
if (e.style) {
r.setFormat(e.style.textAlign);
const n = parseInt(e.style.textIndent, 10) / 20;
@@ -5667,16 +5792,16 @@ function Rb(e) {
}
return { node: r };
}
-function ji(e, t) {
- return Ke(new Qe(e, t));
+function Ji(e, t) {
+ return je(new Qe(e, t));
}
-function se(e) {
+function ae(e) {
return e instanceof Qe;
}
-function _c(e) {
+function Pc(e) {
return e?.type === Qe.getType();
}
-const Is = "v", Gf = 1, $b = [
+const Fs = "v", cp = 1, ek = [
"type",
"marker",
"number",
@@ -5685,7 +5810,7 @@ const Is = "v", Gf = 1, $b = [
"pubnumber",
"content"
];
-class ft extends ze {
+class dt extends Ke {
__marker;
__number;
__sid;
@@ -5693,17 +5818,17 @@ class ft extends ze {
__pubnumber;
__unknownAttributes;
constructor(t = "", r, n, i, s, o, a) {
- super(r ?? t, a), this.__marker = Is, this.__number = t, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o;
+ super(r ?? t, a), this.__marker = Fs, 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 ft(r, n, i, s, o, a, c);
+ return new dt(r, n, i, s, o, a, c);
}
static importJSON(t) {
- return Jf().updateFromJSON(t);
+ return lp().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);
@@ -5778,54 +5903,54 @@ class ft extends ze {
altnumber: this.getAltnumber(),
pubnumber: this.getPubnumber(),
unknownAttributes: this.getUnknownAttributes(),
- version: Gf
+ version: cp
};
}
}
-function Jf(e, t, r, n, i, s) {
- return Ke(new ft(e, t, r, n, i, s));
+function lp(e, t, r, n, i, s) {
+ return je(new dt(e, t, r, n, i, s));
}
-function Ne(e) {
- return e instanceof ft;
+function Pe(e) {
+ return e instanceof dt;
}
-function Yf(e) {
- return e?.type === ft.getType();
+function up(e) {
+ return e?.type === dt.getType();
}
-const Ib = "", Yn = Ib;
-var Zl;
+const tk = "", ei = tk;
+var mu;
(function(e) {
e.LEFT = "left", e.RIGHT = "right";
-})(Zl || (Zl = {}));
-var eu;
+})(mu || (mu = {}));
+var yu;
(function(e) {
e[e.BEFORE = 0] = "BEFORE", e[e.AFTER = 1] = "AFTER";
-})(eu || (eu = {}));
-function Lb() {
- return he(Yn);
+})(yu || (yu = {}));
+function rk() {
+ return pe(ei);
}
-function Db(e) {
+function nk(e) {
const t = e.getTextContent();
- e.setTextContent(t.replaceAll(Yn, ""));
+ e.setTextContent(t.replaceAll(ei, ""));
}
-function cs(e) {
- return e.length > 0 && e.includes(Yn) && e.replaceAll(Yn, "") === "";
+function ps(e) {
+ return e.length > 0 && e.includes(ei) && e.replaceAll(ei, "") === "";
}
-function Cc(e) {
- return M(e) && cs(e.getTextContent());
+function Nc(e) {
+ return v(e) && ps(e.getTextContent());
}
-function Xf(e) {
- return Cb(e) || Ob(e);
+function dp(e) {
+ return Kb(e) || Yb(e);
}
function We(e) {
- return $e(e) || as(e);
+ return $e(e) || fs(e);
}
-function Qf(e, t) {
+function fp(e, t) {
return e.find((r) => We(r) && r.getNumber() === t.toString());
}
-function Ub(e, t = !1) {
+function ik(e, t = !1) {
return e.find((r, n) => (!t || n > 0) && We(r));
}
-function tu(e) {
+function bu(e) {
let t = e;
for (; t && t.getParent() !== null; ) {
const r = t.getPreviousSibling();
@@ -5834,7 +5959,7 @@ function tu(e) {
t = t.getParent();
}
}
-function Zf(e) {
+function pp(e) {
if (!e)
return;
if (We(e))
@@ -5845,13 +5970,13 @@ function Zf(e) {
if (t && We(t))
return t;
}
-function Ht(e) {
+function Yt(e) {
return nt(e, j) ?? void 0;
}
-function Fb(e) {
- return Tt(e) || $e(e) || $(e) || as(e) || ir(e) || je(e) || se(e) || j(e) || Ne(e) || Le(e);
+function sk(e) {
+ return _t(e) || $e(e) || D(e) || fs(e) || dr(e) || Be(e) || ae(e) || j(e) || Pe(e) || Le(e);
}
-function ep(e) {
+function hp(e) {
if (e.anchor.type === "element") {
const r = e.anchor.getNode(), n = e.anchor.offset;
if (n < r.getChildrenSize())
@@ -5860,23 +5985,23 @@ function ep(e) {
const t = e.anchor.getNode();
return t.getNextSibling() ?? t.getParent()?.getNextSibling() ?? null;
}
-function zb(e) {
+function ok(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 vt(e) {
- return Ce(e) || Tt(e);
+function Et(e) {
+ return Se(e) || _t(e);
}
-function Ce(e) {
- return se(e) || ir(e);
+function Se(e) {
+ return ae(e) || dr(e);
}
-function Kb(e) {
- return _c(e) || uo(e);
+function ak(e) {
+ return Pc(e) || fo(e);
}
-function Ls(e, t) {
+function zs(e, t) {
let r = e.getParent();
for (; r; ) {
if (r.getKey() === t)
@@ -5885,97 +6010,97 @@ function Ls(e, t) {
}
return !1;
}
-function mn(e, t) {
- const r = re(t, gn), n = !!(e.cid && r), i = !e.cid && !r;
+function kn(e, t) {
+ const r = ne(t, bn), n = !!(e.cid && r), i = !e.cid && !r;
return e.style === t.getMarker() && (i || n && e.cid === r);
}
-function jb(e, t) {
- const r = D(e) ? e : e.getParent(), n = D(t) ? t : t.getParent(), i = r && n ? Fm(r, n) : void 0;
+function ck(e, t) {
+ const r = F(e) ? e : e.getParent(), n = F(t) ? t : t.getParent(), i = r && n ? iy(r, n) : void 0;
return i ? i.commonAncestor : void 0;
}
-function Bb(e) {
+function lk(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 Xn(e) {
- return e?.type === ze.getType();
+function ti(e) {
+ return e?.type === Ke.getType();
}
-function Vb(e, t) {
+function uk(e, t) {
if (!t)
return;
const r = e.findIndex((n) => n === t);
r && (e.length = r);
}
-function Wb(e, t) {
+function dk(e, t) {
if (!t)
return e;
const r = t.getIndexWithinParent();
return e.splice(r + 1, e.length - r - 1);
}
-function Oe(e, t = !1) {
+function Ne(e, t = !1) {
return `\\${t ? "+" : ""}${e}`;
}
function rt(e, t = !1) {
return `\\${t ? "+" : ""}${e}*`;
}
-function tp(e, t, r) {
- const n = Oe(e);
+function gp(e, t, r) {
+ const n = Ne(e);
if (t?.startsWith(n)) {
const i = t.slice(n.length).replace(/^[\s ]+/, ""), s = /^([^ \u00A0\\]+)/.exec(i);
s && (r = s[1]);
}
return r;
}
-function $t(e, t) {
- let r = Oe(e);
- return t && (r += `${w}${t}`), r += " ", r;
+function Lt(e, t) {
+ let r = Ne(e);
+ return t && (r += `${L}${t}`), r += " ", r;
}
-function Hb(e) {
- const t = e[is];
+function fk(e) {
+ const t = e[ds];
if (t && typeof t == "object" && "textType" in t) {
const r = t.textType;
if (typeof r == "string")
return r;
}
}
-function rp(e) {
- return Ac(e) || Nf(e) && e.textType === "marker" || Xn(e) && Hb(e) === "attribute" ? "" : Xn(e) && e.text !== w ? e.text : Ab(e) ? e.children.map((t) => rp(t)).join("") : "";
+function mp(e) {
+ return $c(e) || Bf(e) && e.textType === "marker" || ti(e) && fk(e) === "attribute" ? "" : ti(e) && e.text !== L ? e.text : Hb(e) ? e.children.map((t) => mp(t)).join("") : "";
}
-function Gb(e) {
- return e.map((r) => rp(r)).filter((r) => r.length > 0).join(" ").trim();
+function pk(e) {
+ return e.map((r) => mp(r)).filter((r) => r.length > 0).join(" ").trim();
}
-function St(e) {
- return " " + e + w;
+function At(e) {
+ return " " + e + L;
}
-function vc(e) {
+function Oc(e) {
const t = [];
for (const r of e) {
- if (!$(r))
+ if (!D(r))
continue;
- const n = np(r);
- n !== It && n.length > 0 && t.push(n);
+ const n = yp(r);
+ n !== Dt && n.length > 0 && t.push(n);
}
return t.join(" ").trim();
}
-function np(e) {
- return P(e) || Cr(e) || M(e) && re(e, oe) === "attribute" ? "" : M(e) ? e.getTextContent() : D(e) ? e.getChildren().map((t) => np(t)).join("") : "";
+function yp(e) {
+ return O(e) || Er(e) || v(e) && ne(e, oe) === "attribute" ? "" : v(e) ? e.getTextContent() : F(e) ? e.getChildren().map((t) => yp(t)).join("") : "";
}
-function Cr(e) {
- return Wt(e) && e.getTextType() === "marker";
+function Er(e) {
+ return Jt(e) && e.getTextType() === "marker";
}
-function Dt(e) {
- return P(e) || Cr(e);
+function Ft(e) {
+ return O(e) || Er(e);
}
-function ru(e, t) {
- Jb(e, t), e.setMarker(t);
+function ku(e, t) {
+ hk(e, t), e.setMarker(t);
}
-function Jb(e, t) {
- const r = e.getMarker(), n = Oe(r), i = Oe(r, !0), s = rt(r), o = rt(r, !0), a = ye.isNoteContentMarker(t);
+function hk(e, t) {
+ const r = e.getMarker(), n = Ne(r), i = Ne(r, !0), s = rt(r), o = rt(r, !0), a = me.isNoteContentMarker(t);
e.getChildren().forEach((c) => {
- if (!Dt(c))
+ if (!Ft(c))
return;
const l = c.getTextContent(), u = l === n || l === i, d = !u && (l === s || l === o);
if (!(!u && !d)) {
@@ -5983,16 +6108,16 @@ function Jb(e, t) {
c.remove();
return;
}
- if (P(c))
+ if (O(c))
c.setMarker(t);
- else if (Cr(c)) {
- const f = l.startsWith(Oe("", !0));
- c.setTextContent(u ? Oe(t, f) : rt(t, f));
+ else if (Er(c)) {
+ const f = l.startsWith(Ne("", !0));
+ c.setTextContent(u ? Ne(t, f) : rt(t, f));
}
}
});
}
-function De(e, t = Om) {
+function De(e, t = Jm) {
const r = { ...e };
return t.forEach((n) => {
Reflect.deleteProperty(r, n);
@@ -6001,25 +6126,25 @@ function De(e, t = Om) {
function Ae(e) {
return Object.fromEntries(Object.entries(e).filter(([, t]) => t !== void 0));
}
-function ip(e) {
+function bp(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 Sc(e) {
+function wc(e) {
if (!N(e))
- return nu(e);
+ return Tu(e);
const t = e.anchor.getNode();
- if (t && (e.anchor.type === "element" && !D(t) || e.anchor.type === "text" && !M(t)))
+ if (t && (e.anchor.type === "element" && !F(t) || e.anchor.type === "text" && !v(t)))
return t ?? void 0;
try {
- return nu(e) ?? t ?? void 0;
+ return Tu(e) ?? t ?? void 0;
} catch (n) {
- if (ip(n))
+ if (bp(n))
return t ?? void 0;
throw n;
}
}
-function Yb(e, t) {
+function gk(e, t) {
if (!t)
return (e + 1).toString();
const r = t.split("-");
@@ -6031,7 +6156,7 @@ function Yb(e, t) {
const i = String.fromCharCode(n[2].charCodeAt(0) + 1);
return `${n[1]}${i}`;
}
-function Mc(e, t) {
+function qc(e, t) {
if (!t)
return !1;
const r = t.split("-").map((n) => parseInt(n));
@@ -6039,49 +6164,49 @@ function Mc(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 sp(e) {
+function kp(e) {
return !!e && e.includes("-");
}
-function op(e) {
+function Tp(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 nu(e) {
+function Tu(e) {
if (!e)
return;
const t = e.getNodes();
if (t.length > 0)
return e.isBackward() ? t[t.length - 1] : t[0];
}
-function Ec(e) {
+function Rc(e) {
if (!e)
return !1;
- if (no(e) || P(e) || Cr(e) || Wt(e) && e.getTextType() === "attribute")
+ if (us(e) || O(e) || Er(e) || Jt(e) && e.getTextType() === "attribute")
return !0;
- if (M(e)) {
- const t = re(e, oe);
- if (t === sr || t === "attribute")
+ if (v(e)) {
+ const t = ne(e, oe);
+ if (t === fr || t === "attribute")
return !0;
const r = e.getTextContent();
- if (r === "" || r === w || cs(r))
+ if (r === "" || r === L || ps(r))
return !0;
}
return !1;
}
-function fo() {
- const e = he(w);
- return mt(e, oe, sr), e.setMode("token"), e;
+function po() {
+ const e = pe(L);
+ return yt(e, oe, fr), e.setMode("token"), e;
}
-function Xb(e) {
+function mk(e) {
const t = e.getTextContent();
- t.startsWith(w) || e.setTextContent(w + t);
+ t.startsWith(L) || e.setTextContent(L + t);
}
-function En(e) {
- return M(e) && re(e, oe) === sr;
+function Pn(e) {
+ return v(e) && ne(e, oe) === fr;
}
-function ap(e) {
+function xp(e) {
const t = e.getFirstChild();
- if (!Dt(t) || t === null || En(t.getNextSibling()))
+ if (!Ft(t) || t === null || Pn(t.getNextSibling()))
return !1;
const r = R();
if (!N(r) || !r.isCollapsed())
@@ -6092,18 +6217,18 @@ function ap(e) {
const i = t.getNextSibling();
return i !== null && n.is(i) && r.anchor.offset === 0;
}
-function si(e) {
+function ui(e) {
const t = [];
let r;
const n = () => {
r && (t.push({ type: "text", segments: r.segments, length: r.length }), r = void 0);
}, i = (s) => {
- if (!Ec(s)) {
- if (_e(s)) {
+ if (!Rc(s)) {
+ if (Ce(s)) {
s.getChildren().forEach(i);
return;
}
- if (M(s) && s.getType() === ze.getType()) {
+ if (v(s) && s.getType() === Ke.getType()) {
r ??= { segments: [], length: 0 }, r.segments.push({ node: s, start: r.length }), r.length += s.getTextContentSize();
return;
}
@@ -6112,20 +6237,20 @@ function si(e) {
};
return e.getChildren().forEach(i), n(), t;
}
-function po(e) {
+function ho(e) {
let t = e.getParent();
- for (; t && _e(t); )
+ for (; t && Ce(t); )
t = t.getParent();
return t;
}
-function Qb(e, t) {
- return si(e).findIndex((r) => r.type === "element" ? r.node.is(t) : r.segments.some((n) => n.node.is(t)));
+function yk(e, t) {
+ return ui(e).findIndex((r) => r.type === "element" ? r.node.is(t) : r.segments.some((n) => n.node.is(t)));
}
-function Zb(e, t) {
- const r = po(e);
+function bk(e, t) {
+ const r = ho(e);
if (!r)
return;
- const n = si(r);
+ const n = ui(r);
for (let i = 0; i < n.length; i++) {
const s = n[i];
if (s.type !== "text")
@@ -6135,7 +6260,7 @@ function Zb(e, t) {
return { parent: r, index: i, offset: o.start + t };
}
}
-function ek(e, t) {
+function kk(e, t) {
if (t < 0 || t > e.length)
return;
for (const n of e.segments) {
@@ -6147,35 +6272,35 @@ function ek(e, t) {
if (r)
return [r.node, t - r.start];
}
-function cp(e, t) {
- const r = si(e), n = e.getChildAtIndex(t);
+function _p(e, t) {
+ const r = ui(e), n = e.getChildAtIndex(t);
if (!n)
return { type: "index", index: r.length };
- if (Ec(n))
- return cp(e, t + 1);
+ if (Rc(n))
+ return _p(e, t + 1);
for (let i = 0; i < r.length; i++) {
const s = r[i];
if (s.type === "element") {
- if (s.node.is(n) || Ls(s.node, n.getKey()))
+ if (s.node.is(n) || zs(s.node, n.getKey()))
return { type: "index", index: i };
continue;
}
for (const o of s.segments)
- if (o.node.is(n) || Ls(o.node, n.getKey()))
+ if (o.node.is(n) || zs(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 tk(e, t) {
+function Tk(e, t) {
if (t <= 0)
return 0;
- const r = si(e);
+ const r = ui(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 ? rk(e, i) : void 0;
+ const n = r[t - 1], i = n.type === "element" ? n.node : n.segments[n.segments.length - 1]?.node, s = i ? xk(e, i) : void 0;
return s ? s.getIndexWithinParent() + 1 : e.getChildrenSize();
}
-function rk(e, t) {
+function xk(e, t) {
let r = t;
for (; r; ) {
const n = r.getParent();
@@ -6184,8 +6309,8 @@ function rk(e, t) {
r = n;
}
}
-const nk = 1;
-class ar extends ze {
+const _k = 1;
+class hr extends Ke {
__marker;
__markerSyntax;
__nested;
@@ -6194,13 +6319,13 @@ class ar extends ze {
// 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(cn(t, r, i), n), this.__marker = t, this.__markerSyntax = r, this.__nested = i;
+ super(fn(t, r, i), n), this.__marker = t, this.__markerSyntax = r, this.__nested = i;
}
static getType() {
return "marker";
}
static clone(t) {
- return new ar(t.__marker, t.__markerSyntax, t.__key, t.__nested);
+ return new hr(t.__marker, t.__markerSyntax, t.__key, t.__nested);
}
static importJSON(t) {
return ot().updateFromJSON(t);
@@ -6211,7 +6336,7 @@ class ar extends ze {
// 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 || cn(r, n, i)
+ text: t.text || fn(r, n, i)
}).getWritable();
return o.__marker = r, o.__markerSyntax = n, o.__nested = i, o;
}
@@ -6219,7 +6344,7 @@ class ar extends ze {
if (this.__marker === t)
return this;
const r = this.getWritable();
- return r.__marker = t, r.__text = cn(t, r.__markerSyntax, r.__nested), r;
+ return r.__marker = t, r.__text = fn(t, r.__markerSyntax, r.__nested), r;
}
getMarker() {
return this.getLatest().__marker;
@@ -6228,7 +6353,7 @@ class ar extends ze {
if (this.__markerSyntax === t)
return this;
const r = this.getWritable();
- return r.__markerSyntax = t, r.__text = cn(r.__marker, t, r.__nested), r;
+ return r.__markerSyntax = t, r.__text = fn(r.__marker, t, r.__nested), r;
}
getMarkerSyntax() {
return this.getLatest().__markerSyntax;
@@ -6237,7 +6362,7 @@ class ar extends ze {
if (this.__nested === t)
return this;
const r = this.getWritable();
- return r.__nested = t, r.__text = cn(r.__marker, r.__markerSyntax, t), r;
+ return r.__nested = t, r.__text = fn(r.__marker, r.__markerSyntax, t), r;
}
getNested() {
return this.getLatest().__nested;
@@ -6260,33 +6385,33 @@ class ar extends ze {
// 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: nk
+ version: _k
};
}
}
function ot(e, t, r) {
- return Ke(new ar(e, t, void 0, r));
+ return je(new hr(e, t, void 0, r));
}
-function P(e) {
- return e instanceof ar;
+function O(e) {
+ return e instanceof hr;
}
-function Ac(e) {
- return e?.type === ar.getType();
+function $c(e) {
+ return e?.type === hr.getType();
}
-function Wr(e) {
- return e.getTextContent() === cn(e.getMarker(), e.getMarkerSyntax(), e.getNested());
+function Xr(e) {
+ return e.getTextContent() === fn(e.getMarker(), e.getMarkerSyntax(), e.getNested());
}
-function ik(e) {
- e.setTextContent(cn(e.getMarker(), e.getMarkerSyntax(), e.getNested()));
+function Ck(e) {
+ e.setTextContent(fn(e.getMarker(), e.getMarkerSyntax(), e.getNested()));
}
-function cn(e, t, r = !1) {
- return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : Oe(e, r);
+function fn(e, t, r = !1) {
+ return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : Ne(e, r);
}
-const lp = 1, sk = "attribute-run";
+const Cp = 1, Sk = "attribute-run";
function Jo(e) {
return e === "va" || e === "vp" || e === "ca" || e === "cp" ? `usfm_${e}` : void 0;
}
-class vr extends Jt {
+class Ar extends Qt {
__runKind;
constructor(t, r) {
super(r), this.__runKind = t;
@@ -6296,10 +6421,10 @@ class vr extends Jt {
}
static clone(t) {
const { __runKind: r, __key: n } = t;
- return new vr(r, n);
+ return new Ar(r, n);
}
static importJSON(t) {
- return up(t.runKind).updateFromJSON(t);
+ return Sp(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
@@ -6324,7 +6449,7 @@ class vr extends Jt {
}
createDOM() {
const t = document.createElement("span");
- t.classList.add(sk);
+ t.classList.add(Sk);
const r = Jo(this.__runKind);
return r !== void 0 && t.classList.add(r), t;
}
@@ -6345,7 +6470,7 @@ class vr extends Jt {
...super.exportJSON(),
type: this.getType(),
runKind: this.getRunKind(),
- version: lp
+ version: Cp
};
}
// Mutation
@@ -6356,18 +6481,18 @@ class vr extends Jt {
return !0;
}
}
-function up(e) {
- return Ke(new vr(e));
+function Sp(e) {
+ return je(new Ar(e));
}
-function Be(e) {
- return e instanceof vr;
+function ze(e) {
+ return e instanceof Ar;
}
-const ok = /* @__PURE__ */ new Set(["closed"]);
-function tr(e, t) {
- const r = Object.entries(e).filter(([n, i]) => i !== void 0 && !ok.has(n));
+const vk = /* @__PURE__ */ new Set(["closed"]);
+function or(e, t) {
+ const r = Object.entries(e).filter(([n, i]) => i !== void 0 && !vk.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 dp(e, t) {
+function vp(e, t) {
if (!t || t.length === 0)
return e;
const r = {};
@@ -6377,16 +6502,16 @@ function dp(e, t) {
Object.hasOwn(r, n) || (r[n] = i);
}), r;
}
-function fp(e) {
- const t = Object.keys(e).filter((n) => !$y.includes(n)), r = [
+function Mp(e) {
+ const t = Object.keys(e).filter((n) => !rb.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 pp(e, t, r, n) {
- return dp(
+function Ep(e, t, r, n) {
+ return vp(
// 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
@@ -6399,125 +6524,125 @@ function pp(e, t, r, n) {
n
);
}
-function qi(e) {
- return e.getChildren().find((t) => P(t) && t.getMarkerSyntax() === "closing" && t.getMarker() === e.getMarker());
+function Li(e) {
+ return e.getChildren().find((t) => O(t) && t.getMarkerSyntax() === "closing" && t.getMarker() === e.getMarker());
}
-function ak(e) {
+function Mk(e) {
const t = e.getUnknownAttributes();
- return !t || !Object.keys(t).some((n) => n !== "closed") ? !1 : qi(e) === void 0 && hp(e) === void 0;
+ return !t || !Object.keys(t).some((n) => n !== "closed") ? !1 : Li(e) === void 0 && Ap(e) === void 0;
}
-function hp(e) {
- return e.getChildren().find((t) => M(t) && re(t, oe) === "attribute");
+function Ap(e) {
+ return e.getChildren().find((t) => v(t) && ne(t, oe) === "attribute");
}
-function Bi(e, t) {
- return ls(e.getNextSibling(), t);
+function Yi(e, t) {
+ return hs(e.getNextSibling(), t);
}
-const ck = /^[ \u00A0]+$/;
-function Pc(e) {
- if (Wr(e))
+const Ek = /^[ \u00A0]+$/;
+function Ic(e) {
+ if (Xr(e))
return !0;
if (e.getMarkerSyntax() !== "opening")
return !1;
- const t = Oe(e.getMarker(), e.getNested()), r = e.getTextContent();
- return r.startsWith(t) && ck.test(r.slice(t.length));
+ const t = Ne(e.getMarker(), e.getNested()), r = e.getTextContent();
+ return r.startsWith(t) && Ek.test(r.slice(t.length));
}
-function ls(e, t) {
+function hs(e, t) {
let r, n, i, s;
- return Be(e) && e.getRunKind() === t && (s = e, e = e.getFirstChild()), P(e) && e.getMarkerSyntax() === "opening" && e.getMarker() === t && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the
+ return ze(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.
- Pc(e) && (r = e, e = e.getNextSibling()), M(e) && re(e, oe) === "attribute" && (n = e, e = e.getNextSibling()), P(e) && e.getMarkerSyntax() === "closing" && e.getMarker() === t && Wr(e) && (i = e), { opener: r, value: n, closer: i, wrapper: s };
+ Ic(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 };
}
-function Vi(e) {
+function Xi(e) {
const t = e.getChildren();
let r = 0;
for (; r < t.length; ) {
const i = t[r];
- if (!P(i) || i.getMarkerSyntax() !== "opening")
+ if (!O(i) || i.getMarkerSyntax() !== "opening")
break;
r++;
}
const n = t[r];
- if (M(n) && n.getTextContent() === St(e.getCaller()))
+ if (v(n) && n.getTextContent() === At(e.getCaller()))
return n;
}
-function gp(e) {
- const t = Vi(e);
- return t ? ls(t.getNextSibling(), "cat") : {};
+function Pp(e) {
+ const t = Xi(e);
+ return t ? hs(t.getNextSibling(), "cat") : {};
}
-function ho(e) {
+function go(e) {
const t = e.getFirstChild();
- if (!(!M(t) || P(t)) && re(t, oe) !== "attribute")
+ if (!(!v(t) || O(t)) && ne(t, oe) !== "attribute")
return t;
}
-function mp(e) {
- const t = ho(e);
- return t ? ls(t.getNextSibling(), "ca") : {};
+function Np(e) {
+ const t = go(e);
+ return t ? hs(t.getNextSibling(), "ca") : {};
}
-function yp(e) {
- const t = ho(e);
+function Op(e) {
+ const t = go(e);
if (!t)
return;
- const r = ls(t.getNextSibling(), "ca");
+ const r = hs(t.getNextSibling(), "ca");
return r.wrapper ?? r.closer ?? t;
}
-function bp(e) {
- const t = yp(e);
- return t ? ls(t.getNextSibling(), "cp") : {};
+function wp(e) {
+ const t = Op(e);
+ return t ? hs(t.getNextSibling(), "cp") : {};
}
-function kp(e) {
+function qp(e) {
const t = e.getParent();
- if (!$(t))
+ if (!D(t))
return;
const r = t.getMarker();
if (!(r !== "va" && r !== "vp"))
for (let n = t.getPreviousSibling(); n; n = n.getPreviousSibling()) {
- if (Ne(n))
+ if (Pe(n))
return n;
- if (!(P(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || M(n) && re(n, oe) === "attribute" || $(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || Be(n)))
+ if (!(O(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || v(n) && ne(n, oe) === "attribute" || D(n) && (n.getMarker() === "va" || n.getMarker() === "vp") || ze(n)))
return;
}
}
-function go(e) {
+function mo(e) {
let t, r, n, i, s = e.getNextSibling();
- return Be(s) && s.getRunKind() === "milestone" && (i = s, s = s.getFirstChild()), P(s) && s.getMarkerSyntax() === "opening" && s.getMarker() === e.getMarker() && // Typed-spacing licensed ({@link $isCanonicalRunOpenerGlyph}): a trailing space typed on the
+ return ze(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.
- Pc(s) && (t = s, s = s.getNextSibling()), M(s) && re(s, oe) === "attribute" && (r = s, s = s.getNextSibling()), P(s) && s.getMarkerSyntax() === "selfClosing" && Wr(s) && (n = s), { opening: t, attribute: r, closing: n, wrapper: i };
+ Ic(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 };
}
-function Nc(e) {
- return $(po(e));
+function Lc(e) {
+ return D(ho(e));
}
function Aa(e, t) {
if (e.getMarkerSyntax() === "selfClosing")
return;
const r = e.getMarker();
- return r === t.getMarker() ? Nc(t) : t.getChildren().some((i) => $(i) && i.getMarker() === r) ? !0 : void 0;
+ return r === t.getMarker() ? Lc(t) : t.getChildren().some((i) => D(i) && i.getMarker() === r) ? !0 : void 0;
}
-function lk(e) {
+function Ak(e) {
e.isAttached() && e.getChildren().forEach((t) => {
- if (!P(t))
+ if (!O(t))
return;
const r = Aa(t, e);
r !== void 0 && t.setNested(r);
});
}
-function mo(e) {
- return M(e) && e.getType() === ze.getType() && re(e, oe) !== "attribute";
+function yo(e) {
+ return v(e) && e.getType() === Ke.getType() && ne(e, oe) !== "attribute";
}
-function Oc(e, t) {
+function Dc(e, t) {
if (e.getMarkerSyntax() !== "opening" || Aa(e, t) === void 0)
return;
const r = e.getNextSibling();
if (r !== null)
- return P(r) ? Aa(r, t) === !0 ? "spacer" : void 0 : mo(r) ? r.getTextContent().startsWith(w) ? void 0 : "prefix" : "spacer";
+ return O(r) ? Aa(r, t) === !0 ? "spacer" : void 0 : yo(r) ? r.getTextContent().startsWith(L) ? void 0 : "prefix" : "spacer";
}
-function uk(e) {
+function Pk(e) {
if (e.isAttached()) {
for (const t of e.getChildren())
- if (P(t) && Oc(t, e) !== void 0)
+ if (O(t) && Dc(t, e) !== void 0)
return t.getNextSibling()?.getTextContent() ?? "";
}
}
-function Tp(e, t) {
+function Rp(e, t) {
const r = R();
if (!N(r) || !r.isCollapsed())
return !1;
@@ -6527,33 +6652,33 @@ function Tp(e, t) {
const i = e.getNextSibling();
return i !== null && n.is(i) && r.anchor.offset === 0;
}
-function xp(e) {
+function $p(e) {
e.isAttached() && e.getChildren().forEach((t) => {
- if (!P(t))
+ if (!O(t))
return;
- const r = Oc(t, e);
- if (r !== void 0 && !Tp(t, e))
+ const r = Dc(t, e);
+ if (r !== void 0 && !Rp(t, e))
if (r === "prefix") {
const n = t.getNextSibling();
- M(n) && n.setTextContent(w + n.getTextContent());
+ v(n) && n.setTextContent(L + n.getTextContent());
} else
- t.insertAfter(he(w));
+ t.insertAfter(pe(L));
});
}
-function _p(e) {
- return e.isAttached() ? e.getChildren().some((t) => P(t) && Oc(t, e) !== void 0 && Tp(t, e)) : !1;
+function Ip(e) {
+ return e.isAttached() ? e.getChildren().some((t) => O(t) && Dc(t, e) !== void 0 && Rp(t, e)) : !1;
}
-const dk = "file", fk = "src", pk = "colspan", hk = "category", gk = "alt", mk = "closed", yk = "false";
-function bk(e) {
- return e[mk] !== yk;
+const Nk = "file", Ok = "src", wk = "colspan", qk = "category", Rk = "alt", $k = "closed", Ik = "false";
+function Lk(e) {
+ return e[$k] !== Ik;
}
-function kk(e) {
+function Dk(e) {
return Object.fromEntries(Object.entries(e).map(([t, r]) => [
- t === dk ? fk : t,
+ t === Nk ? Ok : t,
r
]));
}
-function Tk(e, t) {
+function Lp(e, t) {
if (e === void 0)
return;
const r = Number(t);
@@ -6568,8 +6693,8 @@ function Tk(e, t) {
}
return n === e.length ? e : `${e}-${Number(e.slice(n)) + r - 1}`;
}
-function Cp(e, t, r) {
- const n = r ?? {}, i = bk(n);
+function Dp(e, t, r) {
+ const n = r ?? {}, i = Lk(n);
switch (e) {
case "optbreak":
return { opening: "//", attributes: "", closingAttributes: "", closing: "" };
@@ -6580,7 +6705,7 @@ function Cp(e, t, r) {
return { opening: `\\${t} `, attributes: "", closingAttributes: "", closing: "" };
case "table:cell":
return {
- opening: `\\${Tk(t, n[pk])} `,
+ opening: `\\${Lp(t, n[wk])} `,
attributes: "",
closingAttributes: "",
closing: ""
@@ -6589,23 +6714,23 @@ function Cp(e, t, r) {
return {
opening: `\\${t} `,
attributes: "",
- closingAttributes: tr(kk(n), void 0),
+ closingAttributes: or(Dk(n), void 0),
closing: i ? `\\${t}*` : ""
};
case "sidebar": {
- const { [hk]: s, ...o } = n;
+ const { [qk]: s, ...o } = n;
return {
opening: "\\esb",
- attributes: (s === void 0 ? "" : ` \\cat ${s}\\cat*`) + tr(o, void 0),
+ attributes: (s === void 0 ? "" : ` \\cat ${s}\\cat*`) + or(o, void 0),
closingAttributes: "",
closing: i ? "\\esbe" : ""
};
}
case "periph": {
- const { [gk]: s, ...o } = n;
+ const { [Rk]: s, ...o } = n;
return {
opening: `\\periph ${s ?? ""}`,
- attributes: tr(o, void 0),
+ attributes: or(o, void 0),
closingAttributes: "",
closing: ""
};
@@ -6614,26 +6739,26 @@ function Cp(e, t, r) {
return {
opening: `\\${t} `,
attributes: "",
- closingAttributes: tr(n, void 0),
+ closingAttributes: or(n, void 0),
closing: i ? `\\${t}*` : ""
};
}
}
-const bt = { wantsRun: !1, valueText: void 0 }, Sr = {};
+const Tt = { wantsRun: !1, valueText: void 0 }, Pr = {};
function Yo(e, t) {
if (t === "va")
return e;
- const r = Bi(e, "va");
+ const r = Yi(e, "va");
return r.wrapper ?? r.closer ?? e;
}
-function wc(e) {
+function Uc(e) {
const t = R();
if (!N(t) || !t.isCollapsed())
return !1;
const r = t.anchor.getNode();
if (r.is(e) && t.anchor.offset === e.getTextContentSize())
return !0;
- if (D(e)) {
+ if (F(e)) {
const i = e.getLastDescendant();
if (i !== null && r.is(i) && t.anchor.offset === i.getTextContentSize())
return !0;
@@ -6641,7 +6766,7 @@ function wc(e) {
const n = e.getNextSibling();
return n !== null && r.is(n) && t.anchor.offset === 0;
}
-function yo(e) {
+function bo(e) {
const { opener: t, closer: r } = e;
if (!t)
return !1;
@@ -6651,48 +6776,48 @@ function yo(e) {
const i = n.anchor.getNode(), s = i.is(t) && n.anchor.offset === t.getTextContentSize();
return r ? s || i.is(r) : s;
}
-function xk(e) {
- return Be(e) ? e.getRunKind() === "va" || e.getRunKind() === "vp" : P(e) ? e.getMarker() === "va" || e.getMarker() === "vp" : M(e) && re(e, oe) === "attribute";
+function Uk(e) {
+ return ze(e) ? e.getRunKind() === "va" || e.getRunKind() === "vp" : O(e) ? e.getMarker() === "va" || e.getMarker() === "vp" : v(e) && ne(e, oe) === "attribute";
}
-function _k(e) {
- if (P(e)) {
+function Fk(e) {
+ if (O(e)) {
const n = e.getMarker();
return n === "va" || n === "vp" ? n : void 0;
}
- if (!M(e) || re(e, oe) !== "attribute")
+ if (!v(e) || ne(e, oe) !== "attribute")
return;
const t = e.getPreviousSibling();
- if (!P(t))
+ if (!O(t))
return;
const r = t.getMarker();
return r === "va" || r === "vp" ? r : void 0;
}
function Xo(e) {
for (let t = e.getPreviousSibling(); t; t = t.getPreviousSibling()) {
- if (Ne(t))
+ if (Pe(t))
return t;
- if (!xk(t))
+ if (!Uk(t))
return;
}
}
-function iu(e) {
+function xu(e) {
return {
kind: e,
- ownerPredicate: (t) => Ne(t),
+ ownerPredicate: (t) => Pe(t),
ownerOf: (t) => {
- if (Be(t))
+ if (ze(t))
return t.getRunKind() === e ? Xo(t) : void 0;
const r = t.getParent();
- return Be(r) ? r.getRunKind() === e ? Xo(r) : void 0 : _k(t) === e ? Xo(t) : void 0;
+ return ze(r) ? r.getRunKind() === e ? Xo(r) : void 0 : Fk(t) === e ? Xo(t) : void 0;
},
expectedPieces: (t) => {
- if (!Ne(t))
- return bt;
+ if (!Pe(t))
+ return Tt;
const r = e === "va" ? t.getAltnumber() : t.getPubnumber();
- return r === void 0 ? bt : { wantsRun: !0, valueText: w + r };
+ return r === void 0 ? Tt : { wantsRun: !0, valueText: L + r };
},
- scanPieces: (t) => Ne(t) ? Bi(Yo(t, e), e) : Sr,
- graceSite: (t, r) => Ne(t) ? !r.opener && !r.closer ? wc(Yo(t, e)) : yo(r) : !1,
+ scanPieces: (t) => Pe(t) ? Yi(Yo(t, e), e) : Pr,
+ graceSite: (t, r) => Pe(t) ? !r.opener && !r.closer ? Uc(Yo(t, e)) : bo(r) : !1,
settleScope: "owner",
deletionPolicy: "retokenize",
byteFormat: {
@@ -6701,44 +6826,44 @@ function iu(e) {
glyphs: "with-value",
glyphMarker: () => e,
closerSyntax: "closing",
- insertRunAfter: (t) => Ne(t) ? Yo(t, e) : void 0
+ insertRunAfter: (t) => Pe(t) ? Yo(t, e) : void 0
}
};
}
-const Ck = {
+const zk = {
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) => $(e),
+ ownerPredicate: (e) => D(e),
ownerOf: () => {
},
- expectedPieces: () => bt,
- scanPieces: () => Sr,
- graceSite: (e) => $(e) && _p(e),
+ expectedPieces: () => Tt,
+ scanPieces: () => Pr,
+ graceSite: (e) => D(e) && Ip(e),
settleScope: "owner",
deletionPolicy: "retokenize",
byteFormat: { writer: "kind-owned", glyphs: "none" }
-}, vk = {
+}, Kk = {
kind: "char",
- ownerPredicate: (e) => $(e),
+ ownerPredicate: (e) => D(e),
ownerOf: (e) => {
- if (!M(e) || re(e, oe) !== "attribute")
+ if (!v(e) || ne(e, oe) !== "attribute")
return;
const t = e.getParent();
- return $(t) ? t : void 0;
+ return D(t) ? t : void 0;
},
expectedPieces: (e) => {
- if (!$(e) || qi(e) === void 0)
- return bt;
- const t = tr(e.getUnknownAttributes() ?? {}, ao(e.getMarker()));
- return t === "" ? bt : { wantsRun: !0, valueText: t };
+ if (!D(e) || Li(e) === void 0)
+ return Tt;
+ const t = or(e.getUnknownAttributes() ?? {}, lo(e.getMarker()));
+ return t === "" ? Tt : { wantsRun: !0, valueText: t };
},
- scanPieces: (e) => $(e) ? { value: hp(e) } : Sr,
+ scanPieces: (e) => D(e) ? { value: Ap(e) } : Pr,
graceSite: (e, t) => {
- if (!$(e) || t.value)
+ if (!D(e) || t.value)
return !1;
- const r = qi(e);
+ const r = Li(e);
if (!r)
return !1;
const n = R();
@@ -6755,54 +6880,54 @@ const Ck = {
byteFormat: {
writer: "owner-children",
glyphs: "none",
- insertRunBefore: (e) => $(e) ? qi(e) : void 0
+ insertRunBefore: (e) => D(e) ? Li(e) : void 0
}
};
-function vp(e) {
- if (P(e))
+function Up(e) {
+ if (O(e))
return e.getMarker() === "cat";
- if (!M(e) || re(e, oe) !== "attribute")
+ if (!v(e) || ne(e, oe) !== "attribute")
return !1;
const t = e.getPreviousSibling();
- return P(t) && t.getMarker() === "cat";
+ return O(t) && t.getMarker() === "cat";
}
-function Sk(e) {
+function jk(e) {
const t = e.getParent();
if (!j(t))
return;
- const r = Vi(t);
+ const r = Xi(t);
if (r)
for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) {
if (n.is(r))
return t;
- if (!vp(n))
+ if (!Up(n))
return;
}
}
-const Mk = {
+const Bk = {
kind: "cat",
ownerPredicate: (e) => j(e),
ownerOf: (e) => {
- if (Be(e))
+ if (ze(e))
return e.getRunKind() === "cat" && j(e.getParent()) ? e.getParent() ?? void 0 : void 0;
const t = e.getParent();
- return Be(t) ? t.getRunKind() === "cat" && j(t.getParent()) ? t.getParent() ?? void 0 : void 0 : vp(e) ? Sk(e) : void 0;
+ return ze(t) ? t.getRunKind() === "cat" && j(t.getParent()) ? t.getParent() ?? void 0 : void 0 : Up(e) ? jk(e) : void 0;
},
expectedPieces: (e) => {
if (!j(e) || e.getIsCollapsed() !== !1)
- return bt;
+ return Tt;
const t = e.getCategory();
- return t === void 0 ? bt : { wantsRun: !0, valueText: w + t };
+ return t === void 0 ? Tt : { wantsRun: !0, valueText: L + t };
},
- scanPieces: (e) => j(e) ? gp(e) : Sr,
+ scanPieces: (e) => j(e) ? Pp(e) : Pr,
graceSite: (e, t) => {
if (!j(e))
return !1;
if (!t.opener && !t.closer) {
- const r = Vi(e);
- return r !== void 0 && wc(r);
+ const r = Xi(e);
+ return r !== void 0 && Uc(r);
}
- return yo(t);
+ return bo(t);
},
settleScope: "owner",
deletionPolicy: "retokenize",
@@ -6812,64 +6937,64 @@ const Mk = {
glyphs: "with-value",
glyphMarker: () => "cat",
closerSyntax: "closing",
- insertRunAfter: (e) => j(e) ? Vi(e) : void 0
+ insertRunAfter: (e) => j(e) ? Xi(e) : void 0
}
};
-function Ek(e) {
- return Be(e) ? e.getRunKind() === "ca" || e.getRunKind() === "cp" : P(e) ? e.getMarker() === "ca" || e.getMarker() === "cp" : M(e) && re(e, oe) === "attribute";
+function Vk(e) {
+ return ze(e) ? e.getRunKind() === "ca" || e.getRunKind() === "cp" : O(e) ? e.getMarker() === "ca" || e.getMarker() === "cp" : v(e) && ne(e, oe) === "attribute";
}
-function Ak(e) {
- if (P(e)) {
+function Wk(e) {
+ if (O(e)) {
const n = e.getMarker();
return n === "ca" || n === "cp" ? n : void 0;
}
- if (!M(e) || re(e, oe) !== "attribute")
+ if (!v(e) || ne(e, oe) !== "attribute")
return;
const t = e.getPreviousSibling();
- if (!P(t))
+ if (!O(t))
return;
const r = t.getMarker();
return r === "ca" || r === "cp" ? r : void 0;
}
-function Pk(e) {
+function Hk(e) {
const t = e.getParent();
if (!$e(t))
return;
- const r = ho(t);
+ const r = go(t);
if (r)
for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) {
if (n.is(r))
return t;
- if (!Ek(n))
+ if (!Vk(n))
return;
}
}
-function su(e) {
- const t = (r) => $e(r) ? e === "ca" ? ho(r) : yp(r) : void 0;
+function _u(e) {
+ const t = (r) => $e(r) ? e === "ca" ? go(r) : Op(r) : void 0;
return {
kind: e,
ownerPredicate: (r) => $e(r),
ownerOf: (r) => {
- if (Be(r))
+ if (ze(r))
return r.getRunKind() === e && $e(r.getParent()) ? r.getParent() ?? void 0 : void 0;
const n = r.getParent();
- return Be(n) ? n.getRunKind() === e && $e(n.getParent()) ? n.getParent() ?? void 0 : void 0 : Ak(r) === e ? Pk(r) : void 0;
+ return ze(n) ? n.getRunKind() === e && $e(n.getParent()) ? n.getParent() ?? void 0 : void 0 : Wk(r) === e ? Hk(r) : void 0;
},
expectedPieces: (r) => {
if (!$e(r))
- return bt;
+ return Tt;
const n = e === "ca" ? r.getAltnumber() : r.getPubnumber();
- return n === void 0 ? bt : { wantsRun: !0, valueText: w + n };
+ return n === void 0 ? Tt : { wantsRun: !0, valueText: L + n };
},
- scanPieces: (r) => $e(r) ? e === "ca" ? mp(r) : bp(r) : Sr,
+ scanPieces: (r) => $e(r) ? e === "ca" ? Np(r) : wp(r) : Pr,
graceSite: (r, n) => {
if (!$e(r))
return !1;
if (!n.opener && !n.closer) {
const i = t(r);
- return i !== void 0 && wc(i);
+ return i !== void 0 && Uc(i);
}
- return yo(n);
+ return bo(n);
},
settleScope: "owner",
deletionPolicy: "retokenize",
@@ -6883,47 +7008,47 @@ function su(e) {
}
};
}
-function Sp(e) {
- if (P(e)) {
+function Fp(e) {
+ if (O(e)) {
const t = e.getMarkerSyntax();
return t === "selfClosing" || t === "opening";
}
- return M(e) && re(e, oe) === "attribute";
+ return v(e) && ne(e, oe) === "attribute";
}
-function Nk(e) {
+function Gk(e) {
for (let t = e.getPreviousSibling(); t; t = t.getPreviousSibling()) {
- if (je(t)) {
- const r = P(e) && e.getMarkerSyntax() === "opening" ? e : void 0;
+ if (Be(t)) {
+ const r = O(e) && e.getMarkerSyntax() === "opening" ? e : void 0;
return !r || r.getMarker() === t.getMarker() ? t : void 0;
}
- if (!Sp(t))
+ if (!Fp(t))
return;
}
}
-const Ok = {
+const Jk = {
kind: "milestone",
- ownerPredicate: (e) => je(e),
+ ownerPredicate: (e) => Be(e),
ownerOf: (e) => {
- const t = Be(e) ? e.getRunKind() === "milestone" ? e : void 0 : Be(e.getParent()) ? e.getParent() : Sp(e) ? e : void 0;
- if (!t || Be(t) && t.getRunKind() !== "milestone")
+ const t = ze(e) ? e.getRunKind() === "milestone" ? e : void 0 : ze(e.getParent()) ? e.getParent() : Fp(e) ? e : void 0;
+ if (!t || ze(t) && t.getRunKind() !== "milestone")
return;
const r = t.getPreviousSibling();
- return Be(t) ? je(r) ? r : void 0 : Nk(t);
+ return ze(t) ? Be(r) ? r : void 0 : Gk(t);
},
expectedPieces: (e) => {
- if (!je(e))
- return bt;
- const t = pp(e.getSid(), e.getEid(), e.getUnknownAttributes(), e.getAttributeOrder()), r = tr(t, lo(e.getMarker()));
- return { wantsRun: !0, valueText: r === "" ? void 0 : w + r };
+ if (!Be(e))
+ return Tt;
+ const t = Ep(e.getSid(), e.getEid(), e.getUnknownAttributes(), e.getAttributeOrder()), r = or(t, uo(e.getMarker()));
+ return { wantsRun: !0, valueText: r === "" ? void 0 : L + r };
},
scanPieces: (e) => {
- if (!je(e))
- return Sr;
- const { opening: t, attribute: r, closing: n, wrapper: i } = go(e);
+ if (!Be(e))
+ return Pr;
+ const { opening: t, attribute: r, closing: n, wrapper: i } = mo(e);
return { opener: t, value: r, closer: n, wrapper: i };
},
graceSite: (e, t) => {
- if (!je(e))
+ if (!Be(e))
return !1;
if (!t.opener && !t.closer) {
const r = R();
@@ -6935,7 +7060,7 @@ const Ok = {
const s = e.getNextSibling();
return s !== null && n.is(s) && r.anchor.offset === 0;
}
- return yo(t);
+ return bo(t);
},
settleScope: "owner",
deletionPolicy: "remove-owner",
@@ -6943,17 +7068,17 @@ const Ok = {
writer: "wrapper",
runKind: "milestone",
glyphs: "unconditional",
- glyphMarker: (e) => je(e) ? e.getMarker() : "",
+ glyphMarker: (e) => Be(e) ? e.getMarker() : "",
closerSyntax: "selfClosing",
insertRunAfter: (e) => e
}
-}, wk = Cp("optbreak", void 0, void 0).opening, qk = {
+}, Yk = Dp("optbreak", void 0, void 0).opening, Xk = {
kind: "optbreak",
ownerPredicate: (e) => Le(e) && e.getTag() === "optbreak",
ownerOf: (e) => {
const t = e.getParent();
if (!(!Le(t) || t.getTag() !== "optbreak"))
- return M(e) || Wt(e) ? t : void 0;
+ return v(e) || Jt(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
@@ -6962,13 +7087,13 @@ const Ok = {
// 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: wk }),
- scanPieces: (e) => Le(e) ? { value: e.getFirstChild() ?? void 0 } : Sr,
+ expectedPieces: () => ({ wantsRun: !0, valueText: Yk }),
+ scanPieces: (e) => Le(e) ? { value: e.getFirstChild() ?? void 0 } : Pr,
graceSite: () => !1,
settleScope: "owner",
deletionPolicy: "remove-owner",
byteFormat: { writer: "read-only", glyphs: "none" }
-}, Rk = {
+}, Qk = {
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
@@ -6981,80 +7106,80 @@ const Ok = {
ownerPredicate: (e) => Le(e) && e.getTag() !== "optbreak",
ownerOf: () => {
},
- expectedPieces: () => bt,
- scanPieces: () => Sr,
+ expectedPieces: () => Tt,
+ scanPieces: () => Pr,
graceSite: () => !1,
settleScope: "owner",
deletionPolicy: "none",
byteFormat: { writer: "read-only", glyphs: "none" }
-}, $k = {
+}, Zk = {
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) => $(e),
+ ownerPredicate: (e) => D(e),
ownerOf: () => {
},
- expectedPieces: () => bt,
- scanPieces: () => Sr,
+ expectedPieces: () => Tt,
+ scanPieces: () => Pr,
graceSite: () => !1,
settleScope: "none",
deletionPolicy: "none",
byteFormat: { writer: "kind-owned", glyphs: "none" }
-}, Wi = [
- Ck,
- vk,
- iu("va"),
- iu("vp"),
- Mk,
- su("ca"),
- su("cp"),
- Ok,
- qk,
- Rk,
- $k
-], Ik = new Map(Wi.map((e) => [e.kind, e]));
-function yn(e) {
- const t = Ik.get(e);
+}, Qi = [
+ zk,
+ Kk,
+ xu("va"),
+ xu("vp"),
+ Bk,
+ _u("ca"),
+ _u("cp"),
+ Jk,
+ Xk,
+ Qk,
+ Zk
+], eT = new Map(Qi.map((e) => [e.kind, e]));
+function Tn(e) {
+ const t = eT.get(e);
if (!t)
throw new Error(`No display-run descriptor registered for kind "${e}"`);
return t;
}
-function bn(e) {
- for (const t of Wi) {
+function xn(e) {
+ for (const t of Qi) {
const r = t.ownerOf(e);
if (r)
return { owner: r, kind: t.kind };
}
}
-function Mp(e) {
- return bn(e) !== void 0;
+function zp(e) {
+ return xn(e) !== void 0;
}
-const Ds = "unmatched", Ep = 2;
-function Ri(e) {
+const Ks = "unmatched", Kp = 2;
+function Di(e) {
return `\\${e}`;
}
-class Mr extends ze {
+class Nr extends Ke {
__marker;
constructor(t = "", r) {
- super(Ri(t), r), this.__marker = t, this.__mode = 1;
+ super(Di(t), r), this.__marker = t, this.__mode = 1;
}
static getType() {
return "unmatched";
}
static clone(t) {
const { __marker: r, __key: n } = t;
- return new Mr(r, n);
+ return new Nr(r, n);
}
static importDOM() {
return {
- [Ds]: (t) => Dk(t) ? {
- conversion: Lk,
+ [Ks]: (t) => rT(t) ? {
+ conversion: tT,
priority: 1
} : null
};
}
static importJSON(t) {
- return qc().updateFromJSON(t);
+ return Fc().updateFromJSON(t);
}
updateFromJSON(t) {
const r = t.marker ?? "", i = super.updateFromJSON({
@@ -7063,7 +7188,7 @@ class Mr extends ze {
format: t.format ?? 0,
mode: t.mode ?? "token",
style: t.style ?? "",
- text: t.text ?? Ri(r)
+ text: t.text ?? Di(r)
}).getWritable();
return i.__marker = r, i;
}
@@ -7071,29 +7196,29 @@ class Mr extends ze {
if (this.__marker === t)
return this;
const r = this.getWritable();
- return r.__marker = t, r.__text = Ri(t), r;
+ return r.__marker = t, r.__text = Di(t), r;
}
getMarker() {
return this.getLatest().__marker;
}
createDOM(t) {
const r = super.createDOM(t);
- return r.setAttribute("data-marker", this.__marker), r.classList.add(Bl), r.title = ou(this.__marker), r;
+ return r.setAttribute("data-marker", this.__marker), r.classList.add(nu), r.title = Cu(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 = ou(this.__marker)), i;
+ return t.__marker !== this.__marker && (r.setAttribute("data-marker", this.__marker), r.title = Cu(this.__marker)), i;
}
exportDOM() {
- const t = document.createElement(Ds);
- return t.setAttribute("data-marker", this.getMarker()), t.classList.add(Bl), t.textContent = this.getTextContent(), { element: t };
+ const t = document.createElement(Ks);
+ return t.setAttribute("data-marker", this.getMarker()), t.classList.add(nu), t.textContent = this.getTextContent(), { element: t };
}
exportJSON() {
return {
...super.exportJSON(),
type: this.getType(),
marker: this.getMarker(),
- version: Ep
+ version: Kp
};
}
canInsertTextBefore() {
@@ -7103,27 +7228,27 @@ class Mr extends ze {
return !1;
}
}
-function Ap(e) {
- return e.getTextContent() === Ri(e.getMarker());
+function jp(e) {
+ return e.getTextContent() === Di(e.getMarker());
}
-function ou(e) {
+function Cu(e) {
return e.endsWith("*") ? "This closing marker has no matching opening marker!" : "This opening marker has no matching closing marker!";
}
-function Lk(e) {
+function tT(e) {
const t = e.getAttribute("data-marker") ?? "";
- return { node: qc(t) };
+ return { node: Fc(t) };
}
-function qc(e) {
- return Ke(new Mr(e));
+function Fc(e) {
+ return je(new Nr(e));
}
-function Dk(e) {
- return e?.tagName.toLowerCase() === Ds;
+function rT(e) {
+ return e?.tagName.toLowerCase() === Ks;
}
-function Hr(e) {
- return e instanceof Mr;
+function Qr(e) {
+ return e instanceof Nr;
}
-const Pp = "table", Pa = "immutable-table", Np = 1, Uk = ["type", "marker", "content"];
-class An extends Jt {
+const Bp = "table", Pa = "immutable-table", Vp = 1, nT = ["type", "marker", "content"];
+class Nn extends Qt {
__unknownAttributes;
constructor(t, r) {
super(r), this.__unknownAttributes = t;
@@ -7132,10 +7257,10 @@ class An extends Jt {
return Pa;
}
static clone(t) {
- return new An(t.__unknownAttributes, t.__key);
+ return new Nn(t.__unknownAttributes, t.__key);
}
static importJSON(t) {
- return Fk().updateFromJSON(t);
+ return iT().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setUnknownAttributes(t.unknownAttributes);
@@ -7160,7 +7285,7 @@ class An extends Jt {
...super.exportJSON(),
type: Pa,
...t !== void 0 && { unknownAttributes: t },
- version: Np
+ version: Vp
};
}
// Shadow root: isolate selection so content doesn't merge across the table boundary.
@@ -7168,30 +7293,30 @@ class An extends Jt {
return !0;
}
}
-function Fk(e) {
- return Ke(new An(e));
+function iT(e) {
+ return je(new Nn(e));
}
-function Op(e) {
- return e instanceof An;
+function Wp(e) {
+ return e instanceof Nn;
}
-function zk(e) {
+function sT(e) {
return e?.type === Pa;
}
-const wp = "table:row", au = "immutable-table-row", qp = 1, Na = "tr", Kk = ["type", "marker", "content"];
-class oi extends Jt {
+const Hp = "table:row", Su = "immutable-table-row", Gp = 1, Na = "tr", oT = ["type", "marker", "content"];
+class di extends Qt {
__marker;
__unknownAttributes;
constructor(t = Na, r, n) {
super(n), this.__marker = t, this.__unknownAttributes = r;
}
static getType() {
- return au;
+ return Su;
}
static clone(t) {
- return new oi(t.__marker, t.__unknownAttributes, t.__key);
+ return new di(t.__marker, t.__unknownAttributes, t.__key);
}
static importJSON(t) {
- return jk().updateFromJSON(t);
+ return aT().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setMarker(t.marker ?? Na).setUnknownAttributes(t.unknownAttributes);
@@ -7223,27 +7348,27 @@ class oi extends Jt {
const t = this.getUnknownAttributes();
return {
...super.exportJSON(),
- type: au,
+ type: Su,
marker: this.getMarker(),
...t !== void 0 && { unknownAttributes: t },
- version: qp
+ version: Gp
};
}
}
-function jk(e, t) {
- return Ke(new oi(e, t));
+function aT(e, t) {
+ return je(new di(e, t));
}
-const Rp = "table:cell", cu = "immutable-table-cell", $p = 1, Oa = "tc1", Bk = [
+const Jp = "table:cell", vu = "immutable-table-cell", Yp = 1, Oa = "tc1", cT = [
"type",
"marker",
"align",
"colspan",
"content"
];
-function Vk(e) {
+function lT(e) {
return e === "start" || e === "center" || e === "end" ? e : void 0;
}
-class ai extends Jt {
+class fi extends Qt {
__marker;
__align;
__colspan;
@@ -7252,14 +7377,14 @@ class ai extends Jt {
super(s), this.__marker = t, this.__align = r, this.__colspan = n, this.__unknownAttributes = i;
}
static getType() {
- return cu;
+ return vu;
}
static clone(t) {
const { __marker: r, __align: n, __colspan: i, __unknownAttributes: s, __key: o } = t;
- return new ai(r, n, i, s, o);
+ return new fi(r, n, i, s, o);
}
static importJSON(t) {
- return Wk().updateFromJSON(t);
+ return uT().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setMarker(t.marker ?? Oa).setAlign(t.align).setColspan(t.colspan).setUnknownAttributes(t.unknownAttributes);
@@ -7301,7 +7426,7 @@ class ai extends Jt {
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 = Vk(this.__align);
+ const n = lT(this.__align);
return n && (r.style.textAlign = n), this.__colspan && r.setAttribute("colspan", this.__colspan), r;
}
updateDOM(t) {
@@ -7311,215 +7436,215 @@ class ai extends Jt {
const t = this.getAlign(), r = this.getColspan(), n = this.getUnknownAttributes();
return {
...super.exportJSON(),
- type: cu,
+ type: vu,
marker: this.getMarker(),
...t !== void 0 && { align: t },
...r !== void 0 && { colspan: r },
...n !== void 0 && { unknownAttributes: n },
- version: $p
+ version: Yp
};
}
}
-function Wk(e, t, r, n) {
- return Ke(new ai(e, t, r, n));
+function uT(e, t, r, n) {
+ return je(new fi(e, t, r, n));
}
-function bo(e, t) {
+function ko(e, t) {
const r = e.getChildAtIndex(t);
- return M(r) ? r : void 0;
+ return v(r) ? r : void 0;
}
-function Gt(e, t) {
- const r = bo(e, t);
+function Xt(e, t) {
+ const r = ko(e, t);
r ? r.select(0, 0) : e.select(t, t);
}
-function Hi(e) {
+function Zi(e) {
return e.getUnknownAttributes()?.closed !== "false";
}
-function Hk(e) {
- return e.getChildren().some((t) => P(t) && t.getMarkerSyntax() === "closing");
+function dT(e) {
+ return e.getChildren().some((t) => O(t) && t.getMarkerSyntax() === "closing");
}
-function Gk(e) {
- return Hi(e) ? void 0 : { closed: "false" };
+function fT(e) {
+ return Zi(e) ? void 0 : { closed: "false" };
}
-function Jk(e, t, r, n) {
- const i = t.getMarker(), s = Nc(t), o = Hk(t);
+function pT(e, t, r, n) {
+ const i = t.getMarker(), s = Lc(t), o = dT(t);
if (n) {
e.append(ot(i, "opening", s));
const [a] = r;
- mo(a) && !a.getTextContent().startsWith(w) && a.setTextContent(w + a.getTextContent());
+ yo(a) && !a.getTextContent().startsWith(L) && a.setTextContent(L + a.getTextContent());
}
e.append(...r), o && e.append(ot(i, "closing", s));
}
-function kn(e) {
- return nt(e, $) ?? void 0;
+function _n(e) {
+ return nt(e, D) ?? void 0;
}
-function Rc(e) {
+function zc(e) {
let t = e.getParent();
- for (; $(t); )
+ for (; D(t); )
t = t.getParent();
return t;
}
function wa(e) {
- const t = Ip(e);
- return e.getChildren().every((r) => P(r) || t && re(r, oe) === "attribute" || M(r) && r.getTextContent().replaceAll(w, "") === "");
+ const t = Xp(e);
+ return e.getChildren().every((r) => O(r) || t && ne(r, oe) === "attribute" || v(r) && r.getTextContent().replaceAll(L, "") === "");
}
-function Ip(e) {
- return Hi(e);
+function Xp(e) {
+ return Zi(e);
}
-function Yk(e, t) {
- const r = e.getUnknownAttributes(), n = r ? tr(r, ao(e.getMarker())) : "";
- n !== "" && t.insertAfter(he(n)), e.remove();
+function hT(e, t) {
+ const r = e.getUnknownAttributes(), n = r ? or(r, lo(e.getMarker())) : "";
+ n !== "" && t.insertAfter(pe(n)), e.remove();
}
-function Xk(e, t) {
- if (Hi(e))
+function gT(e, t) {
+ if (Zi(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", Nc(e)));
+ t && e.append(ot(e.getMarker(), "closing", Lc(e)));
}
-function Qk(e, t) {
- return $(e) && !Hi(e) && !Hi(t);
+function mT(e, t) {
+ return D(e) && !Zi(e) && !Zi(t);
}
-function Zk(e, t, r) {
+function yT(e, t, r) {
wa(e) && e.getChildren().forEach((i) => {
- P(i) || i.remove();
+ O(i) || i.remove();
});
const [n] = t;
- r && mo(n) && !n.getTextContent().startsWith(w) && n.setTextContent(w + n.getTextContent()), e.append(...t);
+ r && yo(n) && !n.getTextContent().startsWith(L) && n.setTextContent(L + n.getTextContent()), e.append(...t);
}
-function eT(e, t, r) {
- const { renderGlyphs: n, closeImplicitSpans: i = !1 } = r, s = Ip(t), o = [];
+function bT(e, t, r) {
+ const { renderGlyphs: n, closeImplicitSpans: i = !1 } = r, s = Xp(t), o = [];
for (let l = e.getNextSibling(); l; ) {
- const u = l.getNextSibling(), d = P(l) && l.getMarkerSyntax() === "closing", f = s && re(l, oe) === "attribute";
+ const u = l.getNextSibling(), d = O(l) && l.getMarkerSyntax() === "closing", f = s && ne(l, oe) === "attribute";
!d && !f && o.push(l), l = u;
}
- const a = Qk(e, t);
+ const a = mT(e, t);
t.insertAfter(e);
let c = e;
if (o.length > 0)
if (a)
- Zk(e, o, n);
+ yT(e, o, n);
else {
- const l = yr(t.getMarker(), Gk(t));
- Jk(l, t, o, n), e.insertAfter(l), wa(l) ? l.remove() : c = l;
+ const l = xr(t.getMarker(), fT(t));
+ pT(l, t, o, n), e.insertAfter(l), wa(l) ? l.remove() : c = l;
}
- i && !a && Xk(t, n), wa(t) && Yk(t, c);
+ i && !a && gT(t, n), wa(t) && hT(t, c);
}
-function Qn(e, t) {
+function ri(e, t) {
let r = e.getParent();
- for (; $(r); )
- eT(e, r, t), r = e.getParent();
+ for (; D(r); )
+ bT(e, r, t), r = e.getParent();
}
-function $c(e) {
- if (M(e) && !P(e)) {
- const t = e.getTextContent().startsWith(w) ? 1 : 0;
+function Kc(e) {
+ if (v(e) && !O(e)) {
+ const t = e.getTextContent().startsWith(L) ? 1 : 0;
e.select(t, t);
return;
}
- if (D(e)) {
- const t = e.getChildren().find((r) => !P(r));
+ if (F(e)) {
+ const t = e.getChildren().find((r) => !O(r));
if (t) {
- $c(t);
+ Kc(t);
return;
}
e.selectEnd();
}
}
-const Jn = /* @__PURE__ */ new WeakMap();
-function tT(e, t) {
- return Jn.set(e, t), () => {
- Jn.get(e) === t && Jn.delete(e);
+const Yn = /* @__PURE__ */ new WeakMap();
+function kT(e, t) {
+ return Yn.set(e, t), () => {
+ Yn.get(e) === t && Yn.delete(e);
};
}
-function lu(e) {
- return Jn.get(e);
+function Mu(e) {
+ return Yn.get(e);
}
-function rT(e) {
- return Jn.get(ss())?.has(e.getKey()) ?? !1;
+function TT(e) {
+ return Yn.get(Xn())?.has(e.getKey()) ?? !1;
}
-function nT(e) {
- Jn.get(ss())?.add(e.getKey());
+function xT(e) {
+ Yn.get(Xn())?.add(e.getKey());
}
-function iT(e) {
+function _T(e) {
return !!(e.opener || e.value || e.closer || e.wrapper);
}
function qa(e) {
return !!(e.opener || e.value || e.closer);
}
-function uu(e) {
+function Eu(e) {
return /^\s/.test(e);
}
-function Ic(e, t) {
+function jc(e, t) {
if (e === t)
return !1;
- if (e === void 0 || t === void 0 || !uu(t) || !uu(e))
+ if (e === void 0 || t === void 0 || !Eu(t) || !Eu(e))
return !0;
const r = t.trim();
return r === "" || e.trim() !== r;
}
-function ko(e, t, r) {
- return r.wantsRun ? Ic(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 : iT(t);
+function To(e, t, r) {
+ return r.wantsRun ? jc(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 : _T(t);
}
-function sT(e, t) {
+function CT(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 Ic(n.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!n.opener || e.byteFormat.closerSyntax !== "none" && !n.closer) ? !1 : n.wrapper === void 0;
+ return jc(n.value?.getTextContent(), r.valueText) || e.byteFormat.glyphs !== "none" && (!n.opener || e.byteFormat.closerSyntax !== "none" && !n.closer) ? !1 : n.wrapper === void 0;
}
-function Lp(e, t) {
+function Qp(e, t) {
return !qa(e.scanPieces(t));
}
-function us(e, t) {
+function gs(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 (!ko(e, n, r))
+ if (!To(e, n, r))
return !1;
const i = R();
if (!N(i) || !i.isCollapsed())
return !1;
const s = i.anchor.getNode(), { wrapper: o, value: a } = n;
- return o && (s.is(o) || Ls(s, o.getKey())) ? !0 : a ? s.is(a) : e.graceSite(t, n);
+ return o && (s.is(o) || zs(s, o.getKey())) ? !0 : a ? s.is(a) : e.graceSite(t, n);
}
-function oT(e, t, r, n) {
- return !r.wantsRun || qa(n) || zm(Fi) ? !1 : ss().getEditorState().read(() => {
- const i = ne(t.getKey());
+function ST(e, t, r, n) {
+ return !r.wantsRun || qa(n) || sy(Vi) ? !1 : Xn().getEditorState().read(() => {
+ const i = se(t.getKey());
return !i || !e.ownerPredicate(i) ? !1 : qa(e.scanPieces(i));
});
}
-function aT(e) {
+function vT(e) {
e.opener?.remove(), e.value?.remove(), e.closer?.remove();
}
-function du(e) {
- const t = he(e);
- return mt(t, oe, "attribute"), t;
+function Au(e) {
+ const t = pe(e);
+ return yt(t, oe, "attribute"), t;
}
-function cT(e, t, r) {
+function MT(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 = up(n);
+ const o = Sp(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 lT(e, t, r, n) {
+function ET(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)
return;
- M(r.value) ? r.value.setTextContent(n.valueText) : f.insertBefore(du(n.valueText));
+ v(r.value) ? r.value.setTextContent(n.valueText) : f.insertBefore(Au(n.valueText));
return;
}
- const l = cT(e, t, r);
+ const l = MT(e, t, r);
if (!l || s === "none" || !o || !a)
return;
const u = r.opener ?? (() => {
@@ -7527,108 +7652,108 @@ function lT(e, t, r, n) {
return p ? p.insertBefore(f) : l.append(f), f;
})();
let d = r.value;
- n.valueText === void 0 ? (d?.remove(), d = void 0) : M(d) ? Ic(d.getTextContent(), n.valueText) && d.setTextContent(n.valueText) : (d = du(n.valueText), u.insertAfter(d)), a !== "none" && !r.closer && (d ?? u).insertAfter(ot(a === "selfClosing" ? "" : o(t), a));
+ n.valueText === void 0 ? (d?.remove(), d = void 0) : v(d) ? jc(d.getTextContent(), n.valueText) && d.setTextContent(n.valueText) : (d = Au(n.valueText), u.insertAfter(d)), a !== "none" && !r.closer && (d ?? u).insertAfter(ot(a === "selfClosing" ? "" : o(t), a));
}
-function Gi(e, t) {
+function es(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 (ko(e, i, n) && !rT(t)) {
- if (oT(e, t, n, i)) {
- nT(t);
+ if (To(e, i, n) && !TT(t)) {
+ if (ST(e, t, n, i)) {
+ xT(t);
return;
}
- if (!us(e, t)) {
+ if (!gs(e, t)) {
if (!n.wantsRun) {
- aT(i);
+ vT(i);
return;
}
- lT(e, t, i, n);
+ ET(e, t, i, n);
}
}
}
-function uT(e, t, r) {
- Gi(e, t), t.isAttached() && us(e, t) && r.add(t.getKey());
+function AT(e, t, r) {
+ es(e, t), t.isAttached() && gs(e, t) && r.add(t.getKey());
}
-function Dp(e) {
- if (!M(e))
+function Zp(e) {
+ if (!v(e))
return !1;
- if (P(e) || Ne(e) || Hr(e))
+ if (O(e) || Pe(e) || Qr(e))
return !0;
- const t = re(e, oe);
- return t === "attribute" || t === sr;
+ const t = ne(e, oe);
+ return t === "attribute" || t === fr;
}
-function Lc(e, t) {
- return P(e) ? !(t === e.getTextContentSize() && e.getMarkerSyntax() !== "opening" && Wr(e) && $(e.getParent())) : !1;
+function Bc(e, t) {
+ return O(e) ? !(t === e.getTextContentSize() && e.getMarkerSyntax() !== "opening" && Xr(e) && D(e.getParent())) : !1;
}
-function dT() {
+function PT() {
const e = R();
- return N(e) ? Lc(e.focus.getNode(), e.focus.offset) : !1;
+ return N(e) ? Bc(e.focus.getNode(), e.focus.offset) : !1;
}
-function Up(e) {
+function eh(e) {
if (e.type !== "text")
return;
const t = e.getNode();
- return M(t) && Dp(t) ? t : void 0;
+ return v(t) && Zp(t) ? t : void 0;
}
-function fT(e) {
- const t = Up(e);
+function NT(e) {
+ const t = eh(e);
if (t)
return e.offset > 0 && e.offset < t.getTextContentSize() ? t : void 0;
}
-function pT(e) {
- const t = Up(e);
+function OT(e) {
+ const t = eh(e);
if (t)
return e.offset === 0 || e.offset === t.getTextContentSize() ? t : void 0;
}
-function fu(e) {
+function Pu(e) {
return { key: e.key, offset: e.offset, type: e.type };
}
-function pu(e, t) {
+function Nu(e, t) {
e.set(t.key, t.offset, t.type);
}
-function hT(e, t) {
- let r = pT(e);
+function wT(e, t) {
+ let r = OT(e);
for (; r; ) {
const n = t === "next" ? r.getNextSibling() : r.getPreviousSibling();
- if (!M(n))
+ if (!v(n))
return;
- if (!Dp(n))
+ if (!Zp(n))
return { node: n, offset: t === "next" ? 0 : n.getTextContentSize() };
r = n;
}
}
-function hu(e, t) {
- const r = hT(e, t);
+function Ou(e, t) {
+ const r = wT(e, t);
return r ? (e.set(r.node.getKey(), r.offset, "text"), !0) : !1;
}
-function Fp(e) {
+function th(e) {
if (e.isCollapsed()) {
- const a = fT(e.anchor);
+ const a = NT(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 = [fu(r), fu(n)], s = hu(r, "next"), o = hu(n, "previous");
- return !s && !o ? !1 : e.isCollapsed() || e.isBackward() !== t ? (pu(r, i[0]), pu(n, i[1]), !1) : !0;
+ const t = e.isBackward(), r = t ? e.focus : e.anchor, n = t ? e.anchor : e.focus, i = [Pu(r), Pu(n)], s = Ou(r, "next"), o = Ou(n, "previous");
+ return !s && !o ? !1 : e.isCollapsed() || e.isBackward() !== t ? (Nu(r, i[0]), Nu(n, i[1]), !1) : !0;
}
-const Us = "verse-block", zp = 1, gT = "verse-block";
-class ci extends Jt {
+const js = "verse-block", rh = 1, qT = "verse-block";
+class pi extends Qt {
/** 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 Us;
+ return js;
}
static clone(t) {
- return new ci(t.__number, t.__key);
+ return new pi(t.__number, t.__key);
}
static importJSON(t) {
- return mT().updateFromJSON(t);
+ return RT().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setNumber(t.number);
@@ -7644,14 +7769,14 @@ class ci extends Jt {
}
/** The first and last verse numbers this block covers. A bridge covers more than one. */
getRange() {
- return op(this.getNumber());
+ return Tp(this.getNumber());
}
createDOM() {
const t = document.createElement("div");
- return t.classList.add(gT), gu(t, this.__number), t;
+ return t.classList.add(qT), wu(t, this.__number), t;
}
updateDOM(t, r) {
- return t.__number !== this.__number && gu(r, this.__number), !1;
+ return t.__number !== this.__number && wu(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
@@ -7673,48 +7798,48 @@ class ci extends Jt {
exportJSON() {
return {
...super.exportJSON(),
- type: Us,
+ type: js,
number: this.getNumber(),
- version: zp
+ version: rh
};
}
canBeEmpty() {
return !1;
}
}
-function gu(e, t) {
- const { start: r, end: n } = op(t), i = !isNaN(r) && !isNaN(n) && r <= n;
- e.setAttribute("data-verse-number", t), mu(e, "data-verse-start", i ? r : NaN), mu(e, "data-verse-end", i ? n : NaN);
+function wu(e, t) {
+ const { start: r, end: n } = Tp(t), i = !isNaN(r) && !isNaN(n) && r <= n;
+ e.setAttribute("data-verse-number", t), qu(e, "data-verse-start", i ? r : NaN), qu(e, "data-verse-end", i ? n : NaN);
}
-function mu(e, t, r) {
+function qu(e, t, r) {
isNaN(r) ? e.removeAttribute(t) : e.setAttribute(t, r.toString());
}
-function mT(e) {
- return Ke(new ci(e));
-}
-function Ji(e) {
- return e instanceof ci;
-}
-function yT(e) {
- return e?.type === Us;
-}
-const bT = [
- Lt,
- or,
- Et,
- ft,
- ye,
- Me,
- Vt,
- ar,
- Mn,
- _r,
+function RT(e) {
+ return je(new pi(e));
+}
+function ts(e) {
+ return e instanceof pi;
+}
+function $T(e) {
+ return e?.type === js;
+}
+const IT = [
+ Ut,
+ pr,
+ Nt,
+ dt,
+ me,
+ Ee,
+ Gt,
+ hr,
+ An,
Mr,
+ Nr,
Qe,
- zr,
- An,
- oi,
- ai,
+ Vr,
+ Nn,
+ di,
+ fi,
// 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
@@ -7722,13 +7847,13 @@ const bT = [
// 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).
- vr,
+ Ar,
{
- replace: lc,
- with: () => jt(),
- withKlass: zr
+ replace: pc,
+ with: () => Wt(),
+ withKlass: Vr
}
-], Fs = {
+], Bs = {
markers: {
id: {
marker: "id",
@@ -14768,15 +14893,15 @@ const bT = [
fontSize: 12
}
}
-}, kT = {
+}, LT = {
paragraph: b.Paragraph,
character: b.Character,
note: b.Note,
milestone: b.Milestone
};
-function TT(e) {
+function DT(e) {
if (!e)
- return nr;
+ return lr;
const t = /* @__PURE__ */ new Map();
return (r) => {
if (t.has(r))
@@ -14785,25 +14910,25 @@ function TT(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: nr(r)?.category ?? k.Uncategorized,
- type: kT[n.styleType] ?? b.Unknown,
+ category: lr(r)?.category ?? T.Uncategorized,
+ type: LT[n.styleType] ?? b.Unknown,
description: n.description ?? "",
hasEndMarker: !!n.endMarker,
- children: nr(r)?.children
+ children: lr(r)?.children
} : void 0;
return t.set(r, i), i;
};
}
-function yu(e, t, r) {
+function Ru(e, t, r) {
const n = {
- type: hr,
- version: pr,
+ type: kr,
+ version: br,
content: e
}, i = t.serializeEditorState(n, r);
- return uo(i.root.children[0]) ? i.root.children[0].children[0] : i.root.children[0];
+ return fo(i.root.children[0]) ? i.root.children[0].children[0] : i.root.children[0];
}
-const Kp = "v", jp = 1, xT = "verse-selected";
-class xt extends ns {
+const nh = "v", ih = 1, UT = "verse-selected";
+class Ct extends ls {
__marker;
__number;
__showMarker;
@@ -14812,25 +14937,25 @@ class xt extends ns {
__pubnumber;
__unknownAttributes;
constructor(t = "", r = !1, n, i, s, o, a) {
- super(a), this.__marker = Kp, this.__number = t, this.__showMarker = r, this.__sid = n, this.__altnumber = i, this.__pubnumber = s, this.__unknownAttributes = o;
+ super(a), this.__marker = nh, 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 xt(r, n, i, s, o, a, c);
+ return new Ct(r, n, i, s, o, a, c);
}
static importDOM() {
return {
- span: (t) => vT(t) ? {
- conversion: CT,
+ span: (t) => KT(t) ? {
+ conversion: zT,
priority: 1
} : null
};
}
static importJSON(t) {
- return Dc().updateFromJSON(t);
+ return Vc().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);
@@ -14905,14 +15030,14 @@ class xt extends ns {
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(ka, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r };
+ return r && En(r) && (r.setAttribute("data-marker", this.getMarker()), r.classList.add(ka, `usfm_${this.getMarker()}`), r.setAttribute("data-number", this.getNumber())), { element: r };
}
decorate() {
- const t = this.getShowMarker() ? $t(this.getMarker(), this.getNumber()) : (
+ const t = this.getShowMarker() ? Lt(this.getMarker(), this.getNumber()) : (
// ZWSP added so double click word selection works without including this number.
- Ns + this.getNumber() + Ns
+ $s + this.getNumber() + $s
);
- return C(_T, { nodeKey: this.getKey(), text: t });
+ return C(FT, { nodeKey: this.getKey(), text: t });
}
exportJSON() {
return {
@@ -14924,14 +15049,14 @@ class xt extends ns {
altnumber: this.getAltnumber(),
pubnumber: this.getPubnumber(),
unknownAttributes: this.getUnknownAttributes(),
- version: jp
+ version: ih
};
}
isSelected(t) {
try {
return super.isSelected(t);
} catch (r) {
- if (ip(r))
+ if (bp(r))
return !1;
throw r;
}
@@ -14941,172 +15066,172 @@ class xt extends ns {
return !1;
}
}
-function _T({ nodeKey: e, text: t }) {
- const [r] = iy(e);
- return C("span", { className: r ? xT : void 0, children: t });
+function FT({ nodeKey: e, text: t }) {
+ const [r] = My(e);
+ return C("span", { className: r ? UT : void 0, children: t });
}
-function CT(e) {
+function zT(e) {
const t = e.getAttribute("data-number") ?? "0";
- return { node: Dc(t) };
+ return { node: Vc(t) };
}
-function Dc(e, t, r, n, i, s) {
- return Ke(new xt(e, t, r, n, i, s));
+function Vc(e, t, r, n, i, s) {
+ return je(new Ct(e, t, r, n, i, s));
}
-function vT(e) {
- return (e?.getAttribute("data-marker") ?? void 0) === Kp;
+function KT(e) {
+ return (e?.getAttribute("data-marker") ?? void 0) === nh;
}
-function Pn(e) {
- return e instanceof xt;
+function On(e) {
+ return e instanceof Ct;
}
-function ST(e) {
- return e?.type === xt.getType();
+function jT(e) {
+ return e?.type === Ct.getType();
}
-function me(e) {
- return Ne(e) || Pn(e);
+function ge(e) {
+ return Pe(e) || On(e);
}
-function Bp(e) {
- return Yf(e) || ST(e);
+function sh(e) {
+ return up(e) || jT(e);
}
-function MT(e) {
- return ET(e).find((t) => se(t));
+function BT(e) {
+ return VT(e).find((t) => ae(t));
}
-function ET(e) {
- return e.some(Ji) ? e.flatMap((t) => Ji(t) ? t.getChildren() : t) : e;
+function VT(e) {
+ return e.some(ts) ? e.flatMap((t) => ts(t) ? t.getChildren() : t) : e;
}
-function To(e) {
- return D(e) ? Ji(e) ? e.getChildren().flatMap(To) : e.getChildren() : [];
+function xo(e) {
+ return F(e) ? ts(e) ? e.getChildren().flatMap(xo) : e.getChildren() : [];
}
-function AT(e, t) {
- return To(e).find((i) => me(i) && Mc(t, i.getNumber()));
+function WT(e, t) {
+ return xo(e).find((i) => ge(i) && qc(t, i.getNumber()));
}
-function PT(e, t) {
- return t === 0 ? MT(e) : e.map((r) => AT(r, t)).filter((r) => r)[0];
+function HT(e, t) {
+ return t === 0 ? BT(e) : e.map((r) => WT(r, t)).filter((r) => r)[0];
}
-function zs(e) {
- return To(e).find((r) => me(r));
+function Vs(e) {
+ return xo(e).find((r) => ge(r));
}
-function Vp(e, t) {
- if (!D(e) || t <= 0)
+function oh(e, t) {
+ if (!F(e) || t <= 0)
return;
const r = e.getChildren();
for (let n = t - 1; n >= 0; n--) {
const i = r[n];
- if (me(i))
+ if (ge(i))
return i;
}
}
-function NT(e) {
+function GT(e) {
const t = e.getParent();
- if (t && D(t)) {
+ if (t && F(t)) {
const n = t.getChildren();
for (let i = e.getIndexWithinParent() + 1; i < n.length; i++) {
const s = n[i];
- if (me(s))
+ if (ge(s))
return s;
}
}
let r = t?.getNextSibling();
for (; r && !We(r); ) {
- const n = zs(r);
+ const n = Vs(r);
if (n)
return n;
r = r.getNextSibling();
}
}
function Ra(e) {
- return To(e).findLast((t) => me(t));
+ return xo(e).findLast((t) => ge(t));
}
-function OT(e) {
- if (!Ne(e))
+function JT(e) {
+ if (!Pe(e))
return 0;
const t = e.getNumber();
return e.getTextContent().startsWith(t) ? t.length : 0;
}
-function wT(e, t, r) {
+function YT(e, t, r) {
if (!r)
return !1;
const n = t.getParent();
- if (r === n && D(r)) {
+ if (r === n && F(r)) {
const i = t.getIndexWithinParent();
return e.anchor.offset <= i;
}
return r.getNextSibling() === t;
}
-function qT(e, t) {
+function XT(e, t) {
const r = t.anchor.getNode();
if (r !== e)
- return wT(t, e, r);
- if (M(e)) {
- const n = OT(e);
+ return YT(t, e, r);
+ if (v(e)) {
+ const n = JT(e);
return t.anchor.offset < n;
}
return !0;
}
-function bu(e) {
+function $u(e) {
const t = e.getNumber(), r = Number.parseInt(t ?? "0", 10);
return {
verseNum: r,
verse: t != null && r.toString() !== t ? t : void 0
};
}
-function RT(e, t) {
+function QT(e, t) {
if (!e)
return { verseNum: 0 };
if (!N(t))
- return bu(e);
+ return $u(e);
const r = Number.parseInt(e.getNumber() ?? "0", 10), n = r <= 1 ? 0 : r - 1;
- return qT(e, t) ? { verseNum: n } : bu(e);
+ return XT(e, t) ? { verseNum: n } : $u(e);
}
-function $T(e) {
- return Fb(e) || Pn(e);
+function ZT(e) {
+ return sk(e) || On(e);
}
-function Uc(e) {
- if (M(e)) {
+function Wc(e) {
+ if (v(e)) {
const t = e.getTextContent();
- !t.endsWith(" ") && !t.endsWith(w) && e.setTextContent(`${t} `);
+ !t.endsWith(" ") && !t.endsWith(L) && e.setTextContent(`${t} `);
}
}
-function Wp(e) {
- if (M(e)) {
+function ah(e) {
+ if (v(e)) {
const t = e.getTextContent();
t.startsWith(" ") && e.setTextContent(t.trimStart());
}
}
-function Hp(e, t) {
- return e.getEditorState().read(() => !ne(t));
+function $a(e, t) {
+ return e.getEditorState().read(() => !se(t));
}
-function IT(e) {
+function ex(e) {
if (!e.isCollapsed())
return !1;
- const t = e.anchor.getNode(), r = Fc(t, e);
+ const t = e.anchor.getNode(), r = Hc(t, e);
let n;
if (r) {
const i = r.getParent();
- if (i && D(i) && D(t) && t === i && e.anchor.offset < r.getIndexWithinParent() && (n = r), !n && i && D(i)) {
+ if (i && F(i) && F(t) && t === i && e.anchor.offset < r.getIndexWithinParent() && (n = r), !n && i && F(i)) {
const s = i.getChildren(), o = r.getIndexWithinParent();
for (let a = o + 1; a < s.length; a++) {
const c = s[a];
- if (me(c)) {
+ if (ge(c)) {
n = c;
break;
}
}
}
if (!n && i) {
- let s = ku(i);
+ let s = Iu(i);
for (; s && !We(s); ) {
- const o = zs(s);
+ const o = Vs(s);
if (o) {
n = o;
break;
}
- s = ku(s);
+ s = Iu(s);
}
}
} else {
let s = t.getTopLevelElement() ?? t;
for (; s; ) {
- const o = zs(s);
+ const o = Vs(s);
if (o) {
n = o;
break;
@@ -15117,22 +15242,22 @@ function IT(e) {
}
return n ? (n.selectNext(0, 0), !0) : !1;
}
-function LT(e) {
+function tx(e) {
if (!e.isCollapsed())
return !1;
- const t = e.anchor.getNode(), r = Fc(t, e);
+ const t = e.anchor.getNode(), r = Hc(t, e);
let n;
if (r) {
const i = r.getParent(), s = t.getTopLevelElement();
- if (i && s && s !== i.getTopLevelElement() && (n = r), !n && i && D(i) && (n = Vp(i, r.getIndexWithinParent())), !n && i) {
- let o = Tu(i);
+ if (i && s && s !== i.getTopLevelElement() && (n = r), !n && i && F(i) && (n = oh(i, r.getIndexWithinParent())), !n && i) {
+ let o = Lu(i);
for (; o && !We(o); ) {
const a = Ra(o);
if (a) {
n = a;
break;
}
- o = Tu(o);
+ o = Lu(o);
}
}
} else {
@@ -15148,69 +15273,69 @@ function LT(e) {
}
return n ? (n.selectNext(0, 0), !0) : !1;
}
-function ku(e) {
+function Iu(e) {
const t = e.getNextSibling();
if (t)
return t;
const r = e.getTopLevelElement();
return r && r !== e ? r.getNextSibling() : null;
}
-function Tu(e) {
+function Lu(e) {
const t = e.getPreviousSibling();
if (t)
return t;
const r = e.getTopLevelElement();
return r && r !== e ? r.getPreviousSibling() : null;
}
-function Fc(e, t) {
- if (D(e) && N(t) && t.anchor.key === e.getKey()) {
+function Hc(e, t) {
+ if (F(e) && N(t) && t.anchor.key === e.getKey()) {
const n = e.getChildAtIndex(t.anchor.offset);
- if (n && me(n))
+ if (n && ge(n))
return n;
- const i = Vp(e, t.anchor.offset);
+ const i = oh(e, t.anchor.offset);
if (i)
return i;
- const s = zs(e);
+ const s = Vs(e);
if (s)
return s;
}
- return zc(e);
+ return Gc(e);
}
-function zc(e) {
+function Gc(e) {
if (!e || We(e))
return;
- if (me(e))
+ if (ge(e))
return e;
- let t = tu(e);
+ let t = bu(e);
for (; t; ) {
if (We(t))
return;
- if (me(t))
+ if (ge(t))
return t;
const r = Ra(t);
if (r)
return r;
- t = tu(t);
+ t = bu(t);
}
}
-const DT = ["style"], UT = ["style", "code"], Ks = ["style", "cid"], FT = [
+const rx = ["style"], nx = ["style", "code"], Ws = ["style", "cid"], ix = [
"style",
"number",
"sid",
"altnumber",
"pubnumber"
-], zT = [
+], sx = [
"style",
"number",
"sid",
"altnumber",
"pubnumber"
-], KT = [
+], ox = [
"style",
"sid",
"eid",
"attributeOrder"
-], jT = ["style", "caller", "category", "contents"], BT = ["tag", "marker", "contents"], VT = [
+], ax = ["style", "caller", "category", "contents"], cx = ["tag", "marker", "contents"], lx = [
"chapter",
"immutable-chapter",
"verse",
@@ -15219,32 +15344,32 @@ const DT = ["style"], UT = ["style", "code"], Ks = ["style", "cid"], FT = [
"note",
"unknown",
"unmatched"
-], Yi = `
+], rs = `
`;
-function WT(e, t) {
- const r = ne(e);
- if (!Mt(r))
+function ux(e, t) {
+ const r = se(e);
+ if (!Pt(r))
return;
- const n = Gp(r, "apply");
+ const n = ch(r, "apply");
return n === void 0 ? void 0 : [{ retain: n }, ...t, { delete: 1 }];
}
-function Gp(e, t = "delta-doc") {
+function ch(e, t = "delta-doc") {
if (!e)
return;
- const r = cf();
+ const r = _f();
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 (Zn(i[d], c)) {
+ if (ni(i[d], c)) {
const f = i[d];
if (i.splice(d, 1), n += 1, a && f.getKey() === a.getKey())
return n - 1;
}
for (let d = s.length - 1; d >= 0; d--)
- Zn(s[d].node, c) && s.splice(d, 1);
+ ni(s[d].node, c) && s.splice(d, 1);
const u = s[s.length - 1];
if (u) {
if (l.getKey() === o)
@@ -15252,56 +15377,56 @@ function Gp(e, t = "delta-doc") {
continue;
}
if (l.getKey() === o) {
- if (br(l) || Mt(l))
+ if (_r(l) || Pt(l))
return n;
- vt(l) && (a = l);
+ Et(l) && (a = l);
}
- if (vt(l) && (i.includes(l) || i.push(l)), Jp(l, t)) {
+ if (Et(l) && (i.includes(l) || i.push(l)), lh(l, t)) {
if (l.getKey() === o)
return n;
s.push({ node: l, position: n }), n += 1;
continue;
}
- n += Kc(l, t);
+ n += Jc(l, t);
}
if (a)
return n;
}
-function xu(e, t, r = "delta-doc") {
- if (e.length < 2 || !JT(e[0]) || !GT(e[1]))
+function Du(e, t, r = "delta-doc") {
+ if (e.length < 2 || !px(e[0]) || !fx(e[1]))
return;
const n = e[0].retain;
- return t.read(() => HT(n, r)?.getKey());
+ return t.read(() => dx(n, r)?.getKey());
}
-function HT(e, t = "delta-doc") {
- const r = cf();
+function dx(e, t = "delta-doc") {
+ const r = _f();
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 (Zn(i[u], o)) {
+ if (ni(i[u], o)) {
const d = i[u];
if (i.splice(u, 1), n === e)
return d;
n += 1;
}
for (let u = s.length - 1; u >= 0; u--)
- Zn(s[u].node, o) && s.splice(u, 1);
+ ni(s[u].node, o) && s.splice(u, 1);
const c = s[s.length - 1];
if (c) {
if (c.position === e)
return c.node;
continue;
}
- if (vt(a) && (i.includes(a) || i.push(a)), Jp(a, t)) {
+ if (Et(a) && (i.includes(a) || i.push(a)), lh(a, t)) {
if (n === e)
return a;
s.push({ node: a, position: n }), n += 1;
continue;
}
- const l = Kc(a, t);
- if (br(a) && l > 0 && e >= n && e < n + l || Mt(a) && n === e)
+ const l = Jc(a, t);
+ if (_r(a) && l > 0 && e >= n && e < n + l || Pt(a) && n === e)
return a;
n += l;
}
@@ -15311,56 +15436,56 @@ function HT(e, t = "delta-doc") {
n += 1;
}
}
-function Zn(e, t) {
- return e ? t ? !Ls(t.node, e.getKey()) : !0 : !1;
+function ni(e, t) {
+ return e ? t ? !zs(t.node, e.getKey()) : !0 : !1;
}
-function br(e) {
- return M(e) && !Mt(e);
+function _r(e) {
+ return v(e) && !Pt(e);
}
-function Mt(e) {
- return We(e) || me(e) || je(e) || j(e) || Le(e) || Hr(e);
+function Pt(e) {
+ return We(e) || ge(e) || Be(e) || j(e) || Le(e) || Qr(e);
}
-function $r(e, t) {
+function Ur(e, t) {
return t?.insert != null && typeof t.insert == "object" && e in t.insert;
}
-function GT(e) {
+function fx(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 && VT.includes(t);
+ return e.insert != null && typeof e.insert == "object" && t in e.insert && lx.includes(t);
}
-function JT(e) {
+function px(e) {
return e.retain != null && typeof e.retain == "number";
}
-function Jp(e, t) {
- return j(e) || Le(e) ? !0 : t === "apply" && D(e) && Mt(e);
+function lh(e, t) {
+ return j(e) || Le(e) ? !0 : t === "apply" && F(e) && Pt(e);
}
-function Yp(e) {
+function uh(e) {
const t = e.getParent();
- return Dt(e) && se(t) && t.getFirstChild() === e;
+ return Ft(e) && ae(t) && t.getFirstChild() === e;
}
-function $a(e) {
+function Ia(e) {
const t = e.getParent();
- return t !== null && nt(t, Be) !== null;
+ return t !== null && nt(t, ze) !== null;
}
-function YT(e) {
+function hx(e) {
const t = e.getParent();
- return $(t) && e.getTextContent() === It && t.getChildrenSize() === 1;
+ return D(t) && e.getTextContent() === Dt && t.getChildrenSize() === 1;
}
-function XT(e) {
+function gx(e) {
const t = e.getParent();
if (!j(t))
return !1;
const r = e.getPreviousSibling();
- return P(r) && r === t.getFirstChild() && e.getTextContent() === St(t.getCaller());
+ return O(r) && r === t.getFirstChild() && e.getTextContent() === At(t.getCaller());
}
-function QT(e) {
- return !Mp(e) && Kc(e, "delta-doc") === e.getTextContentSize();
+function mx(e) {
+ return !zp(e) && Jc(e, "delta-doc") === e.getTextContentSize();
}
-function Kc(e, t) {
- if (Mt(e))
+function Jc(e, t) {
+ if (Pt(e))
return 1;
- if (M(e)) {
+ if (v(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
@@ -15370,25 +15495,25 @@ function Kc(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.
- (Cc(e) || Yp(e) || re(e, oe) === "marker-trailing-space" || // An attribute value keyed by its own state, not by ancestry: a CHAR span's run is a direct
+ (Nc(e) || uh(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
// 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.
- re(e, oe) === "attribute" || $a(e) || // The remaining ops-stream exclusions, so this side and $handleTextNodes count the same
+ ne(e, oe) === "attribute" || Ia(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(gc) || YT(e) || XT(e)) ? 0 : e.getTextContentSize();
+ r.startsWith(xc) || hx(e) || gx(e)) ? 0 : e.getTextContentSize();
}
return 0;
}
-function Ia(e, t) {
- const r = { insert: e.__text }, n = re(e, Fr);
+function La(e, t) {
+ const r = { insert: e.__text }, n = ne(e, Br);
if (n && (r.attributes = { segment: n }), t && t.length > 0) {
- const i = Xp(t);
+ const i = dh(t);
i && (r.attributes = {
...r.attributes,
char: i
@@ -15396,178 +15521,178 @@ function Ia(e, t) {
}
return r;
}
-function _u(e) {
- const t = new Ai();
+function Uu(e) {
+ const t = new wi();
return e.isEmpty() || e.read(() => {
const r = Ue();
if (!r || r.isEmpty())
return;
const n = r.getChildren();
- if (n.length === 1 && ir(n[0]) && (!n[0].getChildren() || n[0].getChildrenSize() === 0))
+ if (n.length === 1 && dr(n[0]) && (!n[0].getChildren() || n[0].getChildrenSize() === 0))
return;
- const i = ZT();
+ const i = yx();
for (const s of i)
t.push(s);
}), t;
}
-function jc(e, t) {
- const r = [], n = ii(e, t), i = [], s = [], o = [], a = /* @__PURE__ */ new Set();
+function Yc(e, t) {
+ const r = [], n = li(e, t), i = [], s = [], o = [], a = /* @__PURE__ */ new Set();
for (let c = 0; c < n.length; c++) {
const l = n[c].node;
- r.push(...Cu(l, c, n, i, s, o, a));
+ r.push(...Fu(l, c, n, i, s, o, a));
}
for (const c of i)
- r.push(...Cu(c, n.length, n, i, s, o, a));
+ r.push(...Fu(c, n.length, n, i, s, o, a));
return r;
}
-function ZT() {
- return jc();
+function yx() {
+ return Yc();
}
-function Cu(e, t, r, n, i, s, o) {
+function Fu(e, t, r, n, i, s, o) {
if (!e)
return [];
const a = [], c = r[t + 1];
- return ex(e, a, n), tx(e, a, i, s, o), rx(e, t, r, i, o, s, a), We(e) && a.push(ox(e)), me(e) && a.push(cx(e)), je(e) && a.push(lx(e)), Hr(e) && a.push(ux(e)), ix(e, a, s), nx(e, a, s), hx(c, s), a;
+ return bx(e, a, n), kx(e, a, i, s, o), Tx(e, t, r, i, o, s, a), We(e) && a.push(Sx(e)), ge(e) && a.push(Mx(e)), Be(e) && a.push(Ex(e)), Qr(e) && a.push(Ax(e)), _x(e, a, s), xx(e, a, s), wx(c, s), a;
}
-function ex(e, t, r) {
+function bx(e, t, r) {
if (!e.isInline()) {
const n = r.pop();
- Tt(n) ? t.push(sx(n)) : se(n) ? t.push(ax(n)) : ir(n) && t.push({ insert: Yi });
+ _t(n) ? t.push(Cx(n)) : ae(n) ? t.push(vx(n)) : dr(n) && t.push({ insert: rs });
}
- vt(e) && (r.includes(e) || r.push(e));
+ Et(e) && (r.includes(e) || r.push(e));
}
-function tx(e, t, r, n, i) {
- if (!M(e) || Ne(e) || Hr(e))
+function kx(e, t, r, n, i) {
+ if (!v(e) || Pe(e) || Qr(e))
return;
const s = e.getParent();
if (j(s) && s.getFirstChild() === e)
return;
- const o = Ht(e) !== void 0;
- if (P(e) && (o || Yp(e) || $a(e) || Mp(e)) || re(e, oe) === "marker-trailing-space")
+ const o = Yt(e) !== void 0;
+ if (O(e) && (o || uh(e) || Ia(e) || zp(e)) || ne(e, oe) === "marker-trailing-space")
return;
let a = e.getTextContent();
- if (cs(a))
+ if (ps(a))
return;
const c = e.getPreviousSibling();
- if (j(s) && P(c) && c === s.getFirstChild() && a === St(s.getCaller()))
+ if (j(s) && O(c) && c === s.getFirstChild() && a === At(s.getCaller()))
return;
- const l = $(s) ? s : void 0, u = l?.getFirstChild();
- o && l && P(u) && c === u && a.startsWith(w) && (a = a.slice(1));
- const d = a.startsWith(gc) || re(e, oe) === "attribute" || $a(e), f = !!l && a === It && l.getChildrenSize() === 1, p = xo(e, n), m = p ? r.filter((T) => p.children.includes(T)) : r, g = Ia(e, m);
+ const l = D(s) ? s : void 0, u = l?.getFirstChild();
+ o && l && O(u) && c === u && a.startsWith(L) && (a = a.slice(1));
+ const d = a.startsWith(xc) || ne(e, oe) === "attribute" || Ia(e), f = !!l && a === Dt && l.getChildrenSize() === 1, p = _o(e, n), m = p ? r.filter((k) => p.children.includes(k)) : r, g = La(e, m);
if (g.insert = a, p) {
- if (!a || a === w || d)
+ if (!a || a === L || d)
return;
p.contentsOps?.push(g);
} else
f || d || t.push(g);
const y = a !== "" && !f && !(d && l);
if (r.length > 0 && y)
- for (const T of r)
- i.add(T);
+ for (const k of r)
+ i.add(k);
}
-function rx(e, t, r, n, i, s, o) {
- $(e) && !n.includes(e) && n.push(e);
+function Tx(e, t, r, n, i, s, o) {
+ D(e) && !n.includes(e) && n.push(e);
const a = r[t + 1];
for (const c of n.toReversed())
- if (Zn(c, a)) {
+ if (ni(c, a)) {
if (n.pop(), !i.has(c)) {
- const l = fx(c), u = xo(c, s);
+ const l = Nx(c), u = _o(c, s);
u ? u.contentsOps?.push(l) : o.push(l);
}
i.delete(c);
}
}
-function nx(e, t, r) {
+function xx(e, t, r) {
if (!j(e))
return;
- const n = dx(e), i = xo(e, r), s = {
+ const n = Px(e), i = _o(e, r), s = {
node: e,
- children: ii(e).map((o) => o.node),
+ children: li(e).map((o) => o.node),
contentsOps: n.insert.note?.contents?.ops
};
r.push(s), i?.contentsOps ? i.contentsOps.push(n) : t.push(n);
}
-function ix(e, t, r) {
+function _x(e, t, r) {
if (!Le(e))
return;
- const n = px(e), i = xo(e, r), s = {
+ const n = Ox(e), i = _o(e, r), s = {
node: e,
- children: ii(e).map((o) => o.node),
+ children: li(e).map((o) => o.node),
contentsOps: n.insert.unknown?.contents?.ops
};
r.push(s), i?.contentsOps ? i.contentsOps.push(n) : t.push(n);
}
-function Gr(e, t) {
+function Zr(e, t) {
const r = t.getUnknownAttributes();
r && Object.assign(e, r);
}
-function sx(e) {
- const t = { style: Ki, code: e.__code };
- return Gr(t, e), { insert: Yi, attributes: { book: t } };
+function Cx(e) {
+ const t = { style: Gi, code: e.__code };
+ return Zr(t, e), { insert: rs, attributes: { book: t } };
}
-function ox(e) {
- const t = { style: $s, number: e.__number };
- return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), Gr(t, e), { insert: { chapter: t } };
+function Sx(e) {
+ const t = { style: Us, 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 ax(e) {
+function vx(e) {
const t = { style: e.__marker };
- return Gr(t, e), { insert: Yi, attributes: { para: t } };
+ return Zr(t, e), { insert: rs, attributes: { para: t } };
}
-function cx(e) {
- const t = { style: Is, number: e.__number };
- return e.__sid && (t.sid = e.__sid), e.__altnumber && (t.altnumber = e.__altnumber), e.__pubnumber && (t.pubnumber = e.__pubnumber), Gr(t, e), { insert: { verse: t } };
+function Mx(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: { verse: t } };
}
-function lx(e) {
+function Ex(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), Gr(t, e), { insert: { milestone: t } };
+ 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 } };
}
-function ux(e) {
+function Ax(e) {
return { insert: { unmatched: { marker: e.__marker } } };
}
-function dx(e) {
+function Px(e) {
const t = {
style: e.__marker,
caller: e.__caller
};
- e.__category && (t.category = e.__category), Gr(t, e), e.getChildrenSize() > 1 && (t.contents = { ops: [] });
- const r = { insert: { note: t } }, n = re(e, Fr);
+ e.__category && (t.category = e.__category), Zr(t, e), e.getChildrenSize() > 1 && (t.contents = { ops: [] });
+ const r = { insert: { note: t } }, n = ne(e, Br);
return n && (r.attributes = { segment: n }), r;
}
-function fx(e) {
- const t = { insert: "" }, r = Xp([e]);
+function Nx(e) {
+ const t = { insert: "" }, r = dh([e]);
return r && (t.attributes = { char: r }), t;
}
-function px(e) {
+function Ox(e) {
const t = { tag: e.getTag() }, r = e.getMarker();
- return r && (t.marker = r), Gr(t, e), e.getChildrenSize() > 0 && (t.contents = { ops: [] }), { insert: { unknown: t } };
+ return r && (t.marker = r), Zr(t, e), e.getChildrenSize() > 0 && (t.contents = { ops: [] }), { insert: { unknown: t } };
}
-function xo(e, t) {
+function _o(e, t) {
for (let r = t.length - 1; r >= 0; r--) {
const n = t[r];
if (n.children.includes(e))
return n;
}
}
-function hx(e, t) {
+function wx(e, t) {
for (let r = t.length - 1; r >= 0; r--)
- Zn(t[r].node, e) && t.splice(r, 1);
+ ni(t[r].node, e) && t.splice(r, 1);
}
-function Xp(e) {
+function dh(e) {
if (e.length === 0)
return;
- const t = e.map(gx);
+ const t = e.map(qx);
return t.length === 1 ? t[0] : t;
}
-function gx(e) {
- const t = { style: e.__marker }, r = re(e, gn);
- return r && (t.cid = r), Gr(t, e), t;
+function qx(e) {
+ const t = { style: e.__marker }, r = ne(e, bn);
+ return r && (t.cid = r), Zr(t, e), t;
}
-const Qp = 1;
-class Bt extends ns {
+const fh = 1;
+class Ht extends ls {
__caller;
__previewText;
__onClick;
- constructor(t = Os, r = "", n, i) {
+ constructor(t = Bi, r = "", n, i) {
super(i), this.__caller = t, this.__previewText = r, this.__onClick = n ?? (() => {
});
}
@@ -15576,18 +15701,18 @@ class Bt extends ns {
}
static clone(t) {
const { __caller: r, __previewText: n, __onClick: i, __key: s } = t;
- return new Bt(r, n, i, s);
+ return new Ht(r, n, i, s);
}
static importDOM() {
return {
- span: (t) => yx(t) ? {
- conversion: mx,
+ span: (t) => $x(t) ? {
+ conversion: Rx,
priority: 1
} : null
};
}
static importJSON(t) {
- return Bc().updateFromJSON(t);
+ return Xc().updateFromJSON(t);
}
updateFromJSON(t) {
return super.updateFromJSON(t).setCaller(t.caller).setPreviewText(t.previewText).setOnClick(t.onClick);
@@ -15628,17 +15753,17 @@ class Bt extends ns {
}
exportDOM(t) {
const { element: r } = super.exportDOM(t);
- return r && Sn(r) && (r.classList.add(this.getType()), r.setAttribute("data-caller", this.getCaller()), r.setAttribute("data-preview-text", this.getPreviewText())), { element: r };
+ return r && En(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, () => bx(t, n), (l) => kx(t, n, s, l), () => Tx(t, n), () => xx(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 === Os && i ? (
+ const n = r.getKey(), i = r.getIsCollapsed(), s = this.__key, o = (c) => this.__onClick?.(c, n, i, () => Ix(t, n), (l) => Lx(t, n, s, l), () => Dx(t, n), () => Ux(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 ? (
// Caller is generated by CSS (footnote or cross-reference sequence, per note marker)
""
- ) : this.__caller === yf && i ? (
+ ) : this.__caller === Of && i ? (
// PT9: the hidden caller displays as `*` when collapsed
"*"
) : this.__caller });
@@ -15649,7 +15774,7 @@ class Bt extends ns {
caller: this.getCaller(),
previewText: this.getPreviewText(),
onClick: this.getOnClick(),
- version: Qp
+ version: fh
};
}
// Mutation
@@ -15657,51 +15782,51 @@ class Bt extends ns {
return !1;
}
}
-function mx(e) {
+function Rx(e) {
const t = e.getAttribute("data-caller") ?? "", r = e.getAttribute("data-preview-text") ?? "";
- return { node: Bc(t, r) };
+ return { node: Xc(t, r) };
}
-function Bc(e, t, r) {
- return Ke(new Bt(e, t, r));
+function Xc(e, t, r) {
+ return je(new Ht(e, t, r));
}
-function yx(e) {
- return e ? e.classList.contains(Bt.getType()) : !1;
+function $x(e) {
+ return e ? e.classList.contains(Ht.getType()) : !1;
}
-function cr(e) {
- return e instanceof Bt;
+function St(e) {
+ return e instanceof Ht;
}
-function bx(e, t) {
+function Ix(e, t) {
return e.getEditorState().read(() => {
- const r = ne(t);
+ const r = se(t);
if (!j(r))
throw new Error(`getNoteCaller: Note node not found: ${t}`);
return r.getCaller();
});
}
-function kx(e, t, r, n) {
+function Lx(e, t, r, n) {
e.update(() => {
- const i = ne(t);
+ const i = se(t);
if (!j(i))
throw new Error(`setNoteCaller: Note node not found: ${t}`);
i.setCaller(n);
- const s = ne(r);
- if (!cr(s))
+ const s = se(r);
+ if (!St(s))
throw new Error(`setNoteCaller: Caller node not found: ${r}`);
s.setCaller(n);
});
}
-function Tx(e, t) {
+function Dx(e, t) {
return e.getEditorState().read(() => {
- const r = ne(t);
+ const r = se(t);
if (!j(r))
throw new Error(`getNoteOps: Note node not found: ${t}`);
- return jc(r);
+ return Yc(r);
});
}
-function xx(e, t) {
+function Ux(e, t) {
return e.getEditorState().read(() => {
let r = 0;
- for (const { node: n } of ii())
+ for (const { node: n } of li())
if (j(n)) {
if (n.getKey() === t)
return r;
@@ -15709,7 +15834,7 @@ function xx(e, t) {
}
});
}
-const _x = [
+const Fx = [
"a",
"b",
"c",
@@ -15736,85 +15861,85 @@ const _x = [
"x",
"y",
"z"
-], Cx = ["†"];
-function Vc(e) {
- if (eh())
+], zx = ["†"];
+function Qc(e) {
+ if (hh())
return;
const { start: t } = e;
let { end: r } = e;
r ??= t;
- let [n, i] = vu(t), [s, o] = vu(r);
+ let [n, i] = zu(t), [s, o] = zu(r);
if (!n || !s || i === void 0 || o === void 0)
return;
- [n, i] = Su(n, i), [s, o] = Su(s, o);
- const a = uc();
- return a.anchor = zl(n.getKey(), i, Mu(n)), a.focus = zl(s.getKey(), o, Mu(s)), a;
+ [n, i] = Ku(n, i), [s, o] = Ku(s, o);
+ const a = hc();
+ return a.anchor = eu(n.getKey(), i, ju(n)), a.focus = eu(s.getKey(), o, ju(s)), a;
}
-function Zp() {
- if (eh())
+function ph() {
+ if (hh())
return;
const e = R();
if (!e || !N(e))
return;
- const t = e.isBackward() ? e.focus.getNode() : e.anchor.getNode(), r = e.isBackward() ? e.focus.offset : e.anchor.offset, n = js(t, r);
+ const t = e.isBackward() ? e.focus.getNode() : e.anchor.getNode(), r = e.isBackward() ? e.focus.offset : e.anchor.offset, n = Hs(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 = js(i, s);
+ const i = e.isBackward() ? e.anchor.getNode() : e.focus.getNode(), s = e.isBackward() ? e.anchor.offset : e.focus.offset, o = Hs(i, s);
return { start: n, end: o };
}
-function vu(e) {
- if (wm(e)) {
- const t = Hd(e.jsonPath);
+function zu(e) {
+ if (Ym(e)) {
+ const t = df(e.jsonPath);
let r = Ue();
for (let n = 0; n < t.length; n++) {
- if (!r || !D(r))
+ if (!r || !F(r))
return [void 0, void 0];
- const i = si(r)[t[n]];
+ const i = ui(r)[t[n]];
if (!i)
return [void 0, void 0];
if (i.type === "text")
- return n !== t.length - 1 ? [void 0, void 0] : ek(i, e.offset) ?? [void 0, void 0];
+ return n !== t.length - 1 ? [void 0, void 0] : kk(i, e.offset) ?? [void 0, void 0];
r = i.node;
}
- return r && D(r) ? [r, tk(r, e.offset)] : [void 0, void 0];
+ return r && F(r) ? [r, Tk(r, e.offset)] : [void 0, void 0];
}
- if (qm(e) || Rm(e)) {
- const t = xi(e.jsonPath);
+ if (Xm(e) || Qm(e)) {
+ const t = vi(e.jsonPath);
if (!t)
return [void 0, void 0];
- if (D(t)) {
+ if (F(t)) {
const n = t.getLastChild();
- if (n && M(n))
+ if (n && v(n))
return [n, n.getTextContent().length];
}
const r = t.getNextSibling();
- return r && D(r) ? [r, 0] : [void 0, void 0];
+ return r && F(r) ? [r, 0] : [void 0, void 0];
}
- if ($m(e)) {
- const t = xi(e.jsonPath);
+ if (Zm(e)) {
+ const t = vi(e.jsonPath);
if (!t)
return [void 0, void 0];
- if (D(t)) {
+ if (F(t)) {
const n = t.getLastChild();
- if (n && M(n))
+ if (n && v(n))
return [n, n.getTextContent().length];
}
const r = t.getNextSibling();
- return r && D(r) ? [r, 0] : [void 0, void 0];
+ return r && F(r) ? [r, 0] : [void 0, void 0];
}
- if (Im(e)) {
- const t = xi(e.jsonPath);
- if (!t || !D(t))
+ if (ey(e)) {
+ const t = vi(e.jsonPath);
+ if (!t || !F(t))
return [void 0, void 0];
const r = Qo(t, "opening");
if (r)
return [r, 0];
const n = t.getFirstChild();
- return n && M(n) ? [n, 0] : [void 0, void 0];
+ return n && v(n) ? [n, 0] : [void 0, void 0];
}
- if (Lm(e)) {
- const t = xi(e.jsonPath);
- if (!t || !D(t))
+ if (ty(e)) {
+ const t = vi(e.jsonPath);
+ if (!t || !F(t))
return [void 0, void 0];
const r = Qo(t, "closing");
if (r) {
@@ -15822,11 +15947,11 @@ function vu(e) {
return [r, s];
}
const n = t.getLastChild();
- return n && M(n) ? [n, n.getTextContent().length] : [void 0, void 0];
+ return n && v(n) ? [n, n.getTextContent().length] : [void 0, void 0];
}
- if (Dm(e)) {
- const t = e.jsonPath.match(/\.(\w+)$|^\$\.(\w+)$|\['([^']+)'\]$/), r = t?.[1] ?? t?.[2] ?? t?.[3], n = xi(e.jsonPath);
- if (!n || !D(n))
+ if (ry(e)) {
+ const t = e.jsonPath.match(/\.(\w+)$|^\$\.(\w+)$|\['([^']+)'\]$/), r = t?.[1] ?? t?.[2] ?? t?.[3], n = vi(e.jsonPath);
+ if (!n || !F(n))
return [void 0, void 0];
if (r === "marker") {
const s = Qo(n, "opening");
@@ -15836,51 +15961,51 @@ function vu(e) {
}
}
const i = n.getFirstChild();
- return i && M(i) ? [i, 0] : [void 0, void 0];
+ return i && v(i) ? [i, 0] : [void 0, void 0];
}
- throw new Error(`Unsupported UsjDocumentLocation type: ${Um(e)}. All UsjDocumentLocation subtypes should be supported: UsjMarkerLocation, UsjClosingMarkerLocation, UsjTextContentLocation, UsjPropertyValueLocation, UsjAttributeKeyLocation, UsjAttributeMarkerLocation, andUsjClosingAttributeMarkerLocation. Received: ${JSON.stringify(e)}`);
+ throw new Error(`Unsupported UsjDocumentLocation type: ${ny(e)}. All UsjDocumentLocation subtypes should be supported: UsjMarkerLocation, UsjClosingMarkerLocation, UsjTextContentLocation, UsjPropertyValueLocation, UsjAttributeKeyLocation, UsjAttributeMarkerLocation, andUsjClosingAttributeMarkerLocation. Received: ${JSON.stringify(e)}`);
}
-function Su(e, t) {
- if (!Cr(e))
+function Ku(e, t) {
+ if (!Er(e))
return [e, t];
const r = e.getTextContent().length;
if (t < 0 || t >= r)
return [e, t];
const n = e.getParent();
- if (!n || !D(n))
+ if (!n || !F(n))
return [e, t];
const i = e.getIndexWithinParent();
return i < 0 ? [e, t] : [n, i];
}
-function Mu(e) {
- return D(e) ? "element" : "text";
+function ju(e) {
+ return F(e) ? "element" : "text";
}
function Qo(e, t) {
const r = e.getChildren();
for (const n of r) {
- if (P(n) && n.getMarkerSyntax() === t || t === "closing" && P(n) && n.getMarkerSyntax() === "selfClosing")
+ if (O(n) && n.getMarkerSyntax() === t || t === "closing" && O(n) && n.getMarkerSyntax() === "selfClosing")
return n;
- if (Cr(n)) {
+ if (Er(n)) {
const s = n.getTextContent().endsWith("*");
if (t === "opening" && !s || t === "closing" && s)
return n;
}
}
}
-function xi(e) {
- const t = new RegExp(/^(\$(?:\.content\[\d+\])*)(?:\.|$|\[)/).exec(e), r = t ? t[1] : e, n = Hd(r);
+function vi(e) {
+ const t = new RegExp(/^(\$(?:\.content\[\d+\])*)(?:\.|$|\[)/).exec(e), r = t ? t[1] : e, n = df(r);
let i = Ue();
for (const s of n) {
- if (!i || !D(i))
+ if (!i || !F(i))
return;
- const o = si(i)[s];
+ const o = ui(i)[s];
i = o?.type === "element" ? o.node : void 0;
}
return i;
}
-function js(e, t) {
- if (P(e)) {
- const r = e.getMarkerSyntax(), n = vx(e), i = n ? en(on(n)) : en(on(e));
+function Hs(e, t) {
+ if (O(e)) {
+ const r = e.getMarkerSyntax(), n = Kx(e), i = n ? on(un(n)) : on(un(e));
if (r === "closing" || r === "selfClosing")
return {
jsonPath: i,
@@ -15896,144 +16021,144 @@ function js(e, t) {
propertyOffset: o
};
}
- if (_e(e)) {
+ if (Ce(e)) {
const r = e.getChildrenSize(), n = e.getChildAtIndex(Math.min(t, r - 1));
- if (M(n)) {
+ if (v(n)) {
const s = t >= r ? n.getTextContentSize() : 0;
- return js(n, s);
+ return Hs(n, s);
}
- const i = po(e);
+ const i = ho(e);
if (i?.is(e.getParent())) {
const s = e.getIndexWithinParent(), o = t >= r ? s + 1 : s;
- return js(i, o);
+ return Hs(i, o);
}
}
- if (D(e)) {
+ if (F(e)) {
const r = e.getChildAtIndex(t);
- if (Cr(r))
+ if (Er(r))
return {
- jsonPath: en(on(e))
+ jsonPath: on(un(e))
};
- const n = cp(e, t);
+ const n = _p(e, t);
return n.type === "text" ? {
- jsonPath: en([...on(e), n.index]),
+ jsonPath: on([...un(e), n.index]),
offset: n.offset
} : {
- jsonPath: en(on(e)),
+ jsonPath: on(un(e)),
offset: n.index
};
}
- if (M(e)) {
- const r = Zb(e, t);
+ if (v(e)) {
+ const r = bk(e, t);
if (r)
return {
- jsonPath: en([
- ...on(r.parent),
+ jsonPath: on([
+ ...un(r.parent),
r.index
]),
offset: r.offset
};
}
- return { jsonPath: en(on(e)), offset: t };
+ return { jsonPath: on(un(e)), offset: t };
}
-function vx(e) {
+function Kx(e) {
const t = e.getParent();
- if (!t || !D(t))
+ if (!t || !F(t))
return;
- const r = Sx(e);
- return r && !vt(r) && !M(r) && !_e(r) ? r : t;
+ const r = jx(e);
+ return r && !Et(r) && !v(r) && !Ce(r) ? r : t;
}
-function Sx(e) {
+function jx(e) {
let t = e.getPreviousSibling();
for (; t; ) {
- if (!Ec(t))
+ if (!Rc(t))
return t;
t = t.getPreviousSibling();
}
}
-function on(e) {
+function un(e) {
const t = [];
let r = e;
for (; r; ) {
- const n = po(r);
+ const n = ho(r);
if (!n)
break;
- const i = Qb(n, r);
+ const i = yk(n, r);
i >= 0 && t.unshift(i), r = n;
}
return t;
}
-function eh() {
+function hh() {
for (let e = Ue().getFirstChild(); e; e = e.getNextSibling())
- if (Ji(e))
+ if (ts(e))
return !0;
return !1;
}
-function th(e, t, r, n, i, s, o) {
- if (!Me.isValidMarker(e))
+function gh(e, t, r, n, i, s, o) {
+ if (!Ee.isValidMarker(e))
throw new Error(`$insertNote: Invalid note marker '${e}'`);
- const a = r ? Vc(r) : R();
+ const a = r ? Qc(r) : R();
if (!N(a))
return;
- const c = Ax(a, e, n, i, s, o);
+ const c = Wx(a, e, n, i, s, o);
if (c === void 0)
return;
- const l = t ?? (Pi(e) === "crossref" ? s.defaultCrossRefCaller ?? "-" : s.defaultFootnoteCaller ?? "+"), u = rh(e, l, c, i, s, void 0, void 0);
- return Ex(u, a, i), u;
+ const l = t ?? (qi(e) === "crossref" ? s.defaultCrossRefCaller ?? "-" : s.defaultFootnoteCaller ?? "+"), u = mh(e, l, c, i, s, void 0, void 0);
+ return Vx(u, a, i), u;
}
-function Wc(e) {
+function Zc(e) {
return e !== "expanded";
}
-function Mx(e) {
+function Bx(e) {
if (!e.isCollapsed())
return;
const { anchor: t } = e;
if (t.type !== "text")
return;
const r = t.getNode();
- if (!M(r) || !$(r.getParent()))
+ if (!v(r) || !D(r.getParent()))
return;
- if (P(r))
+ if (O(r))
return t.offset === 0 && r.getMarkerSyntax() === "closing" ? r : void 0;
if (t.offset !== r.getTextContentSize())
return;
const n = r.getNextSibling();
- return P(n) && n.getMarkerSyntax() === "closing" ? n : void 0;
+ return O(n) && n.getMarkerSyntax() === "closing" ? n : void 0;
}
-function Ex(e, t, r) {
- const n = Wc(r?.noteMode);
- e.setIsCollapsed(n), t.isCollapsed() || Bb(t), Fp(t);
- const i = Mx(t);
- i ? (i.insertBefore(e), e.selectNext(0, 0)) : t.insertNodes([e]), n || e.getChildren().reverse().find($)?.selectEnd();
+function Vx(e, t, r) {
+ const n = Zc(r?.noteMode);
+ e.setIsCollapsed(n), t.isCollapsed() || lk(t), th(t);
+ const i = Bx(t);
+ i ? (i.insertBefore(e), e.selectNext(0, 0)) : t.insertNodes([e]), n || e.getChildren().reverse().find(D)?.selectEnd();
}
-function zn(e, t, r) {
- const n = yr(e);
+function Kn(e, t, r) {
+ const n = xr(e);
n.setUnknownAttributes({ closed: "false" });
const i = r?.markerMode === "editable";
- i ? n.append(ot(e)) : r?.markerMode === "visible" && n.append(mr("marker", Oe(e)));
- const s = t === "" ? It : i ? w + t : t;
- return n.append(he(s)), n;
+ i ? n.append(ot(e)) : r?.markerMode === "visible" && n.append(Tr("marker", Ne(e)));
+ const s = t === "" ? Dt : i ? L + t : t;
+ return n.append(pe(s)), n;
}
-function Ax(e, t, r, n, i, s) {
+function Wx(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;
switch (t) {
case "f":
case "fe":
case "ef":
case "efe":
- if (f !== void 0 && o.push(zn("fr", f, n)), !e.isCollapsed()) {
- const p = Au(e);
- p.length > 0 && o.push(zn("fq", p, n));
+ if (f !== void 0 && o.push(Kn("fr", f, n)), !e.isCollapsed()) {
+ const p = Vu(e);
+ p.length > 0 && o.push(Kn("fq", p, n));
}
- o.push(zn("ft", "", n));
+ o.push(Kn("ft", "", n));
break;
case "x":
case "ex":
- if (f !== void 0 && o.push(zn("xo", f, n)), !e.isCollapsed()) {
- const p = Au(e);
- p.length > 0 && o.push(zn("xq", p, n));
+ if (f !== void 0 && o.push(Kn("xo", f, n)), !e.isCollapsed()) {
+ const p = Vu(e);
+ p.length > 0 && o.push(Kn("xq", p, n));
}
- o.push(zn("xt", "", n));
+ o.push(Kn("xt", "", n));
break;
default:
s?.warn(`$createNoteChildren: Unsupported note marker '${t}'`);
@@ -16041,45 +16166,45 @@ function Ax(e, t, r, n, i, s) {
}
return o;
}
-function rh(e, t, r, n, i, s, o) {
- const a = o === "false", c = a ? !1 : Wc(n?.noteMode), l = bc(e, t, c);
- s && mt(l, Fr, () => s);
+function mh(e, t, r, n, i, s, o) {
+ const a = o === "false", c = a ? !1 : Zc(n?.noteMode), l = Sc(e, t, c);
+ s && yt(l, Br, () => 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 = mr("marker", Oe(e) + " "), a || (f = mr("marker", rt(e))));
+ n?.markerMode === "editable" ? (d = ot(e), u && d.setMode("token"), a || (f = ot(e, "closing"))) : n?.markerMode === "visible" && (d = Tr("marker", Ne(e) + " "), a || (f = Tr("marker", rt(e))));
let p;
if (d && l.append(d), n?.markerMode === "editable" && !c)
- t === "" ? l.append(...r) : (p = he(St(l.__caller)), u && p.setMode("token"), l.append(p, ...r));
+ t === "" ? l.append(...r) : (p = pe(At(l.__caller)), u && p.setMode("token"), l.append(p, ...r));
else {
- const m = () => fo(), g = r.flatMap(Nx(m));
+ const m = () => po(), g = r.flatMap(Gx(m));
if (t === "")
l.append(...g);
else {
- const y = vc(r);
- let T = () => {
+ const y = Oc(r);
+ let k = () => {
};
- i?.noteCallerOnClick && (T = i.noteCallerOnClick), p = Bc(l.__caller, y, T), l.append(p, m(), ...g);
+ i?.noteCallerOnClick && (k = i.noteCallerOnClick), p = Xc(l.__caller, y, k), l.append(p, m(), ...g);
}
}
return f && l.append(f), l;
}
-function Eu(e) {
+function Bu(e) {
if (typeof e == "string") {
- const i = ne(e);
+ const i = se(e);
return j(i) ? i : void 0;
}
- const t = ii();
+ const t = li();
if (t.length <= 0)
return;
const n = t.filter((i) => j(i.node))[e]?.node;
if (j(n))
return n;
}
-function Px(e, t) {
+function Hx(e, t) {
const r = t?.noteMode === "collapsed";
if (e.setIsCollapsed(r), r) {
const n = e.getPreviousSibling();
- if (Pn(n) || !n) {
+ if (On(n) || !n) {
const i = e.getParent();
if (i) {
const s = e.getIndexWithinParent();
@@ -16088,44 +16213,44 @@ function Px(e, t) {
} else
n.selectEnd();
} else
- e.getChildren().reverse().find($)?.selectEnd();
+ e.getChildren().reverse().find(D)?.selectEnd();
}
-function Nx(e) {
- return (t) => Wt(t) ? [t] : [t, e()];
+function Gx(e) {
+ return (t) => Jt(t) ? [t] : [t, e()];
}
-function Ox(e) {
+function Jx(e) {
const t = e.getParent();
return t !== null && nt(t, j) !== null;
}
-function Au(e) {
+function Vu(e) {
if (!N(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] = Jd(e);
+ const r = t[0], n = t[t.length - 1], i = e.anchor.isBefore(e.focus), [s, o] = gc(e);
let a = "";
for (const c of t)
- if (!(j(c) || cr(c) || Ox(c)) && !P(c) && !Hr(c) && re(c, oe) !== "attribute") {
- if (me(c)) {
+ if (!(j(c) || St(c) || Jx(c)) && !O(c) && !Qr(c) && ne(c, oe) !== "attribute") {
+ if (ge(c)) {
a += `\\+fv ${c.getNumber()}\\+fv*`;
continue;
}
- if (M(c)) {
+ if (v(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 nh = [
- Bt,
- xt,
- ...bT
-], wx = [
- ci,
- ...nh
-], qx = vn((e, t) => {
+const yh = [
+ Ht,
+ Ct,
+ ...IT
+], Yx = [
+ pi,
+ ...yh
+], Xx = Mn((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: {
...i,
@@ -16137,28 +16262,28 @@ const nh = [
opacity: o ? 1 : 0
}, ...s, children: n });
});
-function Rx() {
- const [e, t] = de(void 0), [r, n] = de(), i = X(null), s = ge((a, c) => {
+function Qx() {
+ const [e, t] = de(void 0), [r, n] = de(), i = Z(null), s = he((a, c) => {
i.current && i.current();
const l = a.commonAncestorContainer.nodeType === a.commonAncestorContainer.TEXT_NODE ? a : a.commonAncestorContainer;
- i.current = hy(l, c, () => {
- gy(l, c, {
+ i.current = $y(l, c, () => {
+ Iy(l, c, {
placement: "bottom-start",
- middleware: [my(), yy()]
+ middleware: [Ly(), Dy()]
}).then((u) => {
n(u.placement), t((d) => d?.x === u.x && d?.y === u.y ? d : { x: u.x, y: u.y });
}).catch(() => {
t(void 0);
});
});
- }, []), o = ge(() => {
+ }, []), o = he(() => {
i.current && (t(void 0), i.current(), i.current = null);
}, []);
- return K(() => o, [o]), { coords: e, placement: r, updatePosition: s, cleanup: o };
+ return z(() => o, [o]), { coords: e, placement: r, updatePosition: s, cleanup: o };
}
-function $x({ isOpen: e, floatingBoxRef: t }) {
- const { coords: r, updatePosition: n, cleanup: i, placement: s } = Rx();
- return K(() => {
+function Zx({ isOpen: e, floatingBoxRef: t }) {
+ const { coords: r, updatePosition: n, cleanup: i, placement: s } = Qx();
+ return z(() => {
if (!e || !t.current) {
i();
return;
@@ -16171,42 +16296,42 @@ function $x({ isOpen: e, floatingBoxRef: t }) {
return n(o, t.current), i;
}, [i, e, t, n]), { coords: r, placement: s };
}
-const Ix = Mm(qx);
-function ih({ isOpen: e = !1, children: t }) {
- const r = X(null), { coords: n, placement: i } = $x({ isOpen: e, floatingBoxRef: r }), s = Fe(() => n ? typeof t == "function" ? t : () => t : () => null, [t, n]);
- return un(
- C(Ix, { ref: r, coords: n, style: n ? void 0 : { display: "none" }, children: s({ isOpen: e, placement: i }) }),
+const e_ = Bm(Xx);
+function bh({ isOpen: e = !1, children: t }) {
+ const r = Z(null), { coords: n, placement: i } = Zx({ isOpen: e, floatingBoxRef: r }), s = Fe(() => n ? typeof t == "function" ? t : () => t : () => null, [t, n]);
+ return hn(
+ C(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 sh = Vd(void 0);
-function Hc() {
- const e = Wd(sh);
+const kh = lf(void 0);
+function el() {
+ const e = uf(kh);
if (!e)
throw new Error("useMenuContext must be used within a MenuProvider");
return e;
}
-function Lx(e, t) {
+function t_(e, t) {
const [r, n] = de(0), [i, s] = de(-1), o = Fe(() => e ?? [], [e]), a = {
menuItems: o,
activeIndex: r,
selectedIndex: i,
onSelectOption: t ?? (() => {
})
- }, c = ge(() => {
+ }, c = he(() => {
n((d) => {
const f = o.length;
return f ? (d - 1 + f) % f : 0;
});
- }, [o.length]), l = ge(() => {
+ }, [o.length]), l = he(() => {
n((d) => {
const f = o.length;
return f ? (d + 1) % f : 0;
});
- }, [o.length]), u = ge(() => {
+ }, [o.length]), u = he(() => {
const d = o.length;
if (r >= 0 && r < d) {
const f = o[r];
@@ -16222,24 +16347,24 @@ function Lx(e, t) {
setSelectedIndex: s
};
}
-function Dx({ children: e, menuItems: t, onSelectOption: r, ...n }) {
- const i = Lx(t, r);
- return C(sh.Provider, { value: i, children: C("div", { ...n, children: e }) });
+function r_({ children: e, menuItems: t, onSelectOption: r, ...n }) {
+ const i = t_(t, r);
+ return C(kh.Provider, { value: i, children: C("div", { ...n, children: e }) });
}
-const oh = vn(({ index: e, children: t, onMouseEnter: r, onClick: n, ...i }, s) => {
- const { state: { activeIndex: o }, setActiveIndex: a, setSelectedIndex: c, select: l } = Hc(), u = ge((f) => {
+const Th = Mn(({ index: e, children: t, onMouseEnter: r, onClick: n, ...i }, s) => {
+ const { state: { activeIndex: o }, setActiveIndex: a, setSelectedIndex: c, select: l } = el(), u = he((f) => {
l(), c(-1), n?.(f);
- }, [n, l, c]), d = ge((f) => {
+ }, [n, l, c]), d = he((f) => {
a(e), r?.(f);
}, [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 });
});
-function Ux({ children: e, autoIndex: t = !0, ...r }) {
- const n = X(null), { state: { activeIndex: i, menuItems: s } } = Hc(), o = Fe(() => s ? typeof e == "function" ? e : () => e : () => null, [e, s]), a = Fe(() => {
+function n_({ children: e, autoIndex: t = !0, ...r }) {
+ const n = Z(null), { state: { activeIndex: i, menuItems: s } } = el(), o = Fe(() => s ? typeof e == "function" ? e : () => e : () => null, [e, s]), a = Fe(() => {
const c = o(s);
- return t ? Em.map(c, (l, u) => Am(l) && l.type === oh && l.props.index === void 0 ? Pm(l, { index: u }) : l) : c;
+ return t ? Vm.map(c, (l, u) => Wm(l) && l.type === Th && l.props.index === void 0 ? Hm(l, { index: u }) : l) : c;
}, [o, t, s]);
- return K(() => {
+ return z(() => {
if (n.current) {
const c = n.current, l = c.children[i];
if (l) {
@@ -16249,14 +16374,14 @@ function Ux({ children: e, autoIndex: t = !0, ...r }) {
}
}, [i]), C("div", { ref: n, role: "menu", ...r, children: a });
}
-const Fx = (e, t, r) => Ss(e, r).toLowerCase().includes(t.toLowerCase()), Pu = (e) => Object.keys(e).find((t) => typeof e[t] == "string") || "", Ss = (e, t) => {
+const i_ = (e, t, r) => Ns(e, r).toLowerCase().includes(t.toLowerCase()), Wu = (e) => Object.keys(e).find((t) => typeof e[t] == "string") || "", Ns = (e, t) => {
const r = e[t];
return typeof r == "string" ? r : String(r);
};
-function zx(e) {
+function s_(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 ? Pu(r[0]) : "") : (u = n || (r.length > 0 ? Pu(r[0]) : ""), d = (m, g) => Fx(m, g, u));
+ i ? (d = i, u = r.length > 0 ? Wu(r[0]) : "") : (u = n || (r.length > 0 ? Wu(r[0]) : ""), d = (m, g) => i_(m, g, u));
const f = s || u, p = /* @__PURE__ */ new Map();
return r.filter((m) => {
try {
@@ -16265,43 +16390,43 @@ function zx(e) {
return console.warn("Error filtering item:", m, g), !1;
}
}).sort((m, g) => {
- const y = (v) => (p.has(v) || p.set(v, Ss(v, f).toLowerCase()), p.get(v) ?? ""), T = a ? Ss(m, f) : y(m), S = a ? Ss(g, f) : y(g);
- for (const v of c)
- switch (v) {
+ 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) {
case "exact":
- if (T === l && S !== l)
+ if (k === l && _ !== l)
return -1;
- if (S === l && T !== l)
+ if (_ === l && k !== l)
return 1;
break;
case "startsWith":
- if (T.startsWith(l) && !S.startsWith(l))
+ if (k.startsWith(l) && !_.startsWith(l))
return -1;
- if (S.startsWith(l) && !T.startsWith(l))
+ if (_.startsWith(l) && !k.startsWith(l))
return 1;
break;
case "contains": {
- const E = T.indexOf(l), A = S.indexOf(l);
- if (E !== -1 && A === -1)
+ const P = k.indexOf(l), A = _.indexOf(l);
+ if (P !== -1 && A === -1)
return -1;
- if (A !== -1 && E === -1)
+ if (A !== -1 && P === -1)
return 1;
- if (E !== -1 && A !== -1)
- return E - A;
+ if (P !== -1 && A !== -1)
+ return P - A;
break;
}
}
- return T.localeCompare(S);
+ return k.localeCompare(_);
});
}
const Zo = {
- Root: Dx,
- Options: Ux,
- Option: oh
+ Root: r_,
+ Options: n_,
+ Option: Th
};
-function Kx(e) {
+function o_(e) {
const { query: t, items: r, filterBy: n, filter: i, sortBy: s, sortingOptions: o } = e;
- return Fe(() => zx({
+ return Fe(() => s_({
query: t,
items: r,
filterBy: n,
@@ -16310,17 +16435,17 @@ function Kx(e) {
sortingOptions: o
}), [t, r, n, i, s, o]);
}
-function jx() {
- const { moveUp: e, moveDown: t, select: r } = Hc();
+function a_() {
+ const { moveUp: e, moveDown: t, select: r } = el();
return Fe(() => ({
moveUp: e,
moveDown: t,
select: r
}), [e, t, r]);
}
-const Bx = () => {
- const e = jx(), [t] = le();
- K(() => {
+const c_ = () => {
+ const e = a_(), [t] = ce();
+ z(() => {
const r = (n) => {
const s = {
ArrowDown: () => e?.moveDown(),
@@ -16330,43 +16455,43 @@ const Bx = () => {
}[n.key];
return s ? (s(), n.preventDefault(), n.stopPropagation(), !0) : !1;
};
- return t.registerCommand(Tr, r, Ie);
+ return t.registerCommand(Sr, r, Ie);
}, [t, e]);
};
-function Vx() {
- return Bx(), null;
+function l_() {
+ return c_(), null;
}
-const Wx = ["Shift", "Control", "Alt", "Meta"];
-function ah(e) {
- const { options: t, onSelectOption: r, onClose: n, inverse: i, query: s, menuOpenKey: o, onFilterChange: a, passthroughKeys: c } = e, [l] = le(), u = s !== void 0, [d, f] = de(""), p = u ? s ?? "" : d, m = Kx({ query: p, items: t, filterBy: "name" }), g = (y) => {
+const u_ = ["Shift", "Control", "Alt", "Meta"];
+function xh(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 = o_({ query: p, items: t, filterBy: "name" }), g = (y) => {
n?.(), r ? r(y) : y.action(l);
};
- return K(() => {
+ return z(() => {
a?.(p, m);
- }, [a, p, m]), K(() => l.registerCommand(Tr, (y) => {
- if (u || c?.includes(y.key) || Wx.includes(y.key))
+ }, [a, p, m]), z(() => l.registerCommand(Sr, (y) => {
+ if (u || c?.includes(y.key) || u_.includes(y.key))
return !1;
if ((y.ctrlKey || y.metaKey || y.altKey) && !y.getModifierState("AltGraph"))
return n?.(), !1;
- const S = {
+ const _ = {
Escape: () => n?.(),
Backspace: () => {
- p.length === 0 ? n?.() : f((v) => v.slice(0, -1));
+ p.length === 0 ? n?.() : f((S) => S.slice(0, -1));
}
}[y.key];
- return S ? (y.stopPropagation(), y.preventDefault(), S(), !0) : y.key.length === 1 ? (y.stopPropagation(), y.preventDefault(), y.key !== o && f((v) => v + y.key), !0) : !1;
- }, Ie), [l, u, p, o, n, c]), Te(Zo.Root, { className: `autocomplete-menu-container ${i ? "inverse" : ""}`, menuItems: m, onSelectOption: (y) => g(y), children: [!u && C("input", { value: p, type: "text", disabled: !0 }), C(Vx, {}), C(Zo.Options, { className: "autocomplete-menu-options", autoIndex: !1, children: (y) => y.map((S, v) => Te(Zo.Option, { index: v, children: [C("span", { className: "label", children: S.label ?? S.name }), C("span", { className: "description", children: S.description })] }, S.name)) })] });
+ return _ ? (y.stopPropagation(), y.preventDefault(), _(), !0) : y.key.length === 1 ? (y.stopPropagation(), y.preventDefault(), y.key !== o && f((S) => S + y.key), !0) : !1;
+ }, Ie), [l, u, p, o, n, c]), Te(Zo.Root, { className: `autocomplete-menu-container ${i ? "inverse" : ""}`, menuItems: m, onSelectOption: (y) => g(y), children: [!u && C("input", { value: p, type: "text", disabled: !0 }), C(l_, {}), C(Zo.Options, { className: "autocomplete-menu-options", autoIndex: !1, children: (y) => y.map((_, S) => Te(Zo.Option, { index: S, children: [C("span", { className: "label", children: _.label ?? _.name }), C("span", { className: "description", children: _.description })] }, _.name)) })] });
}
-function Hx({ trigger: e, items: t }) {
- const [r] = le(), [n, i] = de(!1), s = ge((o) => {
+function d_({ trigger: e, items: t }) {
+ const [r] = ce(), [n, i] = de(!1), s = he((o) => {
o.key === "Escape" && n ? (i(!1), r.focus()) : o.key === e && !n && (o.preventDefault(), i(!0));
}, [r, e, n]);
- return K(() => r.registerRootListener((o) => {
+ return z(() => r.registerRootListener((o) => {
if (o)
return o.addEventListener("keydown", s), () => {
o.removeEventListener("keydown", s);
};
- }), [r, s]), K(() => r.registerUpdateListener(({ prevEditorState: o, editorState: a }) => {
+ }), [r, s]), z(() => r.registerUpdateListener(({ prevEditorState: o, editorState: a }) => {
const c = o.read(() => {
const l = R();
if (N(l))
@@ -16376,16 +16501,16 @@ function Hx({ trigger: e, items: t }) {
const l = R();
!N(l) || c?.is(l) || i(!1);
});
- }), [r]), t && C(ih, { isOpen: n, children: ({ placement: o }) => C(ah, { options: t, onClose: () => i(!1), inverse: o === "top-start", menuOpenKey: e }) });
+ }), [r]), t && C(bh, { isOpen: n, children: ({ placement: o }) => C(xh, { options: t, onClose: () => i(!1), inverse: o === "top-start", menuOpenKey: e }) });
}
-function Gx({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) {
+function f_({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) {
return { markersMenuItems: Fe(() => {
if (!t || !e)
return;
- const i = nr(t);
+ const i = lr(t);
if (i?.children)
return Object.values(i.children).flatMap((s) => s.map((o) => {
- const a = nr(o), { action: c } = r(o, a);
+ const a = lr(o), { action: c } = r(o, a);
return {
name: o,
label: o,
@@ -16397,15 +16522,15 @@ function Gx({ scriptureReference: e, contextMarker: t, getMarkerAction: r }) {
}));
}, [t, r, e]) };
}
-function $i(e, t) {
+function Ui(e, t) {
return `${e}:${t}`;
}
-function Jx(e, t) {
- K(() => {
+function p_(e, t) {
+ z(() => {
if (!e.hasNodes([Ze]))
throw new Error("AnnotationPlugin: TypedMarkNode not registered on editor!");
const r = /* @__PURE__ */ new Map();
- return Xe(lf(e, Ze, (n) => zi(n.getTypedIDs(), n.getTypedOnClicks(), n.getTypedOnRemoves(), n.getTypedOnMouseEnters(), n.getTypedOnMouseLeaves()), (n, i) => {
+ return He(Cf(e, Ze, (n) => Hi(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) => {
@@ -16416,57 +16541,57 @@ function Jx(e, t) {
}), e.registerMutationListener(Ze, (n) => {
e.getEditorState().read(() => {
for (const [i, s] of n) {
- const o = ne(i);
+ const o = se(i);
let a = {};
- s === "destroyed" ? a = r.get(i) ?? {} : _e(o) && (a = o.getTypedIDs());
+ s === "destroyed" ? a = r.get(i) ?? {} : Ce(o) && (a = o.getTypedIDs());
for (const [c, l] of Object.entries(a))
if (!Ze.isReservedType(c))
for (const u of l) {
- let d = t.get($i(c, u));
- a[c] = l, r.set(i, a), s === "destroyed" ? d !== void 0 && (d.delete(i), d.size === 0 && t.delete($i(c, u))) : (d === void 0 && (d = /* @__PURE__ */ new Set(), t.set($i(c, u), d)), d.has(i) || d.add(i));
+ let d = t.get(Ui(c, u));
+ a[c] = l, r.set(i, a), s === "destroyed" ? d !== void 0 && (d.delete(i), d.size === 0 && t.delete(Ui(c, u))) : (d === void 0 && (d = /* @__PURE__ */ new Set(), t.set(Ui(c, u), d)), d.has(i) || d.add(i));
}
}
});
}, { skipInitialization: !0 }));
}, [e, t]);
}
-const Yx = vn(function({ logger: t }, r) {
- const [n] = le(), i = Fe(() => /* @__PURE__ */ new Map(), []);
- Jx(n, i);
+const h_ = Mn(function({ logger: t }, r) {
+ const [n] = ce(), i = Fe(() => /* @__PURE__ */ new Map(), []);
+ p_(n, i);
const s = (o, a, c) => {
- const l = Array.from(c ?? i.get($i(o, a)) ?? []);
+ const l = Array.from(c ?? i.get(Ui(o, a)) ?? []);
if (l.length !== 0)
for (const u of l) {
- const d = ne(u);
- _e(d) && (d.deleteID(o, a), d.hasNoIDsForEveryType() && Rs(d));
+ const d = se(u);
+ Ce(d) && (d.deleteID(o, a), d.hasNoIDsForEveryType() && Ds(d));
}
};
- return cc(r, () => ({
+ return dc(r, () => ({
setAnnotation(o, a, c, l, u, d, f) {
if (Ze.isReservedType(a))
throw new Error(`setAnnotation: Can't directly set this reserved annotation type '${a}'. Use the appropriate plugin instead.`);
n.update(() => {
- const p = Vc(o);
+ const p = Qc(o);
if (p === void 0) {
t?.error("Failed to find start or end node of the annotation.");
return;
}
- s(a, c), qf(p, a, c, l, u, d, f);
+ s(a, c), Hf(p, a, c, l, u, d, f);
}, { tag: Ta });
},
removeAnnotation(o, a) {
if (Ze.isReservedType(o))
throw new Error(`removeAnnotation: Can't directly remove this reserved annotation type '${o}'. Use the appropriate plugin instead.`);
- const c = i.get($i(o, a));
+ const c = i.get(Ui(o, a));
c === void 0 || c.size === 0 || n.update(() => {
s(o, a, c);
}, { tag: Ta });
}
})), null;
-}), Xx = [];
-function Qx({ ignoreHistoryMergeTagChange: e = !0, ignoreSelectionChange: t = !1, ignoreTags: r = Xx, onChange: n }) {
- const [i] = le();
- return rs(() => {
+}), g_ = [];
+function m_({ ignoreHistoryMergeTagChange: e = !0, ignoreSelectionChange: t = !1, ignoreTags: r = g_, onChange: n }) {
+ const [i] = ce();
+ return cs(() => {
if (n)
return i.registerUpdateListener((s) => {
const { editorState: o, dirtyElements: a, dirtyLeaves: c, prevEditorState: l, tags: u } = s;
@@ -16474,55 +16599,55 @@ function Qx({ ignoreHistoryMergeTagChange: e = !0, ignoreSelectionChange: t = !1
// 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(Yd) && !u.has(Tf) || r.some((f) => u.has(f)) || l.isEmpty())
+ e && u.has(pf) && !u.has(Rf) || r.some((f) => u.has(f)) || l.isEmpty())
return;
- const d = Zx(i, s);
+ const d = y_(i, s);
d.length !== 0 && n(o, i, u, d);
});
}, [i, e, t, r, n]), null;
}
-function Zx(e, { dirtyLeaves: t, prevEditorState: r }) {
- let n = new Ai();
+function y_(e, { dirtyLeaves: t, prevEditorState: r }) {
+ let n = new wi();
return e.getEditorState().read(() => {
- const i = t.values().next().value ?? "", s = ne(i), o = s !== null && Ht(s) !== void 0;
- if (t.size === 1 && M(s) && !o && QT(s)) {
- const a = Gp(s);
+ const i = t.values().next().value ?? "", s = se(i), o = s !== null && Yt(s) !== void 0;
+ if (t.size === 1 && v(s) && !o && mx(s)) {
+ const a = ch(s);
if (a !== void 0) {
const c = r.read(() => {
- const d = ne(i);
- return new Ai([M(d) ? Ia(d) : { insert: "" }]);
- }), l = new Ai([Ia(s)]), u = new Ai(a > 0 ? [{ retain: a }] : []);
+ const d = se(i);
+ return new wi([v(d) ? La(d) : { insert: "" }]);
+ }), l = new wi([La(s)]), u = new wi(a > 0 ? [{ retain: a }] : []);
n = n.concat(u).concat(c.diff(l));
}
} else {
- const a = _u(r), c = _u(e.getEditorState());
+ const a = Uu(r), c = Uu(e.getEditorState());
n = a.diff(c);
}
}), n.ops;
}
-const Gc = "formatted", ch = "unformatted", lh = "paragraph-structure", uh = "standard", dh = "block-verse", e_ = {
- [Gc]: "Formatted",
- [ch]: "Unformatted",
- [lh]: "Paragraph Structure",
- [uh]: "Standard",
- [dh]: "Block Verse"
+const tl = "formatted", _h = "unformatted", Ch = "paragraph-structure", Sh = "standard", vh = "block-verse", b_ = {
+ [tl]: "Formatted",
+ [_h]: "Unformatted",
+ [Ch]: "Paragraph Structure",
+ [Sh]: "Standard",
+ [vh]: "Block Verse"
};
-function li(e) {
+function hi(e) {
return e?.showParaMarkerPrefixes !== !1;
}
-let Jc, Yc;
-function t_(e) {
- const t = fh(e);
+let rl, nl;
+function k_(e) {
+ const t = Mh(e);
if (!t)
throw new Error(`Invalid view mode: ${e}`);
- Jc = e, Yc = t;
+ rl = e, nl = t;
}
-t_(Gc);
-const _A = () => Jc, _o = () => Yc;
-function fh(e) {
+k_(tl);
+const hP = () => rl, Co = () => nl;
+function Mh(e) {
let t;
- switch (e ?? Jc) {
- case Gc:
+ switch (e ?? rl) {
+ case tl:
t = {
markerMode: "hidden",
noteMode: "collapsed",
@@ -16530,7 +16655,7 @@ function fh(e) {
isFormattedFont: !0
};
break;
- case ch:
+ case _h:
t = {
markerMode: "editable",
noteMode: "expanded",
@@ -16538,7 +16663,7 @@ function fh(e) {
isFormattedFont: !1
};
break;
- case lh:
+ case Ch:
t = {
markerMode: "hidden",
noteMode: "collapsed",
@@ -16548,7 +16673,7 @@ function fh(e) {
hasActiveTextFocusBox: !0
};
break;
- case uh:
+ case Sh:
t = {
markerMode: "editable",
noteMode: "collapsed",
@@ -16556,7 +16681,7 @@ function fh(e) {
isFormattedFont: !0
};
break;
- case dh:
+ case vh:
t = {
markerMode: "hidden",
noteMode: "collapsed",
@@ -16568,138 +16693,138 @@ function fh(e) {
}
return t;
}
-function CA(e) {
+function gP(e) {
if (!e)
return;
- const t = Nu(e);
- return Object.keys(e_).find((r) => Pt(Nu(fh(r)), t));
+ const t = Hu(e);
+ return Object.keys(b_).find((r) => wt(Hu(Mh(r)), t));
}
-const r_ = {
+const T_ = {
showCharMarkerTitles: !0,
hasGutterParaMarkers: !1,
hasActiveTextFocusBox: !1,
verseLayout: "inline"
};
-function Nu(e) {
+function Hu(e) {
if (!e)
return e;
const t = Object.fromEntries(Object.entries(e).filter(([, r]) => r !== void 0));
- return { ...r_, ...t };
+ return { ...T_, ...t };
}
-function Co(e) {
+function So(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 n_(e) {
+function x_(e) {
if (e)
- return Xi(e) ? xt : e.markerMode === "editable" ? ft : xt;
+ return ns(e) ? Ct : e.markerMode === "editable" ? dt : Ct;
}
-function Xi(e) {
+function ns(e) {
return e?.verseLayout === "block";
}
-function i_(e) {
- const t = [], r = e ?? Yc;
- return r && (t.push(`${Oy}${r.markerMode}`), r.hasSpacing && t.push(Py), r.isFormattedFont && t.push(Ny)), t;
+function __(e) {
+ const t = [], r = e ?? nl;
+ return r && (t.push(`${Qy}${r.markerMode}`), r.hasSpacing && t.push(Yy), r.isFormattedFont && t.push(Xy)), t;
}
-function s_(e, t, r, n) {
+function C_(e, t, r, n) {
let i = 0;
e.forEach((s) => {
if ("retain" in s)
- i += o_(s, i, t, n);
+ i += S_(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}`), c_(i, s.delete, n);
- } else "insert" in s ? typeof s.insert == "string" ? (n?.debug(`Insert: '${s.insert}'`), i += l_(i, s.insert, s.attributes, t, n)) : typeof s.insert == "object" && s.insert !== null ? (n?.debug(`Insert embed: ${JSON.stringify(s.insert)}`), d_(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}`), M_(i, s.delete, n);
+ } else "insert" in s ? typeof s.insert == "string" ? (n?.debug(`Insert: '${s.insert}'`), i += E_(i, s.insert, s.attributes, t, n)) : typeof s.insert == "object" && s.insert !== null ? (n?.debug(`Insert embed: ${JSON.stringify(s.insert)}`), P_(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 o_(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)}`), a_(t, e.retain, e.attributes, r, n)), e.retain);
+function S_(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)}`), v_(t, e.retain, e.attributes, r, n)), e.retain);
}
-function a_(e, t, r, n, i) {
+function v_(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 = Ue();
function l(u) {
if (s <= 0)
return !0;
- if (br(u)) {
+ 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, T = m < d - f;
- if (y && T) {
- const [, S] = u.splitText(f);
- [g] = S.splitText(m);
- } else y ? [, g] = u.splitText(f) : T && ([g] = u.splitText(m));
- if (Kr(r)) {
- const S = g.getParent();
- if ($(S)) {
- const v = r.char;
- let E;
- Array.isArray(v) ? a >= 0 && a <= v.length - 1 && (E = v[a]) : a === 0 && (E = v);
- const A = E ? mn(E, S) : !1;
- if (A && Array.isArray(v) && v.length > 1) {
- const x = he("");
- g.replace(x);
- const F = typeof r.segment == "string" ? r.segment : void 0, L = ui(v.slice(1), n, g, F);
- let G = x;
- for (const V of L)
- G.insertAfter(V), G = V;
- x.remove(), Nt(r, g);
+ const y = f > 0, k = m < d - f;
+ if (y && k) {
+ const [, _] = u.splitText(f);
+ [g] = _.splitText(m);
+ } else y ? [, g] = u.splitText(f) : k && ([g] = u.splitText(m));
+ if (Wr(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 ? kn(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, w = gi(S.slice(1), n, g, M);
+ let $ = B;
+ for (const Y of w)
+ $.insertAfter(Y), $ = Y;
+ B.remove(), qt(r, g);
} else if (A)
- Nt(r, g);
+ qt(r, g);
else {
g.remove();
- const x = Ou(g, r, n, i);
- if (x && x.length > 0) {
- let F = S;
- for (const L of x)
- F.insertAfter(L), F = L;
+ const B = Gu(g, r, n, i);
+ if (B && B.length > 0) {
+ let M = _;
+ for (const w of B)
+ M.insertAfter(w), M = w;
}
}
} else {
- const v = he("");
- g.replace(v);
- const E = Ou(g, r, n, i);
- if (E && E.length > 0) {
- let A = v;
- for (const x of E)
- A.insertAfter(x), A = x;
- v.remove();
+ const S = pe("");
+ g.replace(S);
+ const P = Gu(g, r, n, i);
+ if (P && P.length > 0) {
+ let A = S;
+ for (const B of P)
+ A.insertAfter(B), A = B;
+ S.remove();
} else
- v.replace(g);
+ S.replace(g);
}
} else
- Nt(r, g);
+ qt(r, g);
s -= m;
}
}
o += d;
- } else if (Mt(u))
- e <= o && o < e + t && s > 0 && (wu(u, r), s -= 1), o += 1;
- else if ($(u)) {
+ } else if (Pt(u))
+ e <= o && o < e + t && s > 0 && (Ju(u, r), s -= 1), o += 1;
+ else if (D(u)) {
a += 1;
let d = !1;
if (e <= o && o < e + t && s > 0)
- if (Kr(r)) {
+ if (Wr(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) {
- La(u, p.style), typeof p.cid == "string" && mt(u, gn, () => p.cid);
- const m = De(p, Ks);
+ Da(u, p.style), typeof p.cid == "string" && yt(u, bn, () => p.cid);
+ const m = De(p, Ws);
m && Object.keys(m).length > 0 ? u.setUnknownAttributes({
...u.getUnknownAttributes() ?? {},
...m
}) : u.setUnknownAttributes(void 0);
}
- } else (r.char === !1 || r.char === null || T_(r.char)) && (d = !0);
+ } else (r.char === !1 || r.char === null || D_(r.char)) && (d = !0);
if (s > 0) {
const f = u.getChildren();
for (const p of f) {
@@ -16710,7 +16835,7 @@ function a_(e, t, r, n, i) {
}
}
d && ba(u), a -= 1;
- } else if (vt(u)) {
+ } else if (Et(u)) {
const d = u.getChildren();
for (const p of d) {
if (s <= 0)
@@ -16720,16 +16845,16 @@ function a_(e, t, r, n, i) {
}
const f = 1;
if (e <= o && o < e + s && s > 0) {
- if (!ir(u))
- wu(u, r);
- else if (Xc(r)) {
- const p = gh(r.para, n);
+ if (!dr(u))
+ Ju(u, r);
+ else if (il(r)) {
+ const p = Ph(r.para, n);
p && u.replace(p, !0);
}
s -= f;
}
o += f;
- } else if (D(u)) {
+ } else if (F(u)) {
const d = u.getChildren();
for (const f of d) {
if (s <= 0)
@@ -16742,14 +16867,14 @@ function a_(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 Ou(e, t, r, n) {
- const i = typeof t.segment == "string" ? t.segment : void 0, s = ui(t.char, r, e, i), o = s.find($);
+function Gu(e, t, r, n) {
+ const i = typeof t.segment == "string" ? t.segment : void 0, s = gi(t.char, r, e, i), o = s.find(D);
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.`), Nt(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.`), qt(t, e);
return;
}
const a = {};
- kh.forEach((u) => {
+ qh.forEach((u) => {
e.hasFormat(u) && (a[u] = "true");
});
const c = {};
@@ -16761,51 +16886,51 @@ function Ou(e, t, r, n) {
...a,
...c
};
- return Object.keys(l).length > 0 && o.setUnknownAttributes(l), Nt(t, e), s;
+ return Object.keys(l).length > 0 && o.setUnknownAttributes(l), qt(t, e), s;
}
-function ph(e, t) {
+function Eh(e, t) {
e.setMarker(t);
const r = e.getFirstChild();
- P(r) ? (r.setMarker(t), r.setTextContent(Oe(t))) : Wt(r) && r.getTextType() === "marker" && r.setTextContent(Oe(t) + w);
+ O(r) ? (r.setMarker(t), r.setTextContent(Ne(t))) : Jt(r) && r.getTextType() === "marker" && r.setTextContent(Ne(t) + L);
}
-function La(e, t) {
+function Da(e, t) {
const r = e.getMarker();
if (e.setMarker(t), t === r)
return;
e.getChildren().forEach((o) => {
- P(o) && o.getMarker() === r && o.setMarker(t);
+ O(o) && o.getMarker() === r && o.setMarker(t);
});
- const n = $(e.getParent()), i = e.getFirstChild();
- Wt(i) && i.getTextType() === "marker" && i.getTextContent() === Oe(r, n) && i.setTextContent(Oe(t, n));
+ const n = D(e.getParent()), i = e.getFirstChild();
+ Jt(i) && i.getTextType() === "marker" && i.getTextContent() === Ne(r, n) && i.setTextContent(Ne(t, n));
const s = e.getLastChild();
- Wt(s) && s.getTextType() === "marker" && s.getTextContent() === rt(r, n) && s.setTextContent(rt(t, n));
+ Jt(s) && s.getTextType() === "marker" && s.getTextContent() === rt(r, n) && s.setTextContent(rt(t, n));
}
-function wu(e, t) {
+function Ju(e, t) {
for (const r of Object.keys(t)) {
const n = t[r];
- if (r === "char" && $(e) && Kr(t)) {
- const i = Da(n);
- if (La(e, i.style), typeof i.cid == "string") {
+ if (r === "char" && D(e) && Wr(t)) {
+ const i = Ua(n);
+ if (Da(e, i.style), typeof i.cid == "string") {
const o = i.cid;
- mt(e, gn, () => o);
+ yt(e, bn, () => o);
}
- const s = De(i, Ks);
+ const s = De(i, Ws);
s && Object.keys(s).length > 0 && e.setUnknownAttributes({
...e.getUnknownAttributes() ?? {},
...s
});
continue;
}
- typeof n == "string" && (We(e) || me(e) || je(e) || j(e) || Le(e) ? e.setUnknownAttributes({
+ typeof n == "string" && (We(e) || ge(e) || Be(e) || j(e) || Le(e) ? e.setUnknownAttributes({
...e.getUnknownAttributes() ?? {},
[r]: n
- }) : (Tt(e) || se(e) || $(e)) && (r === "style" && se(e) ? ph(e, n) : r === "style" && $(e) ? La(e, n) : r === "code" && Tt(e) ? e.setCode(n) : e.setUnknownAttributes({
+ }) : (_t(e) || ae(e) || D(e)) && (r === "style" && ae(e) ? Eh(e, n) : r === "style" && D(e) ? Da(e, n) : r === "code" && _t(e) ? e.setCode(n) : e.setUnknownAttributes({
...e.getUnknownAttributes() ?? {},
[r]: n
- })), r === "segment" && mt(e, Fr, () => n));
+ })), r === "segment" && yt(e, Br, () => n));
}
}
-function c_(e, t, r) {
+function M_(e, t, r) {
if (t <= 0)
return;
const n = Ue();
@@ -16813,16 +16938,16 @@ function c_(e, t, r) {
function o(a) {
if (s <= 0)
return !0;
- if (br(a)) {
+ if (_r(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);
}
i += c;
- } else if (Mt(a))
+ } else if (Pt(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 (vt(a)) {
+ else if (Et(a)) {
const c = a.getChildren().slice(), l = a.getChildren();
for (const u of l) {
if (s <= 0)
@@ -16830,36 +16955,36 @@ function c_(e, t, r) {
if (o(u) && s <= 0)
return !0;
}
- if (e <= i && i < e + s && vt(a)) {
+ if (e <= i && i < e + s && Et(a)) {
s -= 1;
const u = a.getChildren().length;
if (c.length > 0 && u === 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(jt(), !0), r?.debug(`Replaced last ParaNode with ImpliedParaNode at currentIndex: ${i}. Original targetIndex: ${e}, remainingToDelete: ${s}.`));
+ (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(Wt(), !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 && Ce(p)) {
+ if (p && Se(p)) {
let m = i + 1;
const g = p.getChildren();
- for (const T of g) {
+ for (const k of g) {
if (s <= 0)
break;
- const S = i;
- if (i = m, o(T)) {
- i = S;
+ const _ = i;
+ if (i = m, o(k)) {
+ i = _;
break;
}
- br(T) ? m += T.getTextContentSize() : Mt(T) && (m += 1), i = S;
+ _r(k) ? m += k.getTextContentSize() : Pt(k) && (m += 1), i = _;
}
const y = p.getChildren();
- for (const T of y)
- T.remove(), a.append(T);
+ 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}.`);
} else
- a.replace(jt(), !0);
- } else se(a) ? a.replace(jt(), !0) : a.remove();
+ a.replace(Wt(), !0);
+ } else ae(a) ? a.replace(Wt(), !0) : a.remove();
}
i += 1;
- } else if (D(a)) {
+ } else if (F(a)) {
const c = a.getChildren();
for (const l of c) {
if (s <= 0)
@@ -16872,47 +16997,47 @@ function c_(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 l_(e, t, r, n, i) {
- if (t === Yi)
- return qu(e, r, n, i);
- if (t.endsWith(Yi) && !Xc(r)) {
+function E_(e, t, r, n, i) {
+ if (t === rs)
+ return Yu(e, r, n, i);
+ if (t.endsWith(rs) && !il(r)) {
const s = t.slice(0, -1);
let o = 0;
if (s.length > 0) {
- if (Kr(r))
+ if (Wr(r))
throw new Error("Text + LF should not have char attributes");
- o += Bs(e, s, r, i);
+ o += Gs(e, s, r, i);
}
- return o += qu(e + o, r, n, i), o;
- } else return Kr(r) ? u_(e, t, r, n, i) : Bs(e, t, r, i);
+ return o += Yu(e + o, r, n, i), o;
+ } else return Wr(r) ? A_(e, t, r, n, i) : Gs(e, t, r, i);
}
-function u_(e, t, r, n, i) {
+function A_(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 = he(t === "" ? It : t);
- Nt(r, s);
+ const s = pe(t === "" ? Dt : t);
+ qt(r, s);
let o;
{
- let y = function(T) {
- if (br(T)) {
- const S = T.getTextContentSize();
- if (e >= g && e < g + S) {
- const v = T.getParent();
- return $(v) && (o = v), !0;
+ let y = function(k) {
+ if (_r(k)) {
+ const _ = k.getTextContentSize();
+ if (e >= g && e < g + _) {
+ const S = k.getParent();
+ return D(S) && (o = S), !0;
}
- g += S;
- } else if (Mt(T))
+ g += _;
+ } else if (Pt(k))
g += 1;
- else if ($(T)) {
- const S = T.getChildren();
- for (const v of S)
- if (y(v))
+ else if (D(k)) {
+ const _ = k.getChildren();
+ for (const S of _)
+ if (y(S))
return !0;
- } else if (D(T)) {
- const S = T.getChildren();
- for (const v of S)
- if (y(v))
+ } else if (F(k)) {
+ const _ = k.getChildren();
+ for (const S of _)
+ if (y(S))
return !0;
- vt(T) && (g += 1);
+ Et(k) && (g += 1);
}
return !1;
};
@@ -16924,28 +17049,28 @@ function u_(e, t, r, n, i) {
if (Array.isArray(a)) {
if (o) {
const m = a[0];
- m && mn(m, o) ? (a = a.slice(1), a.length === 1 && (a = a[0])) : o = void 0;
+ m && kn(m, o) ? (a = a.slice(1), a.length === 1 && (a = a[0])) : o = void 0;
}
- } else o && (mn(a, o) || (o = void 0));
- const c = typeof r.segment == "string" ? r.segment : void 0, u = ui(a, n, s, c, o ? [o] : void 0);
+ } else o && (kn(a, o) || (o = void 0));
+ const c = typeof r.segment == "string" ? r.segment : void 0, u = gi(a, n, s, c, o ? [o] : void 0);
if (u.length === 0)
return t.length;
- const d = u.find($);
+ 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.`), Bs(e, t, void 0, i);
+ return i?.error(`CharNode style is missing for text "${t}". Attributes: ${JSON.stringify(r.char)}. Falling back to rich text insertion.`), Gs(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 (!hh(e, m, i)) {
+ if (!Ah(e, m, i)) {
p = !1;
break;
}
- return p ? t.length : (i?.error(`Failed to insert CharNode with text "${t}" at index ${e}. Falling back to rich text.`), Bs(e, t, void 0, i));
+ return p ? t.length : (i?.error(`Failed to insert CharNode with text "${t}" at index ${e}. Falling back to rich text.`), Gs(e, t, void 0, i));
}
-function Bs(e, t, r, n) {
+function Gs(e, t, r, n) {
if (t.length <= 0)
return n?.debug("Attempted to insert empty string. No action taken."), 0;
const i = Ue();
@@ -16953,15 +17078,15 @@ function Bs(e, t, r, n) {
function a(c) {
if (o)
return !0;
- if (br(c)) {
+ if (_r(c)) {
const l = c.getTextContentSize();
if (e >= s && e <= s + l) {
- const u = e - s, d = he(t);
- if (Nt(r, d), u === 0)
+ const u = e - s, d = pe(t);
+ if (qt(r, d), u === 0)
c.insertBefore(d);
else if (u === l) {
const f = c.getParent();
- $(f) && !Kr(r) ? f.insertAfter(d) : c.insertAfter(d);
+ D(f) && !Wr(r) ? f.insertAfter(d) : c.insertAfter(d);
} else {
const [, f] = c.splitText(u);
f.insertBefore(d);
@@ -16969,12 +17094,12 @@ function Bs(e, t, r, n) {
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;
}
s += l;
- } else if (Mt(c))
+ } else if (Pt(c))
s += 1;
- else if ($(c)) {
+ else if (D(c)) {
if (!o && e === s) {
- const d = he(t);
- Nt(r, d);
+ 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;
}
@@ -16986,13 +17111,13 @@ function Bs(e, t, r, n) {
break;
}
if (!o && e === s) {
- const d = he(t);
- return Nt(r, d), c.append(d), n?.debug(`Appended text "${t}" to end of CharNode ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0;
+ 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;
}
- } else if (vt(c)) {
+ } else if (Et(c)) {
if (!o && e === s) {
- const d = he(t);
- Nt(r, d);
+ 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;
}
@@ -17004,11 +17129,11 @@ function Bs(e, t, r, n) {
break;
}
if (!o && e === s) {
- const d = he(t);
- return Nt(r, d), c.append(d), n?.debug(`Appended text "${t}" to end of container ${c.getType()} (key: ${c.getKey()}).`), o = !0, !0;
+ 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;
}
s += 1;
- } else if (D(c)) {
+ } else if (F(c)) {
const l = c.getChildren();
for (const u of l) {
if (a(u))
@@ -17021,104 +17146,104 @@ function Bs(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 = he(t);
- Nt(r, c);
- const l = jt().append(c);
+ const c = pe(t);
+ qt(r, c);
+ const l = Wt().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 hh(e, t, r) {
+function Ah(e, t, r) {
const n = Ue();
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(jt().append(t))) : (r?.debug(`$insertNodeAtCharacterOffset: Inserting block node ${t.getType()} directly into empty root. targetIndex: ${e}`), n.append(t)), s = !0, !0;
- if (!D(a))
+ return t.isInline() ? (r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into empty root, wrapped in ImpliedParaNode. targetIndex: ${e}`), n.append(Wt().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))
return !1;
const c = a.getChildren();
for (const l of c) {
if (e === i && !s) {
if (a === n && t.isInline())
- if (Ce(l)) {
+ if (Se(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);
} else
- r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into root before ${l.getType()}, wrapping in ImpliedParaNode. targetIndex: ${e}`), l.insertBefore(jt().append(t));
+ r?.debug(`$insertNodeAtCharacterOffset: Inserting inline node ${t.getType()} into root before ${l.getType()}, wrapping in ImpliedParaNode. targetIndex: ${e}`), l.insertBefore(Wt().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 (br(l)) {
+ 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;
}
i += u;
- } else if (Mt(l))
+ } else if (Pt(l))
i += 1;
- else if ($(l)) {
+ else if (D(l)) {
if (o(l))
return !0;
- } else if (vt(l)) {
+ } else if (Et(l)) {
const u = l;
if (o(u))
return !0;
const d = i;
- if (ir(u) && vt(t) && // Target is at the ImpliedPara's implicit newline
+ 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;
i += 1;
- } else if (D(l) && o(l))
+ } else if (F(l) && o(l))
return !0;
if (s)
return !0;
}
- return D(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(jt().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 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(Wt().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.
- Ce(a) ? ir(a) && se(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() || !Ce(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) : ($(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() || !Ce(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)
+ 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)
) : 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 d_(e, t, r, n, i) {
+function P_(e, t, r, n, i) {
let s;
- return $r("chapter", t) ? s = p_(t.insert.chapter, r) : $r("verse", t) ? s = h_(t.insert.verse, r) : $r("ms", t) ? s = g_(t.insert.ms) : $r("note", t) ? s = mh(t, r, n, i) : $r("unknown", t) ? s = yh(t, r, n, i) : $r("unmatched", t) && (s = y_(t.insert.unmatched, r)), s ? hh(e, s, i) : (i?.error(`$insertEmbedAtCurrentIndex: Cannot create LexicalNode for embed object: ${JSON.stringify(t.insert)}`), !1);
+ return Ur("chapter", t) ? s = O_(t.insert.chapter, r) : Ur("verse", t) ? s = w_(t.insert.verse, r) : Ur("ms", t) ? s = q_(t.insert.ms) : Ur("note", t) ? s = Nh(t, r, n, i) : Ur("unknown", t) ? s = Oh(t, r, n, i) : Ur("unmatched", t) && (s = $_(t.insert.unmatched, r)), s ? Ah(e, s, i) : (i?.error(`$insertEmbedAtCurrentIndex: Cannot create LexicalNode for embed object: ${JSON.stringify(t.insert)}`), !1);
}
-function qu(e, t, r, n) {
+function Yu(e, t, r, n) {
let i;
- Xc(t) ? i = gh(t.para, r) : k_(t) && (i = f_(t.book)), i ??= jt();
- const s = i, o = se(s), a = ir(s);
+ il(t) ? i = Ph(t.para, r) : L_(t) && (i = N_(t.book)), i ??= Wt();
+ const s = i, o = ae(s), a = dr(s);
let c = 0, l = !1;
function u(d) {
if (l)
return !0;
- if (br(d)) {
+ if (_r(d)) {
const f = d.getTextContentSize();
if (e >= c && e <= c + f) {
const p = d.getParent();
- if (se(p) && (o || a)) {
+ 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, T = g?.getPreviousSibling();
- for (; T; ) {
- const S = T;
- T = T.getPreviousSibling(), y ? y.insertBefore(S) : s.append(S), y = S;
+ let y, k = g?.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;
}
}
c += f;
- } else if (Mt(d))
+ } else if (Pt(d))
c += 1;
- else if (vt(d)) {
+ else if (Et(d)) {
const f = d.getChildren();
for (const p of f) {
if (u(p))
@@ -17127,16 +17252,16 @@ function qu(e, t, r, n) {
break;
}
if (e === c) {
- if (ir(d) && s)
+ 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 (se(d) && s) {
+ 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 (c += 1, e === c && se(d) && s)
+ 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 (D(d)) {
+ } else if (F(d)) {
const f = d.getChildren();
for (const p of f) {
if (u(p))
@@ -17149,73 +17274,73 @@ function qu(e, t, r, n) {
}
return u(Ue()), l || n?.warn(`Could not find location to handle newline with para attributes at targetIndex ${e}. Final currentIndex: ${c}.`), 1;
}
-function f_(e) {
+function N_(e) {
const { style: t, code: r } = e;
- if (!t || t !== Ki || !r || !Lt.isValidBookCode(r))
+ if (!t || t !== Gi || !r || !Ut.isValidBookCode(r))
return;
- const n = De(e, UT);
- return If(r, n);
+ const n = De(e, nx);
+ return Yf(r, n);
}
-function gh(e, t) {
+function Ph(e, t) {
const { style: r } = e;
if (!r)
return;
- const n = De(e, DT), i = ji(r, n);
- if (!li(t))
+ const n = De(e, rx), i = Ji(r, n);
+ if (!hi(t))
return i;
if (t.markerMode === "editable")
- i.append(ot(r), fo());
+ i.append(ot(r), po());
else if (t.markerMode === "visible" || t.hasGutterParaMarkers) {
- const s = Oe(r) + w;
- i.append(t.hasGutterParaMarkers ? ob(s) : mr("marker", s));
+ const s = Ne(r) + L;
+ i.append(t.hasGutterParaMarkers ? vb(s) : Tr("marker", s));
}
return i;
}
-function p_(e, t) {
+function O_(e, t) {
if (!e)
return;
const { number: r, sid: n, altnumber: i, pubnumber: s } = e;
if (!r)
return;
- const o = De(e, FT);
+ const o = De(e, ix);
let a;
if (t.markerMode === "editable")
- a = Uf(r, n, i, s, o);
+ a = Zf(r, n, i, s, o);
else {
const c = t.markerMode === "visible";
- a = xc(r, c, n, i, s, o);
+ a = Ac(r, c, n, i, s, o);
}
return a;
}
-function h_(e, t) {
+function w_(e, t) {
if (!e)
return;
const { style: r, number: n, sid: i, altnumber: s, pubnumber: o } = e;
if (!n)
return;
- const a = De(e, zT);
+ const a = De(e, sx);
let c;
if (t.markerMode === "editable") {
if (!r)
return;
- const l = $t(r, n);
- c = Jf(n, l, i, s, o, a);
+ const l = Lt(r, n);
+ c = lp(n, l, i, s, o, a);
} else {
const l = t.markerMode === "visible";
- c = Dc(n, l, i, s, o, a);
+ c = Vc(n, l, i, s, o, a);
}
return c;
}
-function g_(e) {
+function q_(e) {
if (!e)
return;
const { style: t, sid: r, eid: n, attributeOrder: i } = e;
if (!t)
return;
- const s = De(e, KT);
- return Cf(t, r, n, s, i);
+ const s = De(e, ox);
+ return Lf(t, r, n, s, i);
}
-function mh(e, t, r, n) {
+function Nh(e, t, r, n) {
const i = e.insert;
if (!i.note)
return;
@@ -17223,50 +17348,50 @@ function mh(e, t, r, n) {
if (!s || o == null)
return;
o === "" && n?.warn("Note has empty caller. Only use for note editing.");
- const l = De(i.note, jT), u = typeof l?.closed == "string" ? l.closed : void 0, d = e.attributes?.segment;
+ const l = De(i.note, ax), 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 (Kr(g.attributes)) {
- const y = ui(g.attributes.char, t, he(g.insert), void 0, bh(g.attributes.char, p), !1, t.markerMode === "editable");
+ if (Wr(g.attributes)) {
+ const y = gi(g.attributes.char, t, pe(g.insert), void 0, wh(g.attributes.char, p), !1, t.markerMode === "editable");
p.push(...y);
} else
- p.push(he(g.insert));
- return rh(s, o, p, t, r, f, u).setCategory(a).setUnknownAttributes(l);
+ p.push(pe(g.insert));
+ return mh(s, o, p, t, r, f, u).setCategory(a).setUnknownAttributes(l);
}
-function yh(e, t, r, n) {
+function Oh(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 = De(i, BT), l = Tc(s, o, c), u = a?.ops ?? [];
- u.length > 0 && m_(u, t, r, n).forEach((p) => l.append(p));
+ const c = De(i, cx), l = Ec(s, o, c), u = a?.ops ?? [];
+ u.length > 0 && R_(u, t, r, n).forEach((p) => l.append(p));
const d = e.attributes?.segment;
- return typeof d == "string" && mt(l, Fr, () => d), l;
+ return typeof d == "string" && yt(l, Br, () => d), l;
}
-function m_(e, t, r, n) {
+function R_(e, t, r, n) {
const i = [];
for (const s of e) {
if (typeof s.insert == "string") {
- if (Kr(s.attributes)) {
- const o = he(s.insert), a = ui(s.attributes.char, t, o, void 0, bh(s.attributes.char, i));
+ if (Wr(s.attributes)) {
+ const o = pe(s.insert), a = gi(s.attributes.char, t, o, void 0, wh(s.attributes.char, i));
i.push(...a);
} else
- i.push(he(s.insert));
+ i.push(pe(s.insert));
continue;
}
if (!(!s.insert || typeof s.insert != "object")) {
- if ($r("unknown", s)) {
- const o = yh(s, t, r, n);
+ if (Ur("unknown", s)) {
+ const o = Oh(s, t, r, n);
o && i.push(o);
continue;
}
- if ($r("note", s)) {
- const o = mh(s, t, r, n);
+ if (Ur("note", s)) {
+ const o = Nh(s, t, r, n);
o && i.push(o);
continue;
}
@@ -17275,62 +17400,62 @@ function m_(e, t, r, n) {
}
return i;
}
-function y_(e, t) {
+function $_(e, t) {
if (!e)
return;
const { marker: r } = e;
if (!r)
return;
- const n = qc(r);
+ const n = Fc(r);
return t.markerMode === "editable" && n.setMode("normal"), n;
}
-function bh(e, t) {
+function wh(e, t) {
if (!(!Array.isArray(e) && e.style === "fp" && !e.cid))
return t;
}
-function Da(e) {
+function Ua(e) {
return e.style.startsWith("+") ? { ...e, style: e.style.slice(1) } : e;
}
-function ui(e, t, r, n, i, s = !1, o = !1) {
- M(r) && r.getTextContentSize() === 0 && r.setTextContent(It);
+function gi(e, t, r, n, i, s = !1, o = !1) {
+ v(r) && r.getTextContentSize() === 0 && r.setTextContent(Dt);
const a = () => {
- o && M(r) && r.getTextContent() !== It && r.setTextContent(w + r.getTextContent());
+ o && v(r) && r.getTextContent() !== Dt && r.setTextContent(L + r.getTextContent());
};
if (Array.isArray(e)) {
if (e.length === 0)
throw new Error("Empty charAttr array");
- const c = e.map(Da), l = c[0], u = i?.[i.length - 1];
- if ($(u) && mn(l, u))
- return c.length > 1 ? ui(c.slice(1), t, r, void 0, void 0, !0, o).forEach((p) => u.append(p)) : r && u.append(r), [];
+ const c = e.map(Ua), l = c[0], u = i?.[i.length - 1];
+ if (D(u) && kn(l, u))
+ return c.length > 1 ? gi(c.slice(1), t, r, void 0, void 0, !0, o).forEach((p) => u.append(p)) : r && u.append(r), [];
a();
const d = c.reduceRight((f, p, m) => {
- const g = yr(p.style, De(p, Ks));
- if (typeof p.cid == "string" && mt(g, gn, () => p.cid), n && m === c.length - 1 && mt(g, Fr, () => n), f)
- if ($(f)) {
- const y = f.getMarker(), T = [];
- ta(y, T, t, !0), T.forEach((v) => g.append(v)), g.append(f);
- const S = [];
- ea(f, S, t, !0), S.forEach((v) => g.append(v));
+ const g = xr(p.style, De(p, Ws));
+ if (typeof p.cid == "string" && yt(g, bn, () => p.cid), n && m === c.length - 1 && yt(g, Br, () => n), f)
+ if (D(f)) {
+ const y = f.getMarker(), k = [];
+ ta(y, k, t, !0), k.forEach((S) => g.append(S)), g.append(f);
+ const _ = [];
+ ea(f, _, t, !0), _.forEach((S) => g.append(S));
} else
g.append(f);
return g;
}, r);
return ta(l.style, d, t, s), ea(d, d, t, s), [d];
} else {
- const c = Da(e), l = i?.[i.length - 1];
- if ($(l) && mn(c, l))
+ const c = Ua(e), l = i?.[i.length - 1];
+ if (D(l) && kn(c, l))
return r && l.append(r), [];
a();
- const u = yr(c.style, De(c, Ks));
- return typeof c.cid == "string" && mt(u, gn, () => c.cid), n && mt(u, Fr, () => n), r && u.append(r), ta(c.style, u, t, s), ea(u, u, t, s), [u];
+ const u = xr(c.style, De(c, Ws));
+ return typeof c.cid == "string" && yt(u, bn, () => c.cid), n && yt(u, Br, () => n), r && u.append(r), ta(c.style, u, t, s), ea(u, u, t, s), [u];
}
}
function ea(e, t, r, n = !1) {
- e.getUnknownAttributes()?.closed !== "false" && b_(e.getMarker(), t, r, !1, n);
+ e.getUnknownAttributes()?.closed !== "false" && I_(e.getMarker(), t, r, !1, n);
}
function ta(e, t, r, n = !1) {
let i;
- if (r?.markerMode === "editable" ? i = ot(e, "opening", n) : r?.markerMode === "visible" && (i = mr("marker", Oe(e, n))), i)
+ if (r?.markerMode === "editable" ? i = ot(e, "opening", n) : r?.markerMode === "visible" && (i = Tr("marker", Ne(e, n))), i)
if (Array.isArray(t))
t.push(i);
else {
@@ -17338,37 +17463,37 @@ function ta(e, t, r, n = !1) {
s ? s.insertBefore(i) : t.append(i);
}
}
-function b_(e, t, r, n = !1, i = !1) {
+function I_(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 = mr("marker", n ? rt("") : rt(e, i))), s && (Array.isArray(t) ? t.push(s) : t.append(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));
}
-function k_(e) {
+function L_(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 Xc(e) {
+function il(e) {
return !!e && !!e.para && typeof e.para == "object" && e.para !== null && "style" in e.para && typeof e.para.style == "string";
}
-function Kr(e) {
+function Wr(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 T_(e) {
+function D_(e) {
return typeof e == "object" && e !== null && !Array.isArray(e) && Object.keys(e).length === 0;
}
-function Nt(e, t) {
+function qt(e, t) {
if (e)
for (const r of Object.keys(e)) {
if (r === "segment" && typeof e[r] == "string") {
const n = e[r];
- mt(t, Fr, () => n);
+ yt(t, Br, () => n);
continue;
}
- if (x_(r)) {
+ if (U_(r)) {
const n = !!e[r], i = r, s = t.hasFormat(i);
(n && !s || !n && s) && t.toggleFormat(i);
}
}
}
-const kh = [
+const qh = [
"bold",
"underline",
"strikethrough",
@@ -17381,100 +17506,100 @@ const kh = [
"uppercase",
"capitalize"
];
-function x_(e) {
- return kh.includes(e);
+function U_(e) {
+ return qh.includes(e);
}
-function __() {
- const [e] = le();
- return K(() => e.registerCommand(io, (t) => (C_(t), !1), fn), [e]), null;
+function F_() {
+ const [e] = ce();
+ return z(() => e.registerCommand(ao, (t) => (z_(t), !1), mn), [e]), null;
}
-function C_(e) {
- if (v_(e.target))
+function z_(e) {
+ if (K_(e.target))
return;
const t = R();
- N(t) && S_(t);
+ N(t) && j_(t);
}
-function di(e) {
+function mi(e) {
let t = e.getFirstChild(), r = 0;
for (; t !== null; )
- if (Dt(t))
- r++, t = t.getNextSibling(), M(t) && t.getTextContent() === w && (r++, t = t.getNextSibling());
- else if (me(t))
+ if (Ft(t))
+ r++, t = t.getNextSibling(), v(t) && t.getTextContent() === L && (r++, t = t.getNextSibling());
+ else if (ge(t))
r++, t = t.getNextSibling();
else
break;
- return r === 0 ? !1 : (Gt(e, r), !0);
+ return r === 0 ? !1 : (Xt(e, r), !0);
}
-function v_(e) {
- if (!Xd(e))
+function K_(e) {
+ if (!hf(e))
return !1;
- const t = os(e);
- if (!ab(t))
+ const t = ci(e);
+ if (!Mb(t))
return !1;
const r = t.getParent();
- return r ? Ce(r) ? di(r) : (Gt(r, t.getIndexWithinParent() + 1), !0) : !1;
+ return r ? Se(r) ? mi(r) : (Xt(r, t.getIndexWithinParent() + 1), !0) : !1;
}
-function S_(e) {
+function j_(e) {
if (!e.isCollapsed())
return !1;
const { anchor: t } = e;
if (t.type !== "element" || t.offset !== 0)
return !1;
- const r = ne(t.key);
- if (!Ce(r))
+ const r = se(t.key);
+ if (!Se(r))
return !1;
const n = r.getFirstChild();
- return !Cr(n) && !Pn(n) ? !1 : di(r);
-}
-function M_() {
- const [e] = le();
- return K(() => {
- const t = (r) => r instanceof KeyboardEvent && !E_(r) || !Th() ? !1 : (r instanceof Event && r.preventDefault(), !0);
- return Xe(
- e.registerCommand(Tr, t, Ie),
- e.registerCommand(dc, t, Ie),
+ return !Er(n) && !On(n) ? !1 : mi(r);
+}
+function B_() {
+ const [e] = ce();
+ return z(() => {
+ const t = (r) => r instanceof KeyboardEvent && !V_(r) || !sl() ? !1 : (r instanceof Event && r.preventDefault(), !0);
+ return He(
+ e.registerCommand(Sr, t, Ie),
+ e.registerCommand(mc, t, Ie),
// 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 records what a cut would cover
// and claims nothing, so either order of the two is correct: with no removal, nothing it
// armed can be reaped.
- e.registerCommand(dr, t, fr),
- e.registerCommand(pn, t, fr),
+ e.registerCommand(yr, t, ar),
+ e.registerCommand(Qn, t, ar),
// 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(fc, (r) => {
+ e.registerCommand(yc, (r) => {
if (!(r instanceof Event) || !(r.target instanceof Node))
return !1;
- const n = os(r.target);
- return !n || !Tn(n) ? !1 : (r.preventDefault(), !0);
+ const n = ci(r.target);
+ return !n || !Hr(n) ? !1 : (r.preventDefault(), !0);
}, Ie),
- e.registerCommand(Km, t, Ie),
- e.registerCommand(jm, t, Ie),
- e.registerCommand(Bm, t, Ie)
+ e.registerCommand(oy, t, Ie),
+ e.registerCommand(ay, t, Ie),
+ e.registerCommand(cy, t, Ie)
);
}, [e]), null;
}
-function E_(e) {
+function V_(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 Tn(e) {
- return nt(e, (t) => Le(t) || Op(t)) ?? void 0;
+function Hr(e) {
+ return nt(e, (t) => Le(t) || Wp(t)) ?? void 0;
}
-function Th() {
+function sl() {
const e = R();
- return N(e) ? Tn(e.anchor.getNode()) !== void 0 || Tn(e.focus.getNode()) !== void 0 : !1;
+ return N(e) ? Hr(e.anchor.getNode()) !== void 0 || Hr(e.focus.getNode()) !== void 0 : !1;
}
-function A_(e, t, r) {
+function W_(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 P_(e, t) {
+function H_(e, t) {
if (typeof window > "u")
return !1;
const r = window.getSelection();
@@ -17503,24 +17628,24 @@ function P_(e, t) {
if (!c)
return !1;
const u = document.createRange();
- return u.setStartAfter(c), l ? u.setEndBefore(l) : u.setEnd(n, n.childNodes.length), A_(s, Array.from(u.getClientRects()), t);
+ return u.setStartAfter(c), l ? u.setEndBefore(l) : u.setEnd(n, n.childNodes.length), W_(s, Array.from(u.getClientRects()), t);
} catch {
return !1;
}
}
-function N_(e, t, r, n) {
- if (!B_(t) || P_(e, r))
+function G_(e, t, r, n) {
+ if (!cC(t) || H_(e, r))
return !1;
- const i = r === "up" ? LT(t) : IT(t);
+ const i = r === "up" ? tx(t) : ex(t);
return i && n.preventDefault(), i;
}
-function O_({ viewOptions: e }) {
- const [t] = le();
- return w_(t, e), null;
+function J_({ viewOptions: e }) {
+ const [t] = ce();
+ return Y_(t, e), null;
}
-function w_(e, t) {
- K(() => {
- if (!e.hasNodes([or, xt, Me]))
+function Y_(e, t) {
+ z(() => {
+ if (!e.hasNodes([pr, Ct, Ee]))
throw new Error("ArrowNavigationPlugin: ImmutableChapterNode, ImmutableVerseNode or NoteNode not registered on editor!");
const r = (n) => {
const i = R();
@@ -17528,7 +17653,7 @@ function w_(e, t) {
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 = Ru(o), d = U_(i, $u(u, n.key) ? "next" : "previous");
+ const u = Xu(o), d = nC(i, Qu(u, n.key) ? "next" : "previous");
return d && n.preventDefault(), d;
}
if (!i.isCollapsed())
@@ -17537,61 +17662,61 @@ function w_(e, t) {
if (n.shiftKey || n.altKey || n.ctrlKey || n.metaKey)
return !1;
const u = n.key === "ArrowUp" ? "up" : "down";
- return N_(e, i, u, n);
+ return G_(e, i, u, n);
}
if (n.key !== "ArrowLeft" && n.key !== "ArrowRight" || !o)
return !1;
- const a = Ru(o), c = n.shiftKey || n.altKey || n.ctrlKey || n.metaKey;
+ const a = Xu(o), c = n.shiftKey || n.altKey || n.ctrlKey || n.metaKey;
let l = !1;
- return $u(a, n.key) ? l = !c && Du(i, "next") || !c && R_(i) || K_(i) || !c && s && Lu(i, "next") : q_(a, n.key) && (l = !c && Du(i, "previous") || !c && $_(i) || j_(i, t) || !c && s && Lu(i, "previous")), l && n.preventDefault(), l;
+ return Qu(a, n.key) ? l = !c && td(i, "next") || !c && Q_(i) || oC(i) || !c && s && ed(i, "next") : X_(a, n.key) && (l = !c && td(i, "previous") || !c && Z_(i) || aC(i, t) || !c && s && ed(i, "previous")), l && n.preventDefault(), l;
};
- return e.registerCommand(Tr, r, Ie);
+ return e.registerCommand(Sr, r, Ie);
}, [e, t]);
}
-function Ru(e) {
+function Xu(e) {
return e.dir || "ltr";
}
-function $u(e, t) {
+function Qu(e, t) {
return e === "ltr" && t === "ArrowRight" || e === "rtl" && t === "ArrowLeft";
}
-function q_(e, t) {
+function X_(e, t) {
return e === "ltr" && t === "ArrowLeft" || e === "rtl" && t === "ArrowRight";
}
-function Ua(e) {
- if (!$(e) || e.getMarker() !== "fp")
+function Fa(e) {
+ if (!D(e) || e.getMarker() !== "fp")
return;
- const t = Ht(e);
+ const t = Yt(e);
if (!(!t || t.getIsCollapsed()))
return e;
}
-function R_(e) {
- const t = Ua(ep(e));
+function Q_(e) {
+ const t = Fa(hp(e));
if (!t)
return !1;
const r = e.anchor;
- return r.type === "text" && r.offset !== r.getNode().getTextContentSize() ? !1 : (Gt(t, 0), !0);
+ return r.type === "text" && r.offset !== r.getNode().getTextContentSize() ? !1 : (Xt(t, 0), !0);
}
-function $_(e) {
+function Z_(e) {
const t = e.anchor, r = t.getNode();
if (t.type === "text") {
- const n = Ua(r.getParent());
- return !n || !r.is(n.getFirstChild()) ? !1 : t.offset === 1 ? (r.select(0, 0), !0) : t.offset !== 0 ? !1 : Iu(n);
+ const n = Fa(r.getParent());
+ return !n || !r.is(n.getFirstChild()) ? !1 : t.offset === 1 ? (r.select(0, 0), !0) : t.offset !== 0 ? !1 : Zu(n);
}
if (t.offset === 0) {
- const n = Ua(r);
- return n ? Iu(n) : !1;
+ const n = Fa(r);
+ return n ? Zu(n) : !1;
}
return !1;
}
-function Iu(e) {
+function Zu(e) {
const t = e.getPreviousSibling();
if (!t)
return !1;
- if (M(t))
+ if (v(t))
return t.select(), !0;
- if (D(t)) {
+ if (F(t)) {
const i = t.getLastDescendant();
- return M(i) ? i.select() : t.selectEnd(), !0;
+ return v(i) ? i.select() : t.selectEnd(), !0;
}
const r = e.getParent();
if (!r)
@@ -17599,39 +17724,39 @@ function Iu(e) {
const n = e.getIndexWithinParent();
return r.select(n, n), !0;
}
-const Vs = typeof Intl.Segmenter > "u" ? void 0 : new Intl.Segmenter(void 0, { granularity: "grapheme" });
-function I_(e) {
- if (Vs)
- for (const { segment: r } of Vs.segment(e))
+const Js = typeof Intl.Segmenter > "u" ? void 0 : new Intl.Segmenter(void 0, { granularity: "grapheme" });
+function eC(e) {
+ if (Js)
+ for (const { segment: r } of Js.segment(e))
return r.length;
const t = e.codePointAt(0);
return t === void 0 ? 0 : String.fromCodePoint(t).length;
}
-function L_(e) {
- if (Vs) {
+function tC(e) {
+ if (Js) {
let n = 0;
- for (const { index: i } of Vs.segment(e))
+ for (const { index: i } of Js.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 xh(e) {
+function Rh(e) {
for (let t = e; t; t = t.getParent())
- if (D(t) && !t.isInline())
+ if (F(t) && !t.isInline())
return t;
}
-function _h(e) {
- return !!e && P(e) && Tn(e) !== void 0;
+function $h(e) {
+ return !!e && O(e) && Hr(e) !== void 0;
}
-function ei(e) {
- return M(e) && !e.isToken() && !_h(e) && e.getTextContentSize() > 0;
+function ii(e) {
+ return v(e) && !e.isToken() && !$h(e) && e.getTextContentSize() > 0;
}
-function Ch(e) {
- return no(e) ? !0 : j(e) ? e.getIsCollapsed() === !0 : M(e) ? (e.isToken() || _h(e)) && e.getTextContentSize() > 0 : Qd(e) ? !je(e) : !1;
+function Ih(e) {
+ return us(e) ? !0 : j(e) ? e.getIsCollapsed() === !0 : v(e) ? (e.isToken() || $h(e)) && e.getTextContentSize() > 0 : co(e) ? !Be(e) : !1;
}
-function ti(e, t, r) {
+function si(e, t, r) {
for (let n = e; n && !n.is(r); ) {
const i = t === "next" ? n.getNextSibling() : n.getPreviousSibling();
if (i)
@@ -17641,46 +17766,46 @@ function ti(e, t, r) {
}
function vo(e, t, r) {
for (let n = e; n; ) {
- if (Ch(n))
+ if (Ih(n))
return n;
- if (D(n)) {
- n = (t === "next" ? n.getFirstChild() : n.getLastChild()) ?? ti(n, t, r);
+ if (F(n)) {
+ n = (t === "next" ? n.getFirstChild() : n.getLastChild()) ?? si(n, t, r);
continue;
}
- if (ei(n))
+ if (ii(n))
return n;
- n = ti(n, t, r);
+ n = si(n, t, r);
}
}
-function Qc(e, t, r, n, i) {
- return r === "element" && D(e) ? e.getChildAtIndex(n === "next" ? t : t - 1) ?? ti(e, n, i) : r === "text" && Ch(e) && (n === "next" ? t < e.getTextContentSize() : t > 0) ? e : ti(e, n, i);
+function ol(e, t, r, n, i) {
+ return r === "element" && F(e) ? e.getChildAtIndex(n === "next" ? t : t - 1) ?? si(e, n, i) : r === "text" && Ih(e) && (n === "next" ? t < e.getTextContentSize() : t > 0) ? e : si(e, n, i);
}
function ra(e, t) {
if (e.kind === "text" && e.offset > 0)
return e;
- const r = Qc(e.node, e.offset, e.kind, "previous", t), n = vo(r, "previous", t);
+ const r = ol(e.node, e.offset, e.kind, "previous", t), n = vo(r, "previous", t);
if (!n)
return e;
- if (ei(n))
+ if (ii(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 D_(e, t) {
- const r = e.getNode(), n = xh(r);
+function rC(e, t) {
+ const r = e.getNode(), n = Rh(r);
if (!n)
return;
- if (e.type === "text" && ei(r)) {
+ if (e.type === "text" && ii(r)) {
if (t === "next" && e.offset < r.getTextContentSize() || t === "previous" && e.offset > 1)
return;
if (t === "previous" && e.offset === 1)
return ra({ kind: "text", node: r, offset: 0 }, n);
}
- const i = Qc(r, e.offset, e.type, t, n), s = vo(i, t, n);
+ const i = ol(r, e.offset, e.type, t, n), s = vo(i, t, n);
if (!s)
return;
- if (ei(s)) {
- const c = s.getTextContent(), l = t === "next" ? I_(c) : L_(c);
+ if (ii(s)) {
+ const c = s.getTextContent(), l = t === "next" ? eC(c) : tC(c);
return ra({ kind: "text", node: s, offset: l }, n);
}
const o = s.getParent();
@@ -17689,47 +17814,47 @@ function D_(e, t) {
const a = s.getIndexWithinParent();
return ra({ kind: "element", node: o, offset: t === "next" ? a + 1 : a }, n);
}
-function vh(e, t, r) {
- const n = r === "collapse" ? e.anchor : e.focus, i = D_(n, t);
+function Lh(e, t, r) {
+ const n = r === "collapse" ? e.anchor : e.focus, i = rC(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 Lu(e, t) {
- return vh(e, t, "collapse");
+function ed(e, t) {
+ return Lh(e, t, "collapse");
}
-function U_(e, t) {
- return vh(e, t, "extend");
+function nC(e, t) {
+ return Lh(e, t, "extend");
}
-function F_(e, t, r) {
+function iC(e, t, r) {
const n = e.getNode();
- if (e.type === "text" && ei(n) && (t === "next" ? e.offset < n.getTextContentSize() : e.offset > 0))
+ if (e.type === "text" && ii(n) && (t === "next" ? e.offset < n.getTextContentSize() : e.offset > 0))
return !1;
- const i = Qc(n, e.offset, e.type, t, r);
+ const i = ol(n, e.offset, e.type, t, r);
return vo(i, t, r) === void 0;
}
-function z_(e, t) {
+function sC(e, t) {
const r = Ue();
for (let n = e; n; ) {
- const i = ti(n, t, r), s = i && vo(i, t, r);
+ const i = si(n, t, r), s = i && vo(i, t, r);
if (!s)
return;
- if (n = Tn(s), !n)
+ if (n = Hr(s), !n)
return s;
}
}
-function Du(e, t) {
+function td(e, t) {
const r = e.anchor, n = r.getNode();
- if (Tn(n))
+ if (Hr(n))
return !1;
- const i = xh(n);
- if (!i || !F_(r, t, i))
+ const i = Rh(n);
+ if (!i || !iC(r, t, i))
return !1;
- const s = ti(i, t, Ue()), o = s && Tn(s);
+ const s = si(i, t, Ue()), o = s && Hr(s);
if (!o)
return !1;
- const a = z_(o, t);
+ const a = sC(o, t);
if (!a)
return !0;
- if (ei(a)) {
+ if (ii(a)) {
const u = t === "next" ? 0 : a.getTextContentSize();
return a.select(u, u), !0;
}
@@ -17739,17 +17864,17 @@ function Du(e, t) {
const l = a.getIndexWithinParent() + (t === "next" ? 0 : 1);
return c.select(l, l), !0;
}
-function Uu(e) {
+function rd(e) {
const t = e.getParent();
if (!t)
return;
const r = e.getIndexWithinParent() + 1;
t.select(r, r);
}
-function K_(e) {
- const t = e.anchor.getNode(), r = ep(e);
- if (j(r) && !P(r.getFirstChild())) {
- if (Ce(t)) {
+function oC(e) {
+ const t = e.anchor.getNode(), r = hp(e);
+ if (j(r) && !O(r.getFirstChild())) {
+ if (Se(t)) {
if (e.anchor.offset === t.getChildrenSize())
return !1;
} else if (!(e.anchor.offset === t.getTextContentSize()))
@@ -17757,33 +17882,33 @@ function K_(e) {
if (r.getIsCollapsed()) {
if (r.is(r.getParent()?.getLastChild())) {
const i = r.getParent()?.getNextSibling();
- return i && !(Ce(i) && di(i)) && i.selectStart(), !0;
+ return i && !(Se(i) && mi(i)) && i.selectStart(), !0;
}
- } else return Wt(r.getFirstChild()) ? r.select(2, 2) : r.select(1, 1), !0;
+ } else return Jt(r.getFirstChild()) ? r.select(2, 2) : r.select(1, 1), !0;
}
- if (Ce(t) && j(r) && r.getIsCollapsed()) {
+ if (Se(t) && j(r) && r.getIsCollapsed()) {
const i = r.getNextSibling();
- return i ? i.selectStart() : Uu(r), !0;
+ return i ? i.selectStart() : rd(r), !0;
}
const n = r?.getParent();
- if (Wt(r) && j(n) && r.is(n?.getLastChild())) {
+ if (Jt(r) && j(n) && r.is(n?.getLastChild())) {
const i = n.getNextSibling();
- return i ? i.selectStart() : n.getIsCollapsed() ? Uu(n) : n.selectEnd(), !0;
+ return i ? i.selectStart() : n.getIsCollapsed() ? rd(n) : n.selectEnd(), !0;
}
return !1;
}
-function j_(e, t) {
- const r = zb(e);
- if (as(r) && !r.getPreviousSibling())
+function aC(e, t) {
+ const r = ok(e);
+ if (fs(r) && !r.getPreviousSibling())
return !0;
if (!(e.anchor.offset === 0))
return !1;
const i = e.anchor.getNode();
- if (Tt(i.getParent()))
+ if (_t(i.getParent()))
return !0;
if (j(r) && r.getIsCollapsed()) {
const o = r.getPreviousSibling();
- if (!Pn(o))
+ if (!On(o))
return !1;
const a = r.getParent();
if (!a)
@@ -17791,7 +17916,7 @@ function j_(e, t) {
const c = r.getIndexWithinParent();
return a.select(c, c), !0;
}
- if (Ce(r) && t?.noteMode === "collapsed") {
+ if (Se(r) && t?.noteMode === "collapsed") {
const o = r.getLastChild();
if (!o)
return !1;
@@ -17804,10 +17929,10 @@ function j_(e, t) {
return c.select(l, l), !0;
}
}
- const s = Ht(i);
+ const s = Yt(i);
if (!s || s.getIsCollapsed())
return !1;
- if (cr(r)) {
+ if (St(r)) {
const o = s.getParent();
if (!o)
return !1;
@@ -17816,60 +17941,60 @@ function j_(e, t) {
}
return !1;
}
-function B_(e) {
+function cC(e) {
if (e.anchor.type === "element")
return !0;
if (e.anchor.offset !== 0)
return !1;
const t = e.anchor.getNode().getPreviousSibling();
- return me(t) && Qd(t);
+ return ge(t) && co(t);
}
-function V_() {
- const [e] = le();
- return W_(e), null;
+function lC() {
+ const [e] = ce();
+ return uC(e), null;
}
-function W_(e) {
- K(() => {
- if (!e.hasNodes([ye]))
+function uC(e) {
+ z(() => {
+ if (!e.hasNodes([me]))
throw new Error("CharNodePlugin: CharNode not registered on editor!");
- return Xe(
- e.registerNodeTransform(ye, J_),
+ return He(
+ e.registerNodeTransform(me, pC),
// 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(ye, lk),
+ e.registerNodeTransform(me, Ak),
// 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(ye, xp),
+ e.registerNodeTransform(me, $p),
// 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(ye, (t) => Gi(yn("char"), t)),
- e.registerNodeTransform(ze, Y_)
+ e.registerNodeTransform(me, (t) => es(Tn("char"), t)),
+ e.registerNodeTransform(Ke, hC)
);
}, [e]);
}
function na(e) {
- return e.getChildren().some(P);
+ return e.getChildren().some(O);
}
-function H_(e, t) {
+function dC(e, t) {
const r = t.getFirstChild();
- if (!P(r) || r.getMarkerSyntax() !== "opening")
+ if (!O(r) || r.getMarkerSyntax() !== "opening")
return !1;
const n = r.getNextSibling();
- if (mo(n)) {
+ if (yo(n)) {
const i = n.getTextContent();
- i.startsWith(w) && (i === w ? n.remove() : n.setTextContent(i.slice(w.length)));
+ i.startsWith(L) && (i === L ? n.remove() : n.setTextContent(i.slice(L.length)));
}
return t.splice(1, 0, e.getChildren()), e.remove(), !0;
}
-function G_(e, t) {
+function fC(e, t) {
const r = t.getLastChild(), n = e.getChildren();
- P(r) && r.getMarkerSyntax() === "closing" ? n.forEach((i) => r.insertBefore(i)) : t.append(...n), e.remove();
+ O(r) && r.getMarkerSyntax() === "closing" ? n.forEach((i) => r.insertBefore(i)) : t.append(...n), e.remove();
}
-function J_(e) {
- if (!$(e))
+function pC(e) {
+ if (!D(e))
return;
if (e.isEmpty()) {
e.remove();
@@ -17880,27 +18005,39 @@ function J_(e) {
const t = e.getMarker();
if (t === "fp")
return;
- const r = re(e, gn), n = e.getUnknownAttributes(), i = e.getNextSibling();
- if ($(i) && mn({ style: t, cid: r }, i) && Pt(n, i.getUnknownAttributes()))
+ const r = ne(e, bn), n = e.getUnknownAttributes(), i = e.getNextSibling();
+ if (D(i) && kn({ style: t, cid: r }, i) && wt(n, i.getUnknownAttributes()))
if (na(i)) {
- if (H_(e, i))
+ if (dC(e, i))
return;
} else
e.append(...i.getChildren()), i.remove();
const s = e.getPreviousSibling();
- $(s) && mn({ style: t, cid: r }, s) && Pt(n, s.getUnknownAttributes()) && (na(s) ? G_(e, s) : (s.append(...e.getChildren()), e.remove()));
+ D(s) && kn({ style: t, cid: r }, s) && wt(n, s.getUnknownAttributes()) && (na(s) ? fC(e, s) : (s.append(...e.getChildren()), e.remove()));
}
-function Y_(e) {
+function hC(e) {
const t = e.getParent();
- if (!$(t) || t.getChildrenSize() !== 1)
+ if (!D(t) || t.getChildrenSize() !== 1)
return;
const r = e.getTextContent();
- r.length > 1 && r.startsWith(It) && (e.setTextContent(r.slice(1)), e.selectEnd());
+ r.length > 1 && r.startsWith(Dt) && (e.setTextContent(r.slice(1)), e.selectEnd());
}
-function Sh(e) {
+function Dh(e) {
return e.replaceAll(" ", " ");
}
-const Zc = (e) => {
+function Uh() {
+ const e = R();
+ return !!e && !e.isCollapsed();
+}
+function Fh(e) {
+ const t = () => !Uh();
+ return He(e.registerCommand(bc, t, kt), e.registerCommand(Qn, t, kt));
+}
+const al = (e) => {
+ e.dispatchCommand(bc, null);
+}, cl = (e) => {
+ e.dispatchCommand(Qn, null);
+}, ll = (e) => {
navigator.clipboard.read().then(async (t) => {
if ((await navigator.permissions.query({
// @ts-expect-error These types are incorrect.
@@ -17912,14 +18049,14 @@ const Zc = (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, Sh(a));
+ n.setData(o, Dh(a));
}
const s = new ClipboardEvent("paste", {
clipboardData: n
});
- e.dispatchCommand(dr, s);
+ e.dispatchCommand(yr, s);
});
-}, el = (e) => {
+}, ul = (e) => {
navigator.clipboard.read().then(async () => {
if ((await navigator.permissions.query({
// @ts-expect-error These types are incorrect.
@@ -17929,117 +18066,127 @@ const Zc = (e) => {
return;
}
const r = new DataTransfer(), n = await navigator.clipboard.readText();
- r.setData("text/plain", Sh(n));
+ r.setData("text/plain", Dh(n));
const i = new ClipboardEvent("paste", {
clipboardData: r
});
- e.dispatchCommand(dr, i);
+ e.dispatchCommand(yr, i);
});
};
-function X_() {
- const [e] = le();
- return K(() => {
+function gC() {
+ const [e] = ce();
+ return z(() => {
const t = (r) => {
const { key: n, shiftKey: i, metaKey: s, ctrlKey: o, altKey: a } = r;
- !(Ps ? s : o) || a || (!i && n.toLowerCase() === "c" ? (r.preventDefault(), e.dispatchCommand(so, null)) : !i && n.toLowerCase() === "x" ? (r.preventDefault(), e.dispatchCommand(pn, null)) : n.toLowerCase() === "v" && (r.preventDefault(), i ? el(e) : Zc(e)));
+ !(Rs ? s : o) || a || (!i && n.toLowerCase() === "c" ? (r.preventDefault(), al(e)) : !i && n.toLowerCase() === "x" ? (r.preventDefault(), cl(e)) : n.toLowerCase() === "v" && (r.preventDefault(), i ? ul(e) : ll(e)));
};
- return e.registerRootListener((r, n) => {
- n !== null && n.removeEventListener("keydown", t), r !== null && r.addEventListener("keydown", t);
- });
+ return He(
+ // 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.
+ Fh(e),
+ e.registerRootListener((r, n) => {
+ n !== null && n.removeEventListener("keydown", t), r !== null && r.addEventListener("keydown", t);
+ })
+ );
}, [e]), null;
}
-function Q_({ logger: e }) {
- const [t] = le();
- return K(() => Xe(
+function mC({ logger: e }) {
+ const [t] = ce();
+ return z(() => He(
// When the backslash or forward slash key is typed.
- t.registerCommand(Tr, (r) => r.key !== "\\" && r.key !== "/" ? !1 : (r.preventDefault(), !0), Hn),
+ t.registerCommand(Sr, (r) => r.key !== "\\" && r.key !== "/" ? !1 : (r.preventDefault(), !0), Gn),
// When the backslash or forward slash character is pasted into the editor.
- t.registerCommand(dr, (r) => {
+ t.registerCommand(yr, (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);
- }, Hn),
+ }, Gn),
// When the backslash or forward slash character is dragged into the editor.
- t.registerCommand(fc, (r) => {
+ t.registerCommand(yc, (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);
- }, Hn)
+ }, Gn)
), [t, e]), null;
}
-function Z_({ index: e, isSelected: t, onClick: r, onMouseEnter: n, option: i }) {
+function yC({ 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 }) });
}
-function eC({ options: e, selectedItemIndex: t, onOptionClick: r, onOptionMouseEnter: n }) {
- return C("div", { className: "typeahead-popover", children: C("ul", { children: e.map((i, s) => C(Z_, { index: s, isSelected: t === s, onClick: () => r(i, s), onMouseEnter: () => n(s), option: i }, i.key)) }) });
+function bC({ options: e, selectedItemIndex: t, onOptionClick: r, onOptionMouseEnter: n }) {
+ return C("div", { className: "typeahead-popover", children: C("ul", { children: e.map((i, s) => C(yC, { index: s, isSelected: t === s, onClick: () => r(i, s), onMouseEnter: () => n(s), option: i }, i.key)) }) });
}
-let tC = 0;
-class _i {
+let kC = 0;
+class Mi {
key;
title;
onSelect;
isDisabled;
constructor(t, r) {
- this.key = `context-menu-option-${tC++}`, this.title = t, this.onSelect = r.onSelect.bind(this), this.isDisabled = r.isDisabled || !1;
+ this.key = `context-menu-option-${kC++}`, this.title = t, this.onSelect = r.onSelect.bind(this), this.isDisabled = r.isDisabled || !1;
}
}
-function rC({ options: e } = {}) {
- const [t] = le(), [r, n] = de(() => !t.isEditable()), [i, s] = de({
+function TC({ options: e } = {}) {
+ const [t] = ce(), [r, n] = de(() => !t.isEditable()), [i, s] = de({
isOpen: !1,
x: 0,
y: 0
}), [o, a] = de(void 0), c = Fe(() => {
const d = [
- new _i("Cut", {
+ // 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 Mi("Cut", {
onSelect: () => {
- t.dispatchCommand(pn, null);
+ cl(t);
},
isDisabled: r
}),
- new _i("Copy", {
+ new Mi("Copy", {
onSelect: () => {
- t.dispatchCommand(so, null);
+ al(t);
}
}),
- new _i("Paste", {
+ new Mi("Paste", {
onSelect: () => {
- Zc(t);
+ ll(t);
},
isDisabled: r
}),
- new _i("Paste as Plain Text", {
+ new Mi("Paste as Plain Text", {
onSelect: () => {
- el(t);
+ ul(t);
},
isDisabled: r
})
- ], f = (e ?? []).map((p) => new _i(p.title, { onSelect: p.onSelect, isDisabled: p.isDisabled }));
+ ], f = (e ?? []).map((p) => new Mi(p.title, { onSelect: p.onSelect, isDisabled: p.isDisabled }));
return [...d, ...f];
- }, [t, r, e]), l = ge(() => {
+ }, [t, r, e]), l = he(() => {
s((d) => ({ ...d, isOpen: !1 })), a(void 0);
}, []);
- K(() => {
+ z(() => Fh(t), [t]), z(() => {
const d = (f) => {
const p = f.target;
- t.getRootElement() === p || Vf(p) || (f.preventDefault(), s({ isOpen: !0, x: f.clientX, y: f.clientY }), a(void 0));
+ t.getRootElement() === p || sp(p) || (f.preventDefault(), s({ isOpen: !0, x: f.clientX, y: f.clientY }), a(void 0));
};
return t.registerRootListener((f, p) => {
p?.removeEventListener("contextmenu", d), f && f.addEventListener("contextmenu", d);
});
- }, [t]), K(() => {
+ }, [t]), z(() => {
if (!i.isOpen)
return;
const d = () => {
l();
};
return globalThis.addEventListener("scroll", d, !0), () => globalThis.removeEventListener("scroll", d, !0);
- }, [i.isOpen, l]), K(() => {
+ }, [i.isOpen, l]), z(() => {
if (!i.isOpen)
return;
const d = () => {
l();
};
return document.addEventListener("pointerdown", d), () => document.removeEventListener("pointerdown", d);
- }, [i.isOpen, l]), K(() => {
+ }, [i.isOpen, l]), z(() => {
if (!i.isOpen)
return;
const d = (f) => {
@@ -18058,17 +18205,17 @@ function rC({ options: e } = {}) {
}
};
return document.addEventListener("keydown", d, !0), () => document.removeEventListener("keydown", d, !0);
- }, [i.isOpen, l, c, o, t]), K(() => t.registerEditableListener((d) => {
+ }, [i.isOpen, l, c, o, t]), z(() => t.registerEditableListener((d) => {
n(!d);
}), [t]);
- const u = X(null);
- return rs(() => {
+ const u = Z(null);
+ return cs(() => {
const d = u.current;
if (!d)
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 ? ly.createPortal(C("div", { ref: u, className: "typeahead-popover auto-embed-menu", style: {
+ }, [i.isOpen, i.x, i.y]), i.isOpen ? Ny.createPortal(C("div", { ref: u, className: "typeahead-popover auto-embed-menu", style: {
left: i.x,
position: "fixed",
top: i.y,
@@ -18076,7 +18223,7 @@ function rC({ options: e } = {}) {
visibility: "hidden",
width: 200,
zIndex: 9999
- }, onPointerDown: (d) => d.stopPropagation(), children: C(eC, { options: c, selectedItemIndex: o, onOptionClick: (d) => {
+ }, onPointerDown: (d) => d.stopPropagation(), children: C(bC, { options: c, selectedItemIndex: o, onOptionClick: (d) => {
d.isDisabled || (t.update(() => {
d.onSelect();
}), l());
@@ -18084,78 +18231,150 @@ function rC({ options: e } = {}) {
a(d);
} }) }), document.body) : null;
}
-function nC() {
- const [e] = le();
- return K(() => e.registerCommand(Tr, (t) => {
+function xC(e, t) {
+ return e.startContainer === t.node && e.startOffset === t.offset;
+}
+function _C(e) {
+ if (!fy(e.node))
+ return "before";
+ const t = e.node.nodeValue?.length ?? 0;
+ return e.offset * 2 < t ? "before" : "after";
+}
+function CC(e) {
+ return St(e);
+}
+function ia(e, t, r) {
+ const n = ci(t.node);
+ if (!co(n) || CC(n))
+ return;
+ const i = e.getElementByKey(n.getKey());
+ if (!i || i === t.node || !i.contains(t.node))
+ return;
+ const s = i.parentNode;
+ if (!s)
+ return;
+ const o = Array.prototype.indexOf.call(s.childNodes, i);
+ if (!(o < 0))
+ return { node: s, offset: r === "before" ? o : o + 1 };
+}
+function SC(e, t) {
+ if (R())
+ return !1;
+ const r = e.getRootElement(), n = ly(r?.ownerDocument.defaultView ?? null);
+ if (!n || n.rangeCount === 0)
+ return !1;
+ const { anchorNode: i, anchorOffset: s, focusNode: o, focusOffset: a } = n;
+ if (!i || !o || !uy(e, i, o))
+ return !1;
+ const c = { node: i, offset: s }, l = { node: o, offset: a };
+ let u, d;
+ if (n.isCollapsed)
+ u = ia(e, c, _C(c)), d = u;
+ else {
+ const y = xC(n.getRangeAt(0), c);
+ u = ia(e, c, y ? "before" : "after"), d = ia(e, l, y ? "after" : "before");
+ }
+ if (!u && !d)
+ return !1;
+ const f = u ?? c, p = d ?? l, m = {
+ anchorNode: f.node,
+ anchorOffset: f.offset,
+ focusNode: p.node,
+ focusOffset: p.offset
+ }, g = dy(m, e);
+ return g ? (Zn(g), g.dirty = !t, t) : !1;
+}
+function vC() {
+ const [e] = ce(), t = Z(!1), r = Z(!1);
+ return z(() => {
+ const n = (s) => {
+ s instanceof PointerEvent && s.button !== 0 || (t.current = !0);
+ }, i = () => {
+ t.current = !1, r.current && (r.current = !1, e.update(() => {
+ const s = R();
+ N(s) && (s.dirty = !0);
+ }));
+ };
+ return e.registerRootListener((s, o) => {
+ const a = o?.ownerDocument;
+ a?.removeEventListener("pointerdown", n, !0), a?.removeEventListener("pointerup", i, !0), a?.removeEventListener("pointercancel", i, !0), t.current = !1, r.current = !1;
+ const c = s?.ownerDocument;
+ c?.addEventListener("pointerdown", n, !0), c?.addEventListener("pointerup", i, !0), c?.addEventListener("pointercancel", i, !0);
+ });
+ }, [e]), z(() => e.registerCommand(ur, () => (SC(e, t.current) && (r.current = !0), !1), ar), [e]), null;
+}
+function MC() {
+ const [e] = ce();
+ return z(() => e.registerCommand(Sr, (t) => {
const { key: r, shiftKey: n, metaKey: i, ctrlKey: s, altKey: o } = t;
- if (!(Ps ? i : s) || o)
+ if (!(Rs ? i : s) || o)
return !1;
const a = r.toLowerCase();
return !(a === "z" && !n) && !(a === "y" || a === "z" && n) ? !1 : (t.preventDefault(), !0);
- }, fr), [e]), null;
+ }, ar), [e]), null;
}
-function iC({ isEditable: e }) {
- const [t] = le();
- return rs(() => {
+function EC({ isEditable: e }) {
+ const [t] = ce();
+ return cs(() => {
t.setEditable(e);
}, [t, e]), null;
}
-function Fu(e) {
- return !!e && Cc(ne(e));
+function nd(e) {
+ return !!e && Nc(se(e));
}
-function Mh(e) {
- const [t] = le(), r = X(void 0), n = ge((i) => {
- const s = R(), o = N(s) && s.isCollapsed() ? s.anchor.key : void 0, a = r.current, c = Fu(a);
+function zh(e) {
+ const [t] = ce(), r = Z(void 0), n = he((i) => {
+ const s = R(), o = N(s) && s.isCollapsed() ? s.anchor.key : void 0, a = r.current, c = nd(a);
a && !c && (r.current = void 0);
let l;
if (i) {
- const u = i.getParentOrThrow(), d = i.getIndexWithinParent() + 1, f = bo(u, d);
+ const u = i.getParentOrThrow(), d = i.getIndexWithinParent() + 1, f = ko(u, d);
if (f)
r.current = f.getKey(), l = f.getKey();
else {
- const p = Lb();
+ const p = rk();
i.insertAfter(p), r.current = p.getKey(), l = p.getKey();
}
- Gt(u, d);
+ Xt(u, d);
}
if (a && c && a !== o && a !== l) {
- const u = ne(a);
- M(u) && u.remove(), r.current === a && (r.current = void 0);
+ const u = se(a);
+ v(u) && u.remove(), r.current === a && (r.current = void 0);
}
}, []);
- return K(() => {
+ return z(() => {
const i = () => {
const a = e(), c = R(), l = N(c) && c.isCollapsed() ? c.anchor.key : void 0, u = r.current;
- (a || u && u !== l) && (Dr(Ur), n(a));
+ (a || u && u !== l) && (Kr(jr), n(a));
}, s = (a) => {
if (a.getKey() !== r.current)
return;
const c = a.getTextContent();
- if (cs(c) || !c.includes(Yn))
+ if (ps(c) || !c.includes(ei))
return;
const l = R(), u = N(l) && l.isCollapsed() && l.anchor.key === a.getKey() ? l.anchor.offset : void 0;
- if (Db(a), r.current = void 0, u !== void 0) {
- const d = c.slice(0, u).split(Yn).length - 1, f = Math.max(0, u - d);
+ if (nk(a), r.current = void 0, u !== void 0) {
+ const d = c.slice(0, u).split(ei).length - 1, f = Math.max(0, u - d);
a.select(f, f);
}
- }, o = Xe(t.registerCommand(gr, () => (i(), !1), fn), t.registerCommand(pc, () => {
+ }, o = He(t.registerCommand(ur, () => (i(), !1), mn), t.registerCommand(kc, () => {
const a = r.current;
if (!a)
return !1;
let c = !1;
return t.getEditorState().read(() => {
- c = Fu(a);
+ c = nd(a);
}), c && t.update(() => {
- const l = ne(a);
- M(l) && l.remove();
- }, { tag: Ur }), r.current = void 0, !1;
- }, fn), t.registerNodeTransform(ze, s));
+ const l = se(a);
+ v(l) && l.remove();
+ }, { tag: jr }), r.current = void 0, !1;
+ }, mn), t.registerNodeTransform(Ke, s));
return () => {
o(), r.current = void 0;
};
}, [t, e, n]), n;
}
-function sC() {
+function AC() {
const e = R();
if (!N(e) || !e.isCollapsed())
return;
@@ -18163,23 +18382,23 @@ function sC() {
if (t.type !== "element")
return;
const r = t.getNode();
- if (!D(r))
+ if (!F(r))
return;
const n = r.getChildren(), i = n[t.offset - 1];
- if (!me(i) || bo(r, t.offset))
+ if (!ge(i) || ko(r, t.offset))
return;
const s = n[t.offset];
- if (s === void 0 || me(s))
+ if (s === void 0 || ge(s))
return i;
}
-function oC() {
- return Mh(sC), null;
+function PC() {
+ return zh(AC), null;
}
-function aC({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, viewOptions: i, logger: s }) {
- const [o] = le();
- return K(() => {
+function NC({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, viewOptions: i, logger: s }) {
+ const [o] = ce();
+ return z(() => {
n.initialize?.(r, s);
- }, [n, s, r]), K(() => {
+ }, [n, s, r]), z(() => {
const a = t?.current ?? e;
n.reset?.();
const c = n.serializeEditorState(a, i);
@@ -18192,46 +18411,46 @@ function aC({ scripture: e, scriptureRef: t, nodeOptions: r, editorAdaptor: n, v
queueMicrotask(() => {
const u = o.getRootElement(), d = u?.ownerDocument.activeElement, f = u != null && d != null && (u === d || u.contains(d));
o.update(() => {
- f || Dr(Vm), o.setEditorState(l), o.dispatchCommand(Wm, void 0);
- }, { tag: bf });
+ f || Kr(py), o.setEditorState(l), o.dispatchCommand(hy, void 0);
+ }, { tag: wf });
});
} catch {
s?.error("LoadStatePlugin: error parsing or setting editor state.");
}
}, [o, n, s, e, t, i]), null;
}
-function cC({ expandedNoteKeyRef: e, nodeOptions: t, viewOptions: r, logger: n }) {
- const [i] = le();
- return lC(t, n), uC(i, e, r, n), null;
+function OC({ expandedNoteKeyRef: e, nodeOptions: t, viewOptions: r, logger: n }) {
+ const [i] = ce();
+ return wC(t, n), qC(i, e, r, n), null;
}
-function lC(e, t) {
- const r = X(void 0), n = X(void 0), i = e.noteCallers, s = e.crossRefCallers;
- K(() => {
+function wC(e, t) {
+ const r = Z(void 0), n = Z(void 0), i = e.noteCallers, s = e.crossRefCallers;
+ z(() => {
let o = i;
- (!o || o.length <= 0) && (o = _x), r.current !== o && (r.current = o, zu("note-callers", o, t));
- }, [t, i]), K(() => {
+ (!o || o.length <= 0) && (o = Fx), r.current !== o && (r.current = o, id("note-callers", o, t));
+ }, [t, i]), z(() => {
let o = s;
- (!o || o.length <= 0) && (o = Cx), n.current !== o && (n.current = o, zu("cross-ref-callers", o, t));
+ (!o || o.length <= 0) && (o = zx), n.current !== o && (n.current = o, id("cross-ref-callers", o, t));
}, [t, s]);
}
-function uC(e, t, r, n) {
- K(() => {
- if (!e.hasNodes([ye, Me, Bt]))
+function qC(e, t, r, n) {
+ z(() => {
+ if (!e.hasNodes([me, Ee, Ht]))
throw new Error("NoteNodePlugin: CharNode, NoteNode or ImmutableNoteCallerNode not registered on editor!");
- const i = (s) => e.update(() => yC(s));
- return Xe(
+ const i = (s) => e.update(() => FC(s));
+ return He(
// Remove NoteNode if it doesn't contain a caller node and ensure typed text goes before it.
- e.registerNodeTransform(Me, (s) => dC(s, r)),
+ e.registerNodeTransform(Ee, (s) => RC(s, r)),
// Update NoteNodeCaller preview text when NoteNode children text is changed.
- e.registerNodeTransform(ye, fC),
- e.registerNodeTransform(ze, pC),
+ e.registerNodeTransform(me, $C),
+ e.registerNodeTransform(Ke, IC),
// Ensure NBSP after caller.
- e.registerNodeTransform(Bt, hC),
+ e.registerNodeTransform(Ht, LC),
// Re-generate all note callers when a note is removed.
- e.registerMutationListener(Bt, (s, { prevEditorState: o }) => gC(s, o)),
+ e.registerMutationListener(Ht, (s, { prevEditorState: o }) => DC(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(gr, () => mC(e, t, r, n), Rt),
+ e.registerCommand(ur, () => UC(e, t, r, n), kt),
// 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);
@@ -18239,52 +18458,52 @@ function uC(e, t, r, n) {
);
}, [e, t, n, r]);
}
-function dC(e, t) {
+function RC(e, t) {
const r = e.getChildren();
- if (!r.some((i) => cr(i)) && t?.markerMode !== "editable" && e.getCaller() !== "" && e.remove(), r.length > 0) {
+ if (!r.some((i) => St(i)) && t?.markerMode !== "editable" && e.getCaller() !== "" && e.remove(), r.length > 0) {
const i = r[0];
- M(i) && !P(i) && i.getTextContent() !== St(e.getCaller()) && e.insertBefore(i);
+ v(i) && !O(i) && i.getTextContent() !== At(e.getCaller()) && e.insertBefore(i);
}
}
-function fC(e) {
- const t = e.getParentOrThrow(), r = t.getChildren(), n = r.find((o) => cr(o));
- if (!$(e) || !j(t) || !n)
+function $C(e) {
+ const t = e.getParentOrThrow(), r = t.getChildren(), n = r.find((o) => St(o));
+ if (!D(e) || !j(t) || !n)
return;
- const i = vc(r);
+ const i = Oc(r);
n.getPreviewText() !== i && n.setPreviewText(i);
const s = e.getNextSibling();
- M(s) ? s.getTextContent() !== w && s.setTextContent(w) : e.insertAfter(he(w));
+ v(s) ? s.getTextContent() !== L && s.setTextContent(L) : e.insertAfter(pe(L));
}
-function pC(e) {
- const t = Ht(e), r = t?.getChildren(), n = r?.find((o) => cr(o));
- if (!M(e) || !j(t) || !n || !r)
+function IC(e) {
+ const t = Yt(e), r = t?.getChildren(), n = r?.find((o) => St(o));
+ if (!v(e) || !j(t) || !n || !r)
return;
const i = e.getParent();
- if (!P(e) && j(i) && e.getTextContent() !== w && (e.setTextContent(w), e.selectEnd()), $(i) && i.getChildrenSize() === 1) {
+ if (!O(e) && j(i) && e.getTextContent() !== L && (e.setTextContent(L), e.selectEnd()), D(i) && i.getChildrenSize() === 1) {
const o = e.getTextContent();
- o.length > 1 && o.startsWith(It) && (e.setTextContent(o.slice(1)), e.selectEnd());
+ o.length > 1 && o.startsWith(Dt) && (e.setTextContent(o.slice(1)), e.selectEnd());
}
- const s = vc(r);
+ const s = Oc(r);
n.getPreviewText() !== s && n.setPreviewText(s);
}
-function hC(e) {
- if (!cr(e))
+function LC(e) {
+ if (!St(e))
return;
const t = e.getNextSibling();
- !M(t) || P(t) ? e.insertAfter(he(w)) : t.getTextContent() !== w && t.setTextContent(w);
+ !v(t) || O(t) ? e.insertAfter(pe(L)) : t.getTextContent() !== L && t.setTextContent(L);
}
-function gC(e, t) {
+function DC(e, t) {
for (const [r, n] of e) {
if (n !== "destroyed")
continue;
const i = t.read(() => {
- const o = ne(r), a = o?.getParent();
- return cr(o) && j(a) && a.getCaller() === Os;
+ const o = se(r), a = o?.getParent();
+ return St(o) && j(a) && a.getCaller() === Bi;
}), s = document.querySelector(".editor-input");
!i || !s || (s.classList.add("reset-counters"), s.offsetHeight, s.classList.remove("reset-counters"));
}
}
-function mC(e, t, r, n) {
+function UC(e, t, r, n) {
if (r?.noteMode !== "expandInline")
return !1;
const i = R();
@@ -18296,8 +18515,8 @@ function mC(e, t, r, n) {
if (a)
t.current !== a.getKey() && (t.current = a.getKey());
else {
- const c = ne(t.current);
- c && !c.getIsCollapsed() && (n?.debug("Cursor moved away from NoteNode, collapsing it"), Ci(e, t.current, n)), t.current = void 0;
+ const c = se(t.current);
+ c && !c.getIsCollapsed() && (n?.debug("Cursor moved away from NoteNode, collapsing it"), Ei(e, t.current, n)), t.current = void 0;
}
}
if (s.offset === 0) {
@@ -18305,7 +18524,7 @@ function mC(e, t, r, n) {
if (j(a)) {
n?.debug("Cursor is just after a NoteNode");
const c = a.getKey();
- a.getIsCollapsed() ? t.current = c : t.current = void 0, Ci(e, c, n);
+ a.getIsCollapsed() ? t.current = c : t.current = void 0, Ei(e, c, n);
}
}
if (s.offset === o.getTextContentSize()) {
@@ -18313,28 +18532,28 @@ function mC(e, t, r, n) {
if (j(a)) {
n?.debug("Cursor is just before a NoteNode");
const c = a.getKey();
- a.getIsCollapsed() ? t.current = c : t.current = void 0, Ci(e, c, n);
+ a.getIsCollapsed() ? t.current = c : t.current = void 0, Ei(e, c, n);
} else if (!a) {
const c = nt(o, (l) => j(l));
- if (c && c.getIsCollapsed() && Ce(c.getParent()) && c.is(c.getParent()?.getLastChild())) {
+ if (c && c.getIsCollapsed() && Se(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, Ci(e, l, n);
+ t.current = l, Ei(e, l, n);
}
}
}
- if (Ce(o)) {
+ if (Se(o)) {
const a = o.getChildAtIndex(s.offset), c = a?.getPreviousSibling();
- if (Pn(c) && j(a)) {
+ if (On(c) && j(a)) {
n?.debug("Cursor is between verse and NoteNode");
const l = a.getKey();
- a.getIsCollapsed() ? t.current = l : t.current = void 0, Ci(e, l, n);
+ a.getIsCollapsed() ? t.current = l : t.current = void 0, Ei(e, l, n);
}
}
return !1;
}
-function Ci(e, t, r) {
- const n = ne(t);
+function Ei(e, t, r) {
+ const n = se(t);
try {
n?.toggleIsCollapsed();
} catch (i) {
@@ -18348,23 +18567,23 @@ function Ci(e, t, r) {
throw i;
}
}
-function yC(e) {
+function FC(e) {
const t = R();
if (!N(t))
return;
const r = t.anchor, n = t.focus, i = r.getNode(), s = n.getNode();
- if (j(i) && M(s)) {
+ if (j(i) && v(s)) {
e.preventDefault();
- const o = uc();
- o.anchor.set(s.getKey(), 0, "text"), o.focus.set(s.getKey(), n.offset, "text"), Ui(o);
+ const o = hc();
+ o.anchor.set(s.getKey(), 0, "text"), o.focus.set(s.getKey(), n.offset, "text"), Zn(o);
}
}
-function zu(e, t, r) {
+function id(e, t, r) {
for (const n of document.styleSheets)
try {
const i = n.cssRules || n.rules;
for (const s of i)
- if (bC(s, e)) {
+ if (zC(s, e)) {
const o = t.map((a) => `"${a}"`).join(" ");
s.symbols = o;
return;
@@ -18374,95 +18593,95 @@ function zu(e, t, r) {
}
r?.warn(`Editor: counter style "${e}" not found.`);
}
-function bC(e, t) {
+function zC(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 So(e) {
+function Mo(e) {
if (e.getIsCollapsed() !== !1)
return [];
const t = [];
for (const n of e.getChildren()) {
- if (!P(n) || n.getMarkerSyntax() !== "opening")
+ if (!O(n) || n.getMarkerSyntax() !== "opening")
break;
t.push(n);
}
- const r = Vi(e);
- return r && t.push(r), t.length > 0 && t.every((n) => M(n) && n.getMode() === "token") ? t : [];
+ const r = Xi(e);
+ return r && t.push(r), t.length > 0 && t.every((n) => v(n) && n.getMode() === "token") ? t : [];
}
-function kC(e) {
+function KC(e) {
const t = e.getParent();
if (j(t))
- return So(t).some((r) => r.is(e)) ? t : void 0;
+ return Mo(t).some((r) => r.is(e)) ? t : void 0;
}
-function Ws(e) {
- const t = So(e), r = t[t.length - 1];
+function Ys(e) {
+ const t = Mo(e), r = t[t.length - 1];
return r ? r.getIndexWithinParent() + 1 : 0;
}
-function TC(e, t) {
+function jC(e, t) {
for (let r = t; r; r = r.getParent())
if (e.is(r.getParent()))
return r;
}
-function xC(e) {
- const t = Hm();
+function BC(e) {
+ const t = gy();
if (!N(t))
return !1;
const { anchor: r } = t, n = r.getNode();
if (e.is(n))
- return r.offset >= Ws(e);
- const i = TC(e, n);
- return i !== void 0 && i.getIndexWithinParent() >= Ws(e);
+ return r.offset >= Ys(e);
+ const i = jC(e, n);
+ return i !== void 0 && i.getIndexWithinParent() >= Ys(e);
}
-function Fa(e) {
+function za(e) {
if (e.type !== "text")
return;
- const t = e.getNode(), r = kC(t);
+ const t = e.getNode(), r = KC(t);
if (r)
- return _C(r, t, e.offset) ? void 0 : r;
+ return VC(r, t, e.offset) ? void 0 : r;
}
-function _C(e, t, r) {
- const n = So(e), i = n[n.length - 1];
+function VC(e, t, r) {
+ const n = Mo(e), i = n[n.length - 1];
return i !== void 0 && i.is(t) && r === i.getTextContentSize();
}
-function CC(e) {
- const t = So(e), r = t[t.length - 1];
- M(r) ? r.select(r.getTextContentSize(), r.getTextContentSize()) : Gt(e, Ws(e));
+function WC(e) {
+ const t = Mo(e), r = t[t.length - 1];
+ v(r) ? r.select(r.getTextContentSize(), r.getTextContentSize()) : Xt(e, Ys(e));
}
-function vC(e = !1) {
+function HC(e = !1) {
const t = R();
if (!N(t))
return !1;
if (!t.isCollapsed())
- return SC(t.anchor, t.focus);
- const r = Fa(t.anchor);
+ return GC(t.anchor, t.focus);
+ const r = za(t.anchor);
if (!r)
return !1;
- if (!e && xC(r)) {
+ if (!e && BC(r)) {
const n = r.getParent();
if (!n)
return !1;
- Gt(n, r.getIndexWithinParent());
+ Xt(n, r.getIndexWithinParent());
} else
- CC(r);
+ WC(r);
return !0;
}
-function SC(e, t) {
- const r = Fa(e), n = Fa(t);
+function GC(e, t) {
+ const r = za(e), n = za(t);
if (!r && !n)
return !1;
const i = e.isBefore(t);
- return r && Ku(e, r, i), n && Ku(t, n, !i), !0;
+ return r && sd(e, r, i), n && sd(t, n, !i), !0;
}
-function Ku(e, t, r) {
+function sd(e, t, r) {
const n = t.getParent();
- r && n ? e.set(n.getKey(), t.getIndexWithinParent(), "element") : e.set(t.getKey(), Ws(t), "element");
+ r && n ? e.set(n.getKey(), t.getIndexWithinParent(), "element") : e.set(t.getKey(), Ys(t), "element");
}
-function MC() {
- const [e] = le(), t = X(!1);
- return K(() => {
+function JC() {
+ const [e] = ce(), t = Z(!1);
+ return z(() => {
const r = () => {
t.current = !0;
}, n = () => {
@@ -18474,45 +18693,45 @@ function MC() {
const a = i?.ownerDocument;
a?.addEventListener("pointerdown", r, !0), a?.addEventListener("pointerup", n, !0), a?.addEventListener("pointercancel", n, !0);
});
- }, [e]), K(() => e.registerCommand(gr, () => (vC(t.current) && Dr(Ur), !1), fn), [e]), null;
+ }, [e]), z(() => e.registerCommand(ur, () => (HC(t.current) && Kr(jr), !1), mn), [e]), null;
}
-function EC({ onChange: e }) {
- const [t] = le();
- return K(() => t.registerCommand(gr, () => {
- const r = Zp();
+function YC({ onChange: e }) {
+ const [t] = ce();
+ return z(() => t.registerCommand(ur, () => {
+ const r = ph();
return e?.(r), !1;
- }, Rt), [t, e]), null;
+ }, kt), [t, e]), null;
}
-function AC() {
- const [e] = le();
- return PC(e), null;
+function XC() {
+ const [e] = ce();
+ return QC(e), null;
}
-function PC(e) {
- K(() => {
+function QC(e) {
+ z(() => {
if (!e.hasNodes([Qe]))
throw new Error("ParaNodePlugin: ParaNode not registered on editor!");
- return e.registerNodeTransform(Qe, (t) => NC(t, e));
+ return e.registerNodeTransform(Qe, (t) => ZC(t, e));
}, [e]);
}
-function NC(e, t) {
- Hp(t, e.getKey()) && Wp(e.getFirstChild()), !(!se(e) || e.getMarker() !== "b" || e.isEmpty() || !t.getEditorState().read(() => {
- const i = ne(e.getKey());
- return se(i) && (i?.isEmpty() ?? !1);
+function ZC(e, t) {
+ $a(t, e.getKey()) && ah(e.getFirstChild()), !(!ae(e) || e.getMarker() !== "b" || e.isEmpty() || !t.getEditorState().read(() => {
+ const i = se(e.getKey());
+ return ae(i) && (i?.isEmpty() ?? !1);
})) && e.clear();
}
-function Eh({ onStateChange: e }) {
- const [t] = le(), [r, n] = de(t), i = X(!1), s = X(!1), o = X(void 0), a = X(void 0), c = ge(() => {
+function Kh({ 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(() => {
const l = R();
let u;
if (N(l)) {
const d = l.anchor.getNode(), f = l.focus.getNode();
- let p = d.getKey() === "root" ? d : nt(d, (T) => {
- const S = T.getParent();
- return S !== null && Gm(S);
+ let p = d.getKey() === "root" ? d : nt(d, (k) => {
+ const _ = k.getParent();
+ return _ !== null && my(_);
});
- p === null && (p = d.getTopLevelElementOrThrow()), Ji(p) && (p = nt(d, se) ?? p);
- const m = p.getKey(), g = r.getElementByKey(m), y = jb(d, f);
- if (y && $T(y) && (u = y.getMarker()), g !== null && (se(p) || Tt(p) || as(p))) {
+ p === null && (p = d.getTopLevelElementOrThrow()), ts(p) && (p = nt(d, ae) ?? p);
+ const m = p.getKey(), g = r.getElementByKey(m), y = ck(d, f);
+ if (y && ZT(y) && (u = y.getMarker()), g !== null && (ae(p) || _t(p) || fs(p))) {
o.current = p.getMarker(), a.current = u, e?.({
canUndo: i.current,
canRedo: s.current,
@@ -18524,23 +18743,23 @@ function Eh({ onStateChange: e }) {
}
a.current = u;
}, [r, e]);
- return K(() => t.registerCommand(gr, (l, u) => (c(), n(u), !1), fr), [t, c]), K(() => Xe(r.registerUpdateListener(({ editorState: l }) => {
+ return z(() => t.registerCommand(ur, (l, u) => (c(), n(u), !1), ar), [t, c]), z(() => He(r.registerUpdateListener(({ editorState: l }) => {
l.read(() => {
c();
});
- }), r.registerCommand(Jm, (l) => (i.current = l, e?.({
+ }), r.registerCommand(yy, (l) => (i.current = l, e?.({
canUndo: i.current,
canRedo: s.current,
blockMarker: o.current,
contextMarker: a.current
- }), !1), fr), r.registerCommand(Ym, (l) => (s.current = l, e?.({
+ }), !1), ar), r.registerCommand(by, (l) => (s.current = l, e?.({
canUndo: i.current,
canRedo: s.current,
blockMarker: o.current,
contextMarker: a.current
- }), !1), fr)), [c, r, e]), null;
+ }), !1), ar)), [c, r, e]), null;
}
-function OC(e) {
+function eS(e) {
if (e.key === "Enter" && !e.shiftKey)
return "insertParagraph";
if (e.key === "Backspace")
@@ -18550,26 +18769,26 @@ function OC(e) {
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey)
return "insertText";
}
-function jr(e) {
- return e ? Ce(e) ? e : nt(e, (r) => Ce(r)) ?? void 0 : void 0;
+function Gr(e) {
+ return e ? Se(e) ? e : nt(e, (r) => Se(r)) ?? void 0 : void 0;
}
-function Ah(e) {
+function jh(e) {
if (!N(e))
return !1;
const t = /* @__PURE__ */ new Set();
for (const r of e.getNodes()) {
- const n = jr(r);
+ const n = Gr(r);
n && t.add(n.getKey());
}
return t.size > 1;
}
-function tl(e) {
- return N(e) && e.isCollapsed() && e.anchor.type === "element" || !N(e) && !Zd(e) ? !1 : e.getNodes().some((t) => me(t));
+function dl(e) {
+ return N(e) && e.isCollapsed() && e.anchor.type === "element" || !N(e) && !fc(e) ? !1 : e.getNodes().some((t) => ge(t));
}
-function Ph(e) {
+function Bh(e) {
if (!N(e) || !e.isCollapsed())
return !1;
- const { anchor: t } = e, r = t.getNode(), n = jr(r);
+ const { anchor: t } = e, r = t.getNode(), n = Gr(r);
if (!n || t.offset !== 0)
return !1;
let i = r;
@@ -18580,13 +18799,13 @@ function Ph(e) {
}
return !0;
}
-function Nh(e) {
+function Vh(e) {
if (!N(e) || !e.isCollapsed())
return !1;
- const { anchor: t } = e, r = t.getNode(), n = jr(r);
+ const { anchor: t } = e, r = t.getNode(), n = Gr(r);
if (!n)
return !1;
- if (D(r)) {
+ if (F(r)) {
if (t.offset !== r.getChildrenSize())
return !1;
} else if (t.offset !== r.getTextContentSize())
@@ -18599,42 +18818,42 @@ function Nh(e) {
}
return !0;
}
-function ju(e, t) {
- return !!za(e, t);
+function od(e, t) {
+ return !!Ka(e, t);
}
-function za(e, t) {
+function Ka(e, t) {
if (!N(e) || !e.isCollapsed())
return;
const { anchor: r } = e, n = r.getNode();
- if (r.type === "element" && D(n)) {
+ if (r.type === "element" && F(n)) {
const s = n.getChildren(), o = t === "backward" ? r.offset - 1 : r.offset;
if (o < 0)
return;
const a = s[o];
- return me(a) ? a : void 0;
+ return ge(a) ? a : void 0;
}
if (t === "backward") {
if (r.offset !== 0)
return;
const s = n.getPreviousSibling();
- return me(s) ? s : void 0;
+ return ge(s) ? s : void 0;
}
if (r.offset !== n.getTextContentSize())
return;
const i = n.getNextSibling();
- return me(i) ? i : void 0;
+ return ge(i) ? i : void 0;
}
-function Hs(e, t) {
+function Xs(e, t) {
if (!N(e))
return !1;
- const r = jr(e.anchor.getNode());
+ const r = Gr(e.anchor.getNode());
return r ? !!(t === "backward" ? r.getPreviousSibling() : r.getNextSibling()) : !1;
}
-function ia(e) {
- return tl(e) || Ah(e);
+function Fi(e) {
+ return dl(e) || jh(e);
}
-function wC(e, t) {
- if (tl(e) || Ah(e))
+function tS(e, t) {
+ if (dl(e) || jh(e))
return !0;
if (!N(e) || !e.isCollapsed())
return !1;
@@ -18642,44 +18861,44 @@ function wC(e, t) {
case "insertParagraph":
return !0;
case "deleteBackward":
- return Ph(e) && Hs(e, "backward") || ju(e, "backward");
+ return Bh(e) && Xs(e, "backward") || od(e, "backward");
case "deleteForward":
- return Nh(e) && Hs(e, "forward") || ju(e, "forward");
+ return Vh(e) && Xs(e, "forward") || od(e, "forward");
case "insertText":
return !1;
}
}
-function qC(e, t) {
+function rS(e, t) {
if (!(!N(e) || !e.isCollapsed())) {
if (t === "deleteBackward") {
- const r = za(e, "backward");
+ const r = Ka(e, "backward");
if (r)
return { kind: "verse", node: r };
- if (Ph(e) && Hs(e, "backward")) {
- const n = jr(e.anchor.getNode());
- if (Ce(n))
+ if (Bh(e) && Xs(e, "backward")) {
+ const n = Gr(e.anchor.getNode());
+ if (Se(n))
return { kind: "para", node: n };
}
return;
}
if (t === "deleteForward") {
- const r = za(e, "forward");
+ const r = Ka(e, "forward");
if (r)
return { kind: "verse", node: r };
- if (Nh(e) && Hs(e, "forward")) {
- const i = jr(e.anchor.getNode())?.getNextSibling();
- if (Ce(i))
+ if (Vh(e) && Xs(e, "forward")) {
+ const i = Gr(e.anchor.getNode())?.getNextSibling();
+ if (Se(i))
return { kind: "para", node: i };
}
return;
}
}
}
-function Bu(e, t) {
+function ad(e, t) {
if (!e)
return !1;
if (t.kind === "verse")
- return Zd(e) && e.has(t.key);
+ return fc(e) && e.has(t.key);
if (t.kind === "selection") {
if (!N(e) || e.isCollapsed() || !t.anchor || !t.focus)
return !1;
@@ -18688,42 +18907,42 @@ function Bu(e, t) {
}
if (!N(e) || e.isCollapsed())
return !1;
- const r = jr(e.anchor.getNode()), n = jr(e.focus.getNode());
+ const r = Gr(e.anchor.getNode()), n = Gr(e.focus.getNode());
return !!r && r.getKey() === t.key && !!n && n.getKey() === t.key;
}
-function Oh(e) {
- if (M(e)) {
+function Wh(e) {
+ if (v(e)) {
const t = e.getTextContentSize();
e.select(t, t);
- } else D(e) ? e.selectEnd() : e.selectNext(0, 0);
+ } else F(e) ? e.selectEnd() : e.selectNext(0, 0);
}
-function RC(e) {
+function nS(e) {
const t = e.getPreviousSibling();
- if (!Ce(t))
+ if (!Se(t))
return;
const r = t.getLastChild(), n = e.getChildren();
- t.append(...n), e.remove(), r ? Oh(r) : di(t) || t.selectStart();
+ t.append(...n), e.remove(), r ? Wh(r) : mi(t) || t.selectStart();
}
-function wh(e) {
- return me(e) || We(e) ? [] : Ce(e) ? e.getChildren().flatMap(wh) : [e];
+function Hh(e) {
+ return ge(e) || We(e) ? [] : Se(e) ? e.getChildren().flatMap(Hh) : [e];
}
-function $C(e) {
+function iS(e) {
const t = [];
for (const r of e) {
- const n = wh(r);
- n.length !== 0 && (Ce(r) && t.length > 0 && t.push(he(" ")), t.push(...n));
+ const n = Hh(r);
+ n.length !== 0 && (Se(r) && t.length > 0 && t.push(pe(" ")), t.push(...n));
}
return t;
}
-function Vu(e, t) {
+function cd(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 IC(e) {
+function sS(e) {
if (Array.isArray(e)) return e;
}
-function LC(e, t) {
+function oS(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;
@@ -18741,142 +18960,142 @@ function LC(e, t) {
return a;
}
}
-function DC() {
+function aS() {
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 UC(e, t) {
- return IC(e) || LC(e, t) || FC(e, t) || DC();
+function cS(e, t) {
+ return sS(e) || oS(e, t) || lS(e, t) || aS();
}
-function FC(e, t) {
+function lS(e, t) {
if (e) {
- if (typeof e == "string") return Vu(e, t);
+ if (typeof e == "string") return cd(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) ? Vu(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) ? cd(e, t) : void 0;
}
}
-const qh = Object.entries, Wu = Object.setPrototypeOf, zC = Object.isFrozen, KC = Object.getPrototypeOf, jC = Object.getOwnPropertyDescriptor;
-let et = Object.freeze, it = Object.seal, Vn = Object.create, Rh = typeof Reflect < "u" && Reflect, Ka = Rh.apply, ja = Rh.construct;
+const Gh = Object.entries, ld = Object.setPrototypeOf, uS = Object.isFrozen, dS = Object.getPrototypeOf, fS = Object.getOwnPropertyDescriptor;
+let et = Object.freeze, it = Object.seal, Wn = Object.create, Jh = typeof Reflect < "u" && Reflect, ja = Jh.apply, Ba = Jh.construct;
et || (et = function(t) {
return t;
});
it || (it = function(t) {
return t;
});
-Ka || (Ka = function(t, r) {
+ja || (ja = 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);
});
-ja || (ja = function(t) {
+Ba || (Ba = 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 Kn = He(Array.prototype.forEach), BC = He(Array.prototype.lastIndexOf), Hu = He(Array.prototype.pop), jn = He(Array.prototype.push), VC = He(Array.prototype.splice), Ir = Array.isArray, Ni = He(String.prototype.toLowerCase), sa = He(String.prototype.toString), Gu = He(String.prototype.match), vi = He(String.prototype.replace), Ju = He(String.prototype.indexOf), WC = He(String.prototype.trim), HC = He(Number.prototype.toString), GC = He(Boolean.prototype.toString), Yu = typeof BigInt > "u" ? null : He(BigInt.prototype.toString), Xu = typeof Symbol > "u" ? null : He(Symbol.prototype.toString), Ye = He(Object.prototype.hasOwnProperty), Si = He(Object.prototype.toString), Je = He(RegExp.prototype.test), an = JC(TypeError);
-function He(e) {
+const jn = Ge(Array.prototype.forEach), pS = Ge(Array.prototype.lastIndexOf), ud = Ge(Array.prototype.pop), Bn = Ge(Array.prototype.push), hS = Ge(Array.prototype.splice), Fr = Array.isArray, Ri = Ge(String.prototype.toLowerCase), sa = Ge(String.prototype.toString), dd = Ge(String.prototype.match), Ai = Ge(String.prototype.replace), fd = Ge(String.prototype.indexOf), gS = Ge(String.prototype.trim), mS = Ge(Number.prototype.toString), yS = Ge(Boolean.prototype.toString), pd = typeof BigInt > "u" ? null : Ge(BigInt.prototype.toString), hd = typeof Symbol > "u" ? null : Ge(Symbol.prototype.toString), Xe = Ge(Object.prototype.hasOwnProperty), Pi = Ge(Object.prototype.toString), Ye = Ge(RegExp.prototype.test), dn = bS(TypeError);
+function Ge(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 Ka(e, t, n);
+ return ja(e, t, n);
};
}
-function JC(e) {
+function bS(e) {
return function() {
for (var t = arguments.length, r = new Array(t), n = 0; n < t; n++)
r[n] = arguments[n];
- return ja(e, r);
+ return Ba(e, r);
};
}
-function pe(e, t) {
- let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : Ni;
- if (Wu && Wu(e, null), !Ir(t))
+function fe(e, t) {
+ let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : Ri;
+ if (ld && ld(e, null), !Fr(t))
return e;
let n = t.length;
for (; n--; ) {
let i = t[n];
if (typeof i == "string") {
const s = r(i);
- s !== i && (zC(t) || (t[n] = s), i = s);
+ s !== i && (uS(t) || (t[n] = s), i = s);
}
e[i] = !0;
}
return e;
}
-function YC(e) {
+function kS(e) {
for (let t = 0; t < e.length; t++)
- Ye(e, t) || (e[t] = null);
+ Xe(e, t) || (e[t] = null);
return e;
}
function st(e) {
- const t = Vn(null);
- for (const n of qh(e)) {
- var r = UC(n, 2);
+ const t = Wn(null);
+ for (const n of Gh(e)) {
+ var r = cS(n, 2);
const i = r[0], s = r[1];
- Ye(e, i) && (Ir(s) ? t[i] = YC(s) : s && typeof s == "object" && s.constructor === Object ? t[i] = st(s) : t[i] = s);
+ Xe(e, i) && (Fr(s) ? t[i] = kS(s) : s && typeof s == "object" && s.constructor === Object ? t[i] = st(s) : t[i] = s);
}
return t;
}
-function XC(e) {
+function TS(e) {
switch (typeof e) {
case "string":
return e;
case "number":
- return HC(e);
+ return mS(e);
case "boolean":
- return GC(e);
+ return yS(e);
case "bigint":
- return Yu ? Yu(e) : "0";
+ return pd ? pd(e) : "0";
case "symbol":
- return Xu ? Xu(e) : "Symbol()";
+ return hd ? hd(e) : "Symbol()";
case "undefined":
- return Si(e);
+ return Pi(e);
case "function":
case "object": {
if (e === null)
- return Si(e);
- const t = e, r = Ft(t, "toString");
+ return Pi(e);
+ const t = e, r = jt(t, "toString");
if (typeof r == "function") {
const n = r(t);
- return typeof n == "string" ? n : Si(n);
+ return typeof n == "string" ? n : Pi(n);
}
- return Si(e);
+ return Pi(e);
}
default:
- return Si(e);
+ return Pi(e);
}
}
-function Ft(e, t) {
+function jt(e, t) {
for (; e !== null; ) {
- const n = jC(e, t);
+ const n = fS(e, t);
if (n) {
if (n.get)
- return He(n.get);
+ return Ge(n.get);
if (typeof n.value == "function")
- return He(n.value);
+ return Ge(n.value);
}
- e = KC(e);
+ e = dS(e);
}
function r() {
return null;
}
return r;
}
-function QC(e) {
+function xS(e) {
try {
- return Je(e, ""), !0;
+ return Ye(e, ""), !0;
} catch {
return !1;
}
}
-const Qu = 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"]), oa = 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"]), aa = 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"]), ZC = 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"]), ca = 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"]), ev = et(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), Zu = et(["#text"]), ed = 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"]), la = 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"]), td = 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"]), Ts = et(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), tv = it(/{{[\w\W]*|^[\w\W]*}}/g), rv = it(/<%[\w\W]*|^[\w\W]*%>/g), nv = it(/\${[\w\W]*/g), iv = it(/^data-[\-\w.\u00B7-\uFFFF]+$/), sv = it(/^aria-[\-\w]+$/), rd = it(
+const gd = 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"]), oa = 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"]), aa = 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"]), _S = 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"]), ca = 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"]), CS = et(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), md = et(["#text"]), yd = 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"]), la = 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"]), bd = 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"]), SS = it(/{{[\w\W]*|^[\w\W]*}}/g), vS = it(/<%[\w\W]*|^[\w\W]*%>/g), MS = it(/\${[\w\W]*/g), ES = it(/^data-[\-\w.\u00B7-\uFFFF]+$/), AS = it(/^aria-[\-\w]+$/), kd = it(
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
// eslint-disable-line no-useless-escape
-), ov = it(/^(?:\w+script|data):/i), av = it(
+), PS = it(/^(?:\w+script|data):/i), NS = it(
/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
// eslint-disable-line no-control-regex
-), cv = it(/^html$/i), lv = it(/^[a-z][.\w]*(-[.\w]+)+$/i), nd = it(/<[/\w!]/g), id = it(/<[/\w]/g), uv = it(/<\/no(script|embed|frames)/i), dv = it(/\/>/i), Ct = {
+), OS = it(/^html$/i), wS = it(/^[a-z][.\w]*(-[.\w]+)+$/i), Td = it(/<[/\w!]/g), xd = it(/<[/\w]/g), qS = it(/<\/no(script|embed|frames)/i), RS = it(/\/>/i), Mt = {
element: 1,
attribute: 2,
text: 3,
@@ -18892,9 +19111,9 @@ const Qu = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a
documentFragment: 11,
notation: 12
// Deprecated
-}, fv = function() {
+}, $S = function() {
return typeof window > "u" ? null : window;
-}, pv = function(t, r) {
+}, IS = function(t, r) {
if (typeof t != "object" || typeof t.createPolicy != "function")
return null;
let n = null;
@@ -18913,7 +19132,7 @@ const Qu = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a
} catch {
return console.warn("TrustedTypes policy " + s + " could not be created."), null;
}
-}, sd = function() {
+}, _d = function() {
return {
afterSanitizeAttributes: [],
afterSanitizeElements: [],
@@ -18925,53 +19144,53 @@ const Qu = et(["a", "abbr", "acronym", "address", "area", "article", "aside", "a
uponSanitizeElement: [],
uponSanitizeShadowNode: []
};
-}, qr = function(t, r, n, i) {
- return Ye(t, r) && Ir(t[r]) ? pe(i.base ? st(i.base) : {}, t[r], i.transform) : n;
+}, Lr = function(t, r, n, i) {
+ return Xe(t, r) && Fr(t[r]) ? fe(i.base ? st(i.base) : {}, t[r], i.transform) : n;
};
-function $h() {
- let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : fv();
- const t = (I) => $h(I);
- if (t.version = "3.4.13", t.removed = [], !e || !e.document || e.document.nodeType !== Ct.document || !e.Element)
+function Yh() {
+ let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : $S();
+ const t = (U) => Yh(U);
+ if (t.version = "3.4.13", t.removed = [], !e || !e.document || e.document.nodeType !== Mt.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 = Ft(f, "cloneNode"), m = Ft(f, "remove"), g = Ft(f, "nextSibling"), y = Ft(f, "childNodes"), T = Ft(f, "parentNode"), S = Ft(f, "shadowRoot"), v = Ft(f, "attributes"), E = o && o.prototype ? Ft(o.prototype, "nodeType") : null, A = o && o.prototype ? Ft(o.prototype, "nodeName") : null, x = o && o.prototype ? Ft(o.prototype, "ownerDocument") : null;
+ 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;
if (typeof s == "function") {
- const I = r.createElement("template");
- I.content && I.content.ownerDocument && (r = I.content.ownerDocument);
- }
- let F, L = "", G, V = !1, ae = 0;
- const ce = function() {
- if (ae > 0)
- throw an('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.');
- }, ie = function(h) {
- ce(), ae++;
+ const U = r.createElement("template");
+ U.content && U.content.ownerDocument && (r = U.content.ownerDocument);
+ }
+ let M, w = "", $, Y = !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++;
try {
- return F.createHTML(h);
+ return M.createHTML(h);
} finally {
- ae--;
+ Q--;
}
- }, ve = function(h) {
- ce(), ae++;
+ }, Oe = function(h) {
+ Me(), Q++;
try {
- return F.createScriptURL(h);
+ return M.createScriptURL(h);
} finally {
- ae--;
- }
- }, Pe = function() {
- return V || (G = pv(d, i), V = !0), G;
- }, Q = r, U = Q.implementation, Z = Q.createNodeIterator, Ee = Q.createDocumentFragment, we = Q.getElementsByTagName, Yt = n.importNode;
- let ee = sd();
- t.isSupported = typeof qh == "function" && typeof T == "function" && U && U.createHTMLDocument !== void 0;
- const _t = tv, Jr = rv, fe = nv, ct = iv, No = sv, hi = ov, Er = av, Ge = lv;
- let lt = rd, ue = null;
- const wn = pe({}, [...Qu, ...oa, ...aa, ...ca, ...Zu]);
- let be = null;
- const gi = pe({}, [...ed, ...la, ...td, ...Ts]);
- let Se = Object.seal(Vn(null, {
+ Q--;
+ }
+ }, be = function() {
+ return Y || ($ = IS(d, i), Y = !0), $;
+ }, er = r, we = er.implementation, en = er.createNodeIterator, gr = er.createDocumentFragment, vt = er.getElementsByTagName, te = n.importNode;
+ let E = _d();
+ t.isSupported = typeof Gh == "function" && typeof k == "function" && we && we.createHTMLDocument !== void 0;
+ const J = SS, le = vS, W = MS, xe = ES, pt = AS, zt = PS, ht = NS, Je = wS;
+ let ct = kd, ue = null;
+ const qn = fe({}, [...gd, ...oa, ...aa, ...ca, ...md]);
+ let ye = null;
+ const ki = fe({}, [...yd, ...la, ...bd, ...vs]);
+ let ve = Object.seal(Wn(null, {
tagNameCheck: {
writable: !0,
configurable: !1,
@@ -18990,8 +19209,8 @@ function $h() {
enumerable: !0,
value: !1
}
- })), Ar = null, Pr = null;
- const Xt = Object.seal(Vn(null, {
+ })), Or = null, wr = null;
+ const tr = Object.seal(Wn(null, {
tagCheck: {
writable: !0,
configurable: !1,
@@ -19005,10 +19224,10 @@ function $h() {
value: null
}
}));
- let Nr = !0, Yr = !0, mi = !1, ds = !0, Ut = !1, O = !0, z = !1, H = !1, J = null, xe = null, ut = !1, At = !1, Xr = !1, Qr = !1, Cl = !0, vl = !1;
- const Sl = "user-content-";
- let Oo = !0, fs = !1, qn = {}, Qt = null;
- const wo = pe({}, [
+ let qr = !0, tn = !0, Ti = !1, ms = !0, Kt = !1, q = !0, K = !1, G = !1, X = null, _e = null, lt = !1, Ot = !1, rn = !1, nn = !1, Il = !0, Ll = !1;
+ const Dl = "user-content-";
+ let Oo = !0, ys = !1, Rn = {}, rr = null;
+ const wo = fe({}, [
"annotation-xml",
"audio",
"colgroup",
@@ -19045,244 +19264,244 @@ function $h() {
"video",
"xmp"
]);
- let Ml = null;
- const El = pe({}, ["audio", "video", "img", "source", "image", "track"]);
+ let Ul = null;
+ const Fl = fe({}, ["audio", "video", "img", "source", "image", "track"]);
let qo = null;
- const Al = pe({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), ps = "http://www.w3.org/1998/Math/MathML", hs = "http://www.w3.org/2000/svg", Zt = "http://www.w3.org/1999/xhtml";
- let Rn = Zt, Ro = !1, $o = null;
- const um = pe({}, [ps, hs, Zt], sa), Pl = et(["mi", "mo", "mn", "ms", "mtext"]);
- let Io = pe({}, Pl);
- const Nl = et(["annotation-xml"]);
- let Lo = pe({}, Nl);
- const dm = pe({}, ["title", "style", "font", "a", "script"]);
- let yi = null;
- const fm = ["application/xhtml+xml", "text/html"], pm = "text/html";
- let qe = null, $n = null;
- const hm = r.createElement("form"), Ol = function(h) {
+ const zl = 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", nr = "http://www.w3.org/1999/xhtml";
+ let $n = nr, Ro = !1, $o = null;
+ const Am = fe({}, [bs, ks, nr], sa), Kl = et(["mi", "mo", "mn", "ms", "mtext"]);
+ let Io = fe({}, Kl);
+ const jl = et(["annotation-xml"]);
+ let Lo = fe({}, jl);
+ const Pm = fe({}, ["title", "style", "font", "a", "script"]);
+ let xi = null;
+ const Nm = ["application/xhtml+xml", "text/html"], Om = "text/html";
+ let qe = null, In = null;
+ const wm = r.createElement("form"), Bl = function(h) {
return h instanceof RegExp || h instanceof Function;
}, Do = function() {
let h = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
- if ($n && $n === h)
+ if (In && In === h)
return;
- (!h || typeof h != "object") && (h = {}), h = st(h), yi = // eslint-disable-next-line unicorn/prefer-includes
- fm.indexOf(h.PARSER_MEDIA_TYPE) === -1 ? pm : h.PARSER_MEDIA_TYPE, qe = yi === "application/xhtml+xml" ? sa : Ni, ue = qr(h, "ALLOWED_TAGS", wn, {
+ (!h || typeof h != "object") && (h = {}), h = st(h), xi = // eslint-disable-next-line unicorn/prefer-includes
+ Nm.indexOf(h.PARSER_MEDIA_TYPE) === -1 ? Om : h.PARSER_MEDIA_TYPE, qe = xi === "application/xhtml+xml" ? sa : Ri, ue = Lr(h, "ALLOWED_TAGS", qn, {
transform: qe
- }), be = qr(h, "ALLOWED_ATTR", gi, {
+ }), ye = Lr(h, "ALLOWED_ATTR", ki, {
transform: qe
- }), $o = qr(h, "ALLOWED_NAMESPACES", um, {
+ }), $o = Lr(h, "ALLOWED_NAMESPACES", Am, {
transform: sa
- }), qo = qr(h, "ADD_URI_SAFE_ATTR", Al, {
+ }), qo = Lr(h, "ADD_URI_SAFE_ATTR", zl, {
transform: qe,
- base: Al
- }), Ml = qr(h, "ADD_DATA_URI_TAGS", El, {
+ base: zl
+ }), Ul = Lr(h, "ADD_DATA_URI_TAGS", Fl, {
transform: qe,
- base: El
- }), Qt = qr(h, "FORBID_CONTENTS", wo, {
+ base: Fl
+ }), rr = Lr(h, "FORBID_CONTENTS", wo, {
transform: qe
- }), Ar = qr(h, "FORBID_TAGS", st({}), {
+ }), Or = Lr(h, "FORBID_TAGS", st({}), {
transform: qe
- }), Pr = qr(h, "FORBID_ATTR", st({}), {
+ }), wr = Lr(h, "FORBID_ATTR", st({}), {
transform: qe
- }), qn = Ye(h, "USE_PROFILES") ? h.USE_PROFILES && typeof h.USE_PROFILES == "object" ? st(h.USE_PROFILES) : h.USE_PROFILES : !1, Nr = h.ALLOW_ARIA_ATTR !== !1, Yr = h.ALLOW_DATA_ATTR !== !1, mi = h.ALLOW_UNKNOWN_PROTOCOLS || !1, ds = h.ALLOW_SELF_CLOSE_IN_ATTR !== !1, Ut = h.SAFE_FOR_TEMPLATES || !1, O = h.SAFE_FOR_XML !== !1, z = h.WHOLE_DOCUMENT || !1, At = h.RETURN_DOM || !1, Xr = h.RETURN_DOM_FRAGMENT || !1, Qr = h.RETURN_TRUSTED_TYPE || !1, ut = h.FORCE_BODY || !1, Cl = h.SANITIZE_DOM !== !1, vl = h.SANITIZE_NAMED_PROPS || !1, Oo = h.KEEP_CONTENT !== !1, fs = h.IN_PLACE || !1, lt = QC(h.ALLOWED_URI_REGEXP) ? h.ALLOWED_URI_REGEXP : rd, Rn = typeof h.NAMESPACE == "string" ? h.NAMESPACE : Zt, Io = Ye(h, "MATHML_TEXT_INTEGRATION_POINTS") && h.MATHML_TEXT_INTEGRATION_POINTS && typeof h.MATHML_TEXT_INTEGRATION_POINTS == "object" ? st(h.MATHML_TEXT_INTEGRATION_POINTS) : pe({}, Pl), Lo = Ye(h, "HTML_INTEGRATION_POINTS") && h.HTML_INTEGRATION_POINTS && typeof h.HTML_INTEGRATION_POINTS == "object" ? st(h.HTML_INTEGRATION_POINTS) : pe({}, Nl);
- const _ = Ye(h, "CUSTOM_ELEMENT_HANDLING") && h.CUSTOM_ELEMENT_HANDLING && typeof h.CUSTOM_ELEMENT_HANDLING == "object" ? st(h.CUSTOM_ELEMENT_HANDLING) : Vn(null);
- if (Se = Vn(null), Ye(_, "tagNameCheck") && Ol(_.tagNameCheck) && (Se.tagNameCheck = _.tagNameCheck), Ye(_, "attributeNameCheck") && Ol(_.attributeNameCheck) && (Se.attributeNameCheck = _.attributeNameCheck), Ye(_, "allowCustomizedBuiltInElements") && typeof _.allowCustomizedBuiltInElements == "boolean" && (Se.allowCustomizedBuiltInElements = _.allowCustomizedBuiltInElements), it(Se), Ut && (Yr = !1), Xr && (At = !0), qn && (ue = pe({}, Zu), be = Vn(null), qn.html === !0 && (pe(ue, Qu), pe(be, ed)), qn.svg === !0 && (pe(ue, oa), pe(be, la), pe(be, Ts)), qn.svgFilters === !0 && (pe(ue, aa), pe(be, la), pe(be, Ts)), qn.mathMl === !0 && (pe(ue, ca), pe(be, td), pe(be, Ts))), Xt.tagCheck = null, Xt.attributeCheck = null, Ye(h, "ADD_TAGS") && (typeof h.ADD_TAGS == "function" ? Xt.tagCheck = h.ADD_TAGS : Ir(h.ADD_TAGS) && (ue === wn && (ue = st(ue)), pe(ue, h.ADD_TAGS, qe))), Ye(h, "ADD_ATTR") && (typeof h.ADD_ATTR == "function" ? Xt.attributeCheck = h.ADD_ATTR : Ir(h.ADD_ATTR) && (be === gi && (be = st(be)), pe(be, h.ADD_ATTR, qe))), Ye(h, "ADD_URI_SAFE_ATTR") && Ir(h.ADD_URI_SAFE_ATTR) && pe(qo, h.ADD_URI_SAFE_ATTR, qe), Ye(h, "FORBID_CONTENTS") && Ir(h.FORBID_CONTENTS) && (Qt === wo && (Qt = st(Qt)), pe(Qt, h.FORBID_CONTENTS, qe)), Ye(h, "ADD_FORBID_CONTENTS") && Ir(h.ADD_FORBID_CONTENTS) && (Qt === wo && (Qt = st(Qt)), pe(Qt, h.ADD_FORBID_CONTENTS, qe)), Oo && (ue["#text"] = !0), z && pe(ue, ["html", "head", "body"]), ue.table && (pe(ue, ["tbody"]), delete Ar.tbody), h.TRUSTED_TYPES_POLICY) {
+ }), Rn = Xe(h, "USE_PROFILES") ? h.USE_PROFILES && typeof h.USE_PROFILES == "object" ? st(h.USE_PROFILES) : h.USE_PROFILES : !1, qr = h.ALLOW_ARIA_ATTR !== !1, tn = h.ALLOW_DATA_ATTR !== !1, Ti = h.ALLOW_UNKNOWN_PROTOCOLS || !1, ms = h.ALLOW_SELF_CLOSE_IN_ATTR !== !1, Kt = h.SAFE_FOR_TEMPLATES || !1, q = 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, Il = h.SANITIZE_DOM !== !1, Ll = h.SANITIZE_NAMED_PROPS || !1, Oo = h.KEEP_CONTENT !== !1, ys = h.IN_PLACE || !1, ct = xS(h.ALLOWED_URI_REGEXP) ? h.ALLOWED_URI_REGEXP : kd, $n = typeof h.NAMESPACE == "string" ? h.NAMESPACE : nr, Io = 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({}, Kl), Lo = Xe(h, "HTML_INTEGRATION_POINTS") && h.HTML_INTEGRATION_POINTS && typeof h.HTML_INTEGRATION_POINTS == "object" ? st(h.HTML_INTEGRATION_POINTS) : fe({}, jl);
+ const x = Xe(h, "CUSTOM_ELEMENT_HANDLING") && h.CUSTOM_ELEMENT_HANDLING && typeof h.CUSTOM_ELEMENT_HANDLING == "object" ? st(h.CUSTOM_ELEMENT_HANDLING) : Wn(null);
+ if (ve = Wn(null), Xe(x, "tagNameCheck") && Bl(x.tagNameCheck) && (ve.tagNameCheck = x.tagNameCheck), Xe(x, "attributeNameCheck") && Bl(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), Rn && (ue = fe({}, md), ye = Wn(null), Rn.html === !0 && (fe(ue, gd), fe(ye, yd)), Rn.svg === !0 && (fe(ue, oa), fe(ye, la), fe(ye, vs)), Rn.svgFilters === !0 && (fe(ue, aa), fe(ye, la), fe(ye, vs)), Rn.mathMl === !0 && (fe(ue, ca), fe(ye, bd), fe(ye, vs))), tr.tagCheck = null, tr.attributeCheck = null, Xe(h, "ADD_TAGS") && (typeof h.ADD_TAGS == "function" ? tr.tagCheck = h.ADD_TAGS : Fr(h.ADD_TAGS) && (ue === qn && (ue = st(ue)), fe(ue, h.ADD_TAGS, qe))), Xe(h, "ADD_ATTR") && (typeof h.ADD_ATTR == "function" ? tr.attributeCheck = h.ADD_ATTR : Fr(h.ADD_ATTR) && (ye === ki && (ye = st(ye)), fe(ye, h.ADD_ATTR, qe))), Xe(h, "ADD_URI_SAFE_ATTR") && Fr(h.ADD_URI_SAFE_ATTR) && fe(qo, h.ADD_URI_SAFE_ATTR, qe), Xe(h, "FORBID_CONTENTS") && Fr(h.FORBID_CONTENTS) && (rr === wo && (rr = st(rr)), fe(rr, h.FORBID_CONTENTS, qe)), Xe(h, "ADD_FORBID_CONTENTS") && Fr(h.ADD_FORBID_CONTENTS) && (rr === wo && (rr = st(rr)), fe(rr, h.ADD_FORBID_CONTENTS, qe)), Oo && (ue["#text"] = !0), K && fe(ue, ["html", "head", "body"]), ue.table && (fe(ue, ["tbody"]), delete Or.tbody), h.TRUSTED_TYPES_POLICY) {
if (typeof h.TRUSTED_TYPES_POLICY.createHTML != "function")
- throw an('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
+ throw dn('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
if (typeof h.TRUSTED_TYPES_POLICY.createScriptURL != "function")
- throw an('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
- const q = F;
- F = h.TRUSTED_TYPES_POLICY;
+ throw dn('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
+ const I = M;
+ M = h.TRUSTED_TYPES_POLICY;
try {
- L = ie("");
- } catch (B) {
- throw F = q, B;
+ w = re("");
+ } catch (V) {
+ throw M = I, V;
}
- } else h.TRUSTED_TYPES_POLICY === null ? (F = void 0, L = "") : (F === void 0 && (F = Pe()), F && typeof L == "string" && (L = ie("")));
- et && et(h), $n = h;
- }, wl = pe({}, [...oa, ...aa, ...ZC]), ql = pe({}, [...ca, ...ev]), gm = function(h, _, q) {
- return _.namespaceURI === Zt ? h === "svg" : _.namespaceURI === ps ? h === "svg" && (q === "annotation-xml" || Io[q]) : !!wl[h];
- }, mm = function(h, _, q) {
- return _.namespaceURI === Zt ? h === "math" : _.namespaceURI === hs ? h === "math" && Lo[q] : !!ql[h];
- }, ym = function(h, _, q) {
- return _.namespaceURI === hs && !Lo[q] || _.namespaceURI === ps && !Io[q] ? !1 : !ql[h] && (dm[h] || !wl[h]);
- }, bm = function(h) {
- let _ = T(h);
- (!_ || !_.tagName) && (_ = {
- namespaceURI: Rn,
+ } else h.TRUSTED_TYPES_POLICY === null ? (M = void 0, w = "") : (M === void 0 && (M = be()), M && typeof w == "string" && (w = re("")));
+ et && et(h), In = h;
+ }, Vl = fe({}, [...oa, ...aa, ..._S]), Wl = fe({}, [...ca, ...CS]), qm = function(h, x, I) {
+ return x.namespaceURI === nr ? h === "svg" : x.namespaceURI === bs ? h === "svg" && (I === "annotation-xml" || Io[I]) : !!Vl[h];
+ }, Rm = function(h, x, I) {
+ return x.namespaceURI === nr ? h === "math" : x.namespaceURI === ks ? h === "math" && Lo[I] : !!Wl[h];
+ }, $m = function(h, x, I) {
+ return x.namespaceURI === ks && !Lo[I] || x.namespaceURI === bs && !Io[I] ? !1 : !Wl[h] && (Pm[h] || !Vl[h]);
+ }, Im = function(h) {
+ let x = k(h);
+ (!x || !x.tagName) && (x = {
+ namespaceURI: $n,
tagName: "template"
});
- const q = Ni(h.tagName), B = Ni(_.tagName);
- return $o[h.namespaceURI] ? h.namespaceURI === hs ? gm(q, _, B) : h.namespaceURI === ps ? mm(q, _, B) : h.namespaceURI === Zt ? ym(q, _, B) : !!(yi === "application/xhtml+xml" && $o[h.namespaceURI]) : !1;
- }, Or = function(h) {
- jn(t.removed, {
+ const I = Ri(h.tagName), V = Ri(x.tagName);
+ return $o[h.namespaceURI] ? h.namespaceURI === ks ? qm(I, x, V) : h.namespaceURI === bs ? Rm(I, x, V) : h.namespaceURI === nr ? $m(I, x, V) : !!(xi === "application/xhtml+xml" && $o[h.namespaceURI]) : !1;
+ }, Rr = function(h) {
+ Bn(t.removed, {
element: h
});
try {
- T(h).removeChild(h);
+ k(h).removeChild(h);
} catch {
- if (m(h), !T(h))
- throw an("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place");
+ 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");
}
- }, gs = function(h) {
- bi(h);
- const _ = y(h);
- if (_) {
- const B = [];
- Kn(_, (W) => {
- jn(B, W);
- }), Kn(B, (W) => {
+ }, Ts = function(h) {
+ _i(h);
+ const x = y(h);
+ if (x) {
+ const V = [];
+ jn(x, (H) => {
+ Bn(V, H);
+ }), jn(V, (H) => {
try {
- m(W);
+ m(H);
} catch {
}
});
}
- const q = v(h);
- if (q)
- for (let B = q.length - 1; B >= 0; --B) {
- const W = q[B], te = W && W.name;
- if (typeof te == "string")
+ const I = S(h);
+ if (I)
+ for (let V = I.length - 1; V >= 0; --V) {
+ const H = I[V], ie = H && H.name;
+ if (typeof ie == "string")
try {
- h.removeAttribute(te);
+ h.removeAttribute(ie);
} catch {
}
}
- }, Zr = function(h, _) {
+ }, sn = function(h, x) {
try {
- jn(t.removed, {
- attribute: _.getAttributeNode(h),
- from: _
+ Bn(t.removed, {
+ attribute: x.getAttributeNode(h),
+ from: x
});
} catch {
- jn(t.removed, {
+ Bn(t.removed, {
attribute: null,
- from: _
+ from: x
});
}
- if (_.removeAttribute(h), h === "is")
- if (At || Xr)
+ if (x.removeAttribute(h), h === "is")
+ if (Ot || rn)
try {
- Or(_);
+ Rr(x);
} catch {
}
else
try {
- _.setAttribute(h, "");
+ x.setAttribute(h, "");
} catch {
}
- }, km = function(h) {
- const _ = v(h);
- if (_)
- for (let q = _.length - 1; q >= 0; --q) {
- const B = _[q], W = B && B.name;
- if (!(typeof W != "string" || be[qe(W)]))
+ }, Lm = function(h) {
+ const x = S(h);
+ if (x)
+ for (let I = x.length - 1; I >= 0; --I) {
+ const V = x[I], H = V && V.name;
+ if (!(typeof H != "string" || ye[qe(H)]))
try {
- h.removeAttribute(W);
+ h.removeAttribute(H);
} catch {
}
}
- }, bi = function(h) {
- const _ = [h];
- for (; _.length > 0; ) {
- const q = _.pop();
- (E ? E(q) : q.nodeType) === Ct.element && km(q);
- const W = y(q);
- if (W)
- for (let te = W.length - 1; te >= 0; --te)
- _.push(W[te]);
- }
- }, Tm = function(h) {
- if (!O)
+ }, _i = function(h) {
+ const x = [h];
+ for (; x.length > 0; ) {
+ const I = x.pop();
+ (P ? P(I) : I.nodeType) === Mt.element && Lm(I);
+ const H = y(I);
+ if (H)
+ for (let ie = H.length - 1; ie >= 0; --ie)
+ x.push(H[ie]);
+ }
+ }, Dm = function(h) {
+ if (!q)
return;
- const _ = [h];
- for (; _.length > 0; ) {
- const q = _.pop(), B = E ? E(q) : q.nodeType;
- if (B === Ct.processingInstruction || B === Ct.comment && Je(id, q.data)) {
+ const x = [h];
+ for (; x.length > 0; ) {
+ const I = x.pop(), V = P ? P(I) : I.nodeType;
+ if (V === Mt.processingInstruction || V === Mt.comment && Ye(xd, I.data)) {
try {
- m(q);
+ m(I);
} catch {
}
continue;
}
- if (B === Ct.element) {
- const te = q, ke = qe(A ? A(q) : q.nodeName);
+ if (V === Mt.element) {
+ const ie = I, ke = qe(A ? A(I) : I.nodeName);
try {
- te.hasAttribute && te.hasAttribute("patchsrc") && te.removeAttribute("patchsrc"), te.hasAttribute && te.hasAttribute("for") && ke !== "label" && ke !== "output" && te.removeAttribute("for");
+ ie.hasAttribute && ie.hasAttribute("patchsrc") && ie.removeAttribute("patchsrc"), ie.hasAttribute && ie.hasAttribute("for") && ke !== "label" && ke !== "output" && ie.removeAttribute("for");
} catch {
}
}
- const W = y(q);
- if (W)
- for (let te = W.length - 1; te >= 0; --te)
- _.push(W[te]);
- }
- }, Rl = function(h) {
- let _ = null, q = null;
- if (ut)
+ const H = y(I);
+ if (H)
+ for (let ie = H.length - 1; ie >= 0; --ie)
+ x.push(H[ie]);
+ }
+ }, Hl = function(h) {
+ let x = null, I = null;
+ if (lt)
h = " " + h;
else {
- const te = Gu(h, /^[\r\n\t ]+/);
- q = te && te[0];
+ const ie = dd(h, /^[\r\n\t ]+/);
+ I = ie && ie[0];
}
- yi === "application/xhtml+xml" && Rn === Zt && (h = '' + h + "");
- const B = F ? ie(h) : h;
- if (Rn === Zt)
+ xi === "application/xhtml+xml" && $n === nr && (h = '' + h + "");
+ const V = M ? re(h) : h;
+ if ($n === nr)
try {
- _ = new u().parseFromString(B, yi);
+ x = new u().parseFromString(V, xi);
} catch {
}
- if (!_ || !_.documentElement) {
- _ = U.createDocument(Rn, "template", null);
+ if (!x || !x.documentElement) {
+ x = we.createDocument($n, "template", null);
try {
- _.documentElement.innerHTML = Ro ? L : B;
+ x.documentElement.innerHTML = Ro ? w : V;
} catch {
}
}
- const W = _.body || _.documentElement;
- return h && q && W.insertBefore(r.createTextNode(q), W.childNodes[0] || null), Rn === Zt ? we.call(_, z ? "html" : "body")[0] : z ? _.documentElement : W;
- }, $l = function(h) {
- const _ = x ? x(h) : h.ownerDocument;
- return Z.call(
- _ || h,
+ const H = x.body || x.documentElement;
+ return h && I && H.insertBefore(r.createTextNode(I), H.childNodes[0] || null), $n === nr ? vt.call(x, K ? "html" : "body")[0] : K ? x.documentElement : H;
+ }, Gl = function(h) {
+ const x = B ? B(h) : h.ownerDocument;
+ return en.call(
+ x || h,
h,
// eslint-disable-next-line no-bitwise
c.SHOW_ELEMENT | c.SHOW_COMMENT | c.SHOW_TEXT | c.SHOW_PROCESSING_INSTRUCTION | c.SHOW_CDATA_SECTION,
null
);
- }, ms = function(h) {
- return h = vi(h, _t, " "), h = vi(h, Jr, " "), h = vi(h, fe, " "), h;
+ }, xs = function(h) {
+ return h = Ai(h, J, " "), h = Ai(h, le, " "), h = Ai(h, W, " "), h;
}, Uo = function(h) {
- var _;
+ var x;
h.normalize();
- const q = x ? x(h) : h.ownerDocument, B = Z.call(
- q || h,
+ const I = B ? B(h) : h.ownerDocument, V = en.call(
+ I || h,
h,
// eslint-disable-next-line no-bitwise
c.SHOW_TEXT | c.SHOW_COMMENT | c.SHOW_CDATA_SECTION | c.SHOW_PROCESSING_INSTRUCTION,
null
);
- let W = B.nextNode();
- for (; W; )
- W.data = ms(W.data), W = B.nextNode();
- const te = (_ = h.querySelectorAll) === null || _ === void 0 ? void 0 : _.call(h, "template");
- te && Kn(te, (ke) => {
- In(ke.content) && Uo(ke.content);
+ 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 && jn(ie, (ke) => {
+ Ln(ke.content) && Uo(ke.content);
});
- }, ys = function(h) {
- const _ = A ? A(h) : null;
- return typeof _ != "string" || qe(_) !== "form" ? !1 : typeof h.nodeName != "string" || typeof h.textContent != "string" || typeof h.removeChild != "function" || // Realm-safe NamedNodeMap detection: equality against the cached
+ }, _s = function(h) {
+ const x = A ? A(h) : null;
+ return typeof x != "string" || qe(x) !== "form" ? !1 : typeof h.nodeName != "string" || typeof h.textContent != "string" || typeof h.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 !== v(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
+ 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
// 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 !== E(h) || // HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
+ h.nodeType !== P(h) || // 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
@@ -19294,84 +19513,84 @@ function $h() {
// carry a numeric .length, which a typeof-based probe would miss
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
h.childNodes !== y(h);
- }, In = function(h) {
- if (!E || typeof h != "object" || h === null)
+ }, Ln = function(h) {
+ if (!P || typeof h != "object" || h === null)
return !1;
try {
- return E(h) === Ct.documentFragment;
+ return P(h) === Mt.documentFragment;
} catch {
return !1;
}
- }, ki = function(h) {
- if (!E || typeof h != "object" || h === null)
+ }, Ci = function(h) {
+ if (!P || typeof h != "object" || h === null)
return !1;
try {
- return typeof E(h) == "number";
+ return typeof P(h) == "number";
} catch {
return !1;
}
};
- function er(I, h, _) {
- I.length !== 0 && Kn(I, (q) => {
- q.call(t, h, _, $n);
+ function ir(U, h, x) {
+ U.length !== 0 && jn(U, (I) => {
+ I.call(t, h, x, In);
});
}
- const xm = function(h, _) {
- return !!(O && h.hasChildNodes() && !ki(h.firstElementChild) && Je(nd, h.textContent) && Je(nd, h.innerHTML) || O && h.namespaceURI === Zt && _ === "style" && ki(h.firstElementChild) || h.nodeType === Ct.processingInstruction || O && h.nodeType === Ct.comment && Je(id, h.data));
- }, _m = function(h, _, q) {
- if (!Ar[_] && Ul(_) && (Se.tagNameCheck instanceof RegExp && Je(Se.tagNameCheck, _) || Se.tagNameCheck instanceof Function && Se.tagNameCheck(_)))
+ const Um = function(h, x) {
+ return !!(q && h.hasChildNodes() && !Ci(h.firstElementChild) && Ye(Td, h.textContent) && Ye(Td, h.innerHTML) || q && h.namespaceURI === nr && x === "style" && Ci(h.firstElementChild) || h.nodeType === Mt.processingInstruction || q && h.nodeType === Mt.comment && Ye(xd, h.data));
+ }, Fm = function(h, x, I) {
+ if (!Or[x] && Ql(x) && (ve.tagNameCheck instanceof RegExp && Ye(ve.tagNameCheck, x) || ve.tagNameCheck instanceof Function && ve.tagNameCheck(x)))
return !1;
- if (Oo && !Qt[_]) {
- const B = T(h), W = y(h);
- if (W && B) {
- const te = W.length;
- for (let ke = te - 1; ke >= 0; --ke) {
- const Re = h === q ? p(W[ke], !0) : W[ke];
- B.insertBefore(Re, g(h));
+ if (Oo && !rr[x]) {
+ const V = k(h), H = y(h);
+ if (H && V) {
+ const ie = H.length;
+ for (let ke = ie - 1; ke >= 0; --ke) {
+ const Re = h === I ? p(H[ke], !0) : H[ke];
+ V.insertBefore(Re, g(h));
}
}
}
- return Or(h), !0;
- }, Il = function(h, _, q, B) {
- return h.length === 0 ? _ : _ === q || _ === B ? st(_) : _;
- }, Ll = function(h, _) {
- if (er(ee.beforeSanitizeElements, h, null), h !== _ && T(h) === null)
- return fs && bi(h), !0;
- if (ys(h))
- return Or(h), !0;
- const q = qe(A ? A(h) : h.nodeName);
- if (ue = Il(ee.uponSanitizeElement, ue, wn, J), er(ee.uponSanitizeElement, h, {
- tagName: q,
+ return Rr(h), !0;
+ }, Jl = function(h, x, I, V) {
+ return h.length === 0 ? x : x === I || x === V ? st(x) : x;
+ }, Yl = function(h, x) {
+ if (ir(E.beforeSanitizeElements, h, null), h !== x && k(h) === null)
+ return ys && _i(h), !0;
+ if (_s(h))
+ return Rr(h), !0;
+ const I = qe(A ? A(h) : h.nodeName);
+ if (ue = Jl(E.uponSanitizeElement, ue, qn, X), ir(E.uponSanitizeElement, h, {
+ tagName: I,
allowedTags: ue
- }), h !== _ && T(h) === null)
- return fs && bi(h), !0;
- if (xm(h, q))
- return Or(h), !0;
- if (Ar[q] || !(Xt.tagCheck instanceof Function && Xt.tagCheck(q)) && !ue[q]) {
- const W = _m(h, q, _);
- return W === !1 && er(ee.afterSanitizeElements, h, null), W;
- }
- if ((E ? E(h) : h.nodeType) === Ct.element && !bm(h) || (q === "noscript" || q === "noembed" || q === "noframes") && Je(uv, h.innerHTML))
- return Or(h), !0;
- if (Ut && h.nodeType === Ct.text) {
- const W = ms(h.textContent);
- h.textContent !== W && (jn(t.removed, {
+ }), h !== x && k(h) === null)
+ return ys && _i(h), !0;
+ if (Um(h, I))
+ return Rr(h), !0;
+ if (Or[I] || !(tr.tagCheck instanceof Function && tr.tagCheck(I)) && !ue[I]) {
+ const H = Fm(h, I, x);
+ return H === !1 && ir(E.afterSanitizeElements, h, null), H;
+ }
+ if ((P ? P(h) : h.nodeType) === Mt.element && !Im(h) || (I === "noscript" || I === "noembed" || I === "noframes") && Ye(qS, h.innerHTML))
+ return Rr(h), !0;
+ if (Kt && h.nodeType === Mt.text) {
+ const H = xs(h.textContent);
+ h.textContent !== H && (Bn(t.removed, {
element: h.cloneNode()
- }), h.textContent = W);
+ }), h.textContent = H);
}
- return er(ee.afterSanitizeElements, h, null), !1;
- }, Dl = function(h, _, q) {
- if (Pr[_] || O && _ === "patchsrc" || O && _ === "for" && h !== "label" && h !== "output" || Cl && (_ === "id" || _ === "name") && (q in r || q in hm))
+ return ir(E.afterSanitizeElements, h, null), !1;
+ }, Xl = function(h, x, I) {
+ if (wr[x] || q && x === "patchsrc" || q && x === "for" && h !== "label" && h !== "output" || Il && (x === "id" || x === "name") && (I in r || I in wm))
return !1;
- const B = be[_] || Xt.attributeCheck instanceof Function && Xt.attributeCheck(_, h);
- if (!(Yr && Je(ct, _))) {
- if (!(Nr && Je(No, _))) {
- if (B) {
- if (!qo[_]) {
- if (!Je(lt, vi(q, Er, ""))) {
- if (!((_ === "src" || _ === "xlink:href" || _ === "href") && h !== "script" && Ju(q, "data:") === 0 && Ml[h])) {
- if (!(mi && !Je(hi, vi(q, Er, "")))) {
- if (q)
+ const V = ye[x] || tr.attributeCheck instanceof Function && tr.attributeCheck(x, h);
+ if (!(tn && Ye(xe, x))) {
+ if (!(qr && Ye(pt, x))) {
+ if (V) {
+ if (!qo[x]) {
+ if (!Ye(ct, Ai(I, ht, ""))) {
+ if (!((x === "src" || x === "xlink:href" || x === "href") && h !== "script" && fd(I, "data:") === 0 && Ul[h])) {
+ if (!(Ti && !Ye(zt, Ai(I, ht, "")))) {
+ if (I)
return !1;
}
}
@@ -19381,113 +19600,113 @@ function $h() {
// 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
- !(Ul(h) && (Se.tagNameCheck instanceof RegExp && Je(Se.tagNameCheck, h) || Se.tagNameCheck instanceof Function && Se.tagNameCheck(h)) && (Se.attributeNameCheck instanceof RegExp && Je(Se.attributeNameCheck, _) || Se.attributeNameCheck instanceof Function && Se.attributeNameCheck(_, h)) || // Alternative, second condition checks if it's an `is`-attribute, AND
+ !(Ql(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
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
- _ === "is" && Se.allowCustomizedBuiltInElements && (Se.tagNameCheck instanceof RegExp && Je(Se.tagNameCheck, q) || Se.tagNameCheck instanceof Function && Se.tagNameCheck(q)))
+ x === "is" && ve.allowCustomizedBuiltInElements && (ve.tagNameCheck instanceof RegExp && Ye(ve.tagNameCheck, I) || ve.tagNameCheck instanceof Function && ve.tagNameCheck(I)))
) return !1;
}
}
return !0;
- }, Cm = pe({}, ["annotation-xml", "color-profile", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "missing-glyph"]), Ul = function(h) {
- return !Cm[Ni(h)] && Je(Ge, h);
- }, vm = function(h, _, q, B) {
- if (F && typeof d == "object" && typeof d.getAttributeType == "function" && !q)
- switch (d.getAttributeType(h, _)) {
+ }, zm = fe({}, ["annotation-xml", "color-profile", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "missing-glyph"]), Ql = function(h) {
+ return !zm[Ri(h)] && Ye(Je, h);
+ }, Km = function(h, x, I, V) {
+ if (M && typeof d == "object" && typeof d.getAttributeType == "function" && !I)
+ switch (d.getAttributeType(h, x)) {
case "TrustedHTML":
- return ie(B);
+ return re(V);
case "TrustedScriptURL":
- return ve(B);
+ return Oe(V);
}
- return B;
- }, Sm = function(h, _, q, B) {
+ return V;
+ }, jm = function(h, x, I, V) {
try {
- q ? h.setAttributeNS(q, _, B) : h.setAttribute(_, B), ys(h) ? Or(h) : Hu(t.removed);
+ I ? h.setAttributeNS(I, x, V) : h.setAttribute(x, V), _s(h) ? Rr(h) : ud(t.removed);
} catch {
- Zr(_, h);
+ sn(x, h);
}
- }, Fl = function(h) {
- er(ee.beforeSanitizeAttributes, h, null);
- const _ = h.attributes;
- if (!_ || ys(h))
+ }, Zl = function(h) {
+ ir(E.beforeSanitizeAttributes, h, null);
+ const x = h.attributes;
+ if (!x || _s(h))
return;
- be = Il(ee.uponSanitizeAttribute, be, gi, xe);
- const q = {
+ ye = Jl(E.uponSanitizeAttribute, ye, ki, _e);
+ const I = {
attrName: "",
attrValue: "",
keepAttr: !0,
- allowedAttributes: be,
+ allowedAttributes: ye,
forceKeepAttr: void 0
};
- let B = _.length;
- const W = qe(h.nodeName);
- for (; B--; ) {
- const te = _[B], ke = te.name, Re = te.namespaceURI, ht = te.value, gt = qe(ke), zo = ht;
- let dt = ke === "value" ? zo : WC(zo);
- if (q.attrName = gt, q.attrValue = dt, q.keepAttr = !0, q.forceKeepAttr = void 0, er(ee.uponSanitizeAttribute, h, q), dt = q.attrValue, vl && (gt === "id" || gt === "name") && Ju(dt, Sl) !== 0 && (Zr(ke, h), dt = Sl + dt), O && Je(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, dt)) {
- Zr(ke, h);
+ let V = x.length;
+ const H = qe(h.nodeName);
+ for (; V--; ) {
+ const ie = x[V], ke = ie.name, Re = ie.namespaceURI, gt = ie.value, mt = qe(ke), zo = gt;
+ let ut = ke === "value" ? zo : gS(zo);
+ if (I.attrName = mt, I.attrValue = ut, I.keepAttr = !0, I.forceKeepAttr = void 0, ir(E.uponSanitizeAttribute, h, I), ut = I.attrValue, Ll && (mt === "id" || mt === "name") && fd(ut, Dl) !== 0 && (sn(ke, h), ut = Dl + ut), q && Ye(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, ut)) {
+ sn(ke, h);
continue;
}
- if (gt === "attributename" && Gu(dt, "href")) {
- Zr(ke, h);
+ if (mt === "attributename" && dd(ut, "href")) {
+ sn(ke, h);
continue;
}
- if (!q.forceKeepAttr) {
- if (!q.keepAttr) {
- Zr(ke, h);
+ if (!I.forceKeepAttr) {
+ if (!I.keepAttr) {
+ sn(ke, h);
continue;
}
- if (!ds && Je(dv, dt)) {
- Zr(ke, h);
+ if (!ms && Ye(RS, ut)) {
+ sn(ke, h);
continue;
}
- if (Ut && (dt = ms(dt)), !Dl(W, gt, dt)) {
- Zr(ke, h);
+ if (Kt && (ut = xs(ut)), !Xl(H, mt, ut)) {
+ sn(ke, h);
continue;
}
- dt = vm(W, gt, Re, dt), dt !== zo && Sm(h, ke, Re, dt);
+ ut = Km(H, mt, Re, ut), ut !== zo && jm(h, ke, Re, ut);
}
}
- er(ee.afterSanitizeAttributes, h, null);
- }, bs = function(h) {
- let _ = null;
- const q = $l(h);
- for (er(ee.beforeSanitizeShadowDOM, h, null); _ = q.nextNode(); )
- if (er(ee.uponSanitizeShadowNode, _, null), Ll(_, h), Fl(_), In(_.content) && bs(_.content), (E ? E(_) : _.nodeType) === Ct.element) {
- const W = S(_);
- In(W) && (Fo(W), bs(W));
+ ir(E.afterSanitizeAttributes, h, null);
+ }, Cs = function(h) {
+ let x = null;
+ const I = Gl(h);
+ for (ir(E.beforeSanitizeShadowDOM, h, null); x = I.nextNode(); )
+ if (ir(E.uponSanitizeShadowNode, x, null), Yl(x, h), Zl(x), Ln(x.content) && Cs(x.content), (P ? P(x) : x.nodeType) === Mt.element) {
+ const H = _(x);
+ Ln(H) && (Fo(H), Cs(H));
}
- er(ee.afterSanitizeShadowDOM, h, null);
+ ir(E.afterSanitizeShadowDOM, h, null);
}, Fo = function(h) {
- const _ = [{
+ const x = [{
node: h,
shadow: null
}];
- for (; _.length > 0; ) {
- const q = _.pop();
- if (q.shadow) {
- bs(q.shadow);
+ for (; x.length > 0; ) {
+ const I = x.pop();
+ if (I.shadow) {
+ Cs(I.shadow);
continue;
}
- const B = q.node, te = (E ? E(B) : B.nodeType) === Ct.element, ke = y(B);
+ const V = I.node, ie = (P ? P(V) : V.nodeType) === Mt.element, ke = y(V);
if (ke)
for (let Re = ke.length - 1; Re >= 0; --Re)
- _.push({
+ x.push({
node: ke[Re],
shadow: null
});
- if (te) {
- const Re = A ? A(B) : null;
+ if (ie) {
+ const Re = A ? A(V) : null;
if (typeof Re == "string" && qe(Re) === "template") {
- const ht = B.content;
- In(ht) && _.push({
- node: ht,
+ const gt = V.content;
+ Ln(gt) && x.push({
+ node: gt,
shadow: null
});
}
}
- if (te) {
- const Re = S(B);
- In(Re) && _.push({
+ if (ie) {
+ const Re = _(V);
+ Ln(Re) && x.push({
node: null,
shadow: Re
}, {
@@ -19497,138 +19716,138 @@ function $h() {
}
}
};
- return t.sanitize = function(I) {
- let h = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, _ = null, q = null, B = null, W = null;
- if (Ro = !I, Ro && (I = ""), typeof I != "string" && !ki(I) && (I = XC(I), typeof I != "string"))
- throw an("dirty is not a string, aborting");
+ return t.sanitize = function(U) {
+ let h = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, x = null, I = null, V = null, H = null;
+ if (Ro = !U, Ro && (U = ""), typeof U != "string" && !Ci(U) && (U = TS(U), typeof U != "string"))
+ throw dn("dirty is not a string, aborting");
if (!t.isSupported)
- return I;
- H ? (ue = J, be = xe) : Do(h), (ee.uponSanitizeElement.length > 0 || ee.uponSanitizeAttribute.length > 0) && (ue = st(ue)), ee.uponSanitizeAttribute.length > 0 && (be = st(be)), t.removed = [];
- const te = fs && typeof I != "string" && ki(I);
- if (te) {
- Tm(I);
- const ht = A ? A(I) : I.nodeName;
- if (typeof ht == "string") {
- const gt = qe(ht);
- if (!ue[gt] || Ar[gt])
- throw gs(I), an("root node is forbidden and cannot be sanitized in-place");
+ return U;
+ G ? (ue = X, ye = _e) : Do(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" && Ci(U);
+ if (ie) {
+ Dm(U);
+ const gt = A ? A(U) : U.nodeName;
+ if (typeof gt == "string") {
+ const mt = qe(gt);
+ if (!ue[mt] || Or[mt])
+ throw Ts(U), dn("root node is forbidden and cannot be sanitized in-place");
}
- if (ys(I))
- throw gs(I), an("root node is clobbered and cannot be sanitized in-place");
+ if (_s(U))
+ throw Ts(U), dn("root node is clobbered and cannot be sanitized in-place");
try {
- Fo(I);
- } catch (gt) {
- throw gs(I), gt;
+ Fo(U);
+ } catch (mt) {
+ throw Ts(U), mt;
}
- } else if (ki(I))
- _ = Rl(""), q = _.ownerDocument.importNode(I, !0), q.nodeType === Ct.element && q.nodeName === "BODY" || q.nodeName === "HTML" ? _ = q : _.appendChild(q), Fo(q);
+ } else if (Ci(U))
+ x = Hl(""), I = x.ownerDocument.importNode(U, !0), I.nodeType === Mt.element && I.nodeName === "BODY" || I.nodeName === "HTML" ? x = I : x.appendChild(I), Fo(I);
else {
- if (!At && !Ut && !z && // eslint-disable-next-line unicorn/prefer-includes
- I.indexOf("<") === -1)
- return F && Qr ? ie(I) : I;
- if (_ = Rl(I), !_)
- return At ? null : Qr ? L : "";
- }
- _ && ut && Or(_.firstChild);
- const ke = te ? I : _;
+ if (!Ot && !Kt && !K && // eslint-disable-next-line unicorn/prefer-includes
+ U.indexOf("<") === -1)
+ return M && nn ? re(U) : U;
+ if (x = Hl(U), !x)
+ return Ot ? null : nn ? w : "";
+ }
+ x && lt && Rr(x.firstChild);
+ const ke = ie ? U : x;
try {
- const ht = $l(ke);
- for (; B = ht.nextNode(); )
- Ll(B, ke), Fl(B), In(B.content) && bs(B.content);
- } catch (ht) {
- throw te && (gs(I), Kn(t.removed, (gt) => {
- gt.element && bi(gt.element);
- })), ht;
- }
- if (te)
- return Kn(t.removed, (ht) => {
- ht.element && bi(ht.element);
- }), Ut && Uo(I), I;
- if (At) {
- if (Ut && Uo(_), Xr)
- for (W = Ee.call(_.ownerDocument); _.firstChild; )
- W.appendChild(_.firstChild);
+ const gt = Gl(ke);
+ for (; V = gt.nextNode(); )
+ Yl(V, ke), Zl(V), Ln(V.content) && Cs(V.content);
+ } catch (gt) {
+ throw ie && (Ts(U), jn(t.removed, (mt) => {
+ mt.element && _i(mt.element);
+ })), gt;
+ }
+ if (ie)
+ return jn(t.removed, (gt) => {
+ gt.element && _i(gt.element);
+ }), Kt && Uo(U), U;
+ if (Ot) {
+ if (Kt && Uo(x), rn)
+ for (H = gr.call(x.ownerDocument); x.firstChild; )
+ H.appendChild(x.firstChild);
else
- W = _;
- return (be.shadowroot || be.shadowrootmode) && (W = Yt.call(n, W, !0)), W;
+ H = x;
+ return (ye.shadowroot || ye.shadowrootmode) && (H = te.call(n, H, !0)), H;
}
- let Re = z ? _.outerHTML : _.innerHTML;
- return z && ue["!doctype"] && _.ownerDocument && _.ownerDocument.doctype && _.ownerDocument.doctype.name && Je(cv, _.ownerDocument.doctype.name) && (Re = "
-` + Re), Ut && (Re = ms(Re)), F && Qr ? ie(Re) : Re;
+ let Re = K ? x.outerHTML : x.innerHTML;
+ return K && ue["!doctype"] && x.ownerDocument && x.ownerDocument.doctype && x.ownerDocument.doctype.name && Ye(OS, x.ownerDocument.doctype.name) && (Re = "
+` + Re), Kt && (Re = xs(Re)), M && nn ? re(Re) : Re;
}, t.setConfig = function() {
- let I = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
- Do(I), H = !0, J = ue, xe = be;
+ let U = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
+ Do(U), G = !0, X = ue, _e = ye;
}, t.clearConfig = function() {
- $n = null, H = !1, J = null, xe = null, F = G, L = "";
- }, t.isValidAttribute = function(I, h, _) {
- $n || Do({});
- const q = qe(I), B = qe(h);
- return Dl(q, B, _);
- }, t.addHook = function(I, h) {
- typeof h == "function" && Ye(ee, I) && jn(ee[I], h);
- }, t.removeHook = function(I, h) {
- if (Ye(ee, I)) {
+ In = null, G = !1, X = null, _e = null, M = $, w = "";
+ }, t.isValidAttribute = function(U, h, x) {
+ In || Do({});
+ const I = qe(U), V = qe(h);
+ return Xl(I, V, x);
+ }, t.addHook = function(U, h) {
+ typeof h == "function" && Xe(E, U) && Bn(E[U], h);
+ }, t.removeHook = function(U, h) {
+ if (Xe(E, U)) {
if (h !== void 0) {
- const _ = BC(ee[I], h);
- return _ === -1 ? void 0 : VC(ee[I], _, 1)[0];
+ const x = pS(E[U], h);
+ return x === -1 ? void 0 : hS(E[U], x, 1)[0];
}
- return Hu(ee[I]);
+ return ud(E[U]);
}
- }, t.removeHooks = function(I) {
- Ye(ee, I) && (ee[I] = []);
+ }, t.removeHooks = function(U) {
+ Xe(E, U) && (E[U] = []);
}, t.removeAllHooks = function() {
- ee = sd();
+ E = _d();
}, t;
}
-var hv = $h();
-function gv({ structureProtectionMode: e = "off" }) {
- const [t] = le(), r = X(void 0), [n, i] = de(void 0), s = ge((o) => {
+var LS = Yh();
+function DS({ structureProtectionMode: e = "off" }) {
+ const [t] = ce(), r = Z(void 0), [n, i] = de(void 0), s = he((o) => {
r.current = o, i(o);
}, []);
- return K(() => {
+ return z(() => {
if (e === "off")
return;
const o = (p) => {
- const m = OC(p);
+ const m = eS(p);
if (!m)
return !1;
const g = R();
- return e === "protected" ? g && wC(g, m) ? (p.preventDefault(), !0) : !1 : m !== "deleteBackward" && m !== "deleteForward" ? !1 : a(m, p);
+ return e === "protected" ? g && tS(g, m) ? (p.preventDefault(), !0) : !1 : m !== "deleteBackward" && m !== "deleteForward" ? !1 : a(m, p);
}, a = (p, m) => {
const g = R(), y = r.current;
- if (y && g && Bu(g, y)) {
+ if (y && g && ad(g, y)) {
if (s(void 0), m.preventDefault(), p !== y.intent)
return !0;
- const S = ne(y.key) ?? void 0;
+ const _ = se(y.key) ?? void 0;
if (y.kind === "verse") {
- if (S) {
- const v = S.getParent(), E = S.getPreviousSibling(), A = S.getNextSibling();
- S.remove(), E ? Oh(E) : A && M(A) ? A.select(0, 0) : v?.selectStart();
+ if (_) {
+ const S = _.getParent(), P = _.getPreviousSibling(), A = _.getNextSibling();
+ _.remove(), P ? Wh(P) : A && v(A) ? A.select(0, 0) : S?.selectStart();
}
- } else y.kind === "selection" ? N(g) && g.removeText() : Ce(S) && RC(S);
+ } else y.kind === "selection" ? N(g) && g.removeText() : Se(_) && nS(_);
return !0;
}
if (!g)
return !1;
- const T = qC(g, p);
- if (T) {
- if (T.kind === "verse") {
- const S = ef();
- S.add(T.node.getKey()), Ui(S);
+ const k = rS(g, p);
+ if (k) {
+ if (k.kind === "verse") {
+ const _ = gf();
+ _.add(k.node.getKey()), Zn(_);
} else {
- const S = uc();
- S.anchor.set(T.node.getKey(), 0, "element"), S.focus.set(T.node.getKey(), T.node.getChildrenSize(), "element"), Ui(S);
+ const _ = hc();
+ _.anchor.set(k.node.getKey(), 0, "element"), _.focus.set(k.node.getKey(), k.node.getChildrenSize(), "element"), Zn(_);
}
- return s({ key: T.node.getKey(), kind: T.kind, intent: p }), m.preventDefault(), !0;
+ return s({ key: k.node.getKey(), kind: k.kind, intent: p }), m.preventDefault(), !0;
}
- if (N(g) && !g.isCollapsed() && tl(g)) {
- const S = g.getNodes().filter(me).map((A) => A.getKey()), { anchor: v, focus: E } = g;
+ if (N(g) && !g.isCollapsed() && dl(g)) {
+ const _ = g.getNodes().filter(ge).map((A) => A.getKey()), { anchor: S, focus: P } = g;
return s({
kind: "selection",
intent: p,
- key: S[0],
- anchor: { key: v.key, offset: v.offset, type: v.type },
- focus: { key: E.key, offset: E.offset, type: E.type }
+ 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;
}
return !1;
@@ -19636,30 +19855,30 @@ function gv({ structureProtectionMode: e = "off" }) {
if (e !== "protected")
return !1;
const m = R();
- return !m || !ia(m) ? !1 : (p instanceof Event && p.preventDefault(), !0);
+ return !m || !Fi(m) ? !1 : (p instanceof Event && p.preventDefault(), !0);
}, l = (p, m) => {
if (!p)
return !1;
- const g = hv.sanitize(p), y = new DOMParser().parseFromString(g, "text/html"), T = $C(by(t, y)), S = R();
- return N(S) && S.insertNodes(T), m.preventDefault(), !0;
+ const g = LS.sanitize(p), y = new DOMParser().parseFromString(g, "text/html"), k = iS(Uy(t, y)), _ = R();
+ return N(_) && _.insertNodes(k), m.preventDefault(), !0;
}, u = (p) => {
if (e !== "protected")
return !1;
const m = R();
- return m && ia(m) ? (p.preventDefault(), !0) : l(p.clipboardData?.getData("text/html"), p);
+ return m && Fi(m) ? (p.preventDefault(), !0) : l(p.clipboardData?.getData("text/html"), p);
}, d = (p) => {
if (e !== "protected")
return !1;
const m = R();
- return m && ia(m) ? (p.preventDefault(), !0) : l(p.dataTransfer?.getData("text/html"), p);
+ return m && Fi(m) ? (p.preventDefault(), !0) : l(p.dataTransfer?.getData("text/html"), p);
}, f = () => {
const p = r.current;
p && t.getEditorState().read(() => {
- Bu(R(), p) || s(void 0);
+ ad(R(), p) || s(void 0);
});
};
- return Xe(t.registerCommand(Tr, o, Ie), t.registerCommand(pn, c, Ie), t.registerCommand(dr, u, Ie), t.registerCommand(Xm, c, Ie), t.registerCommand(fc, d, Ie), t.registerCommand(dc, c, Ie), t.registerUpdateListener(f));
- }, [t, e, s]), K(() => {
+ return He(t.registerCommand(Sr, o, Ie), t.registerCommand(Qn, c, Ie), t.registerCommand(yr, u, Ie), t.registerCommand(ky, c, Ie), t.registerCommand(yc, d, Ie), t.registerCommand(mc, c, Ie), t.registerUpdateListener(f));
+ }, [t, e, s]), z(() => {
const o = t.getRootElement();
if (!o)
return;
@@ -19669,21 +19888,21 @@ function gv({ structureProtectionMode: e = "off" }) {
};
}, [t, n]), null;
}
-const vA = {
+const mP = {
ltr: "Left-to-right",
rtl: "Right-to-left",
auto: "Automatic"
};
-function mv({ textDirection: e }) {
- const [t] = le();
- return yv(t, e), null;
+function US({ textDirection: e }) {
+ const [t] = ce();
+ return FS(t, e), null;
}
-function yv(e, t) {
- K(() => (od(e, t), e.registerUpdateListener(({ dirtyElements: r }) => {
- r.size > 0 && od(e, t);
+function FS(e, t) {
+ z(() => (Cd(e, t), e.registerUpdateListener(({ dirtyElements: r }) => {
+ r.size > 0 && Cd(e, t);
})), [e, t]);
}
-function od(e, t) {
+function Cd(e, t) {
if (t === "auto")
return;
const r = e.getRootElement();
@@ -19691,19 +19910,19 @@ function od(e, t) {
const n = e._config.theme.placeholder, i = document.getElementsByClassName(n)[0];
i && (i.dir = t);
}
-function bv() {
- const [e] = le();
- return kv(e), null;
+function zS() {
+ const [e] = ce();
+ return KS(e), null;
}
-function kv(e) {
- K(() => {
- if (!e.hasNodes([ye, xt, Me, ze, ft]))
+function KS(e) {
+ z(() => {
+ if (!e.hasNodes([me, Ct, Ee, Ke, dt]))
throw new Error("TextSpacingPlugin: CharNode, ImmutableVerseNode, NoteNode, TextNode or VerseNode not registered on editor!");
- return Xe(
- e.registerNodeTransform(ze, Tv),
- e.registerNodeTransform(ze, (t) => xv(t, e)),
- e.registerNodeTransform(ft, ad),
- e.registerNodeTransform(xt, ad),
+ return He(
+ e.registerNodeTransform(Ke, jS),
+ e.registerNodeTransform(Ke, (t) => BS(t, e)),
+ e.registerNodeTransform(dt, Sd),
+ e.registerNodeTransform(Ct, Sd),
// 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,
@@ -19712,22 +19931,22 @@ function kv(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(ft, (t) => {
- Gi(yn("va"), t), Gi(yn("vp"), t);
+ e.registerNodeTransform(dt, (t) => {
+ es(Tn("va"), t), es(Tn("vp"), t);
})
);
}, [e]);
}
-function Tv(e) {
+function jS(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) || $(n) || $(r) || _e(n) || _e(r) || Le(n) || // An adjacent TextNode is the same logical text run (IME composition and annotation-wrap
+ if (e.getMode() !== "normal" || t.endsWith(" ") && t.length > 1 || j(r) || D(n) || D(r) || Ce(n) || Ce(r) || Le(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.
- M(r) || // An optbreak (`//`) — like a ref — is an inline UnknownNode carrying SIGNIFICANT surrounding
+ v(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
@@ -19738,96 +19957,96 @@ function Tv(e) {
// 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).
- re(e, oe) === "attribute" || // When a verse's/milestone's run rides inside an AttributeRunNode wrapper (AttributeRunNode.ts,
+ ne(e, oe) === "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.
- Be(n))
+ ze(n))
return;
- if (me(e.getPreviousSibling()) && (t === "" || t === " ")) {
+ if (ge(e.getPreviousSibling()) && (t === "" || t === " ")) {
t !== "" && e.setTextContent("");
return;
}
- me(r) && Uc(e);
+ ge(r) && Wc(e);
}
-function xv(e, t) {
+function BS(e, t) {
const r = e.getParent();
- !Le(r) || !e.isAttached() || Hp(t, e.getKey()) && r.insertAfter(e);
+ !Le(r) || !e.isAttached() || $a(t, e.getKey()) && !$a(t, r.getKey()) && r.insertAfter(e);
}
-function ad(e) {
+function Sd(e) {
if (!e.isAttached())
return;
let t = e.getPreviousSibling();
- for (; _e(t); )
+ for (; Ce(t); )
t = t.getLastChild();
- ($(t) || M(t) && _e(t.getParent())) && e.insertBefore(he(" "));
+ (D(t) || v(t) && Ce(t.getParent())) && e.insertBefore(pe(" "));
}
-function rl(e) {
+function fl(e) {
if (!j(e) || e.getIsCollapsed() !== !0)
return;
const t = e.getParent();
- return !t || t.isInline() || e.getNextSiblings().some((n) => !Cc(n)) ? void 0 : e;
+ return !t || t.isInline() || e.getNextSiblings().some((n) => !Nc(n)) ? void 0 : e;
}
-function _v(e) {
+function VS(e) {
if (e.type !== "element")
return;
const t = e.getNode();
- if (D(t))
+ if (F(t))
return t.getChildAtIndex(e.offset - 1) ?? void 0;
}
-function Cv() {
+function WS() {
const e = R();
if (!(!N(e) || !e.isCollapsed()))
- return rl(_v(e.anchor));
+ return fl(VS(e.anchor));
}
-function vv(e) {
+function HS(e) {
const t = R();
let r;
- return N(t) ? t.isCollapsed() && (r = t.anchor.getNode()) : t || (r = Ih(e.target)), r ? rl(nt(r, j)) : void 0;
+ return N(t) ? t.isCollapsed() && (r = t.anchor.getNode()) : t || (r = Xh(e.target)), r ? fl(nt(r, j)) : void 0;
}
-function Ih(e) {
- const t = Qm(e)?.anchorNode;
- if (Xd(t))
- return os(t) ?? void 0;
+function Xh(e) {
+ const t = Ty(e)?.anchorNode;
+ if (hf(t))
+ return ci(t) ?? void 0;
}
-function Sv(e) {
+function GS(e) {
if (R())
return;
- const t = Ih(e);
- return t ? rl(nt(t, j)) : void 0;
+ const t = Xh(e);
+ return t ? fl(nt(t, j)) : void 0;
}
-function Mv() {
- const [e] = le(), t = Mh(Cv);
- return K(() => {
+function JS() {
+ const [e] = ce(), t = zh(WS);
+ return z(() => {
const r = (n) => {
- Dr(Ur), t(n);
+ Kr(jr), t(n);
};
- return Xe(e.registerCommand(gr, () => {
- const n = Sv(e.getRootElement());
+ return He(e.registerCommand(ur, () => {
+ const n = GS(e.getRootElement());
return n && r(n), !1;
- }, fn), e.registerCommand(io, (n) => {
- const i = vv(n);
+ }, mn), e.registerCommand(ao, (n) => {
+ const i = HS(n);
return i && r(i), !1;
- }, fn));
+ }, mn));
}, [e, t]), null;
}
-function Ev({ trigger: e, scriptureReference: t, contextMarker: r, getMarkerAction: n }) {
- const { markersMenuItems: i } = Gx({
+function YS({ trigger: e, scriptureReference: t, contextMarker: r, getMarkerAction: n }) {
+ const { markersMenuItems: i } = f_({
scriptureReference: t,
contextMarker: r,
getMarkerAction: n
});
- return C(Hx, { trigger: e, items: i });
+ return C(d_, { trigger: e, items: i });
}
-function Av({ trigger: e, scrRef: t, contextMarker: r, getMarkerAction: n, editableHarness: i }) {
+function XS({ trigger: e, scrRef: t, contextMarker: r, getMarkerAction: n, editableHarness: i }) {
const { book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l } = t, u = Fe(() => ({ book: s, chapterNum: o, verseNum: a, verse: c, versificationStr: l }), [s, o, a, c, l]);
- return i ? C(Ov, { trigger: e, harness: i }) : C(Ev, { trigger: e, scriptureReference: u, contextMarker: r, getMarkerAction: n });
+ return i ? C(ev, { trigger: e, harness: i }) : C(YS, { trigger: e, scriptureReference: u, contextMarker: r, getMarkerAction: n });
}
-const Pv = [" ", "*"];
-function Nv(e, t) {
+const QS = [" ", "*"];
+function ZS(e, t) {
return {
name: e.marker,
label: e.marker,
@@ -19840,8 +20059,8 @@ function Nv(e, t) {
applyOpts: t
};
}
-function Ov({ trigger: e, harness: t }) {
- const [r] = le(), [n, i] = de(void 0), s = X({ query: "", options: [] }), o = X(0), a = ge((f, p, m) => {
+function ev({ 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 });
@@ -19852,7 +20071,7 @@ function Ov({ trigger: e, harness: t }) {
N(y) && y.insertText(`${e}${f}${m ? " " : ""}`);
});
}, [r, t, e]);
- K(() => Xe(r.registerCommand(Tr, (f) => {
+ z(() => He(r.registerCommand(Sr, (f) => {
if (n) {
if ((f.key === "Enter" || f.key === "Tab") && s.current.options.length === 0)
return f.preventDefault(), f.stopPropagation(), !0;
@@ -19861,7 +20080,7 @@ function Ov({ trigger: e, harness: t }) {
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), Zm(() => {
+ return g ? (a(g, n.items, !1), xy(() => {
const y = t.getContext();
s.current = { query: "", options: [] }, o.current += 1, i(y ? {
trigger: "backslash",
@@ -19893,7 +20112,7 @@ function Ov({ trigger: e, harness: t }) {
items: t.getItems(p),
session: o.current
}), !0) : !1;
- }, Ie), r.registerCommand(tf, (f) => {
+ }, Ie), r.registerCommand(mf, (f) => {
if (n || f === null || f.shiftKey)
return !1;
const p = t.getContext();
@@ -19903,58 +20122,58 @@ function Ov({ trigger: e, harness: t }) {
items: t.getEnterItems(p),
session: o.current
}), !0);
- }, Hn)), [r, e, t, n, a]);
- const c = ge(() => i(void 0), []), l = ge((f, p) => {
+ }, Gn)), [r, e, t, n, a]);
+ const c = he(() => i(void 0), []), l = he((f, p) => {
s.current = { query: f, options: p };
- }, []), u = ge((f) => {
+ }, []), u = he((f) => {
const { markerMenuItem: p, applyOpts: m } = f;
t.apply(p, m);
}, [t]), d = Fe(() => n?.items.map((f) => (
// `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.
- Nv(f, { trigger: n.trigger, literalPrefixLanded: !1 })
+ ZS(f, { trigger: n.trigger, literalPrefixLanded: !1 })
)), [n]);
- return n && C(ih, { isOpen: !0, children: ({ placement: f }) => C(
- ah,
- { options: d ?? [], onSelectOption: u, onClose: c, onFilterChange: l, inverse: f === "top-start", menuOpenKey: e, passthroughKeys: n.trigger === "backslash" ? Pv : void 0 },
+ return n && C(bh, { isOpen: !0, children: ({ placement: f }) => C(
+ xh,
+ { options: d ?? [], onSelectOption: u, onClose: c, onFilterChange: l, inverse: f === "top-start", menuOpenKey: e, passthroughKeys: n.trigger === "backslash" ? QS : void 0 },
n.session
) });
}
-function Lh(e) {
- return e.replaceAll(w, "~").replace(/ {2,}/g, (r) => w.repeat(r.length));
+function Qh(e) {
+ return e.replaceAll(L, "~").replace(/ {2,}/g, (r) => L.repeat(r.length));
}
-function wv(e) {
- return e.replaceAll(w, " ").replaceAll("~", w);
+function tv(e) {
+ return e.replaceAll(L, " ").replaceAll("~", L);
}
-function qv(e) {
+function rv(e) {
return e.replace(/ {2,}/g, " ");
}
-let Gs;
-function Rv(e) {
- e && (Gs = e);
+let Qs;
+function nv(e) {
+ e && (Qs = e);
}
-function Dh(e) {
- return Co(e);
+function Zh(e) {
+ return So(e);
}
-function $v(e, t) {
- return e.isEmpty() ? Gd : Uh(e.toJSON(), t);
+function iv(e, t) {
+ return e.isEmpty() ? ff : eg(e.toJSON(), t);
}
-function Uh(e, t) {
+function eg(e, t) {
if (!e.root || !e.root.children) return;
const r = e.root.children;
- if (r.length === 1 && uo(r[0]) && (!r[0].children || r[0].children.length === 0))
- return Gd;
- if (r.some(yT)) {
- Gs?.error(
+ if (r.length === 1 && fo(r[0]) && (!r[0].children || r[0].children.length === 0))
+ return ff;
+ if (r.some($T)) {
+ Qs?.error(
"Block verse layout is not round-trippable to USJ. VerseBlockNode is read-only view state; use the source USJ instead."
);
return;
}
- const n = Fh(r), i = zt(n, t);
- return i ? { type: hr, version: pr, content: i } : void 0;
+ const n = tg(r), i = Bt(n, t);
+ return i ? { type: kr, version: br, content: i } : void 0;
}
-function Iv(e, t) {
+function sv(e, t) {
const { type: r, marker: n, unknownAttributes: i } = e;
let s;
return e.code !== "" && (s = e.code), Ae({
@@ -19965,10 +20184,10 @@ function Iv(e, t) {
content: t
});
}
-function Lv(e) {
+function ov(e) {
const { marker: t, number: r, sid: n, altnumber: i, pubnumber: s, unknownAttributes: o } = e;
return Ae({
- type: Et.getType(),
+ type: Nt.getType(),
marker: t,
number: r,
sid: n,
@@ -19977,11 +20196,11 @@ function Lv(e) {
...o
});
}
-function Dv(e, t) {
+function av(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 = tp(r, a, c), Ae({
- type: Et.getType(),
+ return c = gp(r, a, c), Ae({
+ type: Nt.getType(),
marker: r,
number: c,
sid: n,
@@ -19990,11 +20209,11 @@ function Dv(e, t) {
...o
});
}
-function Uv(e) {
+function cv(e) {
const { marker: t, sid: r, altnumber: n, pubnumber: i, unknownAttributes: s } = e, { text: o } = e;
let { number: a } = e;
- return a = tp(t, o, a), Ae({
- type: ft.getType(),
+ return a = gp(t, o, a), Ae({
+ type: dt.getType(),
marker: t,
number: a,
sid: r,
@@ -20003,11 +20222,11 @@ function Uv(e) {
...s
});
}
-function Fv(e, t, r) {
+function lv(e, t, r) {
const { type: n, marker: i, unknownAttributes: s } = e, o = i === "" ? void 0 : i;
- if (r?.markerMode === "editable" && !Dh(r) && t) {
+ if (r?.markerMode === "editable" && !Zh(r) && t) {
const [a] = t;
- typeof a == "string" && a.startsWith(w) && (t[0] = a.slice(1));
+ typeof a == "string" && a.startsWith(L) && (t[0] = a.slice(1));
}
return Ae({
type: n,
@@ -20016,7 +20235,7 @@ function Fv(e, t, r) {
content: t
});
}
-function zv(e, t) {
+function uv(e, t) {
const { type: r, marker: n, unknownAttributes: i } = e;
return Ae({
type: r,
@@ -20025,18 +20244,18 @@ function zv(e, t) {
content: t
});
}
-function Kv(e, t) {
+function dv(e, t) {
const { unknownAttributes: r } = e;
- return Ae({ type: Pp, ...r, content: t });
+ return Ae({ type: Bp, ...r, content: t });
}
-function jv(e, t) {
+function fv(e, t) {
const { marker: r, unknownAttributes: n } = e;
- return Ae({ type: wp, marker: r, ...n, content: t });
+ return Ae({ type: Hp, marker: r, ...n, content: t });
}
-function Bv(e, t) {
+function pv(e, t) {
const { marker: r, align: n, colspan: i, unknownAttributes: s } = e;
return Ae({
- type: Rp,
+ type: Jp,
marker: r,
align: n,
colspan: i,
@@ -20044,7 +20263,7 @@ function Bv(e, t) {
content: t
});
}
-function Vv(e, t) {
+function hv(e, t) {
const { type: r, marker: n, caller: i, category: s, unknownAttributes: o } = e;
return Ae({
type: r,
@@ -20055,18 +20274,18 @@ function Vv(e, t) {
content: t
});
}
-function Wn(e) {
+function Hn(e) {
const { type: t, marker: r, sid: n, eid: i, unknownAttributes: s, attributeOrder: o } = e;
return Ae({
type: t,
marker: r === "" ? void 0 : r,
- ...dp({ sid: n, eid: i, ...s }, o)
+ ...vp({ sid: n, eid: i, ...s }, o)
});
}
-function Wv(e) {
+function gv(e) {
return e.text;
}
-function Hv(e, t) {
+function mv(e, t) {
const { tag: r, marker: n, unknownAttributes: i } = e;
return Ae({
type: r,
@@ -20075,163 +20294,163 @@ function Hv(e, t) {
content: t
});
}
-function Gv(e) {
+function yv(e) {
const { marker: t } = e;
return {
- type: Ds,
+ type: Ks,
marker: t === "" ? void 0 : t
};
}
-function cd(e, t) {
+function vd(e, t) {
const r = e[e.length - 1];
r && typeof r == "string" ? e[e.length - 1] = r + t : e.push(t);
}
-function Jv(e, t, r, n, i) {
- const s = Vt.getType(), o = t.filter((l) => !r.includes(l));
+function bv(e, t, r, n, i) {
+ const s = Gt.getType(), o = t.filter((l) => !r.includes(l));
if (r.filter((l) => !t.includes(l)).forEach((l) => {
- const u = Wn({
+ const u = Hn({
type: s,
- marker: Gn,
+ marker: Jn,
eid: l
});
i.push(u);
}), o.forEach((l) => {
- const u = Wn({
+ const u = Hn({
type: s,
- marker: hn,
+ marker: yn,
sid: l
});
i.push(u);
}), t.length === 0) {
- const l = Wn({
+ const l = Hn({
type: s,
- marker: hn
+ marker: yn
});
i.push(l);
}
if (i.push(...e), t.length === 0) {
- const l = Wn({
+ const l = Hn({
type: s,
- marker: Gn
+ marker: Jn
});
i.push(l);
}
- (!n || !wf(n)) && t.forEach((l) => {
- const u = Wn({
+ (!n || !Wf(n)) && t.forEach((l) => {
+ const u = Hn({
type: s,
- marker: Gn,
+ marker: Jn,
eid: l
});
i.push(u);
});
}
-function zt(e, t, r, n = !1) {
+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;
switch (a.type) {
- case Lt.getType():
+ case Ut.getType():
i.push(
- Iv(
+ sv(
l,
- zt(l.children, t)
+ Bt(l.children, t)
)
);
break;
- case or.getType():
- i.push(Lv(a));
+ case pr.getType():
+ i.push(ov(a));
break;
- case Et.getType():
+ case Nt.getType():
i.push(
- Dv(
+ av(
u,
- zt(u.children, t)
+ Bt(u.children, t)
)
);
break;
- case xt.getType():
- case ft.getType():
- i.push(Uv(a));
+ case Ct.getType():
+ case dt.getType():
+ i.push(cv(a));
break;
- case ye.getType():
+ case me.getType():
i.push(
- Fv(
+ lv(
d,
- zt(d.children, t, void 0, !0),
+ Bt(d.children, t, void 0, !0),
t
)
);
break;
case Qe.getType():
i.push(
- zv(
+ uv(
f,
- zt(f.children, t)
+ Bt(f.children, t)
)
);
break;
- case An.getType():
+ case Nn.getType():
i.push(
- Kv(
+ dv(
a,
- zt(a.children, t)
+ Bt(a.children, t)
)
);
break;
- case oi.getType():
+ case di.getType():
i.push(
- jv(
+ fv(
a,
- zt(a.children, t)
+ Bt(a.children, t)
)
);
break;
- case ai.getType():
+ case fi.getType():
i.push(
- Bv(
+ pv(
a,
- zt(a.children, t)
+ Bt(a.children, t)
)
);
break;
- case Me.getType():
+ case Ee.getType():
i.push(
- Vv(
+ hv(
p,
- zt(p.children, t, p.caller)
+ Bt(p.children, t, p.caller)
)
);
break;
- case vr.getType():
- case _r.getType():
- case Bt.getType():
- case rf.getType():
- case ar.getType():
+ case Ar.getType():
+ case Mr.getType():
+ case Ht.getType():
+ case yf.getType():
+ case hr.getType():
break;
case Ze.getType():
- if (s = zt(
+ if (s = Bt(
g.children,
t,
r,
n
), s) {
- const T = g.typedIDs[Lr];
- if (T)
- Jv(s, T, o, e[c + 1], i), o = T;
+ const k = g.typedIDs[zr];
+ if (k)
+ bv(s, k, o, e[c + 1], i), o = k;
else {
- const S = s.shift();
- S && (typeof S == "string" ? cd(i, S) : i.push(S)), s.length > 0 && i.push(...s);
+ const _ = s.shift();
+ _ && (typeof _ == "string" ? vd(i, _) : i.push(_)), s.length > 0 && i.push(...s);
}
}
break;
- case Vt.getType():
- i.push(Wn(a));
+ case Gt.getType():
+ i.push(Hn(a));
break;
- case ze.getType():
+ case Ke.getType():
if (m.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.
- !cs(m.text) && // A byte test, not (only) the separator state tag, and deliberately so: a lone-NBSP
+ !ps(m.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
@@ -20242,65 +20461,65 @@ function zt(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 !== w && !m.text.startsWith(gc) && // Char-span attribute display runs (bare `|…`, no NBSP prefix — see
+ m.text !== L && !m.text.startsWith(xc) && // 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[is]?.textType !== "attribute" && (!r || m.text !== St(r))) {
- let T = Wv(m);
- Dh(t) && (n && T.startsWith(w) && (T = T.slice(1)), T = qv(wv(T))), cd(i, T);
+ m[ds]?.textType !== "attribute" && (!r || m.text !== At(r))) {
+ let k = gv(m);
+ Zh(t) && (n && k.startsWith(L) && (k = k.slice(1)), k = rv(tv(k))), vd(i, k);
}
break;
- case Mn.getType():
+ case An.getType():
i.push(
- Hv(
+ mv(
y,
- zt(y.children, t)
+ Bt(y.children, t)
)
);
break;
- case Mr.getType():
- i.push(Gv(a));
+ case Nr.getType():
+ i.push(yv(a));
break;
- case ci.getType():
- Gs?.error("Block verse layout is not round-trippable to USJ; skipping the block.");
+ case pi.getType():
+ Qs?.error("Block verse layout is not round-trippable to USJ; skipping the block.");
break;
default:
- Gs?.error(`Unexpected node type '${a.type}'!`);
+ Qs?.error(`Unexpected node type '${a.type}'!`);
}
}), i && i.length > 0 ? i : void 0;
}
-function Fh(e) {
- const t = e.findIndex((r) => uo(r));
+function tg(e) {
+ const t = e.findIndex((r) => fo(r));
if (t >= 0) {
- const r = e.slice(0, t), n = e[t].children, i = Fh(e.slice(t + 1));
+ const r = e.slice(0, t), n = e[t].children, i = tg(e.slice(t + 1));
e = [...r, ...n, ...i];
}
return e;
}
const ua = {
- initialize: Rv,
- deserializeEditorState: $v
-}, Yv = /^sd\d*$/, Xv = /* @__PURE__ */ new Set([
+ initialize: nv,
+ deserializeEditorState: iv
+}, kv = /^sd\d*$/, Tv = /* @__PURE__ */ new Set([
...Object.entries(xa).filter(
- ([e, t]) => t.category === k.TitlesHeadings && t.type === b.Paragraph && !Yv.test(e)
+ ([e, t]) => t.category === T.TitlesHeadings && t.type === b.Paragraph && !kv.test(e)
).map(([e]) => e),
"qa"
]);
-function Qv(e, t) {
+function xv(e, t) {
const r = [];
let n;
for (const i of e) {
- if (Lf(i) || Xf(i)) {
+ if (Xf(i) || dp(i)) {
n = void 0, r.push(i);
continue;
}
- if (!Kb(i)) {
- t && Js(i) && t.warn(
+ if (!ak(i)) {
+ t && Zs(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 (_c(i) && Xv.has(i.marker) && !Js(i)) {
+ if (Pc(i) && Tv.has(i.marker) && !Zs(i)) {
n = void 0, r.push(i);
continue;
}
@@ -20308,78 +20527,78 @@ function Qv(e, t) {
n ? n.children.push(i) : r.push(i);
continue;
}
- zh(i.children, t).forEach((s) => {
- const o = Zv(i, s.nodes);
+ rg(i.children, t).forEach((s) => {
+ const o = _v(i, s.nodes);
if (!s.verse) {
if (!o) return;
n ? n.children.push(o) : r.push(o);
return;
}
- n = eS(s.verse), r.push(n), o && n.children.push(o);
+ n = Cv(s.verse), r.push(n), o && n.children.push(o);
});
}
return r;
}
-function zh(e, t) {
+function rg(e, t) {
const r = [{ nodes: [] }], n = (i) => r[r.length - 1].nodes.push(i);
return e.forEach((i) => {
- if (Kh(i)) {
+ if (ng(i)) {
r.push({ verse: i, nodes: [i] });
return;
}
- if (wf(i)) {
- const s = zh(i.children, t), [o, ...a] = s;
- o.nodes.length > 0 && n(ld(i, o.nodes)), a.forEach((c) => {
- r.push({ verse: c.verse, nodes: [ld(i, c.nodes)] });
+ if (Wf(i)) {
+ const s = rg(i.children, t), [o, ...a] = s;
+ o.nodes.length > 0 && n(Md(i, o.nodes)), a.forEach((c) => {
+ r.push({ verse: c.verse, nodes: [Md(i, c.nodes)] });
});
return;
}
- t && Js(i) && t?.warn(
+ t && Zs(i) && t?.warn(
`Verse marker nested inside a '${i.type}' node was not grouped into a block.`
), n(i);
}), r;
}
-function ld(e, t) {
+function Md(e, t) {
return { ...e, children: t };
}
-function Kh(e) {
- return Bp(e) && e.number !== "";
+function ng(e) {
+ return sh(e) && e.number !== "";
}
-function Js(e) {
+function Zs(e) {
const t = e.children;
- return Array.isArray(t) ? t.some((r) => Kh(r) || Js(r)) : !1;
+ return Array.isArray(t) ? t.some((r) => ng(r) || Zs(r)) : !1;
}
-function Zv(e, t) {
+function _v(e, t) {
if (t.length !== 0)
return { ...e, children: t };
}
-function eS(e) {
+function Cv(e) {
return {
- type: Us,
+ type: js,
number: e.number,
children: [],
direction: null,
format: "",
indent: 0,
- version: zp
+ version: rh
};
}
-const ud = Bh([]), tS = {
- type: rf.getType(),
+const Ed = sg([]), Sv = {
+ type: yf.getType(),
version: 1
};
-let nl = [], Y, xn, jh, kt;
-function rS(e, t) {
- nl = [], sS(e), oS(t);
+let pl = [], ee, Cn, ig, xt;
+function vv(e, t) {
+ pl = [], Av(e), Pv(t);
}
-function nS(e = 0) {
+function Mv(e = 0) {
}
-function iS(e, t) {
- Y = t ?? _o();
+function Ev(e, t) {
+ ee = t ?? Co();
let r;
- return e ? (e.type !== hr && kt?.warn(`This USJ type '${e.type}' didn't match the expected type '${hr}'.`), e.version !== pr && kt?.warn(
- `This USJ version '${e.version}' didn't match the expected version '${pr}'.`
- ), e.content.length > 0 ? (r = Ha(Rr(e.content)), Xi(Y) && (r = Qv(r, kt))) : r = [ud]) : r = [ud], jh?.(nl), {
+ return e ? (e.type !== kr && xt?.warn(`This USJ type '${e.type}' didn't match the expected type '${kr}'.`), e.version !== br && xt?.warn(
+ `This USJ version '${e.version}' didn't match the expected version '${br}'.`
+ ), e.content.length > 0 ? (r = Ga(Dr(e.content)), ns(ee) && (r = xv(r, xt))) : r = [Ed]) : r = [Ed], ig?.(pl), {
root: {
children: r,
direction: null,
@@ -20390,32 +20609,32 @@ function iS(e, t) {
}
};
}
-function sS(e) {
- e && (xn = e), e?.addMissingComments && (jh = e.addMissingComments);
+function Av(e) {
+ e && (Cn = e), e?.addMissingComments && (ig = e.addMissingComments);
}
-function oS(e) {
- e && (kt = e);
+function Pv(e) {
+ e && (xt = e);
}
-function il() {
- return Co(Y);
+function hl() {
+ return So(ee);
}
-function aS(e) {
+function Nv(e) {
return !e || e.length !== 1 || typeof e[0] != "string" ? "" : e[0];
}
-function cS(e) {
+function Ov(e) {
let { marker: t } = e;
- t !== Ki && kt?.warn(`Unexpected book marker '${t}'!`), t = t ?? Ki;
+ t !== Gi && xt?.warn(`Unexpected book marker '${t}'!`), t = t ?? Gi;
const { code: r } = e;
- (!r || !Lt.isValidBookCode(r)) && kt?.warn(`Unexpected book code '${r}'!`);
+ (!r || !Ut.isValidBookCode(r)) && xt?.warn(`Unexpected book code '${r}'!`);
const n = [];
- Y?.markerMode === "editable" || Y?.markerMode === "visible" ? n.push(
- yt("marker", Oe(t) + " " + r + w)
- ) : Y?.hasGutterParaMarkers && n.push(yt("marker", Oe(t) + w, !0));
- const i = aS(e.content);
- i && n.push(at(il() ? Lh(i) : i));
- const s = De(e, xb);
+ ee?.markerMode === "editable" || ee?.markerMode === "visible" ? n.push(
+ bt("marker", Ne(t) + " " + r + L)
+ ) : ee?.hasGutterParaMarkers && n.push(bt("marker", Ne(t) + L, !0));
+ const i = Nv(e.content);
+ i && n.push(at(hl() ? Qh(i) : i));
+ const s = De(e, Fb);
return Ae({
- type: Lt.getType(),
+ type: Ut.getType(),
marker: t,
code: r ?? "",
unknownAttributes: s,
@@ -20423,20 +20642,20 @@ function cS(e) {
direction: null,
format: "",
indent: 0,
- version: $f
+ version: Jf
});
}
-function lS(e) {
+function wv(e) {
let { marker: t } = e;
- t !== $s && kt?.warn(`Unexpected chapter marker '${t}'!`), t = t ?? $s;
- const { number: r, sid: n, altnumber: i, pubnumber: s } = e, o = De(e, _b);
+ t !== Us && xt?.warn(`Unexpected chapter marker '${t}'!`), t = t ?? Us;
+ const { number: r, sid: n, altnumber: i, pubnumber: s } = e, o = De(e, zb);
let a;
- Y?.markerMode === "visible" && (a = !0);
+ ee?.markerMode === "visible" && (a = !0);
const c = [
- at($t(t, r) ?? "")
+ at(Lt(t, r) ?? "")
];
- return Y?.markerMode === "editable" && MS(i, s, c), Y?.markerMode === "editable" ? Ae({
- type: Et.getType(),
+ return ee?.markerMode === "editable" && Jv(i, s, c), ee?.markerMode === "editable" ? Ae({
+ type: Nt.getType(),
marker: t,
number: r ?? "",
sid: n,
@@ -20447,9 +20666,9 @@ function lS(e) {
direction: null,
format: "",
indent: 0,
- version: Df
+ version: Qf
}) : Ae({
- type: or.getType(),
+ type: pr.getType(),
marker: t,
number: r ?? "",
showMarker: a,
@@ -20457,16 +20676,16 @@ function lS(e) {
altnumber: i,
pubnumber: s,
unknownAttributes: o,
- version: jf
+ version: np
});
}
-function uS(e) {
+function qv(e) {
let { marker: t } = e;
- t !== Is && kt?.warn(`Unexpected verse marker '${t}'!`), t = t ?? Is;
- const { number: r, sid: n, altnumber: i, pubnumber: s } = e, a = (n_(Y) ?? xt).getType(), c = Y?.markerMode === "editable" ? Gf : jp;
+ t !== Fs && xt?.warn(`Unexpected verse marker '${t}'!`), t = t ?? Fs;
+ const { number: r, sid: n, altnumber: i, pubnumber: s } = e, a = (x_(ee) ?? Ct).getType(), c = ee?.markerMode === "editable" ? cp : ih;
let l, u;
- Y?.markerMode === "editable" ? l = $t(t, r) : Y?.markerMode === "visible" && (u = !0);
- const d = De(e, $b);
+ ee?.markerMode === "editable" ? l = Lt(t, r) : ee?.markerMode === "visible" && (u = !0);
+ const d = De(e, ek);
return Ae({
type: a,
text: l,
@@ -20481,59 +20700,59 @@ function uS(e) {
version: c
});
}
-function dS(e, t = [], r = !1) {
+function Rv(e, t = [], r = !1) {
let { marker: n } = e;
- ye.isValidMarker(n, xn?.extraValidMarkers) || kt?.warn(`Unexpected char marker '${n}'!`), n = n ?? "";
+ me.isValidMarker(n, Cn?.extraValidMarkers) || xt?.warn(`Unexpected char marker '${n}'!`), n = n ?? "";
const i = [];
- if (Y?.markerMode === "editable") {
+ if (ee?.markerMode === "editable") {
const [a] = t;
- Xn(a) ? a.text = w + a.text : a && t.unshift(at(w));
+ ti(a) ? a.text = L + a.text : a && t.unshift(at(L));
}
- t.length === 0 && t.push(at(It)), Ba(e.marker ?? "", i, r), i.push(...t);
- const s = e.closed === "false", o = De(e, Sb);
- return s || _S(n, o, i), s || Va(e.marker ?? "", i, !1, r), Ae({
- type: ye.getType(),
+ t.length === 0 && t.push(at(Dt)), Va(e.marker ?? "", i, r), i.push(...t);
+ const s = e.closed === "false", o = De(e, Bb);
+ return s || Vv(n, o, i), s || Wa(e.marker ?? "", i, !1, r), Ae({
+ type: me.getType(),
marker: n,
unknownAttributes: o,
children: i,
direction: null,
format: "",
indent: 0,
- version: Kf
+ version: rp
});
}
-function Bh(e) {
+function sg(e) {
return {
- type: zr.getType(),
+ type: Vr.getType(),
children: e,
direction: null,
format: "",
indent: 0,
textFormat: 0,
textStyle: "",
- version: Wf
+ version: op
};
}
-function fS(e, t = []) {
+function $v(e, t = []) {
let { marker: r } = e;
- Qe.isValidMarker(r, xn?.extraValidMarkers) || kt?.warn(`Unexpected para marker '${r}'!`), r = r ?? rr;
+ Qe.isValidMarker(r, Cn?.extraValidMarkers) || xt?.warn(`Unexpected para marker '${r}'!`), r = r ?? cr;
const n = [];
- if (li(Y) && (Y?.markerMode === "editable" ? n.push(
- pt(r),
- at(w, sr, "token")
- ) : (Y?.markerMode === "visible" || Y?.hasGutterParaMarkers) && n.push(
- yt(
+ if (hi(ee) && (ee?.markerMode === "editable" ? n.push(
+ ft(r),
+ at(L, fr, "token")
+ ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && n.push(
+ bt(
"marker",
- Oe(r) + w,
- Y?.hasGutterParaMarkers
+ Ne(r) + L,
+ ee?.hasGutterParaMarkers
)
- )), n.push(...t), il()) {
+ )), n.push(...t), hl()) {
const s = n.find(
- (o) => !Ac(o) && !(Xn(o) && o.text === w)
+ (o) => !$c(o) && !(ti(o) && o.text === L)
);
- Xn(s) && !/^ +$/.test(s.text) && (s.text = s.text.replace(/^ +/, (o) => w.repeat(o.length)));
+ ti(s) && !/^ +$/.test(s.text) && (s.text = s.text.replace(/^ +/, (o) => L.repeat(o.length)));
}
- const i = De(e, qb);
+ const i = De(e, Qb);
return Ae({
type: Qe.getType(),
marker: r,
@@ -20544,101 +20763,101 @@ function fS(e, t = []) {
indent: 0,
textFormat: 0,
textStyle: "",
- version: Hf
+ version: ap
});
}
-function sl() {
+function gl() {
return {
direction: null,
format: "",
indent: 0
};
}
-function pS(e, t = []) {
- const r = De(e, Uk);
+function Iv(e, t = []) {
+ const r = De(e, nT);
return Ae({
- ...sl(),
- type: An.getType(),
+ ...gl(),
+ type: Nn.getType(),
unknownAttributes: r,
children: t,
- version: Np
+ version: Vp
});
}
-function hS(e, t = []) {
- const r = De(e, Kk), n = e.marker ?? Na, i = [];
- return Y?.markerMode === "editable" ? i.push(
- pt(n),
- at(w, sr, "token")
- ) : (Y?.markerMode === "visible" || Y?.hasGutterParaMarkers) && i.push(
- yt(
+function Lv(e, t = []) {
+ const r = De(e, oT), n = e.marker ?? Na, i = [];
+ return ee?.markerMode === "editable" ? i.push(
+ ft(n),
+ at(L, fr, "token")
+ ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && i.push(
+ bt(
"marker",
- Oe(n) + w,
- Y?.hasGutterParaMarkers
+ Ne(n) + L,
+ ee?.hasGutterParaMarkers
)
), i.push(...t), Ae({
- ...sl(),
- type: oi.getType(),
+ ...gl(),
+ type: di.getType(),
marker: n,
unknownAttributes: r,
children: i,
- version: qp
+ version: Gp
});
}
-function gS(e, t = []) {
- const { marker: r, align: n, colspan: i } = e, s = [], o = r ?? Oa;
- Y?.markerMode === "editable" ? s.push(
- pt(o),
- at(w, sr, "token")
- ) : (Y?.markerMode === "visible" || Y?.hasGutterParaMarkers) && s.push(
- yt(
+function Dv(e, t = []) {
+ const { marker: r, align: n, colspan: i } = e, s = [], o = r ?? Oa, a = Lp(o, i) ?? o;
+ ee?.markerMode === "editable" ? s.push(
+ ft(a),
+ at(L, fr, "token")
+ ) : (ee?.markerMode === "visible" || ee?.hasGutterParaMarkers) && s.push(
+ bt(
"marker",
- Oe(o) + w,
- Y?.hasGutterParaMarkers
+ Ne(a) + L,
+ ee?.hasGutterParaMarkers
)
), s.push(...t);
- const a = De(
+ const c = De(
e,
- Bk
+ cT
);
return Ae({
- ...sl(),
- type: ai.getType(),
+ ...gl(),
+ type: fi.getType(),
marker: o,
align: n,
colspan: i,
- unknownAttributes: a,
+ unknownAttributes: c,
children: s,
- version: $p
+ version: Yp
});
}
-function mS(e, t) {
- const r = Gb(t);
+function Uv(e, t) {
+ const r = pk(t);
let n = () => {
};
- return xn?.noteCallerOnClick && (n = xn.noteCallerOnClick), Ae({
- type: Bt.getType(),
+ return Cn?.noteCallerOnClick && (n = Cn.noteCallerOnClick), Ae({
+ type: Ht.getType(),
caller: e,
previewText: r,
onClick: n,
- version: Qp
+ version: fh
});
}
-function yS(e, t) {
+function Fv(e, t) {
let { marker: r } = e;
- Me.isValidMarker(r, xn?.extraValidMarkers) || kt?.warn(`Unexpected note marker '${r}'!`), r = r ?? yc;
- const { category: n } = e, i = e.caller ?? "*", s = e.closed === "false", o = s ? !1 : Wc(Y?.noteMode), a = De(e, Ly), c = Y?.isNoteShellEditable === !1 ? "token" : "normal";
+ Ee.isValidMarker(r, Cn?.extraValidMarkers) || xt?.warn(`Unexpected note marker '${r}'!`), r = r ?? Cc;
+ const { category: n } = e, i = e.caller ?? "*", s = e.closed === "false", o = s ? !1 : Zc(ee?.noteMode), a = De(e, ib), c = ee?.isNoteShellEditable === !1 ? "token" : "normal";
let l, u;
- Y?.markerMode === "editable" ? (l = pt(r, "opening", !1, c), s || (u = pt(r, "closing"))) : Y?.markerMode === "visible" && (l = yt("marker", Oe(r) + " "), s || (u = yt("marker", rt(r))));
+ ee?.markerMode === "editable" ? (l = ft(r, "opening", !1, c), s || (u = ft(r, "closing"))) : ee?.markerMode === "visible" && (l = bt("marker", Ne(r) + " "), s || (u = bt("marker", rt(r))));
const d = [];
let f;
- if (l && d.push(l), Y?.markerMode === "editable" && !o)
- f = at(St(i), void 0, c), d.push(f), SS(n, d), d.push(...t);
+ if (l && d.push(l), ee?.markerMode === "editable" && !o)
+ f = at(At(i), void 0, c), d.push(f), Gv(n, d), d.push(...t);
else {
- const p = at(w, sr, "token");
- f = mS(i, t), d.push(f, p, ...t.flatMap(bS(p)));
+ const p = at(L, fr, "token");
+ f = Uv(i, t), d.push(f, p, ...t.flatMap(zv(p)));
}
return u && d.push(u), Ae({
- type: Me.getType(),
+ type: Ee.getType(),
marker: r,
caller: i,
isCollapsed: o,
@@ -20648,30 +20867,30 @@ function yS(e, t) {
direction: null,
format: "",
indent: 0,
- version: vf
+ version: Df
});
}
-function bS(e) {
- return (t) => Nf(t) ? [t] : [t, e];
+function zv(e) {
+ return (t) => Bf(t) ? [t] : [t, e];
}
-function kS(e) {
+function Kv(e) {
let { marker: t } = e;
- (!t || !Vt.isValidMarker(t, xn?.extraValidMarkers)) && kt?.warn(`Unexpected milestone marker '${t}'!`), t = t ?? "";
- const { sid: r, eid: n } = e, i = De(e, mc), s = fp(e);
+ (!t || !Gt.isValidMarker(t, Cn?.extraValidMarkers)) && xt?.warn(`Unexpected milestone marker '${t}'!`), t = t ?? "";
+ const { sid: r, eid: n } = e, i = De(e, _c), s = Mp(e);
return Ae({
- type: Vt.getType(),
+ type: Gt.getType(),
marker: t,
sid: r,
eid: n,
unknownAttributes: i,
attributeOrder: s,
- version: xf
+ version: $f
});
}
-function dd(e, t = []) {
+function Ad(e, t = []) {
return {
type: Ze.getType(),
- typedIDs: { [Lr]: t },
+ typedIDs: { [zr]: t },
children: e,
direction: null,
format: "",
@@ -20679,21 +20898,21 @@ function dd(e, t = []) {
version: 1
};
}
-function TS(e, t) {
- const { marker: r } = e, n = e.type, i = De(e, yb), s = [];
- if (Y?.markerMode === "editable") {
- const { opening: o, attributes: a, closingAttributes: c, closing: l } = Cp(
+function jv(e, t) {
+ const { marker: r } = e, n = e.type, i = De(e, Ib), s = [];
+ if (ee?.markerMode === "editable") {
+ const { opening: o, attributes: a, closingAttributes: c, closing: l } = Dp(
n,
r,
i
);
- o && s.push(yt("marker", o)), a && s.push(yt("attribute", a)), s.push(...t), c && s.push(yt("attribute", c)), l && s.push(yt("marker", l));
+ o && s.push(bt("marker", o)), a && s.push(bt("attribute", a)), s.push(...t), c && s.push(bt("attribute", c)), l && s.push(bt("marker", l));
} else
s.push(...t);
return s.forEach((o) => {
- Xn(o) && (o.mode = "token");
+ ti(o) && (o.mode = "token");
}), Ae({
- type: Mn.getType(),
+ type: An.getType(),
tag: n,
marker: r,
unknownAttributes: i,
@@ -20701,27 +20920,27 @@ function TS(e, t) {
direction: null,
format: "",
indent: 0,
- version: Rf
+ version: Gf
});
}
-function xS(e) {
+function Bv(e) {
return {
- type: Mr.getType(),
+ type: Nr.getType(),
marker: e,
- text: Ri(e),
+ text: Di(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: Y?.markerMode === "editable" ? "normal" : "token",
+ mode: ee?.markerMode === "editable" ? "normal" : "token",
style: "",
- version: Ep
+ version: Kp
};
}
-function pt(e, t = "opening", r = !1, n = "normal") {
+function ft(e, t = "opening", r = !1, n = "normal") {
return {
- type: ar.getType(),
+ type: hr.getType(),
marker: e,
markerSyntax: t,
// Emit the flag only for nested glyphs; absence means non-nested (see MarkerNode.exportJSON).
@@ -20736,7 +20955,7 @@ function pt(e, t = "opening", r = !1, n = "normal") {
}
function at(e, t = void 0, r = "normal") {
const n = {
- type: ze.getType(),
+ type: Ke.getType(),
text: e,
detail: 0,
format: 0,
@@ -20744,285 +20963,285 @@ function at(e, t = void 0, r = "normal") {
style: "",
version: 1
};
- return t !== void 0 && (n[is] = { textType: t }), n;
+ return t !== void 0 && (n[ds] = { textType: t }), n;
}
-function yt(e, t, r = !1) {
+function bt(e, t, r = !1) {
const n = {
- type: _r.getType(),
+ type: Mr.getType(),
text: t,
textType: e,
- version: Pf
+ version: jf
};
- return r && (n[is] = { [kc.key]: !0 }), n;
+ return r && (n[ds] = { [Mc.key]: !0 }), n;
}
-function Qi(e, t) {
+function is(e, t) {
return {
- type: vr.getType(),
+ type: Ar.getType(),
runKind: e,
children: t,
direction: null,
format: "",
indent: 0,
- version: lp
+ version: Cp
};
}
-function Ba(e, t, r = !1) {
- Y?.markerMode === "editable" ? t.push(pt(e, "opening", r)) : Y?.markerMode === "visible" && t.push(yt("marker", Oe(e, r)));
+function Va(e, t, r = !1) {
+ ee?.markerMode === "editable" ? t.push(ft(e, "opening", r)) : ee?.markerMode === "visible" && t.push(bt("marker", Ne(e, r)));
}
-function Va(e, t, r = !1, n = !1) {
- Y?.markerMode === "editable" ? r ? t.push(pt("", "selfClosing")) : t.push(pt(e, "closing", n)) : Y?.markerMode === "visible" && t.push(
- yt(
+function Wa(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(
+ bt(
"marker",
r ? rt("") : rt(e, n)
)
);
}
-function _S(e, t, r) {
- if (Y?.markerMode !== "editable" || !t) return;
- const n = tr(t, ao(e));
+function Vv(e, t, r) {
+ if (ee?.markerMode !== "editable" || !t) return;
+ const n = or(t, lo(e));
n && r.push(at(n, "attribute"));
}
-function fd(e, t) {
- if (e.type !== "ms" || Y?.markerMode !== "editable" && Y?.markerMode !== "visible") return;
- const { marker: r, sid: n, eid: i } = e, s = De(e, mc), o = pp(
+function Pd(e, t) {
+ if (e.type !== "ms" || ee?.markerMode !== "editable" && ee?.markerMode !== "visible") return;
+ const { marker: r, sid: n, eid: i } = e, s = De(e, _c), o = Ep(
n,
i,
s,
- fp(e)
- ), a = tr(o, lo(r ?? ""));
+ Mp(e)
+ ), a = or(o, uo(r ?? ""));
if (!a) return;
- const c = w + a;
- Y?.markerMode === "editable" ? t.push(at(c, "attribute")) : t.push(yt("attribute", c));
+ const c = L + a;
+ ee?.markerMode === "editable" ? t.push(at(c, "attribute")) : t.push(bt("attribute", c));
}
-function CS(e, t) {
+function Wv(e, t) {
const r = e.marker ?? "";
- if (Y?.markerMode === "editable") {
+ if (ee?.markerMode === "editable") {
const n = [];
- Ba(r, n), fd(e, n), Va(r, n, !0), t.push(Qi("milestone", n));
+ Va(r, n), Pd(e, n), Wa(r, n, !0), t.push(is("milestone", n));
} else
- Ba(r, t), fd(e, t), Va(r, t, !0);
+ Va(r, t), Pd(e, t), Wa(r, t, !0);
}
-function pd(e, t, r) {
+function Nd(e, t, r) {
t !== void 0 && r.push(
- Qi(e, [
- pt(e, "opening"),
- at(w + t, "attribute"),
- pt(e, "closing")
+ is(e, [
+ ft(e, "opening"),
+ at(L + t, "attribute"),
+ ft(e, "closing")
])
);
}
-function vS(e, t) {
- Y?.markerMode === "editable" && (pd("va", e.altnumber, t), pd("vp", e.pubnumber, t));
+function Hv(e, t) {
+ ee?.markerMode === "editable" && (Nd("va", e.altnumber, t), Nd("vp", e.pubnumber, t));
}
-function SS(e, t) {
+function Gv(e, t) {
e !== void 0 && t.push(
- Qi("cat", [
- pt("cat", "opening"),
- at(w + e, "attribute"),
- pt("cat", "closing")
+ is("cat", [
+ ft("cat", "opening"),
+ at(L + e, "attribute"),
+ ft("cat", "closing")
])
);
}
-function MS(e, t, r) {
+function Jv(e, t, r) {
e !== void 0 && r.push(
- Qi("ca", [
- pt("ca", "opening"),
- at(w + e, "attribute"),
- pt("ca", "closing")
+ is("ca", [
+ ft("ca", "opening"),
+ at(L + e, "attribute"),
+ ft("ca", "closing")
])
), t !== void 0 && r.push(
- Qi("cp", [
- pt("cp", "opening"),
- at(w + t, "attribute")
+ is("cp", [
+ ft("cp", "opening"),
+ at(L + t, "attribute")
])
);
}
-function hd(e, t) {
+function Od(e, t) {
return e.length <= 0 || t === 0 ? e : e.map((r) => r - t);
}
-function ES(e, t) {
+function Yv(e, t) {
const r = e.indexOf(t, 0);
r > -1 && e.splice(r, 1);
}
-function gd(e, t) {
- t.marker === hn && t.sid !== void 0 && e.push(t.sid), t.marker === Gn && t.eid !== void 0 && ES(e, t.eid);
+function wd(e, t) {
+ t.marker === yn && t.sid !== void 0 && e.push(t.sid), t.marker === Jn && t.eid !== void 0 && Yv(e, t.eid);
}
-function Wa(e, t, r = !1, n = []) {
+function Ha(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 ? [dd(o, [...n])] : o, c = e[i];
- gd(n, c);
- const l = Wa(
+ const o = e.slice(0, i), a = r ? [Ad(o, [...n])] : o, c = e[i];
+ wd(n, c);
+ const l = Ha(
e.slice(i + 1, s),
- hd(t, i + 1),
- c.marker === hn,
+ Od(t, i + 1),
+ c.marker === yn,
n
- ), u = dd(l, [...n]), d = e[s];
- gd(n, d);
- const f = Wa(
+ ), u = Ad(l, [...n]), d = e[s];
+ wd(n, d);
+ const f = Ha(
e.slice(s + 1),
- hd(t, s + 1),
- d.marker === hn,
+ Od(t, s + 1),
+ d.marker === yn,
n
);
return [...a, u, ...f];
}
-function Rr(e, t = !1) {
+function Dr(e, t = !1) {
const r = [], n = [];
return e?.forEach((i) => {
if (typeof i == "string")
- i && n.push(at(il() ? Lh(i) : i));
+ i && n.push(at(hl() ? Qh(i) : i));
else if (!i.type)
- kt?.error("Marker type is missing!");
+ xt?.error("Marker type is missing!");
else
switch (i.type) {
- case Lt.getType():
- n.push(cS(i));
+ case Ut.getType():
+ n.push(Ov(i));
break;
- case Et.getType():
- n.push(lS(i));
+ case Nt.getType():
+ n.push(wv(i));
break;
- case ft.getType():
- Y?.hasSpacing || n.push(tS), n.push(uS(i)), vS(i, n);
+ case dt.getType():
+ ee?.hasSpacing || n.push(Sv), n.push(qv(i)), Hv(i, n);
break;
- case ye.getType():
+ case me.getType():
n.push(
- dS(i, Rr(i.content, !0), t)
+ Rv(i, Dr(i.content, !0), t)
);
break;
case Qe.getType():
- n.push(fS(i, Rr(i.content)));
+ n.push($v(i, Dr(i.content)));
break;
- case Me.getType():
- n.push(yS(i, Rr(i.content)));
+ case Ee.getType():
+ n.push(Fv(i, Dr(i.content)));
break;
- case Vt.getType():
- _f(i.marker ?? "") && (r.push(n.length), i.sid !== void 0 && nl?.push(i.sid)), n.push(kS(i)), CS(i, n);
+ case Gt.getType():
+ If(i.marker ?? "") && (r.push(n.length), i.sid !== void 0 && pl?.push(i.sid)), n.push(Kv(i)), Wv(i, n);
break;
- case Mr.getType():
- n.push(xS(i.marker ?? ""));
+ case Nr.getType():
+ n.push(Bv(i.marker ?? ""));
break;
- case Pp:
- n.push(pS(i, Rr(i.content)));
+ case Bp:
+ n.push(Iv(i, Dr(i.content)));
break;
- case wp:
- n.push(hS(i, Rr(i.content)));
+ case Hp:
+ n.push(Lv(i, Dr(i.content)));
break;
- case Rp:
- n.push(gS(i, Rr(i.content)));
+ case Jp:
+ n.push(Dv(i, Dr(i.content)));
break;
default:
- kt?.warn(`Unknown type-marker '${i.type}-${i.marker}'!`), n.push(TS(i, Rr(i.content)));
+ xt?.warn(`Unknown type-marker '${i.type}-${i.marker}'!`), n.push(jv(i, Dr(i.content)));
}
- }), Wa(n, r);
+ }), Ha(n, r);
}
-function Ha(e) {
+function Ga(e) {
const t = e.findIndex(
- (n) => Lf(n) || Xf(n) || _c(n) || // A table is a block root in its own right; without this it would be swept into an implied
+ (n) => Xf(n) || dp(n) || Pc(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.
- zk(n)
+ sT(n)
);
if (t >= 0) {
- const n = Ha(e.slice(0, t)), i = e[t], s = Ha(e.slice(t + 1));
+ const n = Ga(e.slice(0, t)), i = e[t], s = Ga(e.slice(t + 1));
return [...n, i, ...s];
- } else if (e.some((n) => "text" in n && "mode" in n || Bp(n)))
- return [Bh(e)];
+ } else if (e.some((n) => "text" in n && "mode" in n || sh(n)))
+ return [sg(e)];
return e;
}
-const Br = {
- initialize: rS,
- reset: nS,
- serializeEditorState: iS
+const Jr = {
+ initialize: vv,
+ reset: Mv,
+ serializeEditorState: Ev
};
-function Vh(e) {
- if (e && !P(e)) {
- if (M(e)) return e;
- if (D(e))
+function og(e) {
+ if (e && !O(e)) {
+ if (v(e)) return e;
+ if (F(e))
for (const t of e.getChildren()) {
- const r = Vh(t);
+ const r = og(t);
if (r) return r;
}
}
}
-function AS() {
+function Xv() {
const e = R();
if (!N(e)) return !1;
if (e.isCollapsed()) {
const t = e.anchor.getNode(), r = e.anchor.offset;
- if ((M(t) && !P(t) ? kn(t) : void 0) && M(t)) {
- const i = he(" ");
+ if ((v(t) && !O(t) ? _n(t) : void 0) && v(t)) {
+ const i = pe(" ");
if (r <= 0) t.insertBefore(i);
else if (r >= t.getTextContentSize()) t.insertAfter(i);
else {
const [o] = t.splitText(r);
o.insertAfter(i);
}
- Qn(i, { renderGlyphs: !0, closeImplicitSpans: !0 });
- const s = Vh(i.getNextSibling());
+ ri(i, { renderGlyphs: !0, closeImplicitSpans: !0 });
+ const s = og(i.getNextSibling());
if (s) {
- const o = s.getTextContent(), a = o.startsWith(w) ? w : "", c = o.slice(a.length);
+ const o = s.getTextContent(), a = o.startsWith(L) ? L : "", c = o.slice(a.length);
c.startsWith(" ") && s.setTextContent(a + c.slice(1));
}
return i.select(1, 1), !0;
}
- return M(t) && t.getTextContent()[r] === " " ? (t.select(r + 1, r + 1), !0) : (e.insertText(" "), !0);
+ return v(t) && t.getTextContent()[r] === " " ? (t.select(r + 1, r + 1), !0) : (e.insertText(" "), !0);
}
- for (const t of Wh(e)) {
- if (!kn(t)) continue;
- Qn(t, { renderGlyphs: !0, closeImplicitSpans: !0 });
+ for (const t of ag(e)) {
+ if (!_n(t)) continue;
+ ri(t, { renderGlyphs: !0, closeImplicitSpans: !0 });
const r = t.getLatest(), n = r.getTextContent();
- n.startsWith(w) && r.setTextContent(n.slice(w.length));
+ n.startsWith(L) && r.setTextContent(n.slice(L.length));
}
return !0;
}
-function Wh(e) {
- const [t, r] = Jd(e), [n, i] = e.isBackward() ? [r, t] : [t, r], s = e.getNodes(), o = [];
+function ag(e) {
+ const [t, r] = gc(e), [n, i] = e.isBackward() ? [r, t] : [t, r], s = e.getNodes(), o = [];
return s.forEach((a, c) => {
- if (!M(a) || P(a) || re(a, oe) === "attribute") return;
+ 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);
}), o;
}
-function PS() {
+function Qv() {
const e = R();
if (!N(e)) return !1;
const t = e.focus.getNode();
- return kn(t) ? Ce(Rc(t)) : !1;
+ return _n(t) ? Se(zc(t)) : !1;
}
-function Hh() {
+function cg() {
let e = R();
if (!N(e) || !e.isCollapsed()) return !1;
let t = e.anchor.getNode();
- if (P(t) && !Lc(t, e.anchor.offset)) {
+ if (O(t) && !Bc(t, e.anchor.offset)) {
const c = t.getParent();
- if ($(c) && t.is(c.getLastChild())) {
+ if (D(c) && t.is(c.getLastChild())) {
if (c.selectNext(0, 0), e = R(), !N(e) || !e.isCollapsed()) return !1;
t = e.anchor.getNode();
}
}
- if (!M(t) || P(t) || !kn(t)) return !1;
- const r = Rc(t);
- if (!Ce(r)) return !1;
- const n = he(""), i = e.anchor.offset;
+ if (!v(t) || O(t) || !_n(t)) return !1;
+ const r = zc(t);
+ if (!Se(r)) return !1;
+ const n = pe(""), 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);
}
- Qn(n, { renderGlyphs: !0 });
+ ri(n, { renderGlyphs: !0 });
const s = n.getNextSiblings();
n.remove();
const o = r.insertNewAfter(e, !1);
o.append(...s);
const [a] = s;
- return $(a) ? $c(a) : o.select(0, 0), !0;
+ return D(a) ? Kc(a) : o.select(0, 0), !0;
}
-const Gh = {
+const lg = {
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.
@@ -21031,23 +21250,23 @@ const Gh = {
return { content: [{
type: "chapter",
marker: "c",
- number: `${Qf(Ue().getChildren(), t) !== void 0 ? t + 1 : t}`
+ number: `${fp(Ue().getChildren(), t) !== void 0 ? t + 1 : t}`
}] };
}
},
v: {
action: () => {
- const e = R(), t = Sc(e), r = zc(t);
+ const e = R(), t = wc(e), r = Gc(t);
let n, i = !1;
if (!r)
n = "1";
else {
const o = r.getNumber();
- n = Yb(0, o);
- const a = NT(r);
+ n = gk(0, o);
+ const a = GT(r);
if (a) {
const c = a.getNumber();
- i = n === c || sp(c) && Mc(parseInt(n, 10), c);
+ i = n === c || kp(c) && qc(parseInt(n, 10), c);
}
}
return { content: [{
@@ -21058,30 +21277,30 @@ const Gh = {
}
}
};
-function Ga(e, t) {
- return Me.isValidMarker(e, t) || !!Gh[e] || Qe.isValidMarker(e, t) || ye.isValidMarker(e, t);
+function Ja(e, t) {
+ return Ee.isValidMarker(e, t) || !!lg[e] || Qe.isValidMarker(e, t) || me.isValidMarker(e, t);
}
-function NS(e, t) {
- return ye.isNoteContentMarker(e) ? !1 : ye.isValidMarker(e, t);
+function Zv(e, t) {
+ return me.isNoteContentMarker(e) ? !1 : me.isValidMarker(e, t);
}
-function Jh(e, t, r, n, i, s) {
- const o = th(
+function ug(e, t, r, n, i, s) {
+ const o = gh(
e,
void 0,
void 0,
t,
- n ?? _o(),
+ n ?? Co(),
i ?? {},
s
);
return o && !o.getIsCollapsed() && (r.current = o.getKey()), o?.getKey();
}
-function Ja(e, t, r, n, i, s, o) {
- if (Me.isValidMarker(e, n?.extraValidMarkers)) {
+function Ya(e, t, r, n, i, s, o) {
+ if (Ee.isValidMarker(e, n?.extraValidMarkers)) {
let l;
return { action: (d) => {
d.editor.update(() => {
- l = Jh(
+ l = ug(
e,
d.reference,
t,
@@ -21092,88 +21311,88 @@ function Ja(e, t, r, n, i, s, o) {
}, s);
}, label: void 0, getInsertedNoteKey: () => l };
}
- const a = IS(e, n?.extraValidMarkers);
+ const a = sM(e, n?.extraValidMarkers);
return a ? { action: (l) => {
l.editor.update(() => {
const u = R();
- N(u) && (Fp(u), l.noteText = u.getTextContent());
- const { content: d, highlightInserted: f } = a.action(l), p = yu(d, Br, r), m = jo(p);
+ N(u) && (th(u), l.noteText = u.getTextContent());
+ const { content: d, highlightInserted: f } = a.action(l), p = Ru(d, Jr, r), m = jo(p);
if (N(u)) {
- const g = u.anchor.getNode(), y = g.getParent(), T = kn(g), S = u.anchor.key === u.focus.key;
- if ($(m) && T && S && !da(m, o))
- qS(
+ const g = u.anchor.getNode(), y = g.getParent(), k = _n(g), _ = u.anchor.key === u.focus.key;
+ if (D(m) && k && _ && !da(m, o))
+ rM(
u,
m,
g,
r?.markerMode === "editable"
);
- else if ($(m) && !S && !da(m, o) && RS(u))
- $S(u, m, r?.markerMode === "editable");
+ else if (D(m) && !_ && !da(m, o) && nM(u))
+ iM(u, m, r?.markerMode === "editable");
else if (u.getTextContent().length > 0)
- LS(
+ oM(
u,
() => jo(p)
);
- else if (D(m) && !m.isInline()) {
- const v = u.insertParagraph();
- if (v) {
- const E = v.getChildren();
- m.append(...E), v.replace(m), Ce(m) && di(m) || m.selectStart();
+ else if (F(m) && !m.isInline()) {
+ const S = u.insertParagraph();
+ if (S) {
+ const P = S.getChildren();
+ m.append(...P), S.replace(m), Se(m) && mi(m) || m.selectStart();
}
- } else if ($(m) && M(g) && !P(g) && $(g.getParent()) && u.isCollapsed() && // NEST-able only. A non-NEST style at a caret inside ANY char span — nested or note-level
+ } 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
// — 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.
da(m, o)) {
- const v = g.getParent();
- if ($(v)) {
- const E = u.anchor.offset;
- if (E === 0) g.insertBefore(m);
- else if (E >= g.getTextContentSize()) g.insertAfter(m);
+ 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);
else {
- const [x] = g.splitText(E);
- x.insertAfter(m);
+ const [B] = g.splitText(P);
+ B.insertAfter(m);
}
- m.getChildren().forEach((x) => {
- P(x) && x.setNested(!0);
+ m.getChildren().forEach((B) => {
+ O(B) && B.setNested(!0);
});
- const A = m.getChildren().find((x) => M(x) && !P(x));
- A && M(A) ? A.select(
+ const A = m.getChildren().find((B) => v(B) && !O(B));
+ A && v(A) ? A.select(
A.getTextContentSize(),
A.getTextContentSize()
) : m.selectEnd();
}
- } else if (M(g) && !P(g) && u.isCollapsed() && (j(y) || $(y) && j(y.getParent()))) {
- const v = $(y) ? y : void 0, E = v ? OS(g, u.anchor.offset) : [];
- let x = (v ?? g).insertAfter(m);
- if (Cr(m)) {
- const F = {
- ...r || _o(),
+ } else if (v(g) && !O(g) && u.isCollapsed() && (j(y) || D(y) && j(y.getParent()))) {
+ const S = D(y) ? y : void 0, P = S ? eM(g, u.anchor.offset) : [];
+ let B = (S ?? g).insertAfter(m);
+ if (Er(m)) {
+ const M = {
+ ...r || Co(),
markerMode: "hidden"
- }, L = yu(
+ }, w = Ru(
d,
- Br,
- F
- ), G = jo(L);
- x = x.insertAfter(G);
+ Jr,
+ M
+ ), $ = jo(w);
+ B = B.insertAfter($);
}
- if (E.length > 0 && v) {
- const F = Ys(v).append(...E);
- x.insertAfter(F), v.isEmpty() && v.remove();
- } else M(x.getNextSibling()) || x.insertAfter(he(w));
- D(x) && x.selectEnd();
- } else if (u.insertNodes([m]), HS(m), f) {
- const v = ef();
- v.add(m.getKey()), Ui(v);
- } else if ($(m)) {
- const v = m.getChildren().find((E) => M(E) && !P(E));
- v && M(v) ? v.select(
- v.getTextContentSize(),
- v.getTextContentSize()
+ if (P.length > 0 && S) {
+ const M = eo(S).append(...P);
+ B.insertAfter(M), S.isEmpty() && S.remove();
+ } else v(B.getNextSibling()) || B.insertAfter(pe(L));
+ F(B) && B.selectEnd();
+ } else if (u.insertNodes([m]), mM(m), f) {
+ const S = gf();
+ S.add(m.getKey()), Zn(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();
} else {
- const v = m.getNextSibling();
- v ? v.selectStart() : m.selectStart();
+ const S = m.getNextSibling();
+ S ? S.selectStart() : m.selectStart();
}
} else
u?.insertNodes([m]);
@@ -21181,37 +21400,37 @@ function Ja(e, t, r, n, i, s, o) {
}, label: a?.label } : { action: () => {
}, label: void 0 };
}
-function OS(e, t) {
+function eM(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 da(e, t) {
- return ((t ?? Fs).markers[e.getMarker()]?.occursUnder ?? []).includes("NEST") && e.getUnknownAttributes()?.closed !== "false";
+ return ((t ?? Bs).markers[e.getMarker()]?.occursUnder ?? []).includes("NEST") && e.getUnknownAttributes()?.closed !== "false";
}
-function wS(e, t) {
+function tM(e, t) {
t && e.getChildren().forEach((i) => {
- P(i) && i.setNested(!0);
- }), e.getChildren().some((i) => P(i) && i.getMarkerSyntax() === "closing") || e.append(ot(e.getMarker(), "closing", t));
+ O(i) && i.setNested(!0);
+ }), e.getChildren().some((i) => O(i) && i.getMarkerSyntax() === "closing") || e.append(ot(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 qS(e, t, r, n) {
+function rM(e, t, r, n) {
let i = t;
- if (e.anchor.type === "element" && $(r)) {
+ if (e.anchor.type === "element" && D(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() || !M(r)) {
+ } else if (e.isCollapsed() || !v(r)) {
const o = e.anchor.offset;
- if (M(r) && o > 0 && o < r.getTextContentSize()) {
+ if (v(r) && o > 0 && o < r.getTextContentSize()) {
const [a] = r.splitText(o);
a.insertAfter(t);
- } else M(r) && o >= r.getTextContentSize() ? r.insertAfter(t) : r.insertBefore(t);
+ } else v(r) && o >= r.getTextContentSize() ? r.insertAfter(t) : r.insertBefore(t);
} else {
- const [o, a] = ri(e);
+ const [o, a] = oi(e);
let c = r;
if (o > 0) {
const l = c.splitText(o);
@@ -21219,39 +21438,39 @@ function qS(e, t, r, n) {
}
c.getTextContentSize() > a - o && (c = c.splitText(a - o)[0]), i = c;
}
- if (Qn(i, { renderGlyphs: n }), i !== t) {
- i.insertBefore(t), M(i) && !i.getTextContent().startsWith(w) && i.setTextContent(w + i.getTextContent());
- const o = t.getChildren().find((a) => M(a) && !P(a));
+ if (ri(i, { renderGlyphs: n }), i !== t) {
+ i.insertBefore(t), v(i) && !i.getTextContent().startsWith(L) && i.setTextContent(L + i.getTextContent());
+ const o = t.getChildren().find((a) => v(a) && !O(a));
o ? o.replace(i) : t.append(i);
}
- const s = t.getChildren().find((o) => M(o) && !P(o));
- M(s) ? s.select(s.getTextContentSize(), s.getTextContentSize()) : t.selectEnd();
+ const s = t.getChildren().find((o) => v(o) && !O(o));
+ v(s) ? s.select(s.getTextContentSize(), s.getTextContentSize()) : t.selectEnd();
}
-function RS(e) {
+function nM(e) {
let t, r = !1;
for (const n of e.getNodes()) {
- if (P(n) || $(n)) continue;
- if (!M(n) || n.getType() !== ze.getType() || re(n, oe) === "attribute") return !1;
- const i = Rc(n);
+ if (O(n) || D(n)) continue;
+ if (!v(n) || n.getType() !== Ke.getType() || ne(n, oe) === "attribute") return !1;
+ const i = zc(n);
if (!i) return !1;
if (t === void 0) t = i;
else if (!t.is(i)) return !1;
- kn(n) && (r = !0);
+ _n(n) && (r = !0);
}
return r;
}
-function $S(e, t, r) {
- const n = Wh(e);
+function iM(e, t, r) {
+ const n = ag(e);
if (n.length === 0) return;
n.forEach((a) => {
- if (!kn(a)) return;
- Qn(a, { renderGlyphs: r });
+ if (!_n(a)) return;
+ ri(a, { renderGlyphs: r });
const c = a.getLatest(), l = c.getTextContent();
- l.startsWith(w) && c.setTextContent(l.slice(w.length));
+ l.startsWith(L) && c.setTextContent(l.slice(L.length));
});
const i = n[0].getLatest();
- i.insertBefore(t), i.getTextContent().startsWith(w) || i.setTextContent(w + i.getTextContent());
- const s = t.getChildren().find((a) => M(a) && !P(a));
+ i.insertBefore(t), i.getTextContent().startsWith(L) || i.setTextContent(L + i.getTextContent());
+ const s = t.getChildren().find((a) => v(a) && !O(a));
s ? s.replace(i) : t.append(i);
let o = i.getLatest();
n.slice(1).forEach((a) => {
@@ -21259,24 +21478,24 @@ function $S(e, t, r) {
o.insertAfter(c), o = c;
}), o.select(o.getTextContentSize(), o.getTextContentSize());
}
-function IS(e, t) {
- let r = Gh[e];
+function sM(e, t) {
+ let r = lg[e];
return r || (Qe.isValidMarker(e, t) ? r = {
action: () => ({ content: [{ type: Qe.getType(), marker: e, content: [] }] })
- } : ye.isValidMarker(e, t) && (r = {
+ } : me.isValidMarker(e, t) && (r = {
action: () => {
- const n = { type: ye.getType(), marker: e };
- return (ye.isValidFootnoteMarker(e) || ye.isValidCrossReferenceMarker(e)) && (n.closed = "false"), { content: [n] };
+ const n = { type: me.getType(), marker: e };
+ return (me.isValidFootnoteMarker(e) || me.isValidCrossReferenceMarker(e)) && (n.closed = "false"), { content: [n] };
}
})), r;
}
-function LS(e, t) {
- const r = e.getNodes(), [n, i] = ri(e);
+function oM(e, t) {
+ const r = e.getNodes(), [n, i] = oi(e);
let s;
r.forEach((o, a) => {
- if (D(s) && s.isParentOf(o))
+ if (F(s) && s.isParentOf(o))
return;
- const c = Yh(
+ const c = dg(
o,
a === 0,
a === r.length - 1,
@@ -21288,230 +21507,230 @@ function LS(e, t) {
return;
}
let l = !1;
- s || (s = t(), c.insertBefore(s), l = !0, $(s) && s.getChildren().some((d) => P(d) && d.getMarkerSyntax() === "opening") && wS(s, $(s.getParent()))), US(c, s, l);
- }), (M(s) || D(s)) && s.selectEnd();
+ s || (s = t(), c.insertBefore(s), l = !0, D(s) && s.getChildren().some((d) => O(d) && d.getMarkerSyntax() === "opening") && tM(s, D(s.getParent()))), cM(c, s, l);
+ }), (v(s) || F(s)) && s.selectEnd();
}
-function ri(e) {
+function oi(e) {
const t = e.anchor.offset, r = e.focus.offset;
return e.isBackward() ? [r, t] : [t, r];
}
-function ol(e) {
- return _e(e) || j(e) || j(e.getParent());
+function ml(e) {
+ return Ce(e) || j(e) || j(e.getParent());
}
-function Yh(e, t, r, n, i) {
- if (!ol(e)) {
- if (M(e))
- return DS(e, t, r, n, i);
- if (D(e) && e.isInline())
+function dg(e, t, r, n, i) {
+ if (!ml(e)) {
+ if (v(e))
+ return aM(e, t, r, n, i);
+ if (F(e) && e.isInline())
return e;
}
}
-function DS(e, t, r, n, i) {
+function aM(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 US(e, t, r) {
- if (M(t)) {
- const n = Ya(e, t);
+function cM(e, t, r) {
+ if (v(t)) {
+ const n = Xa(e, t);
t.setTextContent(n), e.remove();
- } else if (D(t)) {
+ } else if (F(t)) {
const n = t.getChildren(), i = n.find(
- (s) => P(s) && s.getMarkerSyntax() !== "opening"
+ (s) => O(s) && s.getMarkerSyntax() !== "opening"
);
if (i)
- i.insertBefore(e), r && n.filter((s) => !P(s)).forEach((s) => s.remove());
+ i.insertBefore(e), r && n.filter((s) => !O(s)).forEach((s) => s.remove());
else if (r) {
const s = t.getChildrenSize();
t.append(e);
for (let o = 0; o < s; o++) t.getFirstChild()?.remove();
} else
t.append(e);
- Ya(e, t), r && $(t) && t.getChildren().some((s) => P(s)) && M(e) && !P(e) && !e.getTextContent().startsWith(w) && e.setTextContent(w + e.getTextContent());
+ Xa(e, t), r && D(t) && t.getChildren().some((s) => O(s)) && v(e) && !O(e) && !e.getTextContent().startsWith(L) && e.setTextContent(L + e.getTextContent());
}
}
-function Ya(e, t) {
+function Xa(e, t) {
let r = e.getTextContent();
- if (M(e) && t.isInline() && r.startsWith(" ") && r.trimStart() !== "") {
+ if (v(e) && t.isInline() && r.startsWith(" ") && r.trimStart() !== "") {
r = r.trimStart(), e.setTextContent(r);
const n = t.getPreviousSibling();
- Uc(n), M(n) || t.insertBefore(he(" "));
+ Wc(n), v(n) || t.insertBefore(pe(" "));
}
return r;
}
-function Xh(e, t, r) {
+function fg(e, t, r) {
if (e.isCollapsed()) {
- const u = e.anchor.getNode(), d = e.anchor.offset, f = _n(u, t);
+ const u = e.anchor.getNode(), d = e.anchor.offset, f = Sn(u, t);
if (!f) return !1;
- const p = M(u) ? u.getTextContentSize() : 0;
- if (md(f, r), M(u) && u.isAttached()) {
- const m = u.getTextContentSize(), g = Math.max(p - m, 0), y = Math.max(0, Math.min(d - g, m)), T = R();
- N(T) && T.setTextNodeRange(u, y, u, y);
+ const p = v(u) ? u.getTextContentSize() : 0;
+ if (qd(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 = R();
+ N(k) && k.setTextNodeRange(u, y, u, y);
}
return !0;
}
- const n = e.getNodes(), i = e.isBackward(), [s, o] = ri(e);
- if (!cl(n, t, s, o)) return !1;
- const a = al(n, s, o);
+ const n = e.getNodes(), i = e.isBackward(), [s, o] = oi(e);
+ if (!bl(n, t, s, o)) return !1;
+ const a = yl(n, s, o);
if (a.length === 0) return !1;
const c = /* @__PURE__ */ new Set();
let l = !1;
return a.forEach((u) => {
- const d = _n(u, t);
+ const d = Sn(u, t);
if (!d || c.has(d.getKey())) return;
c.add(d.getKey());
- const f = tg(d, a);
- f && (md(f, r), l = !0);
- }), rg(a, i), l;
+ const f = mg(d, a);
+ f && (qd(f, r), l = !0);
+ }), yg(a, i), l;
}
-function md(e, t) {
+function qd(e, t) {
e.getChildren().forEach((n) => {
- Dt(n) && n.remove();
+ Ft(n) && n.remove();
});
const r = e.getChildren();
- if (r.length === 0 || e.getTextContent() === It) {
+ if (r.length === 0 || e.getTextContent() === Dt) {
e.remove();
return;
}
t?.markerMode === "editable" && r.forEach((n) => {
const i = n.getTextContent();
- M(n) && i.startsWith(w) && n.setTextContent(i.slice(w.length));
+ v(n) && i.startsWith(L) && n.setTextContent(i.slice(L.length));
}), ba(e);
}
-function al(e, t, r) {
+function yl(e, t, r) {
const n = [];
return e.forEach((i, s) => {
- const o = Yh(
+ const o = dg(
i,
s === 0,
s === e.length - 1,
t,
r
);
- M(o) && n.push(o);
+ v(o) && n.push(o);
}), n;
}
-function _n(e, t) {
+function Sn(e, t) {
let r = e, n;
- for (; r && !Ce(r); ) {
+ for (; r && !Se(r); ) {
if (j(r)) return;
- !n && $(r) && (t === void 0 || r.getMarker() === t) && (n = r), r = r.getParent();
+ !n && D(r) && (t === void 0 || r.getMarker() === t) && (n = r), r = r.getParent();
}
return n;
}
-function Qh(e) {
+function pg(e) {
const t = nt(
e,
- (r) => j(r) || Ce(r)
+ (r) => j(r) || Se(r)
);
return j(t);
}
-function Zh(e) {
+function hg(e) {
return e.filter(
- (t) => !ol(t) && (M(t) || D(t) && t.isInline())
+ (t) => !ml(t) && (v(t) || F(t) && t.isInline())
);
}
-function FS(e, t, r) {
+function lM(e, t, r) {
const n = /* @__PURE__ */ new Set();
return e.forEach((i, s) => {
- if (!M(i) || ol(i)) return;
+ if (!v(i) || ml(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 zS(e, t, r) {
+function uM(e, t, r) {
return e.getChildren().some(
- (n) => D(n) && t.some((i) => n.isParentOf(i)) && !eg(n, r)
+ (n) => F(n) && t.some((i) => n.isParentOf(i)) && !gg(n, r)
);
}
-function cl(e, t, r, n, i) {
- const s = Zh(e), o = FS(e, r, n), a = /* @__PURE__ */ new Set();
+function bl(e, t, r, n, i) {
+ const s = hg(e), o = lM(e, r, n), a = /* @__PURE__ */ new Set();
return s.some((c) => {
- const l = _n(c, t);
- return !l || a.has(l.getKey()) || (a.add(l.getKey()), l.getMarker() === i) ? !1 : !zS(l, s, o);
+ const l = Sn(c, t);
+ return !l || a.has(l.getKey()) || (a.add(l.getKey()), l.getMarker() === i) ? !1 : !uM(l, s, o);
});
}
-function eg(e, t) {
- return e.getAllTextNodes().every((r) => t.has(r.getKey()) || Dt(r));
+function gg(e, t) {
+ return e.getAllTextNodes().every((r) => t.has(r.getKey()) || Ft(r));
}
-function tg(e, t) {
+function mg(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()))
i.push(l);
- else if (D(u) && t.some((d) => u.isParentOf(d))) {
- if (!eg(u, r)) return;
+ else if (F(u) && t.some((d) => u.isParentOf(d))) {
+ if (!gg(u, r)) return;
i.push(l);
}
if (i.length === 0) return;
let s = i[0], o = i[i.length - 1];
- if (s > 0 && Dt(n[s - 1]) && (s -= 1), o < n.length - 1 && Dt(n[o + 1]) && (o += 1), s === 0 && o === n.length - 1) return e;
+ 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;
const a = n.slice(o + 1);
- a.length > 0 && e.insertAfter(Ys(e).append(...a));
+ a.length > 0 && e.insertAfter(eo(e).append(...a));
const c = n.slice(0, s);
- return c.length > 0 && e.insertBefore(Ys(e).append(...c)), e;
+ return c.length > 0 && e.insertBefore(eo(e).append(...c)), e;
}
-function Ys(e) {
- return ey(e);
+function eo(e) {
+ return _y(e);
}
-function rg(e, t) {
+function yg(e, t) {
const r = R(), n = e[0], i = e[e.length - 1];
if (!N(r) || !n.isAttached() || !i.isAttached())
return;
const s = i.getTextContentSize();
t ? r.setTextNodeRange(i, s, n, 0) : r.setTextNodeRange(n, 0, i, s);
}
-function KS(e, t, r) {
+function dM(e, t, r) {
if (e.isCollapsed()) {
- const l = _n(e.anchor.getNode(), r);
- return !l || l.getMarker() === t ? !1 : (ru(l, t), !0);
+ const l = Sn(e.anchor.getNode(), r);
+ return !l || l.getMarker() === t ? !1 : (ku(l, t), !0);
}
- const n = e.getNodes(), [i, s] = ri(e);
- if (!cl(n, r, i, s, t)) return !1;
- const o = al(n, i, s);
+ const n = e.getNodes(), [i, s] = oi(e);
+ if (!bl(n, r, i, s, t)) return !1;
+ const o = yl(n, i, s);
if (o.length === 0) return !1;
const a = /* @__PURE__ */ new Set();
let c = !1;
return o.forEach((l) => {
- const u = _n(l, r);
+ const u = Sn(l, r);
if (!u || a.has(u.getKey()) || (a.add(u.getKey()), u.getMarker() === t)) return;
- const d = tg(u, o);
- d && (ru(d, t), c = !0);
+ const d = mg(u, o);
+ d && (ku(d, t), c = !0);
}), c;
}
-function jS(e, t, r, n) {
+function fM(e, t, r, n) {
if (e.isCollapsed()) return !1;
const i = r?.filter(
(y) => y !== t
- ), s = e.getNodes(), [o, a] = ri(e);
+ ), s = e.getNodes(), [o, a] = oi(e);
if (!!!i?.some(
- (y) => cl(s, y, o, a)
- ) && !BS(s, t)) return !1;
+ (y) => bl(s, y, o, a)
+ ) && !pM(s, t)) return !1;
let l = !1;
i?.forEach((y) => {
- const T = R();
- N(T) && Xh(T, y, n) && (l = !0);
+ const k = R();
+ N(k) && fg(k, y, n) && (l = !0);
});
const u = R();
if (!N(u)) return l;
- const d = u.isBackward(), [f, p] = ri(u), m = al(
+ const d = u.isBackward(), [f, p] = oi(u), m = yl(
u.getNodes(),
f,
p
);
if (m.length === 0) return l;
const g = m.filter(
- (y) => !Qh(y) && !_n(y, t)
+ (y) => !pg(y) && !Sn(y, t)
);
- return g.length > 0 && (VS(g).forEach((y) => WS(y, t)), l = !0), rg(m, d), l;
+ return g.length > 0 && (hM(g).forEach((y) => gM(y, t)), l = !0), yg(m, d), l;
}
-function BS(e, t) {
- return Zh(e).some(
- (r) => !Qh(r) && !_n(r, t)
+function pM(e, t) {
+ return hg(e).some(
+ (r) => !pg(r) && !Sn(r, t)
);
}
-function VS(e) {
+function hM(e) {
const t = [];
let r;
return e.forEach((n) => {
@@ -21519,16 +21738,16 @@ function VS(e) {
r && i?.getNextSibling()?.is(n) ? r.push(n) : (r = [n], t.push(r));
}), t;
}
-function WS(e, t) {
+function gM(e, t) {
const r = e[0].getPreviousSibling(), n = e[e.length - 1].getNextSibling(), i = [r, n].find(
- (a) => $(a) && a.getMarker() === t
- ), s = i ? Ys(i) : yr(t);
- e[0].insertBefore(s), s.append(...e), i === r || Ya(e[0], s);
+ (a) => D(a) && a.getMarker() === t
+ ), s = i ? eo(i) : xr(t);
+ e[0].insertBefore(s), s.append(...e), i === r || Xa(e[0], s);
}
-function HS(e) {
- me(e) && (Uc(e.getPreviousSibling()), Wp(e.getNextSibling()));
+function mM(e) {
+ ge(e) && (Wc(e.getPreviousSibling()), ah(e.getNextSibling()));
}
-const ng = {
+const bg = {
chapter: "chapter",
verse: "verse",
char: "char",
@@ -21543,13 +21762,13 @@ const ng = {
strikethrough: "editor-text-strikethrough",
underlineStrikethrough: "editor-text-underlineStrikethrough"
}
-}, yd = "psc-active-text", xs = "psc-empty-text";
-function GS({ viewOptions: e }) {
- const [t] = le(), r = X(void 0), n = e?.hasActiveTextFocusBox ?? !1;
- return K(() => {
+}, Rd = "psc-active-text", Ms = "psc-empty-text";
+function yM({ viewOptions: e }) {
+ const [t] = ce(), r = Z(void 0), n = e?.hasActiveTextFocusBox ?? !1;
+ return z(() => {
if (!n) return;
function i(o) {
- r.current && t.getElementByKey(r.current)?.classList.remove(yd), r.current = o, o && t.getElementByKey(o)?.classList.add(yd);
+ r.current && t.getElementByKey(r.current)?.classList.remove(Rd), r.current = o, o && t.getElementByKey(o)?.classList.add(Rd);
}
const s = [
// Clicking the ellipsis placeholder (rendered as ::after on the empty verse span) hits the
@@ -21559,59 +21778,59 @@ function GS({ 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(
- io,
+ ao,
(o) => {
const a = o.target;
if (!(a instanceof Element)) return !1;
- const c = a.closest(`.${xs}`);
+ const c = a.closest(`.${Ms}`);
if (!c) return !1;
- const l = os(c);
- if (!me(l)) return !1;
+ const l = ci(c);
+ if (!ge(l)) return !1;
const u = l.getParent();
- if (!D(u)) return !1;
+ if (!F(u)) return !1;
const d = l.getIndexWithinParent() + 1;
return u.select(d, d), !1;
},
- Rt
+ kt
),
t.registerUpdateListener(({ editorState: o }) => {
const { newActiveKey: a, activeVerseKey: c, emptyKeys: l, nonEmptyKeys: u } = o.read(() => {
- const d = fa(), f = JS(), p = [], m = [];
+ const d = fa(), f = bM(), p = [], m = [];
return Ue().getChildren().forEach((g) => {
- if (!D(g)) return;
- const { emptyKeys: y, nonEmptyKeys: T } = XS(g);
- p.push(...y), m.push(...T);
+ if (!F(g)) return;
+ const { emptyKeys: y, nonEmptyKeys: k } = TM(g);
+ p.push(...y), m.push(...k);
}), { newActiveKey: d, activeVerseKey: f, emptyKeys: p, nonEmptyKeys: m };
});
a !== r.current && i(a), l.forEach((d) => {
- d === c ? t.getElementByKey(d)?.classList.remove(xs) : t.getElementByKey(d)?.classList.add(xs);
- }), u.forEach((d) => t.getElementByKey(d)?.classList.remove(xs));
+ d === c ? t.getElementByKey(d)?.classList.remove(Ms) : t.getElementByKey(d)?.classList.add(Ms);
+ }), u.forEach((d) => t.getElementByKey(d)?.classList.remove(Ms));
}),
t.registerCommand(
- pc,
+ kc,
() => (i(void 0), !1),
- Rt
+ kt
),
t.registerCommand(
- ty,
+ Cy,
() => {
const o = t.getEditorState().read(fa);
return o !== r.current && i(o), !1;
},
- Rt
+ kt
)
];
- return i(t.getEditorState().read(fa)), Xe(...s);
+ return i(t.getEditorState().read(fa)), He(...s);
}, [t, n]), null;
}
function fa() {
- return YS(R() ?? void 0)?.getKey();
+ return kM(R() ?? void 0)?.getKey();
}
-function JS() {
+function bM() {
const e = R();
if (!N(e)) return;
const t = e.anchor, r = t.getNode(), n = r.getTopLevelElement();
- if (!D(n)) return;
+ if (!F(n)) return;
let i;
if (r.is(n))
i = t.offset;
@@ -21625,23 +21844,23 @@ function JS() {
const s = n.getChildren();
let o;
for (let a = 0; a < i && a < s.length; a++)
- me(s[a]) && (o = s[a].getKey());
+ ge(s[a]) && (o = s[a].getKey());
return o;
}
-function YS(e) {
+function kM(e) {
if (N(e))
return e.anchor.getNode().getTopLevelElement() ?? void 0;
}
-function XS(e) {
+function TM(e) {
const t = e.getChildren(), r = [], n = [];
for (let i = 0; i < t.length; i++) {
const s = t[i];
- if (!me(s)) continue;
+ if (!ge(s)) continue;
let o = !1;
for (let a = i + 1; a < t.length; a++) {
const c = t[a];
- if (me(c)) break;
- if (!(Wt(c) || P(c)) && c.getTextContent().replaceAll(Ns, "").trim() !== "") {
+ if (ge(c)) break;
+ if (!(Jt(c) || O(c)) && c.getTextContent().replaceAll($s, "").trim() !== "") {
o = !0;
break;
}
@@ -21650,58 +21869,58 @@ function XS(e) {
}
return { emptyKeys: r, nonEmptyKeys: n };
}
-const QS = /^\+/;
-function ll(e, t) {
- const r = t.replace(QS, "");
+const xM = /^\+/;
+function kl(e, t) {
+ const r = t.replace(xM, "");
return Object.hasOwn(e.markers, r) ? e.markers[r] : void 0;
}
-function ig(e, t) {
+function kg(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 ig(e, t) !== void 0;
+function Tg(e, t) {
+ return kg(e, t) !== void 0;
}
-function Xa(e, t) {
- const r = ig(e, t);
+function Qa(e, t) {
+ const r = kg(e, t);
return r ? (r.joins && (e.length = r.keep, e.push(t)), !0) : !1;
}
-function Xs(e, t, r) {
- const n = D(e) ? e.getChildren().filter(P) : [];
+function to(e, t, r) {
+ const n = F(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 ZS(e, t, r, n, i) {
- const s = ll(n, t);
+function _M(e, t, r, n, i) {
+ const s = kl(n, t);
if (!s) {
- Xs(e, "unknown", i);
+ to(e, "unknown", i);
return;
}
const o = s.occursUnder ?? [];
- o.length > 0 && !o.includes(r) && Xs(e, "invalid", i);
+ o.length > 0 && !o.includes(r) && to(e, "invalid", i);
}
-function Ii(e, t, r, n, i) {
+function zi(e, t, r, n, i) {
for (const s of e.getChildren())
- if ($(s)) {
+ if (D(s)) {
const o = s.getMarker();
- i || ZS(s, o, t, r, n), Ii(s, t, r, n, i || o === "xq");
- } else if (me(s)) {
+ i || _M(s, o, t, r, n), zi(s, t, r, n, i || o === "xq");
+ } else if (ge(s)) {
if (i) continue;
- const o = ll(r, "v");
+ const o = kl(r, "v");
o ? (o.occursUnder ?? []).length > 0 && !(o.occursUnder ?? []).includes(t) && n.set(s.getKey(), "invalid") : n.set(s.getKey(), "unknown");
- } else j(s) ? Ii(s, s.getMarker(), r, n, i) : Le(s) || D(s) && Ii(s, t, r, n, i);
+ } else j(s) ? zi(s, s.getMarker(), r, n, i) : Le(s) || F(s) && zi(s, t, r, n, i);
}
-function eM(e, t) {
+function CM(e, t) {
const r = /* @__PURE__ */ new Map(), n = [], i = (o, a) => {
- const c = ll(e, a);
+ const c = kl(e, a);
if (!c) {
- Xs(o, "unknown", r), Xa(n, { marker: a, rank: 0, occursUnder: [] });
+ to(o, "unknown", r), Qa(n, { marker: a, rank: 0, occursUnder: [] });
return;
}
const l = {
@@ -21709,72 +21928,72 @@ function eM(e, t) {
rank: c.rank ?? 0,
occursUnder: c.occursUnder ?? []
};
- Xa(n, l) || Xs(o, "invalid", r);
+ Qa(n, l) || to(o, "invalid", r);
}, s = (o) => !t || t.has(o.getKey());
for (const o of Ue().getChildren())
- Le(o) || (Tt(o) || We(o) ? i(o, o.getMarker()) : se(o) ? (i(o, o.getMarker()), s(o) && Ii(o, o.getMarker(), e, r, !1)) : D(o) && s(o) && Ii(o, "p", e, r, !1));
+ Le(o) || (_t(o) || We(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 tM(e) {
+function SM(e) {
return !!e?.includes("(basic)");
}
-function rM(e) {
+function vM(e) {
if (e !== void 0)
return e.replace(/\s*\(basic\)/g, "").trim();
}
-function og(e, t) {
- return !e.startsWith("zpa") && e !== "c" && Ga(e, t);
+function xg(e, t) {
+ return !e.startsWith("zpa") && e !== "c" && Ja(e, t);
}
-function ul(e, t) {
+function Tl(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 ag(e, t) {
+function _g(e, t) {
const r = [];
for (const n of t) {
- const i = ul(e, n);
- i && Xa(r, i);
+ const i = Tl(e, n);
+ i && Qa(r, i);
}
return r;
}
-function Ms(e, t) {
+function Os(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: tM(e.description)
+ description: vM(e.description),
+ isBasic: SM(e.description)
};
}
-function nM(e, t) {
+function MM(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 Qa(e, t) {
- return e.isBasic !== t.isBasic ? e.isBasic ? -1 : 1 : nM(e.marker, t.marker);
+function Za(e, t) {
+ return e.isBasic !== t.isBasic ? e.isBasic ? -1 : 1 : MM(e.marker, t.marker);
}
-function Za(e, t, r) {
+function ec(e, t, r) {
if (t.noteMarker) return [];
- const n = ag(e, t.previousParaMarkers);
+ const n = _g(e, t.previousParaMarkers);
return Object.values(e.markers).filter(
- (i) => i.styleType === "paragraph" && og(i.marker, r)
+ (i) => i.styleType === "paragraph" && xg(i.marker, r)
).filter((i) => {
- const s = ul(e, i.marker);
- return s !== void 0 && sg(n, s);
- }).map((i) => Ms(i, "paragraph")).sort(Qa);
+ const s = Tl(e, i.marker);
+ return s !== void 0 && Tg(n, s);
+ }).map((i) => Os(i, "paragraph")).sort(Za);
}
-function iM(e, t, r) {
+function EM(e, t, r) {
const { noteMarker: n, paraMarker: i } = t, s = Object.values(e.markers).filter(
- (c) => og(c.marker, r)
+ (c) => xg(c.marker, r)
);
if (n)
return s.filter(
(c) => c.styleType === "character" && (c.occursUnder ?? []).includes(n)
- ).map((c) => Ms(c, "character")).sort(Qa);
+ ).map((c) => Os(c, "character")).sort(Za);
if (!i) return [];
const o = s.filter((c) => {
if (c.styleType !== "character") return !1;
@@ -21782,63 +22001,63 @@ function iM(e, t, r) {
return l.length === 0 || l.includes(i);
}), a = s.filter((c) => c.styleType === "note");
return [
- ...o.map((c) => Ms(c, "character")),
- ...a.map((c) => Ms(c, "note"))
- ].sort(Qa);
+ ...o.map((c) => Os(c, "character")),
+ ...a.map((c) => Os(c, "note"))
+ ].sort(Za);
}
-function sM(e, t) {
+function AM(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 oM(e, t) {
+function PM(e, t) {
return e.isBasic === t.isBasic ? 0 : e.isBasic ? -1 : 1;
}
-function aM(e, t, r) {
+function NM(e, t, r) {
return [
- ...sM(e, t.openCharMarkers),
- ...iM(e, t, r)
- ].sort(oM);
+ ...AM(e, t.openCharMarkers),
+ ...EM(e, t, r)
+ ].sort(PM);
}
-function cM(e, t, r) {
- if (t.source === "paragraph") return Za(e, t, r);
- const n = aM(e, t, r);
- return n.length > 0 ? n : Za(e, t, r);
+function OM(e, t, r) {
+ if (t.source === "paragraph") return ec(e, t, r);
+ const n = NM(e, t, r);
+ return n.length > 0 ? n : ec(e, t, r);
}
-function lM(e, t, r) {
- const n = Za(e, t, r), i = ag(e, t.previousParaMarkers), s = ul(e, "ip"), a = !t.previousParaMarkers.includes("c") && s && sg(i, s) ? "ip" : "p", c = n.findIndex((u) => u.marker === a);
+function wM(e, t, r) {
+ const n = ec(e, t, r), i = _g(e, t.previousParaMarkers), s = Tl(e, "ip"), a = !t.previousParaMarkers.includes("c") && s && Tg(i, s) ? "ip" : "p", c = n.findIndex((u) => u.marker === a);
if (c <= 0) return n;
const [l] = n.splice(c, 1);
return [l, ...n];
}
-const Nn = String.raw`\w-`, cg = "a-z0-9", uM = `[a-z][${cg}]*`, dM = new RegExp(
- String.raw`^\\(\+?[${Nn}]+)[ \u00A0]$`
-), lg = new RegExp(String.raw`^\\(\+?[${Nn}]+)$`), fM = new RegExp(String.raw`^\\\+?[${Nn}]*\*$`), pM = new RegExp(
- String.raw`^\\(\+?[${Nn}]+)(?:[ \u00A0]|$)`
-), hM = new RegExp(
- String.raw`^\\(\+?)([${Nn}]+)`
-), gM = new RegExp(
- String.raw`\\\+?[${Nn}]+(?:\\?\*|[ \u00A0])`
-), mM = new RegExp(
- String.raw`\\\+?[${Nn}]*$`
-), yM = new RegExp(
- String.raw`^\\(${uM})( |$)`
-), bM = new RegExp(
- String.raw`\\[${cg}+*]*$`,
+const Zt = String.raw`\w-`, Cg = "a-z0-9", qM = `[a-z][${Cg}]*`, RM = new RegExp(
+ String.raw`^\\(\+?[${Zt}]+)[ \u00A0]$`
+), Sg = new RegExp(String.raw`^\\(\+?[${Zt}]+)$`), $M = new RegExp(String.raw`^\\\+?[${Zt}]*\*$`), IM = new RegExp(
+ String.raw`^\\(\+?[${Zt}]+)(?:[ \u00A0]|$)`
+), LM = new RegExp(
+ String.raw`^\\(\+?)([${Zt}]+)`
+), DM = new RegExp(
+ String.raw`\\\+?[${Zt}]+(?:\\?\*|[ \u00A0])`
+), UM = new RegExp(
+ String.raw`\\\+?[${Zt}]*$`
+), FM = new RegExp(
+ String.raw`^\\(${qM})( |$)`
+), zM = new RegExp(
+ String.raw`\\[${Cg}+*]*$`,
"i"
), tt = "";
-function ug(e) {
- return e.length > 1 && e.startsWith(w) && e.charAt(1) !== tt ? e.slice(1) : e;
+function vg(e) {
+ return e.length > 1 && e.startsWith(L) && e.charAt(1) !== tt ? e.slice(1) : e;
}
-function bd(e) {
- return Ac(e) ? e.markerSyntax ?? "opening" : void 0;
+function $d(e) {
+ return $c(e) ? e.markerSyntax ?? "opening" : void 0;
}
-function dg(e, t, r, n) {
- const i = { ...n, noteMode: "expanded" }, s = Br.serializeEditorState(
+function Mg(e, t, r, n) {
+ const i = { ...n, noteMode: "expanded" }, s = Jr.serializeEditorState(
{
- type: hr,
- version: pr,
+ type: kr,
+ version: br,
content: [
{
...e.getUnknownAttributes(),
@@ -21854,16 +22073,16 @@ function dg(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 (; bd(a[c]) === "opening"; ) c++;
- if (a[c]?.text !== St(e.getCaller())) return { failure: "caller" };
+ for (; $d(a[c]) === "opening"; ) c++;
+ if (a[c]?.text !== At(e.getCaller())) return { failure: "caller" };
c++;
let u = a.length;
- for (; u > c && bd(a[u - 1]) === "closing"; )
+ for (; u > c && $d(a[u - 1]) === "closing"; )
u--;
const d = a.slice(c, u);
return d.length === 0 ? { failure: "empty" } : { children: d };
}
-function _s(e, t, r) {
+function Es(e, t, r) {
e.spans.push({
key: t.getKey(),
start: e.text.length,
@@ -21871,46 +22090,46 @@ function _s(e, t, r) {
isSentinel: !1
}), e.text += r;
}
-function Mi(e, t) {
- mM.test(e.text) && (e.text += " "), e.spans.push({
+function Ni(e, t) {
+ UM.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;
}
-function qt(e) {
- return e.replaceAll(w, " ");
+function It(e) {
+ return e.replaceAll(L, " ");
}
-function kM(e, t, r = !1) {
- if (Co(t)) return qt(e);
- if (e === w) return " ";
- const n = r && e.startsWith(w), i = n ? e.slice(1) : e;
- return (n ? " " : "") + i.replaceAll(w, "~");
+function KM(e, t, r = !1) {
+ if (So(t)) return It(e);
+ if (e === L) return " ";
+ const n = r && e.startsWith(L), i = n ? e.slice(1) : e;
+ return (n ? " " : "") + i.replaceAll(L, "~");
}
-function Li(e) {
+function Ki(e) {
const t = e.getTextContent();
- return En(e) && /^[\s\u00A0]*$/.test(t) ? " " : t;
+ return Pn(e) && /^[\s\u00A0]*$/.test(t) ? " " : t;
}
-function dl(e, t) {
+function xl(e, t) {
const r = e[t];
- if (!je(r)) return [];
- const { attribute: n, closing: i, wrapper: s } = go(r);
+ if (!Be(r)) return [];
+ const { attribute: n, closing: i, wrapper: s } = mo(r);
if (s) return [s];
const o = r.getNextSibling();
- if (!P(o) || o.getMarkerSyntax() !== "opening" || o.getMarker() !== r.getMarker())
+ if (!O(o) || o.getMarkerSyntax() !== "opening" || o.getMarker() !== r.getMarker())
return [];
const a = [o];
return n && a.push(n), i && a.push(i), a;
}
-function fg(e) {
+function Eg(e) {
return e.some((t) => t.getTextContent().length > 0);
}
-function fl(e, t) {
+function _l(e, t) {
const r = [];
let n = e[t];
for (const i of ["va", "vp"]) {
- const { opener: s, value: o, closer: a, wrapper: c } = Bi(n, i);
+ const { opener: s, value: o, closer: a, wrapper: c } = Yi(n, i);
if (c)
r.push(c), n = c;
else if (s && o && a)
@@ -21920,32 +22139,32 @@ function fl(e, t) {
}
return r;
}
-function pl(e) {
+function Cl(e) {
return !!e.getUnknownAttributes();
}
-function Mo(e, t) {
+function Eo(e, t) {
const r = t(e)?.type;
- return r === b.Milestone || r === void 0 && oo(e);
+ return r === b.Milestone || r === void 0 && vc(e);
}
-function pg(e, t) {
- return je(e) ? !Mo(e.getMarker(), t) : j(e) || Le(e) ? !0 : Ne(e) ? pl(e) : $(e) ? hg(e, t) : !1;
+function Ag(e, t) {
+ return Be(e) ? !Eo(e.getMarker(), t) : j(e) || Le(e) ? !0 : Pe(e) ? Cl(e) : D(e) ? Pg(e, t) : !1;
}
-function hg(e, t) {
- if (ak(e)) return !0;
+function Pg(e, t) {
+ if (Mk(e)) return !0;
const r = e.getMarker();
- return !Jy(r) && t(r) === void 0;
+ return !hb(r) && t(r) === void 0;
}
-const Ot = "", wt = "";
-function kd(e) {
- return e.flatMap((t) => Be(t) ? t.getChildren() : [t]);
+const Rt = "", $t = "";
+function Id(e) {
+ return e.flatMap((t) => ze(t) ? t.getChildren() : [t]);
}
-function Oi(e, t, r, n = !1) {
+function $i(e, t, r, n = !1) {
for (let i = 0; i < e.length; i++) {
const s = e[i];
- if (je(s)) {
- const o = dl(e, i);
- Mo(s.getMarker(), r) && fg(o) ? (t.push(
- Ot,
+ if (Be(s)) {
+ const o = xl(e, i);
+ Eo(s.getMarker(), r) && Eg(o) ? (t.push(
+ Rt,
"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
@@ -21969,60 +22188,60 @@ function Oi(e, t, r, n = !1) {
unknownAttributes: s.getUnknownAttributes() ?? null,
attributeOrder: s.getAttributeOrder() ?? null
})
- ), Oi(kd(o), t, r), t.push(wt)) : t.push(tt), i += o.length;
- } else if (Ne(s)) {
- const o = fl(e, i);
- pl(s) ? t.push(tt) : (t.push(
- Ot,
+ ), $i(Id(o), t, r), t.push($t)) : t.push(tt), i += o.length;
+ } else if (Pe(s)) {
+ const o = _l(e, i);
+ Cl(s) ? t.push(tt) : (t.push(
+ Rt,
"verse",
- qt(s.getTextContent()),
+ It(s.getTextContent()),
JSON.stringify({
number: s.getNumber(),
altnumber: s.getAltnumber() ?? null,
pubnumber: s.getPubnumber() ?? null
})
- ), Oi(kd(o), t, r), t.push(wt)), i += o.length;
- } else P(s) ? t.push(Ot, "marker", qt(s.getTextContent()), wt) : Hr(s) ? t.push(Ot, "unmatched", qt(s.getTextContent()), wt) : pg(s, r) ? t.push(tt) : no(s) ? t.push(" ") : M(s) ? t.push(
- qt(
- n ? ug(Li(s)) : Li(s)
+ ), $i(Id(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) : Ag(s, r) ? t.push(tt) : us(s) ? t.push(" ") : v(s) ? t.push(
+ It(
+ n ? vg(Ki(s)) : Ki(s)
)
- ) : $(s) ? (t.push(Ot, "char", JSON.stringify(s.getUnknownAttributes() ?? null)), Oi(s.getChildren(), t, r, !0), t.push(wt)) : D(s) ? (t.push(Ot, s.getType()), Oi(s.getChildren(), t, r), t.push(wt)) : t.push(tt);
+ ) : D(s) ? (t.push(Rt, "char", JSON.stringify(s.getUnknownAttributes() ?? null)), $i(s.getChildren(), t, r, !0), t.push($t)) : F(s) ? (t.push(Rt, s.getType()), $i(s.getChildren(), t, r), t.push($t)) : t.push(tt);
}
}
-function fi(e, t) {
+function yi(e, t) {
const r = [];
- return Oi(e, r, t), r.join("");
+ return $i(e, r, t), r.join("");
}
-function kr(e) {
+function Cr(e) {
const { children: t } = e;
return Array.isArray(t) ? t : void 0;
}
-function ni(e) {
+function ai(e) {
const { text: t } = e;
return typeof t == "string" ? t : void 0;
}
-function hl(e) {
+function Sl(e) {
return e.type ?? "";
}
-function gg(e, t, r) {
- return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : Oe(e, r);
+function Ng(e, t, r) {
+ return t === "closing" ? rt(e, r) : t === "selfClosing" ? rt("") : Ne(e, r);
}
function pa(e, t) {
const r = e[t];
- if (!(!r || hl(r) !== "attribute-run"))
- return kr(r) ?? [];
+ if (!(!r || Sl(r) !== "attribute-run"))
+ return Cr(r) ?? [];
}
-function pi(e, t) {
+function bi(e, t) {
const r = [];
- return wi(e, r, t), r.join("");
+ return Ii(e, r, t), r.join("");
}
-function wi(e, t, r, n = !1) {
+function Ii(e, t, r, n = !1) {
for (let i = 0; i < e.length; i++) {
- const s = e[i], o = hl(s);
+ const s = e[i], o = Sl(s);
if (o === "ms") {
const l = s, u = pa(e, i + 1);
- u && Mo(l.marker ?? "", r) ? (t.push(
- Ot,
+ u && Eo(l.marker ?? "", r) ? (t.push(
+ Rt,
"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
@@ -22036,7 +22255,7 @@ function wi(e, t, r, n = !1) {
unknownAttributes: l.unknownAttributes ?? null,
attributeOrder: l.attributeOrder ?? null
})
- ), wi(u, t, r), t.push(wt), i += 1) : t.push(tt);
+ ), Ii(u, t, r), t.push($t), i += 1) : t.push(tt);
continue;
}
if (o === "verse") {
@@ -22046,9 +22265,9 @@ function wi(e, t, r, n = !1) {
continue;
}
t.push(
- Ot,
+ Rt,
"verse",
- qt(l.text ?? ""),
+ It(l.text ?? ""),
JSON.stringify({
number: l.number ?? null,
altnumber: l.altnumber ?? null,
@@ -22057,19 +22276,19 @@ function wi(e, t, r, n = !1) {
);
let u = 0, d = pa(e, i + 1 + u);
for (; d; )
- wi(d, t, r), u++, d = pa(e, i + 1 + u);
- t.push(wt), i += u;
+ Ii(d, t, r), u++, d = pa(e, i + 1 + u);
+ t.push($t), i += u;
continue;
}
if (o === "marker") {
const l = s;
t.push(
- Ot,
+ Rt,
"marker",
- qt(
- gg(l.marker ?? "", l.markerSyntax, l.nested)
+ It(
+ Ng(l.marker ?? "", l.markerSyntax, l.nested)
),
- wt
+ $t
);
continue;
}
@@ -22079,7 +22298,7 @@ function wi(e, t, r, n = !1) {
}
if (o === "char") {
const l = s;
- t.push(Ot, "char", JSON.stringify(l.unknownAttributes ?? null)), wi(kr(s) ?? [], t, r, !0), t.push(wt);
+ t.push(Rt, "char", JSON.stringify(l.unknownAttributes ?? null)), Ii(Cr(s) ?? [], t, r, !0), t.push($t);
continue;
}
if (o === "note" || o === "unknown") {
@@ -22087,69 +22306,69 @@ function wi(e, t, r, n = !1) {
continue;
}
if (o === "unmatched") {
- t.push(Ot, "unmatched", qt(ni(s) ?? "")), t.push(wt);
+ t.push(Rt, "unmatched", It(ai(s) ?? "")), t.push($t);
continue;
}
- const a = ni(s);
+ const a = ai(s);
if (a !== void 0) {
- t.push(qt(n ? ug(a) : a));
+ t.push(It(n ? vg(a) : a));
continue;
}
- const c = kr(s);
- c ? (t.push(Ot, o), wi(c, t, r), t.push(wt)) : t.push(tt);
+ const c = Cr(s);
+ c ? (t.push(Rt, o), Ii(c, t, r), t.push($t)) : t.push(tt);
}
}
-function Eo(e) {
+function Ao(e) {
let t = 0;
for (const r of e) {
- const n = kr(r);
+ const n = Cr(r);
if (n) {
- t += Eo(n);
+ t += Ao(n);
continue;
}
- const i = ni(r);
+ const i = ai(r);
if (i !== void 0)
for (const s of i) s === tt && t++;
}
return t;
}
-function Zi(e, t, r, n, i) {
- Cn(e.getChildren(), t, r, n, i);
+function ss(e, t, r, n, i) {
+ vn(e.getChildren(), t, r, n, i);
}
-function Cn(e, t, r, n, i) {
+function vn(e, t, r, n, i) {
const s = () => {
const o = i?.pending === !0;
return i && (i.pending = !1), o;
};
for (let o = 0; o < e.length; o++) {
const a = e[o];
- if (P(a))
- _s(t, a, qt(a.getTextContent()));
- else if (je(a)) {
+ if (O(a))
+ Es(t, a, It(a.getTextContent()));
+ else if (Be(a)) {
s();
- const c = dl(e, o);
- Mo(a.getMarker(), r) && fg(c) ? Cn(c, t, r, n) : Mi(t, [a, ...c]), o += c.length;
+ const c = xl(e, o);
+ Eo(a.getMarker(), r) && Eg(c) ? vn(c, t, r, n) : Ni(t, [a, ...c]), o += c.length;
} else if (j(a) || Le(a))
- s(), Mi(t, [a]);
- else if (Ne(a)) {
+ s(), Ni(t, [a]);
+ else if (Pe(a)) {
s();
- const c = fl(e, o);
- pl(a) ? Mi(t, [a, ...c]) : (_s(t, a, qt(Li(a))), Cn(c, t, r, n)), o += c.length;
- } else if ($(a))
- s(), hg(a, r) ? Mi(t, [a]) : Zi(a, t, r, n, { pending: !0 });
- else if (no(a))
- s(), _s(t, a, " ");
- else if (M(a)) {
- const c = En(a) || re(a, oe) === "attribute", l = s() && !c;
- _s(
+ const c = _l(e, o);
+ Cl(a) ? Ni(t, [a, ...c]) : (Es(t, a, It(Ki(a))), vn(c, t, r, n)), o += c.length;
+ } else if (D(a))
+ s(), Pg(a, r) ? Ni(t, [a]) : ss(a, t, r, n, { pending: !0 });
+ else if (us(a))
+ s(), Es(t, a, " ");
+ else if (v(a)) {
+ const c = Pn(a) || ne(a, oe) === "attribute", l = s() && !c;
+ Es(
t,
a,
- c ? qt(Li(a)) : kM(Li(a), n, l)
+ c ? It(Ki(a)) : KM(Ki(a), n, l)
);
- } else D(a) ? Zi(a, t, r, n, i) : (s(), Mi(t, [a]));
+ } else F(a) ? ss(a, t, r, n, i) : (s(), Ni(t, [a]));
}
}
-function gl(e, t, r) {
+function vl(e, t, r) {
if (e.getUnknownAttributes()) return;
const n = t(e.getMarker())?.type;
if (n !== void 0 && n !== b.Unknown && n !== b.Paragraph)
@@ -22157,12 +22376,12 @@ function gl(e, t, r) {
for (let s = e.getParent(); s !== null; s = s.getParent())
if (Le(s)) return;
const i = { text: "", spans: [], sentinels: [] };
- return Zi(e, i, t, r), i;
+ return ss(e, i, t, r), i;
}
-function mg(e, t) {
+function Og(e, t) {
let r = 0;
const n = (i) => {
- if (M(i)) {
+ if (v(i)) {
let s = i;
for (; s; ) {
const a = s.getTextContent().indexOf(tt);
@@ -22177,94 +22396,94 @@ function mg(e, t) {
}
c.remove(), s = l;
}
- } else D(i) && [...i.getChildren()].forEach(n);
+ } else F(i) && [...i.getChildren()].forEach(n);
};
e.forEach(n);
}
-function ec(e, t = []) {
+function tc(e, t = []) {
for (const r of e)
- Ne(r) ? t.push(r) : D(r) && ec(r.getChildren(), t);
+ Pe(r) ? t.push(r) : F(r) && tc(r.getChildren(), t);
return t;
}
-function yg(e) {
+function wg(e) {
let t = 0;
const r = (n) => {
- if (M(n))
+ if (v(n))
for (const i of n.getTextContent()) i === tt && t++;
- else D(n) && n.getChildren().forEach(r);
+ else F(n) && n.getChildren().forEach(r);
};
return e.forEach(r), t;
}
-function On(e) {
+function wn(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 += On(r.content));
+ else r.content && (t += wn(r.content));
return t;
}
-function TM(e, t, r) {
+function jM(e, t, r) {
const n = { text: "", spans: [], sentinels: [] };
for (const i of e)
- n.text.length > 0 && (n.text += " "), D(i) && Zi(i, n, t, r);
+ n.text.length > 0 && (n.text += " "), F(i) && ss(i, n, t, r);
return { text: n.text, spans: n.spans };
}
-const es = /\s/;
-function bg(e) {
- return e.filter(Ao).length;
+const os = /\s/;
+function qg(e) {
+ return e.filter(Po).length;
}
-function Ao(e) {
+function Po(e) {
if (e.isSentinel) return !1;
- const t = ne(e.key);
- return M(t) && !P(t) && re(t, oe) === "attribute";
+ const t = se(e.key);
+ return v(t) && !O(t) && ne(t, oe) === "attribute";
}
-function xM(e) {
+function BM(e) {
if (e.isSentinel) return !1;
- const t = ne(e.key);
- return P(t) || Ao(e);
+ const t = se(e.key);
+ return O(t) || Po(e);
}
-function Td(e, t, r, n) {
+function Ld(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 && Ao(o)) continue;
+ if (!c && n && Po(o)) continue;
const l = c ? Math.min(o.isSentinel ? 1 : r, a) : a;
for (let u = 0; u < l; u++)
- es.test(e.text[o.start + u]) ? s++ : (i++, s = 0);
+ os.test(e.text[o.start + u]) ? s++ : (i++, s = 0);
if (c) return { nonWsBefore: i, wsRun: s };
}
}
-function ml(e, t, r) {
- const n = Td(e, t, r, !1);
+function Ml(e, t, r) {
+ const n = Ld(e, t, r, !1);
if (!n) return;
- const i = e.spans.find((o) => o.key === t), s = i && !xM(i) ? Td(e, t, r, !0) : void 0;
- return { ...n, documentCoords: s, attributeRunSpans: bg(e.spans) };
+ const i = e.spans.find((o) => o.key === t), s = i && !BM(i) ? Ld(e, t, r, !0) : void 0;
+ return { ...n, documentCoords: s, attributeRunSpans: qg(e.spans) };
}
function ha(e) {
if (e.isSentinel) return !1;
- const t = ne(e.key);
- return P(t) && t.getMarkerSyntax() !== "opening";
+ const t = se(e.key);
+ return O(t) && t.getMarkerSyntax() !== "opening";
}
-function _M(e) {
- const t = ne(e.key);
- if (!P(t)) return !1;
+function VM(e) {
+ const t = se(e.key);
+ if (!O(t)) return !1;
const r = t.getParent();
- return $(r) ? (r.selectNext(0, 0), !0) : !1;
+ return D(r) ? (r.selectNext(0, 0), !0) : !1;
}
-function CM(e) {
- const t = ne(e.key), r = t?.getParent()?.getChildren();
+function WM(e) {
+ const t = se(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 = Ne(t) ? fl(r, n) : je(t) ? dl(r, n) : [];
+ const i = Pe(t) ? _l(r, n) : Be(t) ? xl(r, n) : [];
return (i[i.length - 1] ?? t).selectNext(0, 0), !0;
}
-function kg(e, t, r) {
- const { text: n, spans: i } = e, s = bg(i) === t.attributeRunSpans ? t.documentCoords : void 0, o = s !== void 0;
+function Rg(e, t, r) {
+ const { text: n, spans: i } = e, s = qg(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 && !ha(d);
- if (!(o && Ao(d))) {
+ if (!(o && Po(d))) {
if (u) {
if (!p) continue;
a = { key: d.key, offset: 0 };
@@ -22272,7 +22491,7 @@ function kg(e, t, r) {
}
for (let m = 0; m < f; m++) {
const g = n[d.start + m];
- if (c === 0 && (l === 0 || !es.test(g))) {
+ if (c === 0 && (l === 0 || !os.test(g))) {
if (p) {
a = { key: d.key, offset: m };
break e;
@@ -22280,7 +22499,7 @@ function kg(e, t, r) {
u = !0;
continue e;
}
- c > 0 ? es.test(g) || c-- : l--;
+ c > 0 ? os.test(g) || c-- : l--;
}
if (c === 0 && l === 0) {
if (p) {
@@ -22293,48 +22512,48 @@ function kg(e, t, r) {
}
if (!a) {
const d = i[i.length - 1];
- if (d && ha(d) && _M(d) || d?.isSentinel && CM(d)) return;
+ if (d && ha(d) && VM(d) || d?.isSentinel && WM(d)) return;
const f = [...i].reverse().find((p) => !p.isSentinel && !ha(p));
f && (a = { key: f.key, offset: f.end - f.start });
}
if (a) {
- const d = ne(a.key);
- if (d && M(d)) {
+ const d = se(a.key);
+ if (d && v(d)) {
d.select(a.offset, a.offset);
return;
}
}
- r.find(D)?.selectStart();
+ r.find(F)?.selectStart();
}
-function Tg(e, t, r, n, i) {
+function $g(e, t, r, n, i) {
if (r) {
if (t === void 0) {
- e.find(D)?.selectStart();
+ e.find(F)?.selectStart();
return;
}
- kg(TM(e, n, i), t, e);
+ Rg(jM(e, n, i), t, e);
}
}
-function vM(e, t, r, n, i) {
+function HM(e, t, r, n, i) {
if (!r) return;
if (t === void 0) {
- e.find(D)?.selectStart();
+ e.find(F)?.selectStart();
return;
}
const s = { text: "", spans: [], sentinels: [] };
- Cn(e, s, n, i), kg({ text: s.text, spans: s.spans }, t, e);
+ vn(e, s, n, i), Rg({ text: s.text, spans: s.spans }, t, e);
}
-function xg(e, t) {
+function Ig(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 = gl(g, n, r);
+ const y = vl(g, n, r);
if (!y)
return i?.debug("[MarkerEdit] Tier 2 skipped: paragraph excluded by guard rails"), !1;
s.text.length > 0 && (s.text += " ");
- const T = s.text.length;
+ const k = s.text.length;
y.spans.forEach(
- (S) => s.spans.push({ ...S, start: S.start + T, end: S.end + T })
+ (_) => s.spans.push({ ..._, start: _.start + k, end: _.end + k })
), s.sentinels.push(...y.sentinels), s.text += y.text;
}
let o, a = !1;
@@ -22345,56 +22564,56 @@ function xg(e, t) {
a = !0;
break;
}
- c.isCollapsed() && (o = ml(s, c.anchor.key, c.anchor.offset));
+ c.isCollapsed() && (o = Ml(s, c.anchor.key, c.anchor.offset));
}
- const l = xr(s.text, {
+ const l = vr(s.text, {
getMarker: n
});
if (l.length === 0)
return i?.debug("[MarkerEdit] Tier 2 skipped: tokenizer produced no content"), !1;
- if (On(l) !== s.sentinels.length)
+ if (wn(l) !== s.sentinels.length)
return i?.warn("[MarkerEdit] Tier 2 aborted: sentinel/preserved-node count mismatch"), !1;
- const u = Br.serializeEditorState(
- { type: hr, version: pr, content: l },
+ const u = Jr.serializeEditorState(
+ { type: kr, version: br, content: l },
r
);
- if (pi(u.root.children, n) === fi(e, n))
+ if (bi(u.root.children, n) === yi(e, n))
return i?.debug("[MarkerEdit] Tier 2 skipped: rebuild is a no-op (fixed point)"), !1;
- const d = u.root.children.map((g) => to(g));
- if (yg(d) !== s.sentinels.length)
+ const d = u.root.children.map((g) => so(g));
+ if (wg(d) !== s.sentinels.length)
return i?.warn("[MarkerEdit] Tier 2 aborted: serialized sentinel/preserved-node count mismatch"), !1;
- const f = ec(e).map((g) => ({
+ const f = tc(e).map((g) => ({
number: g.getNumber(),
sid: g.getSid()
})), p = e[0];
- d.forEach((g) => p.insertBefore(g)), mg(d, s.sentinels), e.forEach((g) => g.remove());
- const m = ec(d);
+ d.forEach((g) => p.insertBefore(g)), Og(d, s.sentinels), e.forEach((g) => g.remove());
+ const m = tc(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 Tg(d, o, a, n, r), !0;
+ return $g(d, o, a, n, r), !0;
}
-function _g(e, t, r) {
- if (e.getIsCollapsed() !== !1 || !Me.isValidMarker(e.getMarker())) return;
+function Lg(e, t, r) {
+ if (e.getIsCollapsed() !== !1 || !Ee.isValidMarker(e.getMarker())) return;
const n = e.getChildren();
let i = 0;
for (; i < n.length; ) {
const u = n[i];
- if (!P(u) || u.getMarkerSyntax() !== "opening") break;
+ if (!O(u) || u.getMarkerSyntax() !== "opening") break;
i++;
}
const s = n[i];
- if (!s || !(cr(s) || M(s) && s.getTextContent() === St(e.getCaller()))) return;
+ if (!s || !(St(s) || v(s) && s.getTextContent() === At(e.getCaller()))) return;
i++;
let a = n.length;
for (; a > i; ) {
const u = n[a - 1];
- if (!P(u) || u.getMarkerSyntax() !== "closing") break;
+ if (!O(u) || u.getMarkerSyntax() !== "closing") break;
a--;
}
const c = n.slice(i, a), l = { text: "", spans: [], sentinels: [] };
- return Cn(c, l, t, r), { out: l, contentNodes: c };
+ return vn(c, l, t, r), { out: l, contentNodes: c };
}
-function Cg(e) {
+function Dg(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];
@@ -22403,102 +22622,102 @@ function Cg(e) {
if (i !== "")
return e.shift(), i;
}
-function SM(e, t) {
- const { viewOptions: r, getMarker: n, logger: i } = t, s = _g(e, n, r);
+function GM(e, t) {
+ const { viewOptions: r, getMarker: n, logger: i } = t, s = Lg(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 = R();
if (N(u)) {
- for (let E = u.anchor.getNode(); E; E = E.getParent())
- if (e.is(E)) {
+ for (let P = u.anchor.getNode(); P; P = P.getParent())
+ if (e.is(P)) {
l = !0;
break;
}
- u.isCollapsed() && (c = ml(o, u.anchor.key, u.anchor.offset));
+ u.isCollapsed() && (c = Ml(o, u.anchor.key, u.anchor.offset));
}
- const d = xr(o.text, {
+ const d = vr(o.text, {
getMarker: n,
isNoteContext: !0
});
if (d.length === 0)
return i?.debug("[MarkerEdit] Note Tier 2 skipped: tokenizer produced no content"), !1;
- if (On(d) !== o.sentinels.length)
+ if (wn(d) !== 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")
return i?.warn("[MarkerEdit] Note Tier 2 aborted: unexpected tokenized shape"), !1;
- const p = f.content ?? [], m = Cg(p), g = dg(e, p, m, r);
+ const p = f.content ?? [], m = Dg(p), g = Mg(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"
), !1;
- if (Eo(g.children) !== o.sentinels.length)
+ if (Ao(g.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), pi(g.children, n) === fi(a, n))
+ if (y && e.setCategory(m), bi(g.children, n) === yi(a, n))
return i?.debug("[MarkerEdit] Note Tier 2 skipped: rebuild is a no-op (fixed point)"), y;
- const T = g.children.map((E) => to(E));
- if (yg(T) !== o.sentinels.length)
+ const k = g.children.map((P) => so(P));
+ if (wg(k) !== o.sentinels.length)
return i?.warn("[MarkerEdit] Note Tier 2 aborted: parsed sentinel/preserved-node count mismatch"), y;
- const S = a[0];
- if (S)
- T.forEach((E) => S.insertBefore(E));
+ const _ = a[0];
+ if (_)
+ k.forEach((P) => _.insertBefore(P));
else {
- const E = e.getChildren().find((A) => P(A) && A.getMarkerSyntax() === "closing");
- T.forEach((A) => E ? E.insertBefore(A) : e.append(A));
- }
- mg(T, o.sentinels);
- const v = new Set(o.sentinels.flat().map((E) => E.getKey()));
- return a.forEach((E) => {
- v.has(E.getKey()) || E.remove();
- }), vM(T, c, l, n, r), !0;
-}
-const vg = /* @__PURE__ */ new Set(["ca", "cp"]), yl = "cp";
-function Sg(e) {
- if (!ir(e)) return !1;
+ const P = e.getChildren().find((A) => O(A) && A.getMarkerSyntax() === "closing");
+ k.forEach((A) => P ? P.insertBefore(A) : e.append(A));
+ }
+ Og(k, o.sentinels);
+ const S = new Set(o.sentinels.flat().map((P) => P.getKey()));
+ return a.forEach((P) => {
+ S.has(P.getKey()) || P.remove();
+ }), HM(k, c, l, n, r), !0;
+}
+const Ug = /* @__PURE__ */ new Set(["ca", "cp"]), El = "cp";
+function Fg(e) {
+ if (!dr(e)) return !1;
const t = { text: "", spans: [], sentinels: [] };
- if (Zi(e, t, nr, void 0), t.sentinels.length > 0) return !1;
+ if (ss(e, t, lr, void 0), t.sentinels.length > 0) return !1;
const r = t.text;
if (r.trim() === "") return !1;
- const n = xr(r, { getMarker: nr }), [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 = vr(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;
return s.length === 0 ? !1 : s.every(
- (o) => typeof o == "string" ? o.trim() === "" : (o.type === "char" || o.type === "para") && (o.marker === "ca" || o.marker === yl)
+ (o) => typeof o == "string" ? o.trim() === "" : (o.type === "char" || o.type === "para") && (o.marker === "ca" || o.marker === El)
);
}
-function Po(e) {
+function No(e) {
const t = [];
for (let r = e.getNextSibling(); r; r = r.getNextSibling()) {
- if ($(r) && vg.has(r.getMarker()) || Sg(r)) {
+ if (D(r) && Ug.has(r.getMarker()) || Fg(r)) {
t.push(r);
continue;
}
- se(r) && r.getMarker() === yl && t.push(r);
+ ae(r) && r.getMarker() === El && t.push(r);
break;
}
return t;
}
-function MM(e) {
- const t = (n) => $(n) && vg.has(n.getMarker()) || Sg(n);
- if (t(e) || se(e) && e.getMarker() === yl)
+function JM(e) {
+ const t = (n) => D(n) && Ug.has(n.getMarker()) || Fg(n);
+ if (t(e) || ae(e) && e.getMarker() === El)
for (let n = e.getPreviousSibling(); n; n = n.getPreviousSibling()) {
if ($e(n)) return n;
if (!t(n)) return;
}
}
-function Mg(e, t, r) {
+function zg(e, t, r) {
if (Object.keys(e.getUnknownAttributes() ?? {}).length > 0) return;
- const n = Po(e);
- if (n.some((s) => se(s) && s.getUnknownAttributes())) return;
+ const n = No(e);
+ if (n.some((s) => ae(s) && s.getUnknownAttributes())) return;
const i = { text: "", spans: [], sentinels: [] };
- if (Cn(e.getChildren(), i, t, r), Cn(n, i, t, r), !(i.sentinels.length > 0))
+ if (vn(e.getChildren(), i, t, r), vn(n, i, t, r), !(i.sentinels.length > 0))
return i;
}
-function EM(e, t) {
- const { viewOptions: r, getMarker: n, logger: i } = t, s = [e, ...Po(e)], o = Mg(e, n, r);
+function YM(e, t) {
+ const { viewOptions: r, getMarker: n, logger: i } = t, s = [e, ...No(e)], o = zg(e, n, r);
if (!o)
return i?.debug("[MarkerEdit] Chapter Tier 2 skipped: chapter excluded by guard rails"), !1;
let a, c = !1;
@@ -22509,48 +22728,48 @@ function EM(e, t) {
c = !0;
break;
}
- l.isCollapsed() && (a = ml(o, l.anchor.key, l.anchor.offset));
+ l.isCollapsed() && (a = Ml(o, l.anchor.key, l.anchor.offset));
}
- const u = xr(o.text, { getMarker: n }), [d] = u;
+ const u = vr(o.text, { getMarker: n }), [d] = u;
if (u.length === 0 || typeof d != "object" || d.type !== "chapter")
return i?.debug("[MarkerEdit] Chapter Tier 2 skipped: bytes no longer tokenize as a chapter"), !1;
- if (On(u) !== 0)
+ if (wn(u) !== 0)
return i?.warn("[MarkerEdit] Chapter Tier 2 aborted: unexpected preserved-node placeholder"), !1;
e.getSid() !== void 0 && (d.sid = e.getSid());
- const f = Br.serializeEditorState(
- { type: hr, version: pr, content: u },
+ const f = Jr.serializeEditorState(
+ { type: kr, version: br, content: u },
r
);
- if (pi(f.root.children, n) === fi(s, n)) {
+ if (bi(f.root.children, n) === yi(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;
}
- const p = f.root.children.map((m) => to(m));
- return $e(p[0]) ? (p.forEach((m) => e.insertBefore(m)), s.forEach((m) => m.remove()), Tg(p, a, c, n, r), !0) : (i?.warn("[MarkerEdit] Chapter Tier 2 aborted: serialized output is not a chapter"), !1);
+ const p = f.root.children.map((m) => so(m));
+ return $e(p[0]) ? (p.forEach((m) => e.insertBefore(m)), s.forEach((m) => m.remove()), $g(p, a, c, n, r), !0) : (i?.warn("[MarkerEdit] Chapter Tier 2 aborted: serialized output is not a chapter"), !1);
}
-function ts(e) {
+function as(e) {
let t, r;
for (let n = e; n; n = n.getParent()) {
if (Le(n)) return;
- !t && (j(n) || se(n) || $e(n)) && (t = n), ry(n.getParent()) && (r = n);
+ !t && (j(n) || ae(n) || $e(n)) && (t = n), Sy(n.getParent()) && (r = n);
}
- return t && !t.is(r) ? t : (r ? MM(r) : void 0) ?? t;
+ return t && !t.is(r) ? t : (r ? JM(r) : void 0) ?? t;
}
-function Kt(e, t) {
- const r = ts(e);
- return r ? j(r) ? SM(r, t) : $e(r) ? EM(r, t) : xg([r], t) : !1;
+function Vt(e, t) {
+ const r = as(e);
+ return r ? j(r) ? GM(r, t) : $e(r) ? YM(r, t) : Ig([r], t) : !1;
}
-const AM = /* @__PURE__ */ new Set(["type", "marker", "content", "closed"]);
-function xd(e, t) {
+const XM = /* @__PURE__ */ new Set(["type", "marker", "content", "closed"]);
+function Dd(e, t) {
const r = Object.entries(e).filter(
- (n) => typeof n[1] == "string" && !AM.has(n[0])
+ (n) => typeof n[1] == "string" && !XM.has(n[0])
);
if (r.length !== 0) {
t.push("|");
for (const [n, i] of r) t.push(`${n}="${i}"`);
}
}
-function Es(e, t) {
+function ws(e, t) {
if (e)
for (const r of e) {
if (typeof r == "string") {
@@ -22566,27 +22785,27 @@ function Es(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}`), xd(r, t), t.push("\\*");
+ t.push(`\\${n}`), Dd(r, t), t.push("\\*");
break;
case "unmatched":
t.push(`\\${n}`);
break;
case "char":
- t.push(`\\${n} `), Es(r.content, t), xd(r, t), i !== "false" && t.push(`\\${n}*`);
+ t.push(`\\${n} `), ws(r.content, t), Dd(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*`), Es(r.content, t), i !== "false" && t.push(`\\${n}*`);
+ t.push(`\\${n} ${s ?? ""}`), o !== void 0 && t.push(`\\cat ${o}\\cat*`), ws(r.content, t), i !== "false" && t.push(`\\${n}*`);
break;
}
default:
- t.push(`\\${n} `), Es(r.content, t);
+ t.push(`\\${n} `), ws(r.content, t);
}
}
}
-function _d(e, t, r) {
- const n = ts(e);
- if (!se(n)) return !1;
+function Ud(e, t, r) {
+ const n = as(e);
+ if (!ae(n)) return !1;
const i = R();
if (!N(i) || !i.isCollapsed()) return !1;
let s = !1;
@@ -22596,30 +22815,42 @@ function _d(e, t, r) {
break;
}
if (!s) return !1;
- const o = gl(n, t, r);
+ const o = vl(n, t, r);
if (!o) return !1;
- const a = xr(o.text, { getMarker: t });
+ const a = vr(o.text, { getMarker: t });
if (a.length === 0) return !1;
const c = /* @__PURE__ */ new Map();
for (const u of o.text)
- es.test(u) || c.set(u, (c.get(u) ?? 0) + 1);
+ os.test(u) || c.set(u, (c.get(u) ?? 0) + 1);
const l = [];
- Es(a, l);
- for (const u of l.join("").replaceAll(w, "~")) {
- if (es.test(u)) continue;
+ ws(a, l);
+ for (const u of l.join("").replaceAll(L, "~")) {
+ if (os.test(u)) continue;
const d = c.get(u);
d !== void 0 && d > 0 && c.set(u, d - 1);
}
for (const u of c.values()) if (u > 0) return !0;
return !1;
}
-function PM(e) {
- return [ot(e), fo()];
+function Al(e, t) {
+ return Kg(e, t, b.Paragraph);
+}
+function QM(e, t) {
+ return Kg(e, t, b.Character);
+}
+function Kg(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) || vc(n));
+}
+function ZM(e) {
+ return [ot(e), po()];
}
-function bl(e) {
- Gt(e, 2);
+function Pl(e) {
+ Xt(e, 2);
}
-function NM(e) {
+function eE(e) {
const t = R();
if (!N(t) || !t.isCollapsed()) return !1;
const { anchor: r } = t;
@@ -22627,38 +22858,38 @@ function NM(e) {
const n = e.getFirstChild();
return n !== null && r.key === n.getKey() && r.offset === 0;
}
-function kl(e) {
- const t = NM(e);
- e.splice(0, 0, PM(e.getMarker())), t && bl(e);
+function Nl(e) {
+ const t = eE(e);
+ e.splice(0, 0, ZM(e.getMarker())), t && Pl(e);
}
-function Qs(e, t) {
- e.setMarker(t), kl(e), bl(e);
+function ro(e, t) {
+ e.setMarker(t), Nl(e), Pl(e);
}
-function OM(e, t) {
+function tE(e, t) {
const r = e.getFirstChild();
if (!r) return;
const n = r.getNextSibling();
- if (!En(n)) {
- if (M(n) && !P(n) && /^[ \u00A0]$/.test(n.getTextContent())) {
+ if (!Pn(n)) {
+ if (v(n) && !O(n) && /^[ \u00A0]$/.test(n.getTextContent())) {
if (t.collapsedDeleteCaretParas?.has(e.getKey())) {
t.pendingKeys.add(e.getKey());
return;
}
- n.setTextContent(w), mt(n, oe, sr), n.setMode("token");
+ n.setTextContent(L), yt(n, oe, fr), n.setMode("token");
return;
}
- if (ap(e)) {
+ if (xp(e)) {
t.pendingKeys.add(e.getKey());
return;
}
- r.insertAfter(fo());
+ r.insertAfter(po());
}
}
-function Cd(e, t, r) {
+function Fd(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() : D(n) ? n.getChildrenSize() : 0;
+ const i = e.type === "text" ? n.getTextContentSize() : F(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;
@@ -22668,197 +22899,205 @@ function Cd(e, t, r) {
}
return !0;
}
-function Di(e) {
+function ji(e) {
for (let t = e; t; t = t.getParent())
- if (se(t)) return t;
+ if (ae(t)) return t;
}
-function wM(e) {
+function rE(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 = Di(s);
+ const o = ji(s);
o && i.add(o);
}
return [...i].filter((s) => {
- const o = Di(r.getNode())?.is(s) ?? !1, a = Di(n.getNode())?.is(s) ?? !1;
- return !(o && !Cd(r, s, "start") || a && !Cd(n, s, "end"));
+ const o = ji(r.getNode())?.is(s) ?? !1, a = ji(n.getNode())?.is(s) ?? !1;
+ return !(o && !Fd(r, s, "start") || a && !Fd(n, s, "end"));
});
}
-function tc(e) {
+function rc(e) {
const t = e.wholeParaDeleteExpected;
if (!t) return;
const r = R();
if (!(!N(r) || r.isCollapsed()))
- for (const n of wM(r)) t.add(n.getKey());
+ for (const n of rE(r)) t.add(n.getKey());
}
-function qM(e) {
+function nE(e) {
const t = e.collapsedDeleteCaretParas;
if (!t) return;
const r = R();
if (!N(r) || !r.isCollapsed()) return;
- const n = Di(r.focus.getNode());
+ const n = ji(r.focus.getNode());
n && t.add(n.getKey());
}
-function RM(e) {
+function iE(e) {
const t = R();
- !N(t) || t.isCollapsed() || t.getNodes().some((r) => P(r)) && (tc(e), t.removeText());
+ !N(t) || t.isCollapsed() || t.getNodes().some((r) => O(r)) && (rc(e), t.removeText());
}
-function $M(e, t) {
- if (!li(t.viewOptions)) return;
- if (Dt(e.getFirstChild())) {
- OM(e, t);
+const sE = new RegExp(
+ String.raw`^\\\+?([${Zt}]+)(?:[ \u00A0]|$)`
+);
+function oE(e, t) {
+ const r = sE.exec(e.getTextContent());
+ return !!r && Al(r[1], t);
+}
+function aE(e, t) {
+ if (!hi(t.viewOptions)) return;
+ if (Ft(e.getFirstChild())) {
+ tE(e, t);
return;
}
if (t.splitExpected.current) {
- kl(e), t.logger?.debug(`[MarkerEdit] injected prefix for split para "${e.getMarker()}"`);
+ if (oE(e, t.getMarker)) return;
+ Nl(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) => se(o) && !o.is(e))) {
- Qs(e, rr), 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) => ae(o) && !o.is(e))) {
+ ro(e, cr), 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 (se(r)) {
- const n = e.getChildren().filter((a) => !En(a)), i = R();
+ if (ae(r)) {
+ const n = e.getChildren().filter((a) => !Pn(a)), i = R();
let s = !1;
if (N(i) && i.isCollapsed()) {
const a = i.anchor.getNode();
- a.is(e) ? s = !0 : Di(a)?.is(e) && (s = !n.some(
- (c) => a.is(c) || D(c) && a.getParents().some((l) => l.is(c))
+ 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))
));
}
const o = r.getChildrenSize();
- r.append(...n), e.remove(), s && Gt(r, o), t.logger?.debug("[MarkerEdit] merged marker-deleted para into previous");
+ r.append(...n), e.remove(), s && Xt(r, o), t.logger?.debug("[MarkerEdit] merged marker-deleted para into previous");
return;
}
- Qs(e, rr);
+ ro(e, cr);
}
-function IM(e) {
+function cE(e) {
const t = e.getUnknownAttributes();
if (!t) return;
- const r = tr(t, ao(e.getMarker()));
+ const r = or(t, lo(e.getMarker()));
return r === "" ? void 0 : r;
}
-function LM(e) {
- const t = e.getChildren().filter((s) => !P(s) && re(s, oe) !== "attribute"), r = t[0];
- r && M(r) && r.getTextContent().startsWith(w) && r.setTextContent(r.getTextContent().slice(1));
- const n = IM(e);
- n && t.push(he(n));
+function lE(e) {
+ const t = e.getChildren().filter((s) => !O(s) && ne(s, oe) !== "attribute"), r = t[0];
+ r && v(r) && r.getTextContent().startsWith(L) && r.setTextContent(r.getTextContent().slice(1));
+ const n = cE(e);
+ n && t.push(pe(n));
let i = e;
for (const s of t)
i.insertAfter(s), i = s;
e.remove();
}
-function DM(e, t) {
- const r = e.getChildren(), n = r.some((s) => P(s) && s.getMarkerSyntax() === "opening");
+function uE(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) => M(c) && !P(c) && c.getTextContent() === St(s)
- ), a = ii(e).some(({ node: c }) => P(c));
+ (c) => v(c) && !O(c) && c.getTextContent() === At(s)
+ ), a = li(e).some(({ node: c }) => O(c));
if (!o && !a) return;
r.forEach((c) => {
- P(c) || (M(c) && c.getTextContent() === St(s) && c.setTextContent(` ${s} `), e.insertBefore(c));
+ O(c) || (v(c) && c.getTextContent() === At(s) && c.setTextContent(` ${s} `), e.insertBefore(c));
}), e.remove(), t.logger?.debug(
"[MarkerEdit] unwrapped expanded note whose opening glyph was deleted (content preserved)"
);
return;
}
- const i = r.some((s) => P(s) && s.getMarkerSyntax() === "closing");
+ 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 UM(e, t) {
+function dE(e, t) {
if (e.isEmpty()) return;
const r = e.getFirstChild();
- if (!(P(r) && r.getMarkerSyntax() === "opening")) {
- LM(e), t.logger?.debug(`[MarkerEdit] unwrapped char span "${e.getMarker()}"`);
+ if (!(O(r) && r.getMarkerSyntax() === "opening")) {
+ lE(e), t.logger?.debug(`[MarkerEdit] unwrapped char span "${e.getMarker()}"`);
return;
}
- const i = e.getUnknownAttributes()?.closed !== "false", s = e.getChildren().some((o) => P(o) && o.getMarkerSyntax() === "closing");
- i && !s && Kt(e, t);
+ const i = e.getUnknownAttributes()?.closed !== "false", s = e.getChildren().some((o) => O(o) && o.getMarkerSyntax() === "closing");
+ i && !s && Vt(e, t);
}
-function Eg(e, t, r) {
- if (!P(e.getFirstChild()) && r?.markerMode === "editable" && li(r)) {
- Qs(e, t);
+function jg(e, t, r) {
+ if (!O(e.getFirstChild()) && r?.markerMode === "editable" && hi(r)) {
+ ro(e, t);
return;
}
- ph(e, t);
+ Eh(e, t);
}
-function Ag() {
+function Bg() {
const e = R();
if (!N(e)) return "declined";
if (!e.isCollapsed()) {
- const t = Pg(e);
- return t !== "removed" ? t : (rc(), "handled");
+ const t = Vg(e);
+ return t !== "removed" ? t : (nc(), "handled");
}
- return rc() ? "handled" : "declined";
+ return nc() ? "handled" : "declined";
}
-function FM(e, t) {
+function fE(e, t) {
if (!t) return e;
- const r = yM.exec(e);
+ const r = FM.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 vd(e, t) {
+function zd(e, t) {
const r = R();
if (!N(r)) return "declined";
if (r.isCollapsed()) {
- if (!Ng())
+ if (!Wg())
return "declined";
} else {
- const s = Pg(r);
+ const s = Vg(r);
if (s !== "removed") return s;
}
const [n, ...i] = e.map(
- (s) => FM(s, t)
+ (s) => fE(s, t)
);
- Sd(n ?? "");
+ Kd(n ?? "");
for (const s of i)
- rc(), Sd(s);
+ nc(), Kd(s);
return "handled";
}
-function zM(e) {
+function pE(e) {
const t = e.getRootElement(), r = t?.ownerDocument.getSelection(), n = r?.anchorNode;
if (!t || !r || !n || !t.contains(n)) return !1;
- const i = os(n);
+ const i = ci(n);
if (!i) return !1;
- const s = Ht(i);
- if (!s || s.getIsCollapsed() !== !1 || s.is(i) || !M(i) || P(i)) return !1;
+ const s = Yt(i);
+ if (!s || s.getIsCollapsed() !== !1 || s.is(i) || !v(i) || O(i)) return !1;
const o = Math.min(r.anchorOffset, i.getTextContentSize());
return i.select(o, o), !0;
}
-function Pg(e) {
- const t = Ht(e.anchor.getNode()), r = Ht(e.focus.getNode()), n = t?.getIsCollapsed() === !1, i = r?.getIsCollapsed() === !1;
- return !n && !i ? "declined" : (e.removeText(), KM() ? "removed" : "needs-plain-split");
+function Vg(e) {
+ const t = Yt(e.anchor.getNode()), r = Yt(e.focus.getNode()), n = t?.getIsCollapsed() === !1, i = r?.getIsCollapsed() === !1;
+ return !n && !i ? "declined" : (e.removeText(), hE() ? "removed" : "needs-plain-split");
}
-function Sd(e) {
+function Kd(e) {
if (e === "") return;
const t = R();
N(t) && t.insertText(e);
}
-function KM() {
+function hE() {
const e = R();
if (!N(e) || !e.isCollapsed()) return !1;
- const t = Ht(e.anchor.getNode());
- return !t || t.getIsCollapsed() !== !1 ? !1 : t.getChildren().some((r) => P(r) && r.getMarkerSyntax() === "opening");
+ const t = Yt(e.anchor.getNode());
+ return !t || t.getIsCollapsed() !== !1 ? !1 : t.getChildren().some((r) => O(r) && r.getMarkerSyntax() === "opening");
}
-function Ng() {
+function Wg() {
const e = R();
if (!N(e) || !e.isCollapsed()) return;
- const t = e.anchor.getNode(), r = Ht(t);
+ const t = e.anchor.getNode(), r = Yt(t);
if (!(!r || r.getIsCollapsed() !== !1 || r.is(t)))
return r;
}
-function rc() {
+function nc() {
const e = R();
if (!N(e) || !e.isCollapsed()) return !1;
- const t = e.anchor.getNode(), r = Ng();
+ const t = e.anchor.getNode(), r = Wg();
if (!r) return !1;
let n = t;
for (; !r.is(n.getParent()); ) {
@@ -22866,9 +23105,9 @@ function rc() {
if (!l) return !1;
n = l;
}
- const i = yr("fp", { closed: "false" });
+ const i = xr("fp", { closed: "false" });
i.append(ot("fp"));
- const s = M(t) && !P(t) ? t : void 0, o = e.anchor.offset, a = s?.getTextContentSize() ?? 0, c = s !== void 0 && s.is(n);
+ const s = v(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);
@@ -22876,73 +23115,73 @@ function rc() {
const [, l] = s.splitText(o);
l.insertBefore(i);
}
- Qn(i, { renderGlyphs: !0 });
+ ri(i, { renderGlyphs: !0 });
} else {
const l = s && o < a ? [o === 0 ? s : s.splitText(o)[1]] : [];
n.insertAfter(i);
const [u] = l;
- u && (Xb(u), i.append(u));
+ u && (mk(u), i.append(u));
}
- return i.getChildren().every(P) && i.append(he(It)), Og(i), !0;
+ return i.getChildren().every(O) && i.append(pe(Dt)), Hg(i), !0;
}
-function Og(e) {
- const t = e.getChildren().find((r) => !P(r));
- if (M(t)) {
- const r = t.getTextContent().startsWith(w) ? 1 : 0;
+function Hg(e) {
+ const t = e.getChildren().find((r) => !O(r));
+ if (v(t)) {
+ const r = t.getTextContent().startsWith(L) ? 1 : 0;
t.select(r, r);
return;
}
- if (D(t)) {
- Og(t);
+ if (F(t)) {
+ Hg(t);
return;
}
e.selectEnd();
}
-function jM(e) {
+function gE(e) {
const t = [];
let r = e;
for (; r; )
- $(r) && t.push(r.getMarker()), r = r.getParent();
+ D(r) && t.push(r.getMarker()), r = r.getParent();
return t;
}
-function BM(e) {
+function mE(e) {
const t = e.getTopLevelElement(), r = [];
for (const n of Ue().getChildren()) {
if (t && n.is(t)) break;
- (Tt(n) || We(n) || se(n)) && r.push(n.getMarker());
+ (_t(n) || We(n) || ae(n)) && r.push(n.getMarker());
}
return r;
}
-function VM(e) {
+function yE(e) {
let t = e;
- for (; D(t); ) {
+ for (; F(t); ) {
const r = t.getFirstChild();
if (!r) break;
t = r;
}
return t;
}
-function WM(e, t, r) {
+function bE(e, t, r) {
const n = e.getFirstChild();
if (!n) return !1;
let i = n;
- if (Dt(n)) {
+ if (Ft(n)) {
if (t.is(n)) return !0;
- if (i = n.getNextSibling(), i && En(i)) {
+ if (i = n.getNextSibling(), i && Pn(i)) {
if (t.is(i)) return !0;
i = i.getNextSibling();
}
}
- return i ? t.is(VM(i)) && r === 0 : !1;
+ return i ? t.is(yE(i)) && r === 0 : !1;
}
-function HM(e, t, r) {
+function kE(e, t, r) {
const n = e.getFirstChild();
- if (!n || !Dt(n)) return !1;
+ if (!n || !Ft(n)) return !1;
if (t.is(n)) return !0;
const i = n.getNextSibling();
- return i !== null && En(i) && t.is(i) && r === 0;
+ return i !== null && Pn(i) && t.is(i) && r === 0;
}
-function GM() {
+function TE() {
if (typeof window > "u" || typeof window.getSelection != "function") return;
const e = window.getSelection();
if (!e || e.rangeCount === 0) return;
@@ -22951,67 +23190,67 @@ function GM() {
const { x: r, y: n, width: i, height: s } = t.getBoundingClientRect();
return { x: r, y: n, width: i, height: s };
}
-function JM() {
+function xE() {
const e = R();
if (!N(e)) return;
- const t = e.focus.getNode(), r = e.focus.offset, n = !e.isCollapsed(), i = nt(t, se), s = !n && (!i || HM(i, t, r)) ? "paragraph" : "character", o = Ht(t);
+ const t = e.focus.getNode(), r = e.focus.offset, n = !e.isCollapsed(), i = nt(t, ae), s = !n && (!i || kE(i, t, r)) ? "paragraph" : "character", o = Yt(t);
return {
source: s,
paraMarker: i?.getMarker(),
- previousParaMarkers: BM(t),
- openCharMarkers: jM(t),
+ previousParaMarkers: mE(t),
+ openCharMarkers: gE(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: Lc(t, r),
- anchorRect: GM()
+ inMarkerText: Bc(t, r),
+ anchorRect: TE()
};
}
-function YM() {
+function _E() {
const e = R();
if (!N(e) || !e.isCollapsed()) return;
const t = e.anchor.getNode();
- if (!M(t) || P(t)) return;
- const r = e.anchor.offset, n = t.getTextContent().slice(0, r), i = bM.exec(n);
+ if (!v(t) || O(t)) return;
+ const r = e.anchor.offset, n = t.getTextContent().slice(0, r), i = zM.exec(n);
i && t.spliceText(r - i[0].length, i[0].length, "", !0);
}
-function XM(e, t, r) {
- Eg(e, t, r), bl(e);
+function CE(e, t, r) {
+ jg(e, t, r), Pl(e);
}
-function QM(e, t, r) {
+function SE(e, t, r) {
const n = R();
if (!N(n)) return;
- const i = n.focus.getNode(), s = nt(i, se);
- if (t === "backslash" && s && WM(s, i, n.focus.offset)) {
- XM(s, e, r);
+ const i = n.focus.getNode(), s = nt(i, ae);
+ if (t === "backslash" && s && bE(s, i, n.focus.offset)) {
+ CE(s, e, r);
return;
}
- qg(e, r);
+ Jg(e, r);
}
-function ZM(e, t) {
+function vE(e, t) {
const r = R();
return !N(r) || !r.isCollapsed() ? !1 : (r.insertText(`\\${e}${t?.trailingSpace === !1 ? "" : " "}`), !0);
}
-function wg(e) {
+function Gg(e) {
const t = R();
return N(t) ? (t.insertText(`\\${e}*`), !0) : !1;
}
-function eE(e, t, r, n) {
+function ME(e, t, r, n) {
if (N(R()) || n.logger?.warn(
"$applyMarkerMenuSelection: no range selection — cleanup/insert will no-op (editor blurred?)"
- ), t.literalPrefixLanded && YM(), e.kind === "closeTag") {
- wg(e.marker.replace(/\*$/, ""));
+ ), t.literalPrefixLanded && _E(), e.kind === "closeTag") {
+ Gg(e.marker.replace(/\*$/, ""));
return;
}
- if (e.marker === "fp" && Ag() !== "declined") return;
+ if (e.marker === "fp" && Bg() !== "declined") return;
if (e.kind === "paragraph" && Qe.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers)) {
- QM(e.marker, t.trigger, n.viewOptions);
+ SE(e.marker, t.trigger, n.viewOptions);
return;
}
- if (Me.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers))
- return Jh(
+ if (Ee.isValidMarker(e.marker, n.nodeOptions?.extraValidMarkers))
+ return ug(
e.marker,
r,
n.expandedNoteKeyRef,
@@ -23019,7 +23258,7 @@ function eE(e, t, r, n) {
n.nodeOptions,
n.logger
);
- Ja(
+ Ya(
e.marker,
n.expandedNoteKeyRef,
n.viewOptions,
@@ -23027,132 +23266,120 @@ function eE(e, t, r, n) {
n.logger,
void 0,
n.styleInfo
- ).action({ editor: ss(), reference: r });
+ ).action({ editor: Xn(), reference: r });
}
-function qg(e, t) {
+function Jg(e, t) {
const r = R();
if (!N(r)) return;
- const n = li(t);
- if (Hh()) {
+ const n = hi(t);
+ if (cg()) {
const s = R();
if (!N(s)) return;
- const o = nt(s.anchor.getNode(), se);
+ const o = nt(s.anchor.getNode(), ae);
if (!o) return;
- o.setMarker(e), n && kl(o);
+ o.setMarker(e), n && Nl(o);
return;
}
const i = r.insertParagraph();
- se(i) && (n ? Qs(i, e) : i.setMarker(e));
+ ae(i) && (n ? ro(i, e) : i.setMarker(e));
}
-function tE() {
- const [e] = le();
- return K(() => e.registerCommand(nf, () => !0, Rt), [e]), null;
+function EE() {
+ const [e] = ce();
+ return z(() => e.registerCommand(bf, () => !0, kt), [e]), null;
}
-function Rg(e, t) {
- const r = e.replace(/^\+/, "");
- if (r === "v" || r === "c") return !1;
- const n = t(r)?.type;
- return n !== void 0 && n !== b.Unknown ? n === b.Paragraph : !(Me.isValidMarker(r) || oo(r));
-}
-function rE(e, t) {
- const r = e.replace(/^\+/, "");
- if (r === "v" || r === "c") return !1;
- const n = t(r)?.type;
- return n !== void 0 && n !== b.Unknown ? n === b.Character : !(Me.isValidMarker(r) || oo(r));
-}
-function nE(e, t) {
+function AE(e, t) {
if (!e.startsWith("\\")) return !0;
- const r = pM.exec(e)?.[1];
- return r === void 0 ? !1 : !Rg(r, t);
+ const r = IM.exec(e)?.[1];
+ return r === void 0 ? !1 : !Al(r, t);
}
-function $g(e, t) {
- if (e.getMarkerSyntax() !== "opening" || !nE(e.getTextContent(), t)) return;
+function Yg(e, t) {
+ if (e.getMarkerSyntax() !== "opening" || !AE(e.getTextContent(), t)) return;
const r = e.getParent();
- if (!se(r)) return;
+ if (!ae(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 (se(i))
+ if (ae(i))
return [i, r];
}
-function Ig(e, t) {
- const r = $g(e, t.getMarker);
- return r !== void 0 && xg(r, t);
+function Xg(e, t) {
+ const r = Yg(e, t.getMarker);
+ return r !== void 0 && Ig(r, t);
}
-function iE(e, t) {
+function PE(e, t) {
const r = R();
N(r) && [r.anchor, r.focus].forEach((n) => {
n.key === e.getKey() && n.offset > t && n.set(e.getKey(), t, "text");
});
}
-function Lg(e) {
- const t = hM.exec(e);
+function Qg(e) {
+ const t = LM.exec(e);
if (!t) return;
const r = 1 + t[1].length;
return { start: r, end: r + t[2].length };
}
-function sE(e) {
+function NE(e) {
const t = R();
if (!N(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return;
- const r = Lg(e.getTextContent());
+ const r = Qg(e.getTextContent());
if (!r) return;
const { offset: n } = t.anchor;
if (!(n < r.start || n > r.end))
return n - r.start;
}
-function oE(e) {
+function OE(e) {
const t = R();
if (!N(t) || !t.isCollapsed() || t.anchor.key !== e.getKey()) return;
const r = e.getNextSibling();
- if (j(e.getParent()) && M(r)) {
+ if (j(e.getParent()) && v(r)) {
const n = r.getNextSibling();
- if ($(n)) {
- $c(n);
+ if (D(n)) {
+ Kc(n);
return;
}
}
- M(r) ? r.select(1, 1) : e.select(e.getTextContentSize(), e.getTextContentSize());
+ v(r) ? r.select(1, 1) : e.select(e.getTextContentSize(), e.getTextContentSize());
}
-function Md(e, t) {
+function jd(e, t) {
if (t !== void 0) {
- const r = e.getLatest(), n = Lg(r.getTextContent());
+ const r = e.getLatest(), n = Qg(r.getTextContent());
if (n) {
const i = Math.min(n.start + t, n.end);
r.select(i, i);
return;
}
}
- oE(e);
+ OE(e);
}
-function Ed(e, t) {
+function Bd(e, t) {
return e.replace(/^\+/, "") !== t.replace(/^\+/, "");
}
-function Dg(e, t, r) {
+function Zg(e, t, r) {
if (t.startsWith("+") !== e.getNested())
- return Kt(e, r);
- const n = sE(e), i = e.getParent();
- if (se(i)) {
- if (!Rg(t, r.getMarker))
- return Ig(e, r) ? (r.logger?.debug(
+ return Vt(e, r);
+ const n = NE(e), i = e.getParent();
+ if (ae(i)) {
+ if (!Al(t, r.getMarker))
+ return Xg(e, r) ? (r.logger?.debug(
`[MarkerEdit] unknown-split paragraph rejoined its predecessor on rename to "${t}"`
- ), !0) : Kt(e, r);
+ ), !0) : Vt(e, r);
const s = e.getMarker();
- return i.setMarker(t), e.setMarker(t), Ed(s, t) && Md(e, n), r.logger?.debug(`[MarkerEdit] para marker renamed to "${t}"`), !0;
+ return i.setMarker(t), e.setMarker(t), Bd(s, t) && jd(e, n), r.logger?.debug(`[MarkerEdit] para marker renamed to "${t}"`), !0;
}
- if ($(i) || j(i)) {
+ if (D(i) || j(i)) {
const s = t.replace(/^\+/, "");
- if (!($(i) ? rE(t, r.getMarker) : Me.isValidMarker(s)))
- return Kt(e, r);
+ if (!(D(i) ? QM(t, r.getMarker) : Ee.isValidMarker(s)))
+ return Vt(e, r);
const a = e.getMarker();
if (i.getMarker() !== a)
- return Kt(e, r);
+ return Vt(e, r);
i.setMarker(s);
- const c = i.getChildren().filter(P).filter((l) => l.getMarkerSyntax() === "closing" && l.getMarker() === a).at(-1);
- return c && (iE(c, rt(s, c.getNested()).length), c.setMarker(s)), e.setMarker(s), Ed(a, s) && Md(e, n), r.logger?.debug(`[MarkerEdit] ${i.getType()} marker renamed to "${s}"`), !0;
+ const c = i.getChildren().filter(O).filter((l) => l.getMarkerSyntax() === "closing" && l.getMarker() === a).at(-1);
+ return c && (PE(c, rt(s, c.getNested()).length), c.setMarker(s)), e.setMarker(s), Bd(a, s) && jd(e, n), r.logger?.debug(`[MarkerEdit] ${i.getType()} marker renamed to "${s}"`), !0;
}
- return Kt(e, r);
+ return Vt(e, r);
}
-function aE(e) {
+function wE(e) {
const t = R();
if (!N(t)) return !1;
const r = t.isCollapsed() ? [t.anchor] : [t.anchor, t.focus];
@@ -23172,30 +23399,30 @@ function aE(e) {
}
return !!(!t.isCollapsed() && t.getNodes().some((n) => n.is(e)));
}
-function cE(e, t) {
+function qE(e, t) {
const r = e.getTextContent();
- if (Wr(e)) {
+ if (Xr(e)) {
t.pendingKeys.delete(e.getKey());
return;
}
- if (Be(e.getParent()) && Pc(e)) {
+ if (ze(e.getParent()) && Ic(e)) {
t.pendingKeys.delete(e.getKey());
return;
}
- if (!t.pendingKeys.has(e.getKey()) && !aE(e)) {
- ik(e), t.logger?.debug(
+ if (!t.pendingKeys.has(e.getKey()) && !wE(e)) {
+ Ck(e), t.logger?.debug(
`[MarkerEdit] healed machine-drifted glyph bytes back to "${e.getTextContent()}"`
);
return;
}
if (e.getMarkerSyntax() === "opening") {
- const n = dM.exec(r);
+ const n = RM.exec(r);
if (n) {
- t.pendingKeys.delete(e.getKey()), Dg(e, n[1], t);
+ t.pendingKeys.delete(e.getKey()), Zg(e, n[1], t);
return;
}
- if (fM.test(r)) {
- t.pendingKeys.delete(e.getKey()), Kt(e, t);
+ if ($M.test(r)) {
+ t.pendingKeys.delete(e.getKey()), Vt(e, t);
return;
}
t.pendingKeys.add(e.getKey());
@@ -23203,27 +23430,27 @@ function cE(e, t) {
}
if (e.getMarkerSyntax() === "closing") {
const n = e.getParent(), i = rt(e.getMarker(), e.getNested());
- if ($(n) && e.getMarker() === n.getMarker() && n.getLastChild()?.is(e) && r.startsWith(i) && r.length > i.length) {
- const s = R(), o = N(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));
+ if (D(n) && e.getMarker() === n.getMarker() && n.getLastChild()?.is(e) && r.startsWith(i) && r.length > i.length) {
+ const s = R(), 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));
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 lE(e, t) {
+function RE(e, t) {
if (e.getTextContent() === "") {
t.pendingKeys.delete(e.getKey()), e.remove();
return;
}
- if (Ap(e)) {
+ if (jp(e)) {
t.pendingKeys.delete(e.getKey());
return;
}
t.pendingKeys.add(e.getKey());
}
-function Ug(e) {
- if (!mf(e)?.length)
+function em(e) {
+ if (!Nf(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
@@ -23249,18 +23476,18 @@ function Ug(e) {
valueTerminated: new RegExp(`^\\\\${e}[ ]+([^ \\\\]+)[ ]+$`)
};
}
-const Ei = Ug("v"), uE = Ug("c"), Ad = /^[ \u00A0]*$/;
-function Pd(e, t, r) {
+const Oi = em("v"), $E = em("c"), Vd = /^[ \u00A0]*$/;
+function Wd(e, t, r) {
const n = e.getNextSibling();
- if (M(n) && n.getType() === ze.getType() && n.getMode() === "normal" && re(n, oe) !== "attribute") {
+ if (v(n) && n.getType() === Ke.getType() && n.getMode() === "normal" && ne(n, oe) !== "attribute") {
n.setTextContent(t + n.getTextContent()), r !== void 0 && n.select(r, r);
return;
}
- const i = he(t);
+ const i = pe(t);
e.insertAfter(i), r !== void 0 && i.select(r, r);
}
-function dE(e, t) {
- const r = e.getTextContent(), n = $t("v", e.getNumber());
+function IE(e, t) {
+ const r = e.getTextContent(), n = Lt("v", e.getNumber());
if (r === n) {
t.pendingKeys.delete(e.getKey());
return;
@@ -23268,31 +23495,31 @@ function dE(e, t) {
const i = /^[ \u00A0]+/.exec(r);
if (i) {
const c = r.slice(i[0].length);
- if (Ei.midEdit.test(c)) {
+ if (Oi.midEdit.test(c)) {
t.pendingKeys.add(e.getKey());
return;
}
- const l = Ei.valueAndRest.exec(c);
- if (l && Ad.test(l[2] ?? "")) {
+ const l = Oi.valueAndRest.exec(c);
+ if (l && Vd.test(l[2] ?? "")) {
t.pendingKeys.delete(e.getKey()), l[1] !== e.getNumber() && e.setNumber(l[1]);
return;
}
}
- if (Ei.midEdit.test(r)) {
+ if (Oi.midEdit.test(r)) {
t.pendingKeys.add(e.getKey());
return;
}
- const s = Ei.valueAndRest.exec(r);
+ const s = Oi.valueAndRest.exec(r);
if (!s) {
- const c = Ei.markerRest.exec(r);
+ const c = Oi.markerRest.exec(r);
if (c) {
const [, l, u, d] = c, f = R(), 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($t("v", u));
+ 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;
- Pd(e, d, m);
+ Wd(e, d, m);
return;
}
- t.pendingKeys.delete(e.getKey()), Kt(e, t);
+ t.pendingKeys.delete(e.getKey()), Vt(e, t);
return;
}
const [, o, a] = s;
@@ -23300,141 +23527,141 @@ function dE(e, t) {
t.pendingKeys.add(e.getKey());
return;
}
- if (t.pendingKeys.delete(e.getKey()), Ad.test(a ?? "")) {
+ if (t.pendingKeys.delete(e.getKey()), Vd.test(a ?? "")) {
o !== e.getNumber() && e.setNumber(o);
return;
}
- e.setNumber(o), e.setTextContent($t("v", o)), a && Pd(e, a, a.length);
+ e.setNumber(o), e.setTextContent(Lt("v", o)), a && Wd(e, a, a.length);
}
-const fE = /^[ \u00A0]+([^ \u00A0\\]+)[ \u00A0]+$/;
-function pE(e, t) {
+const LE = /^[ \u00A0]+([^ \u00A0\\]+)[ \u00A0]+$/;
+function DE(e, t) {
const r = e.getParent();
- if (!j(r) || r.getIsCollapsed() !== !1 || !mf(r.getMarker())?.includes("caller")) return !1;
+ if (!j(r) || r.getIsCollapsed() !== !1 || !Nf(r.getMarker())?.includes("caller")) return !1;
const n = r.getChildren();
let i = 0;
for (; i < n.length; ) {
const c = n[i];
- if (!P(c) || c.getMarkerSyntax() !== "opening") break;
+ if (!O(c) || c.getMarkerSyntax() !== "opening") break;
i++;
}
if (!e.is(n[i])) return !1;
const s = e.getTextContent();
- if (s === St(r.getCaller()))
+ if (s === At(r.getCaller()))
return t.pendingKeys.delete(e.getKey()), !0;
- const o = fE.exec(s);
+ const o = LE.exec(s);
if (!o) return !1;
const [, a] = o;
- return t.pendingKeys.delete(e.getKey()), r.setCaller(a), e.setTextContent(St(a)), !0;
+ return t.pendingKeys.delete(e.getKey()), r.setCaller(a), e.setTextContent(At(a)), !0;
}
-function hE(e) {
+function UE(e) {
if (e.getChildrenSize() === 0) {
e.remove();
return;
}
const t = e.getFirstChild();
- if (!M(t)) return;
- const r = $t("c", e.getNumber()), n = t.getTextContent();
+ if (!v(t)) return;
+ const r = Lt("c", e.getNumber()), n = t.getTextContent();
if (n === r) return;
- const i = /^[ \u00A0]+/.exec(n), s = i ? n.slice(i[0].length) : n, o = uE.valueTerminated.exec(s);
+ const i = /^[ \u00A0]+/.exec(n), s = i ? n.slice(i[0].length) : n, o = $E.valueTerminated.exec(s);
o && o[1] !== e.getNumber() && e.setNumber(o[1]);
}
-function Fg(e) {
- if (je(e)) {
- const { wrapper: t } = go(e);
+function tm(e) {
+ if (Be(e)) {
+ const { wrapper: t } = mo(e);
return t && t.getChildrenSize() === 0 ? [t] : [];
}
if (j(e)) {
- const { wrapper: t } = gp(e);
+ const { wrapper: t } = Pp(e);
return t && t.getChildrenSize() === 0 ? [t] : [];
}
if ($e(e)) {
- const t = [], r = mp(e);
+ const t = [], r = Np(e);
r.wrapper && r.wrapper.getChildrenSize() === 0 && t.push(r.wrapper);
- const n = bp(e);
+ const n = wp(e);
return n.wrapper && n.wrapper.getChildrenSize() === 0 && t.push(n.wrapper), t;
}
- if (Ne(e)) {
- const t = [], r = Bi(e, "va");
+ if (Pe(e)) {
+ const t = [], r = Yi(e, "va");
r.wrapper && r.wrapper.getChildrenSize() === 0 && t.push(r.wrapper);
- const n = r.wrapper ?? r.closer ?? e, i = Bi(n, "vp");
+ const n = r.wrapper ?? r.closer ?? e, i = Yi(n, "vp");
return i.wrapper && i.wrapper.getChildrenSize() === 0 && t.push(i.wrapper), t;
}
return [];
}
-function gE(e) {
+function FE(e) {
const t = R();
if (!N(t) || !t.isCollapsed()) return !1;
const r = t.anchor.getNode();
- return Fg(e).some((n) => r.is(n));
+ return tm(e).some((n) => r.is(n));
}
-function mE(e, t, r = "departure") {
+function zE(e, t, r = "departure") {
let n = !1;
const i = r === "departure";
- if (i && se(e) && ap(e))
+ if (i && ae(e) && xp(e))
return t.pendingKeys.add(e.getKey()), { handled: !0, mutated: !1 };
- for (const l of Wi)
- if (l.settleScope !== "none" && l.ownerPredicate(e) && us(l, e) && (i || gE(e)))
+ for (const l of Qi)
+ if (l.settleScope !== "none" && l.ownerPredicate(e) && gs(l, e) && (i || FE(e)))
return t.pendingKeys.add(e.getKey()), { handled: !0, mutated: !1 };
- for (const l of Fg(e))
+ for (const l of tm(e))
l.remove(), n = !0;
let s = !1;
- if ($(e)) {
- const l = uk(e);
- l !== void 0 && Vy(l) && (xp(e), s = !0, n = !0);
+ if (D(e)) {
+ const l = Pk(e);
+ l !== void 0 && ub(l) && ($p(e), s = !0, n = !0);
}
let o = !1, a = !1, c = !1;
- for (const l of Wi)
+ for (const l of Qi)
if (l.settleScope !== "none" && l.ownerPredicate(e)) {
- if (sT(l, e)) {
- Gi(l, e), a = !0, n = !0;
+ if (CT(l, e)) {
+ es(l, e), a = !0, n = !0;
continue;
}
if (l.deletionPolicy === "none") {
o = !0;
continue;
}
- if (l.deletionPolicy === "remove-owner" && Lp(l, e))
+ if (l.deletionPolicy === "remove-owner" && Qp(l, e))
return e.remove(), { handled: !0, mutated: !0 };
- ko(l, l.scanPieces(e), l.expectedPieces(e)) && (c = !0);
+ To(l, l.scanPieces(e), l.expectedPieces(e)) && (c = !0);
}
return (a || s) && !c ? { handled: !0, mutated: n } : { handled: o, mutated: n };
}
-function Nd(e) {
- return M(e) && e.getType() === ze.getType() && e.getMode() === "normal" && re(e, oe) !== "attribute";
+function Hd(e) {
+ return v(e) && e.getType() === Ke.getType() && e.getMode() === "normal" && ne(e, oe) !== "attribute";
}
-function yE(e) {
+function KE(e) {
const t = /* @__PURE__ */ new Set();
if (e === void 0) return t;
t.add(e);
- const r = ne(e);
+ const r = se(e);
if (!r?.isAttached()) return t;
- for (let n = r.getPreviousSibling(); n && Nd(n); n = n.getPreviousSibling())
+ for (let n = r.getPreviousSibling(); n && Hd(n); n = n.getPreviousSibling())
t.add(n.getKey());
- for (let n = r.getNextSibling(); n && Nd(n); n = n.getNextSibling())
+ for (let n = r.getNextSibling(); n && Hd(n); n = n.getNextSibling())
t.add(n.getKey());
return t;
}
-function Cs(e, t, r = "departure") {
+function As(e, t, r = "departure") {
let n = !1;
if (e.pendingKeys.size === 0) return n;
- const i = yE(t), s = [...e.pendingKeys].filter((a) => !i.has(a)), o = /* @__PURE__ */ new Set();
+ const i = KE(t), s = [...e.pendingKeys].filter((a) => !i.has(a)), o = /* @__PURE__ */ new Set();
for (const a of s) {
- const c = ne(a);
+ const c = se(a);
if (!c?.isAttached()) {
e.pendingKeys.delete(a);
continue;
}
- if (P(c)) {
+ if (O(c)) {
e.pendingKeys.delete(a);
const p = c.getTextContent();
- if (Wr(c)) continue;
- const m = lg.exec(p);
- c.getMarkerSyntax() === "opening" && m ? n = Dg(c, m[1], e) || n : r === "idle" && _d(c, e.getMarker, e.viewOptions) ? e.pendingKeys.add(a) : Ig(c, e) ? (n = !0, e.logger?.debug(
+ if (Xr(c)) continue;
+ const m = Sg.exec(p);
+ c.getMarkerSyntax() === "opening" && m ? n = Zg(c, m[1], e) || n : r === "idle" && Ud(c, e.getMarker, e.viewOptions) ? e.pendingKeys.add(a) : Xg(c, e) ? (n = !0, e.logger?.debug(
"[MarkerEdit] unknown-split paragraph rejoined its predecessor on marker degradation"
- )) : n = Kt(c, e) || n;
+ )) : n = Vt(c, e) || n;
continue;
}
- const l = bn(c)?.owner, u = l?.isAttached() ? l : c, d = u.getKey();
+ const l = xn(c)?.owner, u = l?.isAttached() ? l : c, d = u.getKey();
if (o.has(d)) {
a !== d && e.pendingKeys.delete(a);
continue;
@@ -23444,65 +23671,65 @@ function Cs(e, t, r = "departure") {
continue;
}
e.pendingKeys.delete(a), a !== d && e.pendingKeys.delete(d), o.add(d);
- const f = mE(u, e, r);
+ const f = zE(u, e, r);
if (n = f.mutated || n, !f.handled) {
- if (r === "idle" && _d(u, e.getMarker, e.viewOptions)) {
+ if (r === "idle" && Ud(u, e.getMarker, e.viewOptions)) {
e.pendingKeys.add(d);
continue;
}
- n = Kt(u, e) || n;
+ n = Vt(u, e) || n;
}
}
return n;
}
-function zg(e) {
- if (Hr(e.getNextSibling())) return !0;
+function rm(e) {
+ if (Qr(e.getNextSibling())) return !0;
for (let t = e.getParent(); t; t = t.getParent())
- if ($(t)) return qi(t) !== void 0;
+ if (D(t)) return Li(t) !== void 0;
return !1;
}
-function bE(e) {
- const t = bn(e);
+function jE(e) {
+ const t = xn(e);
if (!t) return !1;
- const r = yn(t.kind);
- return !ko(
+ const r = Tn(t.kind);
+ return !To(
r,
r.scanPieces(t.owner),
r.expectedPieces(t.owner)
);
}
-function Od(e) {
+function Gd(e) {
for (let t = e.getParent(); t; t = t.getParent())
- if (Tt(t) || Le(t) || Op(t)) return !0;
+ if (_t(t) || Le(t) || Wp(t)) return !0;
return !1;
}
-function kE(e, t) {
- const r = e.getTextContent(), n = re(e, oe), i = e.getParent();
+function BE(e, t) {
+ const r = e.getTextContent(), n = ne(e, oe), i = e.getParent();
if (n !== "attribute" && $e(i)) {
- r.replace(/^[ \u00A0]+/, "") === $t("c", i.getNumber()) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey());
+ r.replace(/^[ \u00A0]+/, "") === Lt("c", i.getNumber()) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey());
return;
}
- if (pE(e, t)) return;
+ if (DE(e, t)) return;
if (n === "attribute") {
- bE(e) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey());
+ jE(e) ? t.pendingKeys.delete(e.getKey()) : t.pendingKeys.add(e.getKey());
return;
}
if (!r.includes("\\")) {
- if (r.includes("|") && zg(e)) t.pendingKeys.add(e.getKey());
- else if (r.includes("//") && !Od(e))
+ if (r.includes("|") && rm(e)) t.pendingKeys.add(e.getKey());
+ else if (r.includes("//") && !Gd(e))
t.pendingKeys.add(e.getKey());
- else if (kp(e)) t.pendingKeys.add(e.getKey());
- else if ($e(ts(e))) t.pendingKeys.add(e.getKey());
+ else if (qp(e)) t.pendingKeys.add(e.getKey());
+ else if ($e(as(e))) t.pendingKeys.add(e.getKey());
else {
const a = e.getParent();
- $(a) && _p(a) && t.pendingKeys.add(a.getKey()), t.pendingKeys.delete(e.getKey());
+ D(a) && Ip(a) && t.pendingKeys.add(a.getKey()), t.pendingKeys.delete(e.getKey());
}
return;
}
- if (Od(e)) return;
+ if (Gd(e)) return;
const s = R(), o = N(s) && s.isCollapsed() && s.anchor.key === e.getKey() ? r.slice(0, s.anchor.offset) : r;
- if (gM.test(o)) {
- if (eb(r)) {
+ if (DM.test(o)) {
+ if (kb(r)) {
t.pendingKeys.add(e.getKey());
return;
}
@@ -23510,178 +23737,340 @@ function kE(e, t) {
t.pendingKeys.add(e.getKey());
return;
}
- t.rebuildAttempted.add(r), Kt(e, t);
+ t.rebuildAttempted.add(r), Vt(e, t);
} else
t.pendingKeys.add(e.getKey());
}
-function TE(e, t) {
- return e.deletionPolicy !== "remove-owner" || e.byteFormat.writer !== "read-only" ? !1 : Lp(e, t);
+function VE(e, t) {
+ return e.deletionPolicy !== "remove-owner" || e.byteFormat.writer !== "read-only" ? !1 : Qp(e, t);
}
-function xE(e) {
+function WE(e) {
const t = (r) => {
- if (P(r)) {
- Wr(r) || e.pendingKeys.add(r.getKey());
+ if (O(r)) {
+ Xr(r) || e.pendingKeys.add(r.getKey());
return;
}
- if (Hr(r)) {
- Ap(r) || e.pendingKeys.add(r.getKey());
+ if (Qr(r)) {
+ jp(r) || e.pendingKeys.add(r.getKey());
return;
}
- for (const n of Wi)
- n.settleScope !== "none" && n.ownerPredicate(r) && (us(n, r) || TE(n, r)) && e.pendingKeys.add(r.getKey());
- if (Ne(r)) {
- r.getTextContent() !== $t("v", r.getNumber()) && e.pendingKeys.add(r.getKey());
+ for (const n of Qi)
+ n.settleScope !== "none" && n.ownerPredicate(r) && (gs(n, r) || VE(n, r)) && e.pendingKeys.add(r.getKey());
+ if (Pe(r)) {
+ r.getTextContent() !== Lt("v", r.getNumber()) && e.pendingKeys.add(r.getKey());
return;
}
- if (M(r)) {
- if (r.getType() !== ze.getType() || re(r, oe) === "attribute") return;
+ if (v(r)) {
+ if (r.getType() !== Ke.getType() || ne(r, oe) === "attribute") return;
const n = r.getParent();
if ($e(n)) {
- r.getTextContent() !== $t("c", n.getNumber()) && e.pendingKeys.add(r.getKey());
+ r.getTextContent() !== Lt("c", n.getNumber()) && e.pendingKeys.add(r.getKey());
return;
}
const i = r.getTextContent();
- (i.includes("\\") || i.includes("|") && zg(r) || i.includes("//") || kp(r) !== void 0) && e.pendingKeys.add(r.getKey());
+ (i.includes("\\") || i.includes("|") && rm(r) || i.includes("//") || qp(r) !== void 0) && e.pendingKeys.add(r.getKey());
return;
}
- if ($(r)) {
+ if (D(r)) {
r.getChildren().forEach(t);
return;
}
- if (!Le(r) && !Tt(r)) {
- if (Be(r) && r.getChildrenSize() === 0) {
- const n = bn(r)?.owner;
+ if (!Le(r) && !_t(r)) {
+ if (ze(r) && r.getChildrenSize() === 0) {
+ const n = xn(r)?.owner;
n && e.pendingKeys.add(n.getKey());
return;
}
- D(r) && r.getChildren().forEach(t);
+ F(r) && r.getChildren().forEach(t);
}
};
Ue().getChildren().forEach(t);
}
-function _E(e) {
+const ic = "usfm:", HE = "\uFEFF", GE = /^usfm_(.+)$/;
+function JE(e) {
+ return e.nodeType === Node.ELEMENT_NODE;
+}
+function YE(e) {
+ return e.replace(
+ /%([0-9a-fA-F]{4})/g,
+ (t, r) => String.fromCharCode(Number.parseInt(r, 16))
+ );
+}
+function XE(e) {
+ return e.startsWith(ic) ? YE(e.slice(ic.length)).replace(/\r\n?|\n/g, " ") : "";
+}
+function nm(e) {
+ for (const t of e.classList) {
+ const r = GE.exec(t);
+ if (r) return r[1];
+ }
+}
+function QE(e) {
+ const t = nm(e);
+ if (t !== void 0)
+ return e.classList.contains("nested") ? `+${t}` : t;
+}
+function ZE(e) {
+ const t = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_COMMENT);
+ for (let r = t.nextNode(); r; r = t.nextNode())
+ if ((r.nodeValue ?? "").startsWith(ic)) return !0;
+ for (const r of e.querySelectorAll("*")) {
+ const { classList: n } = r;
+ if (!(!n.contains("usfmopen") && !n.contains("usfmclosed")) && nm(r) !== void 0)
+ return !0;
+ }
+ return !1;
+}
+function im(e, t, r) {
+ if (e.nodeType === Node.TEXT_NODE) {
+ t || r.push(e.nodeValue ?? "");
+ return;
+ }
+ if (e.nodeType === Node.COMMENT_NODE) {
+ r.push(XE(e.nodeValue ?? ""));
+ return;
+ }
+ if (!JE(e)) return;
+ const { classList: n } = e, i = (u) => e.childNodes.forEach((d) => im(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" ? QE(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 eA(e) {
+ const { body: t } = new DOMParser().parseFromString(e, "text/html");
+ if (t.querySelectorAll("script,style,template").forEach((n) => n.remove()), !ZE(t)) return;
+ const r = [];
+ return t.childNodes.forEach((n) => im(n, !1, r)), r.join("").replaceAll(HE, "").replaceAll(L, " ").replace(/\r\n?/g, `
+`).replace(/\n+/g, `
+`).replace(/^\n|\n$/g, "");
+}
+function tA(e) {
const t = e.getTextContent();
if (!t.includes(" ")) return;
- const r = re(e, oe);
- if (r === "attribute" || r === sr) return;
+ const r = ne(e, oe);
+ if (r === "attribute" || r === fr) return;
for (let o = e.getParent(); o; o = o.getParent())
- if (Tt(o) || $e(o) || Le(o)) return;
- const n = t.startsWith(w) && $(e.getParent()), i = n ? t.slice(1) : t, s = (n ? w : "") + i.replace(/ (?=[ \u00A0])/g, w).replace(new RegExp("(?<=\\u00A0) ", "g"), w);
+ if (_t(o) || $e(o) || Le(o)) return;
+ const n = t.startsWith(L) && D(e.getParent()), i = n ? t.slice(1) : t, s = (n ? L : "") + i.replace(/ (?=[ \u00A0])/g, L).replace(new RegExp("(?<=\\u00A0) ", "g"), L);
s !== t && e.setTextContent(s);
}
-function CE(e) {
+function rA(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 nc(e) {
- const t = e && typeof e == "object" && "clipboardData" in e ? e.clipboardData : void 0;
- if (!t) return;
- const r = (o) => o.replace(/\r\n?/g, `
-`), n = r(t.getData("text/plain")), i = t.getData("text/html"), s = i ? r(CE(i)) : "";
+function nA(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 sc(e, t) {
+ const r = e && typeof e == "object" && "clipboardData" in e ? e.clipboardData : void 0;
+ if (!r) return;
+ const n = (c) => c.replace(/\r\n?/g, `
+`), i = n(r.getData("text/plain")), s = r.getData("text/html"), o = s ? n(rA(s)) : "", a = s ? eA(s) : void 0;
return {
- plainText: n,
- html: i,
- htmlText: s,
- text: n || s,
- isInternal: !!t.getData("application/x-lexical-editor")
+ text: a !== void 0 ? n(a) : i || o,
+ isInternal: nA(
+ r.getData("application/x-lexical-editor"),
+ t
+ )
};
}
-function vE(e) {
- const t = nc(e);
- if (!t || t.isInternal) return !1;
- const { plainText: r, html: n, htmlText: i } = t, s = r.includes(w) ? r : n.includes(w) || i.includes(w) ? i : void 0;
- if (!s) return !1;
- const o = R();
- if (!N(o)) return !1;
- e?.preventDefault();
- const a = s.replaceAll(w, "~"), c = a.split(`
+const sm = String.raw`\\(?:\+?[${Zt}]+\*?|\*)`, iA = new RegExp(String.raw`(${sm})\u00A0`, "g"), sA = new RegExp(String.raw`\u00A0(?=${sm})`, "g");
+function Ol(e) {
+ return e.replace(/^\u00A0/gm, " ").replace(iA, "$1 ").replace(sA, "").replaceAll(L, "~");
+}
+const oA = new RegExp(
+ String.raw`\\c(?![${Zt}])[ \u00A0]*[^\s\\]*`,
+ "g"
+), aA = new RegExp(String.raw`\\id(?![${Zt}])[^\n\\]*`, "g");
+function wl(e) {
+ return e.split(`
+`).map((t) => {
+ const r = t.replace(oA, "").replace(aA, "");
+ return r === "" && t !== "" ? void 0 : r;
+ }).filter((t) => t !== void 0).join(`
`);
+}
+function oc(e) {
+ if (v(e) && ne(e, oe) === "attribute") return !0;
+ for (let t = e; t; t = t.getParent())
+ if (ze(t)) return !0;
+ return !1;
+}
+function cA(e) {
+ return oc(e.anchor.getNode()) || oc(e.focus.getNode());
+}
+function lA(e) {
+ const { anchor: t, focus: r } = e;
+ return t.key === r.key && oc(t.getNode());
+}
+function uA(e, t) {
+ const n = lA(e) ? t : Ol(wl(t));
+ e.insertText(n.replace(/\n/g, " "));
+}
+function dA(e, t = !1, r = () => {
+}) {
+ const n = sc(e, Xn()._config.namespace);
+ if (!n) return !1;
+ const i = R(), s = N(i) && cA(i);
+ if (!s && n.isInternal || t && N(i) && Fi(i))
+ return !1;
+ const { text: o } = n;
+ if (!o || !N(i)) return !1;
+ if (e?.preventDefault(), s)
+ return uA(i, o), !0;
+ const a = Ol(wl(o)), c = a.split(`
+`);
+ if (t)
+ return i.insertText(c.join(" ")), !0;
if (c.length < 2)
- return o.insertText(a), !0;
- o.isCollapsed() || o.removeText();
- const l = ss();
+ return i.insertText(a), !0;
+ r(), i.isCollapsed() || i.removeText();
+ const l = Xn();
return c.forEach((u, d) => {
- if (d > 0 && l.dispatchCommand(As, void 0), u === "") return;
+ if (d > 0 && l.dispatchCommand(qs, void 0), u === "") return;
const f = R();
N(f) && f.insertText(u);
}), !0;
}
-function SE(e) {
- const { body: t } = new DOMParser().parseFromString(e, "text/html"), r = t.ownerDocument.createTreeWalker(t, NodeFilter.SHOW_TEXT), n = [];
- for (let a = r.nextNode(); a; a = r.nextNode()) n.push(a);
- const i = n.map((a) => a.nodeValue ?? "").join(""), s = i.replace(
- /\u00A0+/g,
- (a, c) => a.length >= 2 || c === 0 || c + a.length === i.length ? a : " "
- );
- if (s === i) return e;
- let o = 0;
- for (const a of n) {
- const c = (a.nodeValue ?? "").length;
- a.nodeValue = s.slice(o, o + c), o += c;
+function fA(e) {
+ if (e.getTextContent() !== L) return !1;
+ const t = e.getParent();
+ return j(t) ? !St(e.getPreviousSibling()) : !1;
+}
+function pA(e, t) {
+ if (t || e.getTextContent() !== L) return "";
+ const r = e.getParent();
+ if (!j(r) || !St(e.getPreviousSibling())) return "";
+ const n = r.getCategory();
+ return n ? `\\cat ${n}\\cat*` : "";
+}
+function hA(e) {
+ const t = e.getParent();
+ return (j(t) ? t.getCaller() : void 0) || Bi;
+}
+function gA(e) {
+ const t = e.getParent();
+ return !t || Hr(t) === void 0;
+}
+function mA(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] = gc(e);
+ let l = "", u = !0;
+ for (const d of t) {
+ if (F(d) && !d.isInline()) {
+ !u && gA(d) && (l += `
+`), u = !d.isEmpty();
+ continue;
+ }
+ if (u = !1, St(d))
+ (d !== n || !e.isCollapsed()) && (l += ` ${hA(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 += fA(d) ? "" : f.replaceAll(L, " ") + pA(d, d === n);
+ } else (co(d) || us(d)) && (d !== n || !e.isCollapsed()) && (l += d.getTextContent());
}
- return t.innerHTML;
+ return l;
}
-function ME(e) {
+function yA(e) {
+ return e.split(`
+`).map((t) => t.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")).map(
+ (t) => t ? `${t}
` : "
"
+ ).join("");
+}
+function bA(e) {
const t = R();
if (!N(t) || t.isCollapsed()) return;
- const r = {
- "text/plain": t.getTextContent().replaceAll(w, " ")
- }, n = oy(e), i = ay(e);
- return n && (r["text/html"] = SE(n)), i && (r["application/x-lexical-editor"] = i), r;
+ const r = mA(t), n = {
+ "text/plain": r,
+ "text/html": yA(r)
+ };
+ if (sl()) return n;
+ const i = Ay(e);
+ return i && (n["application/x-lexical-editor"] = i), n;
}
-function wd(e, t, r) {
+function Jd(e, t, r) {
const n = R();
- if (!N(n) || n.isCollapsed()) return !1;
- const i = ME(t);
+ if (!N(n) || n.isCollapsed())
+ return (!e || !("clipboardData" in e)) && !Uh();
+ const i = bA(t);
if (!i) return !1;
+ const s = !i["text/plain"] && !i["application/x-lexical-editor"];
if (!e || !("clipboardData" in e))
- return sy(t, null, i), r && n.removeText(), !0;
+ return s || Ey(t, null, i), r && n.removeText(), !0;
if (e.clipboardData == null) return !1;
- e.preventDefault();
- for (const [s, o] of Object.entries(i)) e.clipboardData.setData(s, o);
+ if (e.preventDefault(), !s)
+ for (const [o, a] of Object.entries(i)) e.clipboardData.setData(o, a);
return r && n.removeText(), !0;
}
-const Kg = sf(
+const om = kf(
"COMMIT_PENDING_MARKERS_COMMAND"
);
function ga(e) {
const t = e();
- return Dr(Yd), Dr(Tf), t;
+ return Kr(pf), Kr(Rf), t;
}
-const qd = 8, EE = 1e3;
-function Bn(e, t) {
- const r = Ne(e) ? ["va", "vp"] : je(e) ? ["milestone"] : j(e) ? ["cat"] : (
+const Yd = 8, kA = 1e3;
+function Vn(e, t) {
+ const r = Pe(e) ? ["va", "vp"] : Be(e) ? ["milestone"] : j(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(yn(n), e, t.pendingKeys);
+ AT(Tn(n), e, t.pendingKeys);
}
-function AE(e, t) {
+function TA(e, t) {
const r = (n, i) => {
- if (i.updateTags.has(hc) || i.updateTags.has(Fi)) return;
+ if (i.updateTags.has(Tc) || i.updateTags.has(Vi)) return;
const s = [];
i.prevEditorState.read(() => {
for (const [o, a] of n) {
if (a !== "destroyed") continue;
- const c = ne(o);
+ const c = se(o);
if (!c) continue;
- const l = bn(c);
+ const l = xn(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 = ne(o.getKey());
- c?.isAttached() && yn(a).expectedPieces(c).wantsRun && t.pendingKeys.add(c.getKey());
+ const c = se(o.getKey());
+ c?.isAttached() && Tn(a).expectedPieces(c).wantsRun && t.pendingKeys.add(c.getKey());
}
});
};
- return Xe(
+ return He(
// 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
@@ -23692,121 +24081,122 @@ function AE(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(ze, r),
- e.registerMutationListener(ar, r),
- e.registerMutationListener(_r, r),
- e.registerMutationListener(vr, r)
+ e.registerMutationListener(Ke, r),
+ e.registerMutationListener(hr, r),
+ e.registerMutationListener(Mr, r),
+ e.registerMutationListener(Ar, r)
);
}
-function PE(e, t, r) {
- return Xe(
+function xA(e, t, r) {
+ return He(
e.registerCommand(
- dr,
+ yr,
(n) => {
- if (Th()) return !1;
- const i = nc(n);
+ if (sl()) return !1;
+ const i = sc(n, e._config.namespace);
if (!i) return !1;
const s = i.text;
if (s.includes(`
`)) {
- const a = (r ? s.replaceAll(w, "~") : s).split(`
+ const a = (r ? Ol(wl(s)) : s).split(`
`);
- let c = vd(a, t.getMarker);
- if (c === "declined" && zM(e) && (c = vd(a, t.getMarker)), c === "handled")
+ let c = zd(a, t.getMarker);
+ if (c === "declined" && pE(e) && (c = zd(a, t.getMarker)), c === "handled")
return n?.preventDefault(), !0;
}
return !1;
},
- fr
+ ar
),
e.registerCommand(
- dr,
+ yr,
(n) => {
- const i = nc(n);
+ const i = sc(n, e._config.namespace);
if (!i || i.isInternal || !i.text) return !1;
const s = i.text.split(`
`);
- if (s.length < 2 || !PS()) return !1;
- n?.preventDefault();
+ if (s.length < 2 || !Qv()) return !1;
const o = R();
- return N(o) && !o.isCollapsed() && o.removeText(), s.forEach((a, c) => {
- if (c > 0 && e.dispatchCommand(As, void 0), a === "") return;
+ return t.structureProtectionMode === "protected" && N(o) && Fi(o) ? !1 : (n?.preventDefault(), N(o) && !o.isCollapsed() && o.removeText(), s.forEach((a, c) => {
+ if (c > 0 && e.dispatchCommand(qs, void 0), a === "") return;
const l = R();
N(l) && l.insertText(a);
- }), !0;
+ }), !0);
},
Ie
),
e.registerCommand(
- dr,
+ yr,
() => (t.splitExpected.current = !0, !1),
- Rt
+ kt
)
);
}
-function NE({
+function _A({
viewOptions: e,
getMarker: t,
logger: r,
- markerSettleDelayMs: n
+ markerSettleDelayMs: n,
+ structureProtectionMode: i = "off"
}) {
- const [i] = le(), s = e?.markerMode === "editable", o = !!e && Co(e), a = X(void 0), c = X(n);
- return K(() => {
- c.current = n;
- const l = a.current;
- l && (e && (l.viewOptions = e), l.getMarker = t ?? nr, l.logger = r);
- }, [e, t, r, n]), K(() => {
- if (!s || !e) return;
- const l = {
+ const [s] = ce(), o = e?.markerMode === "editable", a = !!e && So(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 = {
viewOptions: e,
- getMarker: t ?? nr,
+ getMarker: t ?? lr,
pendingKeys: /* @__PURE__ */ new Set(),
splitExpected: { current: !1 },
wholeParaDeleteExpected: /* @__PURE__ */ new Set(),
collapsedDeleteCaretParas: /* @__PURE__ */ new Set(),
rebuildAttempted: /* @__PURE__ */ new Set(),
- logger: r
+ logger: r,
+ structureProtectionMode: i
};
- a.current = l;
- const u = tT(i, l.pendingKeys);
- let d, f = !1, p, m = !1, g = !1, y = 0;
- const T = () => y < qd ? !1 : (l.logger?.warn(
- `[MarkerEdit] settle cascade exceeded ${qd} consecutive mutating passes; leaving ${l.pendingKeys.size} node(s) pending. This is a rebuild that never reaches a fixed point — pending keys: ${[...l.pendingKeys].join(", ")}`
- ), !0), S = (x, F = "departure") => {
- i.update(() => {
- y = ga(
- () => Cs(l, x, F)
- ) ? y + 1 : 0;
+ c.current = u;
+ const d = kT(s, u.pendingKeys);
+ let f, p = !1, m, g = !1, y = !1, k = 0;
+ const _ = () => k < Yd ? !1 : (u.logger?.warn(
+ `[MarkerEdit] settle cascade exceeded ${Yd} 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, w = "departure") => {
+ s.update(() => {
+ k = ga(
+ () => As(u, M, w)
+ ) ? k + 1 : 0;
});
};
- let v;
- const E = () => {
- if (v !== void 0 && clearTimeout(v), v = void 0, g || l.pendingKeys.size === 0) return;
- const x = c.current ?? EE;
- x < 0 || (v = setTimeout(() => {
- v = void 0, !(g || l.pendingKeys.size === 0) && (f || T() || S(void 0, "idle"));
- }, x));
- }, A = Xe(
- i.registerNodeTransform(ar, (x) => {
- if (i.isComposing()) return;
- cE(x, l);
- const F = bn(x);
- F && (Ne(F.owner) || j(F.owner) || $e(F.owner) || je(F.owner) && go(F.owner).wrapper === void 0) && Bn(F.owner, l);
+ let P;
+ const A = () => {
+ if (P !== void 0 && clearTimeout(P), P = void 0, y || u.pendingKeys.size === 0) return;
+ const M = l.current ?? kA;
+ M < 0 || (P = setTimeout(() => {
+ P = void 0, !(y || u.pendingKeys.size === 0) && (p || _() || S(void 0, "idle"));
+ }, M));
+ }, B = He(
+ s.registerNodeTransform(hr, (M) => {
+ if (s.isComposing()) return;
+ qE(M, u);
+ const w = xn(M);
+ w && (Pe(w.owner) || j(w.owner) || $e(w.owner) || Be(w.owner) && mo(w.owner).wrapper === void 0) && Vn(w.owner, u);
}),
- i.registerNodeTransform(ft, (x) => {
- i.isComposing() || (dE(x, l), Bn(x, l));
+ s.registerNodeTransform(dt, (M) => {
+ s.isComposing() || (IE(M, u), Vn(M, u));
}),
- i.registerNodeTransform(Et, (x) => {
- i.isComposing() || (hE(x), x.isAttached() && Bn(x, l));
+ s.registerNodeTransform(Nt, (M) => {
+ s.isComposing() || (UE(M), M.isAttached() && Vn(M, u));
}),
- i.registerNodeTransform(Qe, (x) => {
- i.isComposing() || $M(x, l);
+ s.registerNodeTransform(Qe, (M) => {
+ s.isComposing() || aE(M, u);
}),
- i.registerNodeTransform(ye, (x) => {
- if (!i.isComposing()) {
- UM(x, l);
- for (const F of ["separator", "char"])
- x.isAttached() && us(yn(F), x) && l.pendingKeys.add(x.getKey());
+ s.registerNodeTransform(me, (M) => {
+ if (!s.isComposing()) {
+ dE(M, u);
+ for (const w of ["separator", "char"])
+ M.isAttached() && gs(Tn(w), M) && u.pendingKeys.add(M.getKey());
}
}),
// Self-healing milestone display run (the shared $syncDisplayRun driver,
@@ -23822,29 +24212,29 @@ function NE({
// 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).
- i.registerNodeTransform(Vt, (x) => {
- i.isComposing() || Bn(x, l);
+ s.registerNodeTransform(Gt, (M) => {
+ s.isComposing() || Vn(M, u);
}),
- i.registerNodeTransform(vr, (x) => {
- if (i.isComposing()) return;
- const F = bn(x);
- F && (je(F.owner) || Ne(F.owner) || j(F.owner) || $e(F.owner)) && Bn(F.owner, l);
+ s.registerNodeTransform(Ar, (M) => {
+ if (s.isComposing()) return;
+ const w = xn(M);
+ w && (Be(w.owner) || Pe(w.owner) || j(w.owner) || $e(w.owner)) && Vn(w.owner, u);
}),
- i.registerNodeTransform(Me, (x) => {
- i.isComposing() || (DM(x, l), Bn(x, l));
+ s.registerNodeTransform(Ee, (M) => {
+ s.isComposing() || (uE(M, u), Vn(M, u));
}),
// 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.
- i.registerNodeTransform(Mr, (x) => {
- i.isComposing() || lE(x, l);
+ s.registerNodeTransform(Nr, (M) => {
+ s.isComposing() || RE(M, u);
}),
// 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.
- i.registerNodeTransform(ze, (x) => {
- i.isComposing() || kE(x, l);
+ s.registerNodeTransform(Ke, (M) => {
+ s.isComposing() || BE(M, u);
}),
// 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
@@ -23875,157 +24265,165 @@ function NE({
// 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.
- i.registerMutationListener(
- ze,
- (x) => {
- i.getEditorState().read(() => {
- for (const [F, L] of x) {
- if (L === "destroyed") continue;
- const G = ne(F);
- !G || re(G, oe) !== "attribute" || Be(G.getParent()) || i.getElementByKey(F)?.classList.add("attribute");
+ s.registerMutationListener(
+ Ke,
+ (M) => {
+ s.getEditorState().read(() => {
+ for (const [w, $] of M) {
+ if ($ === "destroyed") continue;
+ const Y = se(w);
+ !Y || ne(Y, oe) !== "attribute" || ze(Y.getParent()) || s.getElementByKey(w)?.classList.add("attribute");
}
});
},
{ skipInitialization: !1 }
),
- AE(i, l),
- ...o ? [
- i.registerNodeTransform(ze, (x) => {
- i.isComposing() || _E(x);
+ TA(s, u),
+ ...a ? [
+ s.registerNodeTransform(Ke, (M) => {
+ s.isComposing() || tA(M);
}),
- i.registerCommand(
- so,
- (x) => wd(
+ s.registerCommand(
+ bc,
+ (M) => Jd(
// 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.
- x && typeof x == "object" && "clipboardData" in x ? x : null,
- i,
+ M && typeof M == "object" && "clipboardData" in M ? M : null,
+ s,
!1
),
Ie
),
- i.registerCommand(
- pn,
- (x) => wd(
- x && typeof x == "object" && "clipboardData" in x ? x : null,
- i,
+ s.registerCommand(
+ Qn,
+ (M) => Jd(
+ M && typeof M == "object" && "clipboardData" in M ? M : null,
+ s,
!0
),
Ie
),
- i.registerCommand(
- dr,
- (x) => vE(
+ s.registerCommand(
+ yr,
+ (M) => dA(
// Same jsdom-safe duck-check as COPY above.
- x && typeof x == "object" && "clipboardData" in x ? x : null
+ M && typeof M == "object" && "clipboardData" in M ? M : null,
+ u.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;
+ }
),
Ie
)
] : [],
- i.registerCommand(
- pn,
- () => (tc(l), !1),
- fr
+ s.registerCommand(
+ Qn,
+ () => (rc(u), !1),
+ ar
),
- i.registerCommand(
- dc,
- () => (i.isComposing() || RM(l), !1),
- Hn
+ s.registerCommand(
+ mc,
+ () => (s.isComposing() || iE(u), !1),
+ Gn
),
- i.registerCommand(
- io,
- () => (f = !1, y = 0, E(), !1),
- Rt
+ s.registerCommand(
+ ao,
+ () => (p = !1, k = 0, A(), !1),
+ kt
),
- i.registerCommand(
- Tr,
- (x) => (f = !1, y = 0, E(), (x.key === "Backspace" || x.key === "Delete") && (tc(l), qM(l)), i.isComposing() || !x.ctrlKey || x.altKey || x.shiftKey || x.metaKey || x.key !== " " && x.code !== "Space" || !AS() ? !1 : (x.preventDefault(), !0)),
+ s.registerCommand(
+ Sr,
+ (M) => (p = !1, k = 0, A(), (M.key === "Backspace" || M.key === "Delete") && (rc(u), nE(u)), s.isComposing() || !M.ctrlKey || M.altKey || M.shiftKey || M.metaKey || M.key !== " " && M.code !== "Space" || !Xv() ? !1 : (M.preventDefault(), !0)),
Ie
),
- i.registerCommand(
- tf,
- (x) => {
- const F = Ag();
- F === "needs-plain-split" && i.dispatchCommand(As, void 0);
- const L = F !== "declined" || dT();
- return L && x?.preventDefault(), Cs(l), L;
+ s.registerCommand(
+ mf,
+ (M) => {
+ const w = Bg();
+ w === "needs-plain-split" && s.dispatchCommand(qs, void 0);
+ const $ = w !== "declined" || PT();
+ return $ && M?.preventDefault(), As(u), $;
},
Ie
),
- i.registerCommand(
- As,
- () => (l.splitExpected.current = !0, Hh()),
+ s.registerCommand(
+ qs,
+ () => (u.splitExpected.current = !0, cg()),
Ie
),
- PE(i, l, o),
- i.registerCommand(
- Kg,
+ xA(s, u, a),
+ s.registerCommand(
+ om,
() => {
- if (f) return !0;
- const x = i.getRootElement(), F = x?.ownerDocument, L = !!x && !!F && F.hasFocus() && x.contains(F.activeElement);
- let G;
- if (L) {
- const V = R();
- G = N(V) ? V.focus.key : d;
+ if (p) return !0;
+ const M = s.getRootElement(), w = M?.ownerDocument, $ = !!M && !!w && w.hasFocus() && M.contains(w.activeElement);
+ let Y;
+ if ($) {
+ const Q = R();
+ Y = N(Q) ? Q.focus.key : f;
}
- return ga(() => Cs(l, G)), !0;
+ return ga(() => As(u, Y)), !0;
},
- Rt
+ kt
),
- i.registerCommand(
- pc,
+ s.registerCommand(
+ kc,
() => {
- if (f) return !1;
- const x = R(), F = N(x) ? x.focus.key : d;
- return ga(() => Cs(l, F)), !1;
+ if (p) return !1;
+ const M = R(), w = N(M) ? M.focus.key : f;
+ return ga(() => As(u, w)), !1;
},
- Rt
+ kt
),
- i.registerUpdateListener(({ editorState: x, tags: F }) => {
- l.splitExpected.current = !1, l.wholeParaDeleteExpected?.clear(), l.collapsedDeleteCaretParas?.clear(), l.rebuildAttempted.clear();
- const L = x.read(() => {
- const V = R();
- return N(V) ? V.focus.key : void 0;
- }), G = p;
- if (L !== void 0 && (p = L), F.has(hc)) {
- l.pendingKeys.clear(), x.read(() => xE(l)), f = !0, L !== void 0 && (d = L);
+ s.registerUpdateListener(({ editorState: M, tags: w }) => {
+ u.splitExpected.current = !1, u.wholeParaDeleteExpected?.clear(), u.collapsedDeleteCaretParas?.clear(), u.rebuildAttempted.clear();
+ const $ = M.read(() => {
+ const Q = R();
+ return N(Q) ? Q.focus.key : void 0;
+ }), Y = m;
+ if ($ !== void 0 && (m = $), w.has(Tc)) {
+ u.pendingKeys.clear(), M.read(() => WE(u)), p = !0, $ !== void 0 && (f = $);
return;
}
- if (F.has(Ur)) {
- L !== void 0 && L !== G && (f = !0);
+ if (w.has(jr)) {
+ $ !== void 0 && $ !== Y && (p = !0);
return;
}
- f || (L !== void 0 && (d = L), E(), !(m || L === void 0) && [...l.pendingKeys].some((V) => V !== L) && (m = !0, queueMicrotask(() => {
- m = !1, !g && (T() || S(d));
+ p || ($ !== void 0 && (f = $), A(), !(g || $ === void 0) && [...u.pendingKeys].some((Q) => Q !== $) && (g = !0, queueMicrotask(() => {
+ g = !1, !y && (_() || S(f));
})));
})
);
return () => {
- g = !0, v !== void 0 && clearTimeout(v), v = void 0, u(), A(), a.current = void 0;
+ y = !0, P !== void 0 && clearTimeout(P), P = void 0, d(), B(), c.current = void 0;
};
- }, [i, s, o]), null;
+ }, [s, o, a]), null;
}
-const OE = ["status_unknown", "status_invalid"], jg = {
+const CA = ["status_unknown", "status_invalid"], am = {
unknown: "This marker is not in the stylesheet!",
invalid: "This marker is not valid here!"
-}, wE = Object.values(jg);
-function qE(e, t) {
+}, SA = Object.values(am);
+function vA(e, t) {
e.classList.toggle("status_unknown", t === "unknown"), e.classList.toggle("status_invalid", t === "invalid");
- const r = jg[t];
+ const r = am[t];
e.getAttribute("aria-description") !== r && (e.setAttribute("aria-description", r), e.title = r);
}
-function Rd(e) {
- e.classList.remove(...OE), e.removeAttribute("aria-description"), wE.includes(e.title) && e.removeAttribute("title");
+function Xd(e) {
+ e.classList.remove(...CA), e.removeAttribute("aria-description"), SA.includes(e.title) && e.removeAttribute("title");
}
-function RE(e, t, r, n) {
+function MA(e, t, r, n) {
const i = (a) => a.read(() => Ue().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 = ne(l)?.getTopLevelElement();
+ const u = se(l)?.getTopLevelElement();
u && a.add(u.getKey());
};
for (const l of r.keys()) c(l);
@@ -24033,40 +24431,40 @@ function RE(e, t, r, n) {
return a;
});
}
-function $E(e) {
- const t = ne(e), r = t?.getTopLevelElement();
- return !t || !r ? !1 : t.getKey() === r.getKey() ? !0 : P(t) && t.getParent()?.getKey() === r.getKey();
+function EA(e) {
+ const t = se(e), r = t?.getTopLevelElement();
+ return !t || !r ? !1 : t.getKey() === r.getKey() ? !0 : O(t) && t.getParent()?.getKey() === r.getKey();
}
-function IE({
+function AA({
viewOptions: e,
styleInfo: t,
logger: r
}) {
- const [n] = le(), i = e?.markerMode === "editable";
- return K(() => {
+ const [n] = ce(), i = e?.markerMode === "editable";
+ return z(() => {
if (!i) return;
- const s = t ?? Fs;
+ const s = t ?? Bs;
let o = /* @__PURE__ */ new Map();
const a = (l) => {
n.isComposing() || n.getEditorState().read(() => {
- const u = eM(s, l);
+ const u = CM(s, l);
let d = u;
if (l) {
d = new Map(u);
for (const [f, p] of o) {
- if (d.has(f) || $E(f)) continue;
- const m = ne(f)?.getTopLevelElement();
+ if (d.has(f) || EA(f)) continue;
+ const m = se(f)?.getTopLevelElement();
!m || l.has(m.getKey()) || d.set(f, p);
}
}
for (const [f] of o) {
if (d.has(f)) continue;
const p = n.getElementByKey(f);
- p && Rd(p);
+ p && Xd(p);
}
for (const [f, p] of d) {
const m = n.getElementByKey(f);
- m && qE(m, p);
+ m && vA(m, p);
}
o = d, r?.debug(`[MarkerValidation] pass: ${d.size} flagged`);
});
@@ -24075,7 +24473,7 @@ function IE({
const c = n.registerUpdateListener(
({ editorState: l, prevEditorState: u, dirtyElements: d, dirtyLeaves: f }) => {
d.size === 0 && f.size === 0 || a(
- RE(l, u, d, f)
+ MA(l, u, d, f)
);
}
);
@@ -24083,30 +24481,30 @@ function IE({
c();
for (const [l] of o) {
const u = n.getElementByKey(l);
- u && Rd(u);
+ u && Xd(u);
}
};
}, [n, i, t, r]), null;
}
-function Bg(e, t, r) {
+function cm(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 = kr(o);
- a && D(s) && Bg(s.getChildren(), a, r);
+ const a = Cr(o);
+ a && F(s) && cm(s.getChildren(), a, r);
}
}
-function Vg(e, t) {
+function lm(e, t) {
let r = 0;
const n = (i) => {
for (let s = 0; s < i.length; s++) {
- const o = i[s], a = kr(o);
+ const o = i[s], a = Cr(o);
if (a) {
n(a);
continue;
}
- const c = ni(o);
+ const c = ai(o);
if (c === void 0 || !c.includes(tt)) continue;
const l = c.split(tt), u = [];
for (let d = 0; d < l.length; d++) {
@@ -24124,7 +24522,7 @@ function Vg(e, t) {
};
n(e);
}
-function Wg(e, t, r) {
+function um(e, t, r) {
const n = [];
for (const i of e.sentinels) {
const s = [];
@@ -24138,27 +24536,27 @@ function Wg(e, t, r) {
}
return n;
}
-function Hg(e, t) {
+function dm(e, t) {
const r = [];
for (const n of e)
- pg(n, t) || ((se(n) || $(n)) && r.push(n.getMarker()), D(n) && r.push(...Hg(n.getChildren(), t)));
+ Ag(n, t) || ((ae(n) || D(n)) && r.push(n.getMarker()), F(n) && r.push(...dm(n.getChildren(), t)));
return r;
}
-function Gg(e) {
+function fm(e) {
const t = [];
for (const r of e) {
- const n = hl(r);
+ const n = Sl(r);
(n === "para" || n === "char") && t.push(r.marker ?? "");
- const i = kr(r);
- i && t.push(...Gg(i));
+ const i = Cr(r);
+ i && t.push(...fm(i));
}
return t;
}
-function Tl(e, t, r) {
- const n = Hg(e, r), i = Gg(t);
+function ql(e, t, r) {
+ const n = dm(e, r), i = fm(t);
return n.length === i.length && n.every((s, o) => s === i[o]);
}
-function LE(e, t) {
+function PA(e, t) {
if (!e || e.input.run.length === 0) return;
const r = R();
let n, i;
@@ -24166,13 +24564,13 @@ function LE(e, t) {
if (!r.isCollapsed()) return;
n = r.focus.getNode(), i = r.focus.offset;
} else if (t)
- n = ne(t.key), i = t.offset;
+ n = se(t.key), i = t.offset;
else
return;
- if (!(!M(n) || !n.isAttached()) && !(e.nodeKey !== void 0 && n.getKey() !== e.nodeKey) && n.getTextContent().slice(0, i).endsWith(e.input.run))
+ if (!(!v(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 xl(e, t) {
+function Rl(e, t) {
const r = t.node.getKey(), n = e.spans.find(
(o) => !o.isSentinel && o.key === r
);
@@ -24180,78 +24578,78 @@ function xl(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 DE(e, t, r, n, i) {
+function NA(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 T = gl(y, o, s);
- if (!T) return;
+ const k = vl(y, o, s);
+ if (!k) return;
c.text.length > 0 && (c.text += " ");
- const S = c.text.length;
- T.spans.forEach(
- (v) => c.spans.push({ ...v, start: v.start + S, end: v.end + S })
- ), c.sentinels.push(...T.sentinels), c.text += T.text;
+ const _ = c.text.length;
+ k.spans.forEach(
+ (S) => c.spans.push({ ...S, start: S.start + _, end: S.end + _ })
+ ), c.sentinels.push(...k.sentinels), c.text += k.text;
}
- const l = i ? xl(c, i) : c.text, u = xr(l, {
+ const l = i ? Rl(c, i) : c.text, u = vr(l, {
getMarker: o
});
if (u.length === 0) return;
- if (On(u) !== c.sentinels.length) {
+ if (wn(u) !== c.sentinels.length) {
a?.warn("[MarkerEdit] Settled USJ skipped: sentinel/preserved-node count mismatch");
return;
}
- const d = Br.serializeEditorState(
- { type: hr, version: pr, content: u },
+ const d = Jr.serializeEditorState(
+ { type: kr, version: br, content: u },
s
).root.children;
- if (Eo(d) !== c.sentinels.length) {
+ if (Ao(d) !== c.sentinels.length) {
a?.warn(
"[MarkerEdit] Settled USJ skipped: serialized sentinel/preserved-node count mismatch"
);
return;
}
- const f = Wg(c, t, n);
+ const f = um(c, t, n);
if (!f) {
a?.warn("[MarkerEdit] Settled USJ skipped: a preserved node had no serialized form");
return;
}
- if (pi(d, o) === fi(e, o) && Tl(e, d, o)) {
+ if (bi(d, o) === yi(e, o) && ql(e, d, o)) {
a?.debug("[MarkerEdit] Settled USJ skipped: rebuild is a no-op (fixed point)");
return;
}
- Vg(d, f);
- const m = UE(e), g = Jg(d);
+ lm(d, f);
+ const m = OA(e), g = pm(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;
}
-function UE(e) {
+function OA(e) {
const t = [], r = (n) => {
- Ne(n) ? t.push({ number: n.getNumber(), sid: n.getSid() }) : D(n) && n.getChildren().forEach(r);
+ Pe(n) ? t.push({ number: n.getNumber(), sid: n.getSid() }) : F(n) && n.getChildren().forEach(r);
};
return e.forEach(r), t;
}
-function Jg(e) {
+function pm(e) {
const t = [];
for (const r of e) {
- Yf(r) && t.push(r);
- const n = kr(r);
- n && t.push(...Jg(n));
+ up(r) && t.push(r);
+ const n = Cr(r);
+ n && t.push(...pm(n));
}
return t;
}
-function FE(e, t, r, n, i) {
- const { viewOptions: s, getMarker: o, logger: a } = r, c = _g(e, o, s);
+function wA(e, t, r, n, i) {
+ const { viewOptions: s, getMarker: o, logger: a } = r, c = Lg(e, o, s);
if (!c) return;
const { out: l, contentNodes: u } = c;
if (u.length === 0) return;
- const d = i ? xl(l, i) : l.text, f = xr(d, {
+ const d = i ? Rl(l, i) : l.text, f = vr(d, {
getMarker: o,
isNoteContext: !0
});
if (f.length === 0) return;
- if (On(f) !== l.sentinels.length) {
+ if (wn(f) !== l.sentinels.length) {
a?.warn("[MarkerEdit] Settled note USJ skipped: sentinel/preserved-node count mismatch");
return;
}
@@ -24260,53 +24658,53 @@ function FE(e, t, r, n, i) {
a?.warn("[MarkerEdit] Settled note USJ skipped: unexpected tokenized shape");
return;
}
- const m = p.content ?? [], g = Cg(m), y = e.getCategory() !== g, T = dg(e, m, g, s);
- if (T.failure !== void 0) {
- T.failure === "shape" ? a?.warn("[MarkerEdit] Settled note USJ skipped: unexpected serialized shape") : T.failure === "caller" && a?.warn("[MarkerEdit] Settled note USJ skipped: serialized note lacks the caller");
+ const m = p.content ?? [], g = Dg(m), y = e.getCategory() !== g, k = Mg(e, m, g, 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 S = T.children;
- if (Eo(S) !== l.sentinels.length) {
+ const _ = k.children;
+ if (Ao(_) !== l.sentinels.length) {
a?.warn(
"[MarkerEdit] Settled note USJ skipped: serialized sentinel/preserved-node count mismatch"
);
return;
}
- const v = Wg(l, t, n);
- if (!v) {
+ const S = um(l, t, n);
+ if (!S) {
a?.warn("[MarkerEdit] Settled note USJ skipped: a preserved node had no serialized form");
return;
}
- if (pi(S, o) === fi(u, o) && Tl(u, S, o)) {
+ if (bi(_, o) === yi(u, o) && ql(u, _, o)) {
if (y)
return { rebuilt: void 0, contentNodes: u, category: g, categoryChanged: y };
a?.debug("[MarkerEdit] Settled note USJ skipped: rebuild is a no-op (fixed point)");
return;
}
- return Vg(S, v), { rebuilt: S, contentNodes: u, category: g, categoryChanged: y };
+ return lm(_, S), { rebuilt: _, contentNodes: u, category: g, categoryChanged: y };
}
-function $d(e) {
+function Qd(e) {
return e.$?.textType;
}
-function zE(e, t) {
+function qA(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 && $d(e) === $d(t);
+ return r.type === "text" && n.type === "text" && r.format === n.format && r.style === n.style && r.mode === n.mode && r.detail === n.detail && Qd(e) === Qd(t);
}
-function KE(e) {
+function RA(e) {
const t = [];
for (const r of e) {
- const n = ne(r);
+ const n = se(r);
n?.isAttached() && Le(n) && n.getTag() === "optbreak" && n.getChildrenSize() === 0 && t.push(n);
}
return t;
}
-function jE(e) {
- if (!P(e) || e.getMarkerSyntax() !== "opening") return;
+function $A(e) {
+ if (!O(e) || e.getMarkerSyntax() !== "opening") return;
const t = e.getParent();
if (!j(t)) return;
const r = e.getTextContent();
- if (Wr(e)) return;
- const n = lg.exec(r);
+ if (Xr(e)) return;
+ const n = Sg.exec(r);
if (!n) return;
const i = n[1];
if (i.startsWith("+")) return;
@@ -24314,172 +24712,172 @@ function jE(e) {
if (t.getMarker() === s)
return { glyph: e, note: t, oldMarker: s, newMarker: i };
}
-function Id(e, t) {
+function Zd(e, t) {
const r = e;
- r.marker = t, r.text = gg(t, r.markerSyntax, r.nested);
+ r.marker = t, r.text = Ng(t, r.markerSyntax, r.nested);
}
-function BE(e, t) {
+function IA(e, t) {
const { glyph: r, note: n, oldMarker: i, newMarker: s } = e;
- if (!Me.isValidMarker(s)) return;
+ if (!Ee.isValidMarker(s)) return;
const o = t.get(n.getKey());
o && (o.node.marker = s);
const a = t.get(r.getKey());
- a && Id(a.node, s);
- const c = n.getChildren().filter(P).filter((u) => u.getMarkerSyntax() === "closing" && u.getMarker() === i).at(-1), l = c && t.get(c.getKey());
- l && Id(l.node, s);
+ a && Zd(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 && Zd(l.node, s);
}
-function VE(e, t, r) {
- const { viewOptions: n, getMarker: i, logger: s } = t, o = Mg(e, i, n);
+function LA(e, t, r) {
+ const { viewOptions: n, getMarker: i, logger: s } = t, o = zg(e, i, n);
if (!o) return;
- const a = r ? xl(o, r) : o.text, c = xr(a, {
+ const a = r ? Rl(o, r) : o.text, c = vr(a, {
getMarker: i
}), [l] = c;
if (c.length === 0 || typeof l != "object" || l.type !== "chapter")
return;
- if (On(c) !== 0) {
+ if (wn(c) !== 0) {
s?.warn("[MarkerEdit] Settled chapter USJ skipped: unexpected preserved-node placeholder");
return;
}
e.getSid() !== void 0 && (l.sid = e.getSid());
- const u = Br.serializeEditorState(
- { type: hr, version: pr, content: c },
+ const u = Jr.serializeEditorState(
+ { type: kr, version: br, content: c },
n
).root.children;
if (u.length === 0) return;
- const d = [e, ...Po(e)];
- if (!(e.getNumber() !== (l.number ?? "") || e.getAltnumber() !== l.altnumber || e.getPubnumber() !== l.pubnumber) && pi(u, i) === fi(d, i) && Tl(d, u, i)) {
+ const d = [e, ...No(e)];
+ if (!(e.getNumber() !== (l.number ?? "") || e.getAltnumber() !== l.altnumber || e.getPubnumber() !== l.pubnumber) && bi(u, i) === yi(d, i) && ql(d, u, i)) {
s?.debug("[MarkerEdit] Settled chapter USJ skipped: rebuild is a no-op (fixed point)");
return;
}
return u;
}
-function WE(e, t, r, n, i) {
- const s = LE(n, i);
+function DA(e, t, r, n, i) {
+ const s = PA(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) : $e(y) ? l.set(y.getKey(), y) : o.set(y.getKey(), [y]);
};
for (const y of t) {
- const T = ne(y);
- if (!T?.isAttached()) continue;
- const S = ts(T);
- if (S) {
- if (d(S), P(T)) {
- const v = $g(T, r.getMarker);
- v && a.push(v);
+ const k = se(y);
+ if (!k?.isAttached()) continue;
+ const _ = as(k);
+ if (_) {
+ if (d(_), O(k)) {
+ const S = Yg(k, r.getMarker);
+ S && a.push(S);
}
- if (j(S)) {
- const v = jE(T);
- v && u.set(S.getKey(), v);
+ if (j(_)) {
+ const S = $A(k);
+ S && u.set(_.getKey(), S);
}
}
}
const f = /* @__PURE__ */ new Set();
for (const y of a)
- y.some((T) => f.has(T.getKey())) || (y.forEach((T) => {
- f.add(T.getKey()), o.delete(T.getKey());
+ y.some((k) => f.has(k.getKey())) || (y.forEach((k) => {
+ f.add(k.getKey()), o.delete(k.getKey());
}), o.set(y[0].getKey(), y));
if (s) {
- const y = ts(s.node);
+ const y = as(s.node);
y && d(y);
}
- const p = KE(t);
+ const p = RA(t);
if (o.size === 0 && c.size === 0 && l.size === 0 && p.length === 0)
return;
const m = new Set(p.map((y) => y.getKey())), g = /* @__PURE__ */ new Map();
- Bg(Ue().getChildren(), e.root.children, g);
- for (const y of u.values()) BE(y, g);
+ cm(Ue().getChildren(), e.root.children, g);
+ for (const y of u.values()) IA(y, g);
for (const y of c.values()) {
- const T = g.get(y.getKey()), S = T ? kr(T.node) : void 0;
- if (!T || !S) continue;
- const v = FE(y, g, r, m, s);
- if (!v) continue;
- if (v.categoryChanged) {
- const x = T.node;
- v.category === void 0 ? delete x.category : x.category = v.category;
- }
- if (!v.rebuilt) continue;
- const E = g.get(v.contentNodes[0].getKey());
- if (!E) continue;
- const A = S.indexOf(E.node);
- A < 0 || S.splice(A, v.contentNodes.length, ...v.rebuilt);
+ const k = g.get(y.getKey()), _ = k ? Cr(k.node) : void 0;
+ if (!k || !_) continue;
+ const S = wA(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);
}
for (const y of o.values()) {
- const T = g.get(y[0].getKey());
- if (!T) continue;
- const S = DE(y, g, r, m, s);
- if (!S) continue;
- const v = T.siblings.indexOf(T.node);
- v < 0 || T.siblings.splice(v, y.length, ...S);
+ const k = g.get(y[0].getKey());
+ if (!k) continue;
+ const _ = NA(y, g, r, m, s);
+ if (!_) continue;
+ const S = k.siblings.indexOf(k.node);
+ S < 0 || k.siblings.splice(S, y.length, ..._);
}
for (const y of l.values()) {
- const T = g.get(y.getKey());
- if (!T) continue;
- const S = 1 + Po(y).length, v = VE(y, r, s);
- if (!v) continue;
- const E = T.siblings.indexOf(T.node);
- E < 0 || T.siblings.splice(E, S, ...v);
+ const k = g.get(y.getKey());
+ if (!k) continue;
+ const _ = 1 + No(y).length, S = LA(y, r, s);
+ if (!S) continue;
+ const P = k.siblings.indexOf(k.node);
+ P < 0 || k.siblings.splice(P, _, ...S);
}
for (const y of p) {
- const T = g.get(y.getKey());
- if (!T) continue;
- const S = T.siblings.indexOf(T.node);
- if (S < 0) continue;
- T.siblings.splice(S, 1);
- const v = T.siblings[S - 1], E = T.siblings[S], A = v && ni(v), x = E && ni(E);
- v && E && A !== void 0 && x !== void 0 && zE(v, E) && (v.text = A + x, T.siblings.splice(S, 1));
+ const k = g.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 && ai(S), B = P && ai(P);
+ S && P && A !== void 0 && B !== void 0 && qA(S, P) && (S.text = A + B, k.siblings.splice(_, 1));
}
- return Uh(e, r.viewOptions);
+ return eg(e, r.viewOptions);
}
-function HE({
+function UA({
viewOptions: e,
logger: t
}) {
- const [r] = le(), n = li(e) && (e?.markerMode === "visible" || (e?.hasGutterParaMarkers ?? !1));
- return K(() => {
+ const [r] = ce(), n = hi(e) && (e?.markerMode === "visible" || (e?.hasGutterParaMarkers ?? !1));
+ return z(() => {
if (n)
return r.registerNodeTransform(
Qe,
- (i) => GE(i, t)
+ (i) => FA(i, t)
);
}, [r, n, t]), null;
}
-function GE(e, t) {
- e.getMarker() !== rr && (e.isEmpty() || Dt(e.getFirstChild()) || (t?.debug(
- `[ParaMarkerPrefixGuard] Resetting paragraph "${e.getMarker()}" → "${rr}" (key ${e.getKey()})`
- ), e.setMarker(rr)));
+function FA(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 JE({
+function zA({
scrRef: e,
onScrRefChange: t
}) {
- const [r] = le(), n = X({
+ const [r] = ce(), n = Z({
phase: "idle",
pendingEchoes: [],
scrRef: e,
onScrRefChange: t,
sawDocument: !1
});
- return K(() => {
+ return z(() => {
const i = n.current, s = i.scrRef;
- i.scrRef = e, i.onScrRefChange = t, Zs(s, e) || YE(i, r, e);
- }, [r, e, t]), K(
+ i.scrRef = e, i.onScrRefChange = t, no(s, e) || KA(i, r, e);
+ }, [r, e, t]), z(
() => r.registerMutationListener(
- Lt,
+ Ut,
(i, { prevEditorState: s }) => {
const o = [...i.values()];
if (o.every((c) => c === "destroyed")) return;
- const a = ic(r);
- Ld(n.current, r, a, {
+ const a = ac(r);
+ ef(n.current, r, a, {
hasCreated: o.includes("created"),
hasDestroyed: o.includes("destroyed"),
- isSameDocumentReload: vs(s) === vs(r.getEditorState())
+ isSameDocumentReload: Ps(s) === Ps(r.getEditorState())
});
},
{ skipInitialization: !1 }
),
[r]
- ), K(() => {
+ ), z(() => {
const i = (a) => a.read(
() => new Set(
Ue().getChildren().filter(We).map((c) => c.getKey())
@@ -24491,14 +24889,14 @@ function JE({
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 && (ic(r) || Ld(n.current, r, void 0, {
+ f && (ac(r) || ef(n.current, r, void 0, {
hasCreated: f,
hasDestroyed: [...u].some((p) => !d.has(p)),
- isSameDocumentReload: vs(a) === vs(c)
+ isSameDocumentReload: Ps(a) === Ps(c)
}));
};
- return Xe(
- ...[Et, or].map(
+ return He(
+ ...[Nt, pr].map(
(a) => r.registerMutationListener(
a,
(c, { prevEditorState: l }) => o(l),
@@ -24506,140 +24904,140 @@ function JE({
)
)
);
- }, [r]), K(
+ }, [r]), z(
() => r.registerCommand(
- gr,
+ ur,
() => {
const i = n.current;
- return i.phase === "idle" && e1(i, QE()), !1;
+ return i.phase === "idle" && WA(i, BA()), !1;
},
- Rt
+ kt
),
[r]
- ), K(() => {
+ ), z(() => {
const i = (s) => {
[...s.values()].some(
(a) => a === "created" || a === "destroyed"
- ) && queueMicrotask(() => r.dispatchCommand(gr, void 0));
+ ) && queueMicrotask(() => r.dispatchCommand(ur, void 0));
};
- return Xe(
- r.registerMutationListener(xt, i),
- r.registerMutationListener(ft, i)
+ return He(
+ r.registerMutationListener(Ct, i),
+ r.registerMutationListener(dt, i)
);
- }, [r]), K(() => {
- const i = () => i1(n.current);
+ }, [r]), z(() => {
+ const i = () => YA(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 YE(e, t, r) {
- if (XE(e, r)) return;
+function KA(e, t, r) {
+ if (jA(e, r)) return;
e.phase = "navigating", e.pendingEchoes.length = 0;
- const n = ic(t);
- (!n || n === r.book) && t.update(() => Yg(r.chapterNum, r.verseNum), {
- tag: Ur
+ const n = ac(t);
+ (!n || n === r.book) && t.update(() => hm(r.chapterNum, r.verseNum), {
+ tag: jr
});
}
-function XE(e, t) {
- const r = e.pendingEchoes.findIndex((n) => Zs(n, t));
+function jA(e, t) {
+ const r = e.pendingEchoes.findIndex((n) => no(n, t));
return r < 0 ? !1 : (e.pendingEchoes.splice(0, r + 1), e.phase = "idle", !0);
}
-function QE() {
- const e = R(), t = Sc(e);
+function BA() {
+ const e = R(), t = wc(e);
if (!t) return;
- const r = _l(), n = Zf(t);
+ const r = $l(), n = pp(t);
if (!n && !r) return;
const i = n ? parseInt(n.getNumber() ?? "1", 10) : 1;
if (Number.isNaN(i)) return;
- const s = Fc(t, e), { verseNum: o, verse: a } = RT(s ?? void 0, e);
+ const s = Hc(t, e), { verseNum: o, verse: a } = QT(s ?? void 0, e);
if (!Number.isNaN(o))
return { book: r?.getCode() || void 0, chapterNum: i, verseNum: o, verse: a };
}
-function ic(e) {
- return e.getEditorState().read(() => _l()?.getCode() || void 0);
+function ac(e) {
+ return e.getEditorState().read(() => $l()?.getCode() || void 0);
}
-function _l() {
- return Ue().getChildren().find(Tt);
+function $l() {
+ return Ue().getChildren().find(_t);
}
-function Ld(e, t, r, n) {
+function ef(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) && ma(e, t);
return;
}
- n.hasCreated && n.hasDestroyed ? (n.isSameDocumentReload || ma(e, t), e.phase = "navigating") : i && ma(e, t), r && r !== e.scrRef.book && Zg(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 && ym(e, { ...e.scrRef, book: r }) && (e.phase = "navigating");
}
function ma(e, t) {
queueMicrotask(() => {
t.update(
- () => Yg(e.scrRef.chapterNum, e.scrRef.verseNum),
- { tag: Ur }
+ () => hm(e.scrRef.chapterNum, e.scrRef.verseNum),
+ { tag: jr }
);
});
}
-function Yg(e, t) {
- const r = Sc(R()), n = zc(r)?.getNumber(), i = Zf(r);
- if ((i ? parseInt(i.getNumber() ?? "1", 10) : 1) === e && n && (sp(n) ? Qg(t, n) : parseInt(n, 10) === t))
+function hm(e, t) {
+ const r = wc(R()), n = Gc(r)?.getNumber(), i = pp(r);
+ if ((i ? parseInt(i.getNumber() ?? "1", 10) : 1) === e && n && (kp(n) ? mm(t, n) : parseInt(n, 10) === t))
return;
- const o = Ue().getChildren(), a = Qf(o, e);
+ const o = Ue().getChildren(), a = fp(o, e);
if (!a) return;
- const c = Wb(o, a), l = Ub(c, !0);
- Vb(c, l);
+ const c = dk(o, a), l = ik(c, !0);
+ uk(c, l);
let u;
try {
- u = PT(c, t);
+ u = HT(c, t);
} catch {
return;
}
- u && (se(u) ? !M(u.getFirstChild()) && di(u) || Gt(u, 0) : ZE(u));
+ u && (ae(u) ? !v(u.getFirstChild()) && mi(u) || Xt(u, 0) : VA(u));
}
-function ZE(e) {
+function VA(e) {
const t = e.getParent();
if (!t) return;
const r = e.getIndexWithinParent() + 1, n = t.getChildAtIndex(r);
- if (!n || me(n)) {
- Gt(t, r);
+ if (!n || ge(n)) {
+ Xt(t, r);
return;
}
- const i = bo(t, r);
+ const i = ko(t, r);
if (i) {
i.select(0, 0);
return;
}
- if (M(e)) {
+ if (v(e)) {
const o = e.getTextContentSize();
e.select(o, o);
return;
}
- const s = D(n) && !j(n) ? Xg(n) : void 0;
- s ? s.select(0, 0) : Gt(t, r);
+ const s = F(n) && !j(n) ? gm(n) : void 0;
+ s ? s.select(0, 0) : Xt(t, r);
}
-function Xg(e) {
+function gm(e) {
const t = e.getFirstChild();
- if (M(t)) return t;
- if (D(t) && !j(t)) return Xg(t);
+ if (v(t)) return t;
+ if (F(t) && !j(t)) return gm(t);
}
-function vs(e) {
+function Ps(e) {
return e.read(() => {
const t = Ue().getChildren().find(We);
- return `${_l()?.getCode() ?? ""}|${t?.getNumber() ?? ""}`;
+ return `${$l()?.getCode() ?? ""}|${t?.getNumber() ?? ""}`;
});
}
-function e1(e, t) {
- e.phase !== "navigating" && t && (t1(t, e.scrRef) || Zg(e, r1(t, e.scrRef)));
+function WA(e, t) {
+ e.phase !== "navigating" && t && (HA(t, e.scrRef) || ym(e, GA(t, e.scrRef)));
}
-function t1(e, t) {
- return e.book && e.book !== t.book || e.chapterNum !== t.chapterNum ? !1 : e.verse ? Qg(t.verseNum, e.verse) : t.verseNum === e.verseNum;
+function HA(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 Qg(e, t) {
+function mm(e, t) {
try {
- return Mc(e, t);
+ return qc(e, t);
} catch {
return !1;
}
}
-function r1(e, t) {
+function GA(e, t) {
const r = {
book: e.book || t.book,
chapterNum: e.chapterNum,
@@ -24647,26 +25045,26 @@ function r1(e, t) {
};
return e.verse != null && (r.verse = e.verse), t.versificationStr != null && (r.versificationStr = t.versificationStr), r;
}
-const n1 = 8;
-function Zg(e, t) {
- return Zs(t, e.scrRef) || e.pendingEchoes.some((r) => Zs(r, t)) ? !1 : (e.pendingEchoes.push(t), e.pendingEchoes.length > n1 && e.pendingEchoes.shift(), e.onScrRefChange(t), !0);
+const JA = 8;
+function ym(e, t) {
+ return no(t, e.scrRef) || e.pendingEchoes.some((r) => no(r, t)) ? !1 : (e.pendingEchoes.push(t), e.pendingEchoes.length > JA && e.pendingEchoes.shift(), e.onScrRefChange(t), !0);
}
-function Zs(e, t) {
+function no(e, t) {
return e.book === t.book && e.chapterNum === t.chapterNum && e.verseNum === t.verseNum && (e.verse ?? void 0) === (t.verse ?? void 0);
}
-function i1(e) {
+function YA(e) {
e.phase = "idle";
}
-function s1(e) {
- return Tt(e) ? `${e.__code}` : $e(e) ? `${e.__marker} "${e.__number}"` : $(e) ? `${e.__marker}` : as(e) ? `${e.__marker} "${e.__number}"` : cr(e) ? `${e.__caller}` : Pn(e) ? `${e.__marker} "${e.__number}"` : j(e) ? `${e.__marker} "${e.__caller}"` + (e.__isCollapsed ? " (collapsed)" : " (expanded)") : se(e) ? `${e.__marker}` : M(e) ? `"${e.__text}"${o1(e)}` : _e(e) ? `ids: [ ${JSON.stringify(e.getTypedIDs())} ]` : Ne(e) ? `${e.__marker} "${e.__number}"` : "";
+function XA(e) {
+ return _t(e) ? `${e.__code}` : $e(e) ? `${e.__marker} "${e.__number}"` : D(e) ? `${e.__marker}` : fs(e) ? `${e.__marker} "${e.__number}"` : St(e) ? `${e.__caller}` : On(e) ? `${e.__marker} "${e.__number}"` : j(e) ? `${e.__marker} "${e.__caller}"` + (e.__isCollapsed ? " (collapsed)" : " (expanded)") : ae(e) ? `${e.__marker}` : v(e) ? `"${e.__text}"${QA(e)}` : Ce(e) ? `ids: [ ${JSON.stringify(e.getTypedIDs())} ]` : Pe(e) ? `${e.__marker} "${e.__number}"` : "";
}
-function o1(e) {
- return e.__state ? " " + JSON.stringify(e.__state.toJSON()[is]) : "";
+function QA(e) {
+ return e.__state ? " " + JSON.stringify(e.__state.toJSON()[ds]) : "";
}
-function a1() {
- const [e] = le();
+function ZA() {
+ const [e] = ce();
return /* @__PURE__ */ C(
- cy,
+ Py,
{
viewClassName: "tree-view-output",
treeTypeButtonClassName: "debug-treetype-button",
@@ -24674,32 +25072,32 @@ function a1() {
timeTravelButtonClassName: "debug-timetravel-button",
timeTravelPanelSliderClassName: "debug-timetravel-panel-slider",
timeTravelPanelButtonClassName: "debug-timetravel-panel-button",
- customPrintNode: s1,
+ customPrintNode: XA,
editor: e
}
);
}
-const em = Vd(null), Dd = 4;
-function c1({
+const bm = lf(null), tf = 4;
+function e1({
children: e,
className: t,
onClick: r,
title: n
}) {
- const i = X(null), s = Wd(em);
+ const i = Z(null), s = uf(bm);
if (s === null)
throw new Error("DropDownItem must be used within a DropDown");
const { registerItem: o } = s;
- return K(() => {
+ return z(() => {
i && i.current && o(i);
}, [i, o]), /* @__PURE__ */ C("button", { className: t, onClick: r, ref: i, title: n, type: "button", children: e });
}
-function l1({
+function t1({
children: e,
dropDownRef: t,
onClose: r
}) {
- const [n, i] = de(), [s, o] = de(), a = ge(
+ const [n, i] = de(), [s, o] = de(), a = he(
(u) => {
i((d) => d ? [...d, u] : [u]);
},
@@ -24713,12 +25111,12 @@ function l1({
return n[p === -1 ? n.length - 1 : p];
}) : d === "ArrowDown" && o((f) => f ? n[n.indexOf(f) + 1] : n[0]);
}, l = Fe(() => ({ registerItem: a }), [a]);
- return K(() => {
+ return z(() => {
const u = s ?? n?.[0];
u?.current && u.current.focus();
- }, [n, s]), /* @__PURE__ */ C(em.Provider, { value: l, children: /* @__PURE__ */ C("div", { className: "dropdown", ref: t, onKeyDown: c, children: e }) });
+ }, [n, s]), /* @__PURE__ */ C(bm.Provider, { value: l, children: /* @__PURE__ */ C("div", { className: "dropdown", ref: t, onKeyDown: c, children: e }) });
}
-function u1({
+function r1({
disabled: e = !1,
buttonLabel: t,
buttonAriaLabel: r,
@@ -24727,16 +25125,16 @@ function u1({
children: s,
stopCloseOnClickSelf: o
}) {
- const a = X(null), c = X(null), [l, u] = de(!1), d = () => {
+ const a = Z(null), c = Z(null), [l, u] = de(!1), d = () => {
u(!1), c && c.current && c.current.focus();
};
- return K(() => {
+ 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 + Dd}px`, p.style.left = `${Math.min(g, window.innerWidth - p.offsetWidth - 20)}px`;
+ p.style.top = `${m + f.offsetHeight + tf}px`, p.style.left = `${Math.min(g, window.innerWidth - p.offsetWidth - 20)}px`;
}
- }, [a, c, l]), K(() => {
+ }, [a, c, l]), z(() => {
const f = c.current;
if (f !== null && l) {
const p = (m) => {
@@ -24749,12 +25147,12 @@ function u1({
}
return () => {
};
- }, [a, c, l, o]), K(() => {
+ }, [a, c, l, o]), z(() => {
const f = () => {
if (l) {
const p = c.current, m = a.current;
if (p !== null && m !== null) {
- const { top: g } = p.getBoundingClientRect(), y = g + p.offsetHeight + Dd;
+ const { top: g } = p.getBoundingClientRect(), y = g + p.offsetHeight + tf;
y !== m.getBoundingClientRect().top && (m.style.top = `${y}px`);
}
}
@@ -24762,7 +25160,7 @@ function u1({
return document.addEventListener("scroll", f), () => {
document.removeEventListener("scroll", f);
};
- }, [c, a, l]), /* @__PURE__ */ Te(dn, { children: [
+ }, [c, a, l]), /* @__PURE__ */ Te(gn, { children: [
/* @__PURE__ */ Te(
"button",
{
@@ -24779,13 +25177,13 @@ function u1({
]
}
),
- l && un(
- /* @__PURE__ */ C(l1, { dropDownRef: a, onClose: d, children: s }),
+ l && hn(
+ /* @__PURE__ */ C(t1, { dropDownRef: a, onClose: d, children: s }),
document.body
)
] });
}
-const sc = {
+const cc = {
m: "m - Paragraph - Margin - No First Line Indent",
ms: "ms - Heading - Major Section Level 1",
nb: "nb - Paragraph - No Break with Previous Paragraph",
@@ -24796,8 +25194,8 @@ const sc = {
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.
-}, oc = {
- ...sc,
+}, lc = {
+ ...cc,
// File / header
cl: "cl - Chapter - Publishing Label",
h: "h - File - Header",
@@ -24902,27 +25300,27 @@ const sc = {
// Tables
tr: "tr - Table - Row"
};
-function d1({
+function n1({
editorRef: e,
blockMarker: t,
disabled: r = !1
}) {
return /* @__PURE__ */ C(
- u1,
+ r1,
{
disabled: r,
buttonClassName: "toolbar-item block-controls",
- buttonIconClassName: "icon block-marker " + f1(t),
- buttonLabel: p1(t),
+ buttonIconClassName: "icon block-marker " + i1(t),
+ buttonLabel: s1(t),
buttonAriaLabel: "Formatting options for block type",
- children: Object.keys(sc).map((n) => /* @__PURE__ */ Te(
- c1,
+ children: Object.keys(cc).map((n) => /* @__PURE__ */ Te(
+ e1,
{
- className: "item block-marker " + h1(t === n),
+ className: "item block-marker " + o1(t === n),
onClick: () => e.current?.formatPara(n),
children: [
/* @__PURE__ */ C("i", { className: "icon block-marker " + n }),
- /* @__PURE__ */ C("span", { className: "text usfm_" + n, children: sc[n] })
+ /* @__PURE__ */ C("span", { className: "text usfm_" + n, children: cc[n] })
]
},
n
@@ -24930,50 +25328,50 @@ function d1({
}
);
}
-function f1(e) {
- return e && e in oc ? e : "ban";
+function i1(e) {
+ return e && e in lc ? e : "ban";
}
-function p1(e) {
- return e && e in oc ? oc[e] : "No Style";
+function s1(e) {
+ return e && e in lc ? lc[e] : "No Style";
}
-function h1(e) {
+function o1(e) {
return e ? "active dropdown-item-active" : "";
}
-function Ud() {
+function rf() {
return /* @__PURE__ */ C("div", { className: "divider" });
}
-const g1 = vn(function({ editorRef: t, isReadonly: r = !1, onStateChange: n }, i) {
- const [s] = le(), [o, a] = de(s), [c, l] = de(), [u, d] = de(!1), [f, p] = de(!1), m = ge(
+const a1 = Mn(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(
({
canUndo: g,
canRedo: y,
- blockMarker: T,
- contextMarker: S
+ blockMarker: k,
+ contextMarker: _
}) => {
- d(g), p(y), l(T), n?.({
+ d(g), p(y), l(k), n?.({
canUndo: g,
canRedo: y,
- blockMarker: T,
- contextMarker: S
+ blockMarker: k,
+ contextMarker: _
});
},
[n]
);
- return K(() => s.registerCommand(
- gr,
+ return z(() => s.registerCommand(
+ ur,
(g, y) => (a(y), !1),
- fr
- ), [s]), /* @__PURE__ */ Te(dn, { children: [
- /* @__PURE__ */ C(Eh, { onStateChange: m }),
+ ar
+ ), [s]), /* @__PURE__ */ Te(gn, { children: [
+ /* @__PURE__ */ C(Kh, { onStateChange: m }),
/* @__PURE__ */ Te("div", { className: "toolbar", children: [
/* @__PURE__ */ C(
"button",
{
disabled: !u || r,
onClick: () => {
- o.dispatchCommand(of, void 0);
+ o.dispatchCommand(Tf, void 0);
},
- title: Ps ? "Undo (⌘Z)" : "Undo (Ctrl+Z)",
+ title: Rs ? "Undo (⌘Z)" : "Undo (Ctrl+Z)",
type: "button",
className: "toolbar-item spaced",
"aria-label": "Undo",
@@ -24985,35 +25383,35 @@ const g1 = vn(function({ editorRef: t, isReadonly: r = !1, onStateChange: n }, i
{
disabled: !f || r,
onClick: () => {
- o.dispatchCommand(af, void 0);
+ o.dispatchCommand(xf, void 0);
},
- title: Ps ? "Redo (⌘Y)" : "Redo (Ctrl+Y)",
+ title: Rs ? "Redo (⌘Y)" : "Redo (Ctrl+Y)",
type: "button",
className: "toolbar-item",
"aria-label": "Redo",
children: /* @__PURE__ */ C("i", { className: "format redo" })
}
),
- /* @__PURE__ */ C(Ud, {}),
- o === s && /* @__PURE__ */ Te(dn, { children: [
+ /* @__PURE__ */ C(rf, {}),
+ o === s && /* @__PURE__ */ Te(gn, { children: [
/* @__PURE__ */ C(
- d1,
+ n1,
{
editorRef: t,
blockMarker: c,
disabled: r
}
),
- /* @__PURE__ */ C(Ud, {})
+ /* @__PURE__ */ C(rf, {})
] }),
/* @__PURE__ */ C("div", { ref: i, className: "end-container" })
] })
] });
-}), m1 = _o(), y1 = {}, b1 = {};
-function k1() {
+}), c1 = Co(), l1 = {}, u1 = {};
+function d1() {
return /* @__PURE__ */ C("div", { className: "editor-placeholder", children: "Enter some Scripture..." });
}
-const tm = vn(function({
+const km = Mn(function({
defaultUsj: t,
scrRef: r,
onScrRefChange: n,
@@ -25024,596 +25422,602 @@ const tm = vn(function({
logger: c,
children: l
}, u) {
- const d = X(null), f = X(null), p = X(null), m = X(t), g = X(void 0), y = X(void 0), T = X(void 0), S = X(void 0), v = X(!1), [E, A] = de(t), [x, F] = de(0), [L, G] = de(), {
- isReadonly: V = !1,
- structureProtectionMode: ae = "off",
- hasExternalUI: ce = !1,
- hasSpellCheck: ie = !1,
- textDirection: ve = "ltr",
- markerMenuTrigger: Pe = "\\",
- view: Q,
- nodes: U,
- debug: Z = !1,
- contextMenu: Ee,
- styleInfo: we,
- markerSettleDelayMs: Yt
- } = a ?? b1, ee = Q ?? m1, _t = Xi(ee) && (ee.markerMode !== "hidden" || !ee.hasSpacing || ee.hasGutterParaMarkers || ee.hasActiveTextFocusBox) ? {
- ...ee,
+ 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), [w, $] = de(), {
+ isReadonly: Y = !1,
+ structureProtectionMode: Q = "off",
+ hasExternalUI: Me = !1,
+ hasSpellCheck: re = !1,
+ textDirection: Oe = "ltr",
+ markerMenuTrigger: be = "\\",
+ view: er,
+ nodes: we,
+ debug: en = !1,
+ contextMenu: gr,
+ styleInfo: vt,
+ markerSettleDelayMs: te
+ } = a ?? u1, E = er ?? c1, J = ns(E) && (E.markerMode !== "hidden" || !E.hasSpacing || E.hasGutterParaMarkers || E.hasActiveTextFocusBox) ? {
+ ...E,
markerMode: "hidden",
hasSpacing: !0,
hasGutterParaMarkers: !1,
hasActiveTextFocusBox: !1
- } : ee, Jr = X(_t);
- Pt(Jr.current, _t) || (Jr.current = _t);
- const fe = Jr.current, ct = Fe(() => U ?? y1, [U]), No = Fe(() => Ee, [Ee]), hi = Fe(
- () => TT(we ?? Fs),
- [we]
- ), Er = X(c);
- Pt(Er.current, c) || (Er.current = c);
- const Ge = Er.current, lt = Xi(fe), ue = V || lt, wn = _t !== ee;
- K(() => {
- lt && !V && Ge?.error(
+ } : E, le = Z(J);
+ wt(le.current, J) || (le.current = J);
+ const W = le.current, xe = Fe(() => we ?? l1, [we]), pt = Fe(() => gr, [gr]), zt = Fe(
+ () => DT(vt ?? Bs),
+ [vt]
+ ), ht = Z(c);
+ wt(ht.current, c) || (ht.current = c);
+ const Je = ht.current, ct = ns(W), ue = Y || ct, qn = J !== E;
+ z(() => {
+ ct && !Y && Je?.error(
"Editor: the block verse layout is read-only; ignoring `isReadonly: false`. Set `isReadonly: true` alongside `verseLayout: 'block'`."
- ), wn && Ge?.warn(
+ ), qn && Je?.warn(
"Editor: a visible `markerMode`, `hasSpacing: false`, `hasGutterParaMarkers` and `hasActiveTextFocusBox` are not supported with the block verse layout and are ignored."
);
- }, [lt, V, wn, Ge]);
- const be = X(null), gi = Fe(() => {
- if (fe.markerMode !== "editable") return;
- const O = we ?? Fs;
+ }, [ct, Y, qn, Je]);
+ const ye = Z(null), ki = Fe(() => {
+ if (W.markerMode !== "editable") return;
+ const q = vt ?? Bs;
return {
- getContext: () => be.current?.getMarkerMenuContext(),
+ getContext: () => ye.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: (z) => cM(
- O,
- z,
- ct.extraValidMarkers
+ getItems: (K) => OM(
+ q,
+ K,
+ xe.extraValidMarkers
),
- getEnterItems: (z) => lM(
- O,
- z,
- ct.extraValidMarkers
+ getEnterItems: (K) => wM(
+ q,
+ K,
+ xe.extraValidMarkers
),
- apply: (z, H) => {
- const J = be.current;
- J && (H.trigger === "enter" ? J.splitParagraphWithMarker(z.marker) : J.applyMarkerMenuSelection(z, H));
+ apply: (K, G) => {
+ const X = ye.current;
+ X && (G.trigger === "enter" ? X.splitParagraphWithMarker(K.marker) : X.applyMarkerMenuSelection(K, G));
},
- commitTypedCloser: (z) => {
- be.current?.commitTypedCloser(z);
+ commitTypedCloser: (K) => {
+ ye.current?.commitTypedCloser(K);
}
};
- }, [fe, we, ct.extraValidMarkers]), Se = (O) => {
- v.current || (v.current = !0, Er.current?.warn(
- `Editor: cannot ${O} in the block verse layout; its paragraphs are split across verse blocks, so editor content indexes do not match the source USJ.`
+ }, [W, vt, xe.extraValidMarkers]), ve = (q) => {
+ S.current || (S.current = !0, ht.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.`
));
- }, Ar = (O) => {
- if (lt)
+ }, Or = (q) => {
+ if (ct)
throw new Error(
- `Cannot ${O} 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.`
);
- }, Pr = (O) => {
- if (Ar(O), ue) throw new Error(`Cannot ${O} in readonly mode`);
- }, Xt = Fe(
+ }, wr = (q) => {
+ if (Or(q), ue) throw new Error(`Cannot ${q} in readonly mode`);
+ }, tr = Fe(
() => ({
namespace: "platformEditor",
- theme: { ...ng, showCharMarkerTitles: fe.showCharMarkerTitles },
+ theme: { ...bg, showCharMarkerTitles: W.showCharMarkerTitles },
editable: !ue,
editorState: void 0,
// Handling of errors during update
- onError(O) {
- throw O;
+ onError(q) {
+ throw q;
},
// Registered per layout so an editor that isn't using block verse never holds its node.
- nodes: [Ze, ...lt ? wx : nh]
+ nodes: [Ze, ...ct ? Yx : yh]
}),
- [ue, lt, fe.showCharMarkerTitles]
+ [ue, ct, W.showCharMarkerTitles]
);
- ua.initialize(Ge);
- function Nr(O) {
- if (O !== void 0 && !NS(O, ct.extraValidMarkers))
- throw new Error(`Unsupported character marker '${O}'`);
- }
- const Yr = ge(() => {
- const O = d.current;
- if (!O) return m.current;
- const z = lu(O), H = y.current;
- if ((!z || z.size === 0) && !H) return m.current;
- const J = O.getEditorState(), xe = J.toJSON();
- return J.read(
- () => WE(
- xe,
- z ?? /* @__PURE__ */ new Set(),
- { viewOptions: fe, getMarker: hi, logger: Ge },
- H,
- T.current
+ ua.initialize(Je);
+ function qr(q) {
+ if (q !== void 0 && !Zv(q, xe.extraValidMarkers))
+ throw new Error(`Unsupported character marker '${q}'`);
+ }
+ const tn = he(() => {
+ const q = d.current;
+ if (!q) return m.current;
+ const K = Mu(q), G = y.current;
+ if ((!K || K.size === 0) && !G) return m.current;
+ const X = q.getEditorState(), _e = X.toJSON();
+ return X.read(
+ () => DA(
+ _e,
+ K ?? /* @__PURE__ */ new Set(),
+ { viewOptions: W, getMarker: zt, logger: Je },
+ G,
+ k.current
)
) ?? m.current;
- }, [fe, hi, Ge]), mi = {
+ }, [W, zt, Je]), Ti = {
focus() {
d.current?.focus();
},
isFocused() {
- const O = d.current?.getRootElement();
- return !!O && O.ownerDocument.activeElement === O;
+ const q = d.current?.getRootElement();
+ return !!q && q.ownerDocument.activeElement === q;
},
undo() {
- d.current?.dispatchCommand(of, void 0);
+ d.current?.dispatchCommand(Tf, void 0);
},
redo() {
- d.current?.dispatchCommand(af, void 0);
+ d.current?.dispatchCommand(xf, 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() {
- Pr("cut"), d.current?.dispatchCommand(pn, null);
+ wr("cut"), d.current && cl(d.current);
},
copy() {
- d.current?.dispatchCommand(so, null);
+ d.current && al(d.current);
},
paste() {
- Pr("paste"), d.current && Zc(d.current);
+ wr("paste"), d.current && ll(d.current);
},
pastePlainText() {
- Pr("paste as plain text"), d.current && el(d.current);
+ wr("paste as plain text"), d.current && ul(d.current);
},
getUsj() {
- return Yr();
+ return tn();
},
commitPendingMarkerEdits() {
d.current?.update(
() => {
- d.current?.dispatchCommand(Kg, void 0);
+ d.current?.dispatchCommand(om, void 0);
},
{ discrete: !0 }
);
},
- setTransientInput(O) {
- if (!O) {
+ setTransientInput(q) {
+ if (!q) {
y.current = void 0;
return;
}
- const z = d.current?.getEditorState().read(() => {
- const H = R();
- return N(H) && H.isCollapsed() ? H.focus.key : void 0;
+ const K = d.current?.getEditorState().read(() => {
+ const G = R();
+ return N(G) && G.isCollapsed() ? G.focus.key : void 0;
});
- y.current = { input: O, nodeKey: z ?? T.current?.key };
+ y.current = { input: q, nodeKey: K ?? k.current?.key };
},
- setUsj(O) {
- if (!Pt(m.current, O)) {
- m.current = O, y.current = void 0;
- const z = Pt(E, O);
- A(O), z && F((H) => H + 1);
+ setUsj(q) {
+ if (!wt(m.current, q)) {
+ m.current = q, y.current = void 0;
+ const K = wt(P, q);
+ A(q), K && M((G) => G + 1);
}
},
- applyUpdate(O, z = "remote") {
- if (lt && z === "remote") {
- Er.current?.error(
+ applyUpdate(q, K = "remote") {
+ if (ct && K === "remote") {
+ ht.current?.error(
"Editor: ignoring a remote update in the block verse layout; reload the view with the new USJ instead."
);
return;
}
- Ar("apply an update"), d.current?.update(
+ Or("apply an update"), d.current?.update(
() => {
- z === "remote" && Dr(Fi), s_(O, fe, ct, Ge);
+ K === "remote" && Kr(Vi), C_(q, W, xe, Je);
},
{ discrete: !0 }
);
- const H = d.current?.getEditorState();
- if (!H) return;
- const J = ua.deserializeEditorState(H, fe);
- if (J) {
- const xe = !Pt(m.current, J);
- if (xe && (m.current = J), xe || !Pt(E, J)) {
- const ut = xu(O, H, "apply");
- S.current = J, s?.(J, O, z, ut);
+ const G = d.current?.getEditorState();
+ if (!G) return;
+ const X = ua.deserializeEditorState(G, W);
+ if (X) {
+ const _e = !wt(m.current, X);
+ if (_e && (m.current = X), _e || !wt(P, X)) {
+ const lt = Du(q, G, "apply");
+ _.current = X, s?.(X, q, K, lt);
}
}
},
- replaceEmbedUpdate(O, z) {
- const H = d.current?.read(() => WT(O, z));
- H ? this.applyUpdate(H) : c?.warn(
- `replaceEmbedUpdate: no embed found for key "${O}" — update dropped (stale key after a setUsj reload?)`
+ replaceEmbedUpdate(q, K) {
+ const G = d.current?.read(() => ux(q, K));
+ G ? this.applyUpdate(G) : c?.warn(
+ `replaceEmbedUpdate: no embed found for key "${q}" — update dropped (stale key after a setUsj reload?)`
);
},
getSelection() {
- if (lt) {
- Se("get the selection");
+ if (ct) {
+ ve("get the selection");
return;
}
- return d.current?.read(Zp);
+ return d.current?.read(ph);
},
- setSelection(O) {
- if (lt) {
- Se("set the selection");
+ setSelection(q) {
+ if (ct) {
+ ve("set the selection");
return;
}
d.current?.update(() => {
- const z = Vc(O);
- z !== void 0 && (Ui(z), Dr(kf));
+ const K = Qc(q);
+ K !== void 0 && (Zn(K), Kr(qf));
});
},
- setAnnotation(O, z, H, J, xe) {
- if (lt) {
- Se("set an annotation");
+ setAnnotation(q, K, G, X, _e) {
+ if (ct) {
+ ve("set an annotation");
return;
}
- let ut, At, Xr, Qr;
- typeof J == "function" || J === void 0 ? (ut = J, At = xe) : (ut = J.onClick, At = J.onRemove, Xr = J.onMouseEnter, Qr = J.onMouseLeave), f.current?.setAnnotation(
- O,
- Xl(z),
- H,
- ut,
- At,
- Xr,
- Qr
+ 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(
+ q,
+ hu(K),
+ G,
+ lt,
+ Ot,
+ rn,
+ nn
);
},
- removeAnnotation(O, z) {
- f.current?.removeAnnotation(Xl(O), z);
+ removeAnnotation(q, K) {
+ f.current?.removeAnnotation(hu(q), K);
},
- formatPara(O) {
- Pr("format a paragraph"), d.current?.update(() => {
- const z = R();
- if (!N(z)) {
+ formatPara(q) {
+ wr("format a paragraph"), d.current?.update(() => {
+ const K = R();
+ if (!N(K)) {
c?.warn(
- `formatPara refused: no range selection to retag with "${O}" (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;
}
- dy(z, () => ji(O));
- const H = R();
- if (!N(H)) return;
- const J = /* @__PURE__ */ new Set();
- H.getNodes().forEach((xe) => {
- const ut = xe.getTopLevelElement();
- se(ut) && J.add(ut);
- }), J.forEach((xe) => Eg(xe, O, fe));
+ wy(K, () => Ji(q));
+ const G = R();
+ 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) => jg(_e, q, W));
});
},
- getElementByKey(O) {
+ getElementByKey(q) {
return d.current?.read(
- () => d.current?.getElementByKey(O) ?? void 0
+ () => d.current?.getElementByKey(q) ?? void 0
);
},
- removeCharacterMarker(O) {
+ removeCharacterMarker(q) {
if (ue) throw new Error("Cannot remove character marker in readonly mode");
- Nr(O);
- let z = !1;
+ qr(q);
+ let K = !1;
return d.current?.update(
() => {
- const H = R();
- N(H) && (z = Xh(H, O, fe));
+ const G = R();
+ N(G) && (K = fg(G, q, W));
},
{ discrete: !0 }
- ), z;
+ ), K;
},
- replaceCharacterMarker(O, z) {
+ replaceCharacterMarker(q, K) {
if (ue) throw new Error("Cannot replace character marker in readonly mode");
- Nr(O), Nr(z);
- let H = !1;
+ qr(q), qr(K);
+ let G = !1;
return d.current?.update(
() => {
- const J = R();
- N(J) && (H = KS(J, O, z));
+ const X = R();
+ N(X) && (G = dM(X, q, K));
},
{ discrete: !0 }
- ), H;
+ ), G;
},
- extendCharacterMarker(O, z) {
+ extendCharacterMarker(q, K) {
if (ue) throw new Error("Cannot extend character marker in readonly mode");
- Nr(O), z?.forEach(
- (J) => Nr(J)
+ qr(q), K?.forEach(
+ (X) => qr(X)
);
- let H = !1;
+ let G = !1;
return d.current?.update(
() => {
- const J = R();
- N(J) && (H = jS(
- J,
- O,
- z,
- fe
+ const X = R();
+ N(X) && (G = fM(
+ X,
+ q,
+ K,
+ W
));
},
{ discrete: !0 }
- ), H;
+ ), G;
},
- insertMarker(O) {
+ insertMarker(q) {
if (ue) 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 (!Ga(O, ct.extraValidMarkers))
- throw new Error(`Unsupported marker '${O}'`);
- const z = Ja(
- O,
+ if (!Ja(q, xe.extraValidMarkers))
+ throw new Error(`Unsupported marker '${q}'`);
+ const K = Ya(
+ q,
g,
- fe,
- ct,
- Ge,
+ W,
+ xe,
+ Je,
void 0,
- we
+ vt
);
- return z.action({ editor: d.current, reference: r }), z.getInsertedNoteKey?.();
+ return K.action({ editor: d.current, reference: r }), K.getInsertedNoteKey?.();
},
getMarkerMenuContext() {
- if (!V)
- return d.current?.getEditorState().read(() => JM());
+ if (!Y)
+ return d.current?.getEditorState().read(() => xE());
},
- applyMarkerMenuSelection(O, z) {
- if (V) throw new Error("Cannot apply marker menu selection in readonly mode");
+ applyMarkerMenuSelection(q, K) {
+ if (Y) 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 (O.kind !== "closeTag" && !Ga(O.marker, ct.extraValidMarkers))
- throw new Error(`Unsupported marker '${O.marker}'`);
- let H;
+ if (q.kind !== "closeTag" && !Ja(q.marker, xe.extraValidMarkers))
+ throw new Error(`Unsupported marker '${q.marker}'`);
+ let G;
return d.current.update(() => {
- H = eE(O, z, r, {
+ G = ME(q, K, r, {
expandedNoteKeyRef: g,
- viewOptions: fe,
- nodeOptions: ct,
+ viewOptions: W,
+ nodeOptions: xe,
logger: c,
- styleInfo: we
+ styleInfo: vt
});
- }), H;
+ }), G;
},
- splitParagraphWithMarker(O) {
- if (V) throw new Error("Cannot split paragraph in readonly mode");
+ splitParagraphWithMarker(q) {
+ if (Y) throw new Error("Cannot split paragraph in readonly mode");
d.current && d.current.update(() => {
- qg(O, fe);
+ Jg(q, W);
});
},
- commitTypedMarker(O, z) {
- if (V) throw new Error("Cannot commit a typed marker in readonly mode");
+ commitTypedMarker(q, K) {
+ if (Y) throw new Error("Cannot commit a typed marker in readonly mode");
if (!d.current) return !1;
- let H = !1;
+ let G = !1;
return d.current.update(() => {
- H = ZM(O, z), H || c?.warn(
+ G = vE(q, K), G || c?.warn(
"commitTypedMarker refused: requires a collapsed range selection (wrap a selection via applyMarkerMenuSelection instead)"
);
- }), H;
+ }), G;
},
- commitTypedCloser(O) {
- if (V) throw new Error("Cannot commit a typed closing marker in readonly mode");
+ commitTypedCloser(q) {
+ if (Y) throw new Error("Cannot commit a typed closing marker in readonly mode");
if (!d.current) return !1;
- let z = !1;
+ let K = !1;
return d.current.update(() => {
- z = wg(O), z || c?.warn(
+ K = Gg(q), K || c?.warn(
"commitTypedCloser refused: requires a range selection to commit the closer at"
);
- }), z;
- },
- insertNote(O, z, H) {
- Pr("insert a note"), d.current?.update(() => {
- const J = th(
- O,
- z,
- H,
+ }), K;
+ },
+ insertNote(q, K, G) {
+ wr("insert a note"), d.current?.update(() => {
+ const X = gh(
+ q,
+ K,
+ G,
r,
- fe,
- ct,
- Ge
+ W,
+ xe,
+ Je
);
- J && !J.getIsCollapsed() && (g.current = J.getKey());
+ X && !X.getIsCollapsed() && (g.current = X.getKey());
});
},
- selectNote(O) {
+ selectNote(q) {
d.current?.update(() => {
- const z = Eu(O);
- z && (Px(z, fe), z.getIsCollapsed() || (g.current = z.getKey()));
+ const K = Bu(q);
+ K && (Hx(K, W), K.getIsCollapsed() || (g.current = K.getKey()));
});
},
- getNoteOps(O) {
+ getNoteOps(q) {
return d.current?.read(() => {
- const z = Eu(O);
- if (z)
- return jc(z);
+ const K = Bu(q);
+ if (K)
+ return Yc(K);
});
},
get toolbarEndRef() {
return p;
}
};
- be.current = mi, cc(u, () => mi), K(() => {
- const O = d.current;
- if (O)
- return O.registerUpdateListener(({ editorState: z }) => {
- z.read(() => {
- const H = R();
- if (!N(H) || !H.isCollapsed()) return;
- const J = H.focus.getNode();
- M(J) && (T.current = { key: J.getKey(), offset: H.focus.offset });
+ ye.current = Ti, dc(u, () => Ti), z(() => {
+ const q = d.current;
+ if (q)
+ return q.registerUpdateListener(({ editorState: K }) => {
+ K.read(() => {
+ const G = R();
+ if (!N(G) || !G.isCollapsed()) return;
+ const X = G.focus.getNode();
+ v(X) && (k.current = { key: X.getKey(), offset: G.focus.offset });
});
});
}, []);
- const ds = ge(
- (O, z, H, J) => {
- if (lt) return;
- const xe = ua.deserializeEditorState(O, fe);
- if (xe) {
- const ut = !Pt(m.current, xe);
- if (ut && (m.current = xe), ut || !Pt(E, xe)) {
- const At = xu(J, O);
- S.current = xe, s?.(xe, J, "local", At);
+ const ms = he(
+ (q, K, G, X) => {
+ if (ct) return;
+ const _e = ua.deserializeEditorState(q, W);
+ if (_e) {
+ const lt = !wt(m.current, _e);
+ if (lt && (m.current = _e), lt || !wt(P, _e)) {
+ const Ot = Du(X, q);
+ _.current = _e, s?.(_e, X, "local", Ot);
}
}
},
- [E, s, fe, lt]
+ [P, s, W, ct]
);
- K(() => {
- const O = d.current;
- if (!(!O || !s))
- return O.registerUpdateListener(({ tags: z, dirtyElements: H, dirtyLeaves: J }) => {
- !z.has(hc) && (H.size === 0 && J.size === 0 || z.has(Fi) || !lu(O)?.size) || queueMicrotask(() => {
- const xe = Yr();
- !xe || Pt(S.current, xe) || (S.current = xe, s(xe, void 0, "local", void 0));
+ z(() => {
+ const q = d.current;
+ if (!(!q || !s))
+ return q.registerUpdateListener(({ tags: K, dirtyElements: G, dirtyLeaves: X }) => {
+ !K.has(Tc) && (G.size === 0 && X.size === 0 || K.has(Vi) || !Mu(q)?.size) || queueMicrotask(() => {
+ const _e = tn();
+ !_e || wt(_.current, _e) || (_.current = _e, s(_e, void 0, "local", void 0));
});
});
- }, [s, Yr]);
- const Ut = ge(
- (O) => {
- G(O.contextMarker), o?.(O);
+ }, [s, tn]);
+ const Kt = he(
+ (q) => {
+ $(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(uf, { initialConfig: Xt, children: [
- /* @__PURE__ */ C(iC, { isEditable: !ue }),
+ /* @__PURE__ */ Te(Sf, { initialConfig: tr, children: [
+ /* @__PURE__ */ C(EC, { isEditable: !ue }),
/* @__PURE__ */ Te("div", { className: "editor-container", children: [
- ce ? /* @__PURE__ */ C(Eh, { onStateChange: Ut }) : /* @__PURE__ */ C(
+ Me ? /* @__PURE__ */ C(Kh, { onStateChange: Kt }) : /* @__PURE__ */ C(
"div",
{
className: "editor-toolbar-container" + (ue ? "-readonly" : "-editable"),
children: /* @__PURE__ */ C(
- g1,
+ a1,
{
ref: p,
- editorRef: be,
+ editorRef: ye,
isReadonly: ue,
- onStateChange: Ut
+ onStateChange: Kt
}
)
}
),
/* @__PURE__ */ Te("div", { className: "editor-inner", children: [
- /* @__PURE__ */ C(ff, { editorRef: d }),
+ /* @__PURE__ */ C(Mf, { editorRef: d }),
/* @__PURE__ */ C(
- uy,
+ Oy,
{
contentEditable: /* @__PURE__ */ C(
- df,
+ vf,
{
- className: `editor-input usfm ${i_(fe).join(" ")}${fe.hasGutterParaMarkers ? " psc-gutter-markers" : ""}${fe.hasActiveTextFocusBox ? " psc-active-focus" : ""}`,
- spellCheck: ie
+ className: `editor-input usfm ${__(W).join(" ")}${W.hasGutterParaMarkers ? " psc-gutter-markers" : ""}${W.hasActiveTextFocusBox ? " psc-active-focus" : ""}`,
+ spellCheck: re
}
),
- placeholder: /* @__PURE__ */ C(k1, {}),
- ErrorBoundary: pf
+ placeholder: /* @__PURE__ */ C(d1, {}),
+ ErrorBoundary: Ef
}
),
- ce && /* @__PURE__ */ C(nC, {}),
- /* @__PURE__ */ C(hf, {}),
- r && n && /* @__PURE__ */ C(JE, { scrRef: r, onScrRefChange: n }),
- r && !ce && /* @__PURE__ */ C(
- Av,
+ Me && /* @__PURE__ */ C(MC, {}),
+ /* @__PURE__ */ C(Af, {}),
+ r && n && /* @__PURE__ */ C(zA, { scrRef: r, onScrRefChange: n }),
+ r && !Me && /* @__PURE__ */ C(
+ XS,
{
- trigger: Pe,
+ trigger: be,
scrRef: r,
- contextMarker: L,
- getMarkerAction: (O) => Ja(
- O,
+ contextMarker: w,
+ getMarkerAction: (q) => Ya(
+ q,
g,
- fe,
- ct,
- Ge,
+ W,
+ xe,
+ Je,
void 0,
- we
+ vt
),
- editableHarness: gi
+ editableHarness: ki
}
),
/* @__PURE__ */ C(
- aC,
+ NC,
{
- scripture: E,
+ scripture: P,
scriptureRef: m,
- nodeOptions: ct,
- editorAdaptor: Br,
- viewOptions: fe,
- logger: Ge
+ nodeOptions: xe,
+ editorAdaptor: Jr,
+ viewOptions: W,
+ logger: Je
},
- x
+ B
),
- /* @__PURE__ */ C(EC, { onChange: i }),
+ /* @__PURE__ */ C(YC, { onChange: i }),
/* @__PURE__ */ C(
- Qx,
+ m_,
{
- onChange: ds,
+ onChange: ms,
ignoreSelectionChange: !0,
ignoreHistoryMergeTagChange: !0,
- ignoreTags: wy
+ ignoreTags: Zy
}
),
- /* @__PURE__ */ C(GS, { viewOptions: fe }),
- /* @__PURE__ */ C(Yx, { ref: f, logger: Ge }),
- /* @__PURE__ */ C(O_, { viewOptions: fe }),
- /* @__PURE__ */ C(V_, {}),
- /* @__PURE__ */ C(X_, {}),
- fe?.markerMode !== "editable" && /* @__PURE__ */ C(Q_, { logger: Ge }),
- /* @__PURE__ */ C(rC, { options: No }),
- /* @__PURE__ */ C(oC, {}),
- /* @__PURE__ */ C(tE, {}),
+ /* @__PURE__ */ C(yM, { viewOptions: W }),
+ /* @__PURE__ */ C(h_, { ref: f, logger: Je }),
+ /* @__PURE__ */ C(J_, { viewOptions: W }),
+ /* @__PURE__ */ C(lC, {}),
+ /* @__PURE__ */ C(gC, {}),
+ W?.markerMode !== "editable" && /* @__PURE__ */ C(mC, { logger: Je }),
+ /* @__PURE__ */ C(TC, { options: pt }),
+ /* @__PURE__ */ C(vC, {}),
+ /* @__PURE__ */ C(PC, {}),
+ /* @__PURE__ */ C(EE, {}),
/* @__PURE__ */ C(
- NE,
+ _A,
{
- viewOptions: fe,
- getMarker: hi,
- logger: Ge,
- markerSettleDelayMs: Yt
+ viewOptions: W,
+ getMarker: zt,
+ logger: Je,
+ markerSettleDelayMs: te,
+ structureProtectionMode: Q
}
),
/* @__PURE__ */ C(
- IE,
+ AA,
{
- styleInfo: we,
- viewOptions: fe,
- logger: Ge
+ styleInfo: vt,
+ viewOptions: W,
+ logger: Je
}
),
/* @__PURE__ */ C(
- cC,
+ OC,
{
expandedNoteKeyRef: g,
- nodeOptions: ct,
- viewOptions: fe,
- logger: Ge
+ nodeOptions: xe,
+ viewOptions: W,
+ logger: Je
}
),
- /* @__PURE__ */ C(MC, {}),
- /* @__PURE__ */ C(M_, {}),
- /* @__PURE__ */ C(__, {}),
- /* @__PURE__ */ C(HE, { viewOptions: fe, logger: Ge }),
- /* @__PURE__ */ C(AC, {}),
- /* @__PURE__ */ C(gv, { structureProtectionMode: ae }),
- /* @__PURE__ */ C(mv, { textDirection: ve }),
- /* @__PURE__ */ C(bv, {}),
- /* @__PURE__ */ C(Mv, {}),
+ /* @__PURE__ */ C(JC, {}),
+ /* @__PURE__ */ C(B_, {}),
+ /* @__PURE__ */ C(F_, {}),
+ /* @__PURE__ */ C(UA, { viewOptions: W, logger: Je }),
+ /* @__PURE__ */ C(XC, {}),
+ /* @__PURE__ */ C(DS, { structureProtectionMode: Q }),
+ /* @__PURE__ */ C(US, { textDirection: Oe }),
+ /* @__PURE__ */ C(zS, {}),
+ /* @__PURE__ */ C(JS, {}),
l
] }),
- Z && /* @__PURE__ */ C(a1, {})
+ en && /* @__PURE__ */ C(ZA, {})
] })
- ] }, fe.verseLayout ?? "inline")
+ ] }, W.verseLayout ?? "inline")
);
-}), SA = vn(function(t, r) {
+}), yP = Mn(function(t, r) {
const { children: n, ...i } = t;
- return /* @__PURE__ */ C(tm, { ref: r, ...i });
+ return /* @__PURE__ */ C(km, { ref: r, ...i });
});
-function rm() {
+function Tm() {
return Math.random().toString(36).replace(/[^a-z]+/g, "").substr(0, 5);
}
-function eo(e, t, r, n, i) {
+function io(e, t, r, n, i) {
return {
author: t,
content: e,
deleted: i === void 0 ? !1 : i,
- id: r === void 0 ? rm() : r,
+ id: r === void 0 ? Tm() : r,
timeStamp: n === void 0 ? performance.timeOrigin + performance.now() : n,
type: "comment"
};
}
-function nm(e, t, r) {
+function xm(e, t, r) {
return {
comments: t,
- id: r === void 0 ? rm() : r,
+ id: r === void 0 ? Tm() : r,
quote: e,
type: "thread"
};
}
-function Fd(e) {
+function nf(e) {
return {
comments: Array.from(e.comments),
id: e.id,
@@ -25621,7 +26025,7 @@ function Fd(e) {
type: "thread"
};
}
-function T1(e) {
+function f1(e) {
return {
author: e.author,
content: "[Deleted Comment]",
@@ -25636,7 +26040,7 @@ function ya(e) {
for (const r of t)
r();
}
-class x1 {
+class p1 {
_editor;
_comments;
_changeListeners;
@@ -25688,7 +26092,7 @@ class x1 {
for (let o = 0; o < i.length; o++) {
const a = i[o];
if (a.type === "thread" && a.id === r.id) {
- const c = Fd(a);
+ const c = nf(a);
i.splice(o, 1, c);
const l = n !== void 0 ? n : c.comments.length;
if (this.isCollaborative() && s !== null) {
@@ -25725,7 +26129,7 @@ class x1 {
for (let o = 0; o < n.length; o++) {
const a = n[o];
if (a.type === "thread" && a.id === r.id) {
- const c = Fd(a);
+ const c = nf(a);
n.splice(o, 1, c);
const l = c.comments;
if (s = l.indexOf(t), this.isCollaborative() && i !== null) {
@@ -25744,7 +26148,7 @@ class x1 {
}), n.splice(s, 1);
return this._comments = n, ya(this), t.type === "comment" ? {
index: s,
- markedComment: T1(t)
+ markedComment: f1(t)
} : null;
}
/**
@@ -25774,16 +26178,16 @@ class x1 {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_getCollabComments() {
const t = this._collabProvider;
- return t !== null ? t.doc.get("comments", Kl) : null;
+ return t !== null ? t.doc.get("comments", tu) : null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_createCollabSharedMap(t) {
- const r = new jl(), n = t.type, i = t.id;
+ const r = new ru(), 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 Kl();
+ const s = new tu();
t.comments.forEach((o, a) => {
const c = this._createCollabSharedMap(o);
s.insert(a, [c]);
@@ -25807,50 +26211,50 @@ class x1 {
} catch {
}
}, s = this._editor.registerCommand(
- My,
+ Hy,
(a) => (n !== void 0 && i !== void 0 && (a ? (this.logger?.info("Comments connected!"), n()) : (this.logger?.info("Comments disconnected!"), i())), !1),
- Rt
+ kt
), o = (a, c) => {
if (c.origin !== this) {
for (const l of a)
- if (l instanceof Ey) {
+ if (l instanceof Gy) {
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, T = u.parent, S = u === r ? void 0 : T instanceof jl && this._comments.find((v) => v.id === T.get("id"));
+ const m = p.insert, g = p.retain, y = p.delete, k = u.parent, _ = u === r ? void 0 : k instanceof ru && this._comments.find((S) => S.id === k.get("id"));
if (Array.isArray(m)) {
- const v = f;
- m.slice().reverse().forEach((E) => {
- const A = E.get("id"), F = E.get("type") === "thread" ? nm(
- E.get("quote"),
- E.get("comments").toArray().map(
- (L) => eo(
- L.get("content"),
- L.get("author"),
- L.get("id"),
- L.get("timeStamp"),
- L.get("deleted")
+ const S = f;
+ m.slice().reverse().forEach((P) => {
+ const A = P.get("id"), M = P.get("type") === "thread" ? xm(
+ P.get("quote"),
+ P.get("comments").toArray().map(
+ (w) => io(
+ w.get("content"),
+ w.get("author"),
+ w.get("id"),
+ w.get("timeStamp"),
+ w.get("deleted")
)
),
A
- ) : eo(
- E.get("content"),
- E.get("author"),
+ ) : io(
+ P.get("content"),
+ P.get("author"),
A,
- E.get("timeStamp"),
- E.get("deleted")
+ P.get("timeStamp"),
+ P.get("deleted")
);
this._withLocalTransaction(() => {
- this.addComment(F, S, v);
+ this.addComment(M, _, S);
});
});
} else if (typeof g == "number")
f += g;
else if (typeof y == "number")
- for (let v = 0; v < y; v++) {
- const E = S === void 0 || S === !1 ? this._comments[f] : S.comments[f];
+ for (let S = 0; S < y; S++) {
+ const P = _ === void 0 || _ === !1 ? this._comments[f] : _.comments[f];
this._withLocalTransaction(() => {
- this.deleteCommentOrThread(E, S);
+ this.deleteCommentOrThread(P, _);
}), f++;
}
}
@@ -25862,22 +26266,22 @@ class x1 {
});
}
}
-function _1(e) {
+function h1(e) {
const [t, r] = de(e.getComments());
- return K(() => e.registerOnChange(() => {
+ return z(() => e.registerOnChange(() => {
r(e.getComments());
}), [e]), t;
}
-function C1({
+function g1({
onClose: e,
children: t,
title: r,
closeOnClickOutside: n
}) {
- const i = X(null);
- return K(() => {
+ const i = Z(null);
+ return z(() => {
i.current !== null && i.current.focus();
- }, []), K(() => {
+ }, []), z(() => {
let s = null;
const o = (l) => {
l.key === "Escape" && e();
@@ -25903,26 +26307,26 @@ function C1({
/* @__PURE__ */ C("div", { className: "Modal__content", children: t })
] }) });
}
-function v1({
+function m1({
onClose: e,
children: t,
title: r,
closeOnClickOutside: n = !1
}) {
- return un(
- /* @__PURE__ */ C(C1, { onClose: e, title: r, closeOnClickOutside: n, children: t }),
+ return hn(
+ /* @__PURE__ */ C(g1, { onClose: e, title: r, closeOnClickOutside: n, children: t }),
document.body
);
}
-function im() {
- const [e, t] = de(null), r = ge(() => {
+function _m() {
+ const [e, t] = de(null), r = he(() => {
t(null);
}, []), n = Fe(() => {
if (e === null)
return null;
const { title: s, content: o, closeOnClickOutside: a } = e;
- return /* @__PURE__ */ C(v1, { onClose: r, title: s, closeOnClickOutside: a, children: o });
- }, [e, r]), i = ge(
+ return /* @__PURE__ */ C(m1, { onClose: r, title: s, closeOnClickOutside: a, children: o });
+ }, [e, r]), i = he(
(s, o, a = !1) => {
t({
closeOnClickOutside: a,
@@ -25934,14 +26338,14 @@ function im() {
);
return [n, i];
}
-const S1 = {
- ...ng,
+const y1 = {
+ ...bg,
paragraph: "CommentEditorTheme__paragraph"
};
-function M1(...e) {
+function b1(...e) {
return e.filter(Boolean).join(" ");
}
-function Vr({
+function Yr({
"data-test-id": e,
children: t,
className: r,
@@ -25954,7 +26358,7 @@ function Vr({
"button",
{
disabled: i,
- className: M1(
+ className: b1(
"Button__root",
i && "Button__disabled",
s && "Button__small",
@@ -25968,46 +26372,46 @@ function Vr({
}
);
}
-function E1({
+function k1({
className: e
}) {
- return /* @__PURE__ */ C(df, { className: e || "ContentEditable__root" });
+ return /* @__PURE__ */ C(vf, { className: e || "ContentEditable__root" });
}
-function A1({
+function T1({
children: e,
className: t
}) {
return /* @__PURE__ */ C("div", { className: t || "Placeholder__root", children: e });
}
-const zd = sf("INSERT_INLINE_COMMAND");
-function P1({
+const sf = kf("INSERT_INLINE_COMMAND");
+function x1({
anchorKey: e,
editor: t,
showComments: r,
onAddComment: n
}) {
- const i = X(null), s = ge(() => {
+ const i = Z(null), s = he(() => {
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`;
}
}, [e, t]);
- return K(() => (window.addEventListener("resize", s), () => {
+ return z(() => (window.addEventListener("resize", s), () => {
window.removeEventListener("resize", s);
- }), [t, s]), rs(() => {
+ }), [t, s]), cs(() => {
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" }) }) });
}
-function N1({ onEscape: e }) {
- const [t] = le();
- return K(() => t.registerCommand(
- nf,
+function _1({ onEscape: e }) {
+ const [t] = ce();
+ return z(() => t.registerCommand(
+ bf,
(r) => e(r),
- Hn
+ Gn
), [t, e]), null;
}
-function sm({
+function Cm({
className: e,
autoFocus: t,
onEscape: r,
@@ -26015,91 +26419,91 @@ function sm({
editorRef: i,
placeholder: s = "Type a comment..."
}) {
- return /* @__PURE__ */ C(uf, { initialConfig: {
+ return /* @__PURE__ */ C(Sf, { initialConfig: {
namespace: "Commenting",
nodes: [],
onError: (a) => {
throw a;
},
- theme: S1
+ theme: y1
}, children: /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox_EditorContainer", children: [
/* @__PURE__ */ C(
- Cy,
+ By,
{
- contentEditable: /* @__PURE__ */ C(E1, { className: e }),
- placeholder: /* @__PURE__ */ C(A1, { children: s }),
- ErrorBoundary: pf
+ contentEditable: /* @__PURE__ */ C(k1, { className: e }),
+ placeholder: /* @__PURE__ */ C(T1, { children: s }),
+ ErrorBoundary: Ef
}
),
- /* @__PURE__ */ C(_y, { onChange: n }),
- /* @__PURE__ */ C(hf, {}),
- t !== !1 && /* @__PURE__ */ C(ky, {}),
- /* @__PURE__ */ C(N1, { onEscape: r }),
- /* @__PURE__ */ C(Ty, {}),
- i !== void 0 && /* @__PURE__ */ C(ff, { editorRef: i })
+ /* @__PURE__ */ C(jy, { onChange: n }),
+ /* @__PURE__ */ C(Af, {}),
+ t !== !1 && /* @__PURE__ */ C(Fy, {}),
+ /* @__PURE__ */ C(_1, { onEscape: r }),
+ /* @__PURE__ */ C(zy, {}),
+ i !== void 0 && /* @__PURE__ */ C(Mf, { editorRef: i })
] }) });
}
-function om(e, t) {
- return ge(
+function Sm(e, t) {
+ return he(
(r, n) => {
r.read(() => {
- e(vy()), t(!Sy(n.isComposing(), !0));
+ e(Vy()), t(!Wy(n.isComposing(), !0));
});
},
[t, e]
);
}
-function O1({
+function C1({
editor: e,
cancelAddComment: t,
submitAddComment: r
}) {
- const [n, i] = de(""), [s, o] = de(!1), a = X(null), c = Fe(
+ const [n, i] = de(""), [s, o] = de(!1), a = Z(null), c = Fe(
() => ({
container: document.createElement("div"),
elements: []
}),
[]
- ), l = X(null), u = cm(), d = ge(() => {
+ ), l = Z(null), u = Mm(), d = he(() => {
e.getEditorState().read(() => {
const g = R();
if (N(g)) {
l.current = g.clone();
- const y = g.anchor, T = g.focus, S = fy(
+ const y = g.anchor, k = g.focus, _ = qy(
e,
y.getNode(),
y.offset,
- T.getNode(),
- T.offset
- ), v = a.current;
- if (S !== null && v !== null) {
- const { left: E, bottom: A, width: x } = S.getBoundingClientRect(), F = py(e, S);
- let L = F.length === 1 ? E + x / 2 - 125 : E - 125;
- L < 10 && (L = 10), v.style.left = `${L}px`, v.style.top = `${A + 20 + (window.pageYOffset || document.documentElement.scrollTop)}px`;
- const G = F.length, { container: V } = c, ae = c.elements, ce = ae.length;
- for (let ie = 0; ie < G; ie++) {
- const ve = F[ie];
- let Pe = ae[ie];
- Pe === void 0 && (Pe = document.createElement("span"), ae[ie] = Pe, V.appendChild(Pe));
- const U = `position:absolute;top:${ve.top + (window.pageYOffset || document.documentElement.scrollTop)}px;left:${ve.left}px;height:${ve.height}px;width:${ve.width}px;background-color:rgba(255, 212, 0, 0.3);pointer-events:none;z-index:5;`;
- Pe.style.cssText = U;
+ k.getNode(),
+ k.offset
+ ), S = a.current;
+ if (_ !== null && S !== null) {
+ const { left: P, bottom: A, width: B } = _.getBoundingClientRect(), M = Ry(e, _);
+ let w = M.length === 1 ? P + B / 2 - 125 : P - 125;
+ w < 10 && (w = 10), S.style.left = `${w}px`, S.style.top = `${A + 20 + (window.pageYOffset || document.documentElement.scrollTop)}px`;
+ const $ = M.length, { container: Y } = c, Q = c.elements, Me = Q.length;
+ for (let re = 0; re < $; re++) {
+ const Oe = M[re];
+ let be = Q[re];
+ be === void 0 && (be = document.createElement("span"), Q[re] = be, Y.appendChild(be));
+ const we = `position:absolute;top:${Oe.top + (window.pageYOffset || document.documentElement.scrollTop)}px;left:${Oe.left}px;height:${Oe.height}px;width:${Oe.width}px;background-color:rgba(255, 212, 0, 0.3);pointer-events:none;z-index:5;`;
+ be.style.cssText = we;
}
- for (let ie = ce - 1; ie >= G; ie--) {
- const ve = ae[ie];
- V.removeChild(ve), ae.pop();
+ for (let re = Me - 1; re >= $; re--) {
+ const Oe = Q[re];
+ Y.removeChild(Oe), Q.pop();
}
}
}
});
}, [e, c]);
- rs(() => {
+ cs(() => {
d();
const g = c.container, y = document.body;
return y !== null ? (y.appendChild(g), () => {
y.removeChild(g);
}) : () => {
};
- }, [c.container, d]), K(() => (window.addEventListener("resize", d), () => {
+ }, [c.container, d]), z(() => (window.addEventListener("resize", d), () => {
window.removeEventListener("resize", d);
}), [d]);
const f = (g) => (g.preventDefault(), t(), !0), p = () => {
@@ -26109,16 +26513,16 @@ function O1({
return y ? y.getTextContent() : "";
});
g.length > 100 && (g = g.slice(0, 99) + "…"), r(
- nm(g, [eo(n, u)]),
+ xm(g, [io(n, u)]),
!0,
void 0,
l.current
), l.current = null;
}
- }, m = om(i, o);
+ }, m = Sm(i, o);
return /* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox", ref: a, children: [
/* @__PURE__ */ C(
- sm,
+ Cm,
{
className: "CommentPlugin_CommentInputBox_Editor",
onEscape: f,
@@ -26126,9 +26530,9 @@ function O1({
}
),
/* @__PURE__ */ Te("div", { className: "CommentPlugin_CommentInputBox_Buttons", children: [
- /* @__PURE__ */ C(Vr, { onClick: t, className: "CommentPlugin_CommentInputBox_Button", children: "Cancel" }),
+ /* @__PURE__ */ C(Yr, { onClick: t, className: "CommentPlugin_CommentInputBox_Button", children: "Cancel" }),
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
onClick: p,
disabled: !s,
@@ -26139,15 +26543,15 @@ function O1({
] })
] });
}
-function w1({
+function S1({
submitAddComment: e,
thread: t,
placeholder: r
}) {
- const [n, i] = de(""), [s, o] = de(!1), a = X(null), c = cm(), l = om(i, o);
- return /* @__PURE__ */ Te(dn, { children: [
+ const [n, i] = de(""), [s, o] = de(!1), a = Z(null), c = Mm(), l = Sm(i, o);
+ return /* @__PURE__ */ Te(gn, { children: [
/* @__PURE__ */ C(
- sm,
+ Cm,
{
className: "CommentPlugin_CommentsPanel_Editor",
autoFocus: !1,
@@ -26158,14 +26562,14 @@ function w1({
}
),
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
className: "CommentPlugin_CommentsPanel_SendButton",
onClick: () => {
if (s) {
- e(eo(n, c), !1, t);
+ e(io(n, c), !1, t);
const d = a.current;
- d !== null && d.dispatchCommand(ny, void 0);
+ d !== null && d.dispatchCommand(vy, void 0);
}
},
disabled: !s,
@@ -26174,19 +26578,19 @@ function w1({
)
] });
}
-function am({
+function vm({
commentOrThread: e,
deleteCommentOrThread: t,
onClose: r,
thread: n = void 0
}) {
- return /* @__PURE__ */ Te(dn, { children: [
+ return /* @__PURE__ */ Te(gn, { children: [
"Are you sure you want to delete this ",
e.type,
"?",
/* @__PURE__ */ Te("div", { className: "Modal__content", children: [
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
onClick: () => {
t(e, n), r();
@@ -26196,7 +26600,7 @@ function am({
),
" ",
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
onClick: () => {
r();
@@ -26207,14 +26611,14 @@ function am({
] })
] });
}
-function Kd({
+function of({
comment: e,
deleteComment: t,
thread: r,
rtf: n
}) {
const [i, s] = de(0);
- K(() => {
+ z(() => {
const u = () => {
s(performance.timeOrigin + performance.now());
};
@@ -26224,7 +26628,7 @@ function Kd({
window.clearInterval(d);
};
}, []);
- const o = Math.round((e.timeStamp - i) / 1e3), a = Math.round(o / 60), [c, l] = im();
+ const o = Math.round((e.timeStamp - i) / 1e3), a = Math.round(o / 60), [c, l] = _m();
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 }),
@@ -26234,13 +26638,13 @@ function Kd({
] })
] }),
/* @__PURE__ */ C("p", { className: e.deleted ? "CommentPlugin_CommentsPanel_DeletedComment" : "", children: e.content }),
- !e.deleted && /* @__PURE__ */ Te(dn, { children: [
+ !e.deleted && /* @__PURE__ */ Te(gn, { children: [
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
onClick: () => {
l("Delete Comment", (u) => /* @__PURE__ */ C(
- am,
+ vm,
{
commentOrThread: e,
deleteCommentOrThread: t,
@@ -26257,7 +26661,7 @@ function Kd({
] })
] });
}
-function q1({
+function v1({
activeIDs: e,
comments: t,
deleteCommentOrThread: r,
@@ -26265,7 +26669,7 @@ function q1({
submitAddComment: i,
markNodeMap: s
}) {
- const [o] = le(), [a, c] = de(0), [l, u] = im(), d = Fe(
+ const [o] = ce(), [a, c] = de(0), [l, u] = _m(), d = Fe(
() => new Intl.RelativeTimeFormat("en", {
localeMatcher: "best fit",
numeric: "auto",
@@ -26273,7 +26677,7 @@ function q1({
}),
[]
);
- return K(() => {
+ return z(() => {
const f = setTimeout(() => {
c(a + 1);
}, 1e4);
@@ -26291,8 +26695,8 @@ function q1({
const y = document.activeElement;
o.update(
() => {
- const T = Array.from(g)[0], S = ne(T);
- _e(S) && S.selectStart();
+ const k = Array.from(g)[0], _ = se(k);
+ Ce(_) && _.selectStart();
},
{
onUpdate() {
@@ -26310,11 +26714,11 @@ function q1({
/* @__PURE__ */ C("span", { children: f.quote })
] }),
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
onClick: () => {
u("Delete Thread", (g) => /* @__PURE__ */ C(
- am,
+ vm,
{
commentOrThread: f,
deleteCommentOrThread: r,
@@ -26329,7 +26733,7 @@ function q1({
l
] }),
/* @__PURE__ */ C("ul", { className: "CommentPlugin_CommentsPanel_List_Thread_Comments", children: f.comments.map((g) => /* @__PURE__ */ C(
- Kd,
+ of,
{
comment: g,
deleteComment: r,
@@ -26339,7 +26743,7 @@ function q1({
g.id
)) }),
/* @__PURE__ */ C("div", { className: "CommentPlugin_CommentsPanel_List_Thread_Editor", children: /* @__PURE__ */ C(
- w1,
+ S1,
{
submitAddComment: i,
thread: f,
@@ -26350,7 +26754,7 @@ function q1({
},
p
) : /* @__PURE__ */ C(
- Kd,
+ of,
{
comment: f,
deleteComment: r,
@@ -26360,18 +26764,18 @@ function q1({
);
}) });
}
-function R1({
+function M1({
activeIDs: e,
deleteCommentOrThread: t,
comments: r,
submitAddComment: n,
markNodeMap: i
}) {
- const s = X(null), o = r.length === 0;
+ 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(
- q1,
+ v1,
{
activeIDs: e,
comments: r,
@@ -26383,11 +26787,11 @@ function R1({
)
] });
}
-function cm() {
- const e = gf(), { yjsDocMap: t, name: r } = e;
+function Mm() {
+ const e = Pf(), { yjsDocMap: t, name: r } = e;
return t.has("comments") ? r : "Scripture User";
}
-function $1({
+function E1({
providerFactory: e,
setCommentStore: t,
onChange: r,
@@ -26395,173 +26799,173 @@ function $1({
commentContainerRef: i,
logger: s
}) {
- const o = gf(), [a] = le(), c = Fe(() => {
- const L = new x1(a, s);
- return r && L.registerOnChange(r), t?.(L), L;
- }, [a, s, r, t]), l = _1(c), u = Fe(() => /* @__PURE__ */ new Map(), []), [d, f] = de(), [p, m] = de([]), [g, y] = de(!1), [T, S] = de(!1), { yjsDocMap: v } = o;
- K(() => {
+ const o = Pf(), [a] = ce(), c = Fe(() => {
+ const w = new p1(a, s);
+ return r && w.registerOnChange(r), t?.(w), w;
+ }, [a, s, r, t]), l = h1(c), u = Fe(() => /* @__PURE__ */ new Map(), []), [d, f] = de(), [p, m] = de([]), [g, y] = de(!1), [k, _] = de(!1), { yjsDocMap: S } = o;
+ z(() => {
if (e) {
- const L = e("comments", v);
- return c.registerCollaboration(L);
+ const w = e("comments", S);
+ return c.registerCollaboration(w);
}
return () => {
};
- }, [c, e, v]);
- const E = ge(() => {
+ }, [c, e, S]);
+ const P = he(() => {
a.update(() => {
- const L = R();
- L !== null && (L.dirty = !0);
+ const w = R();
+ w !== null && (w.dirty = !0);
}), y(!1);
- }, [a]), A = ge(
- (L, G) => {
- if (L.type === "comment") {
- const V = c.deleteCommentOrThread(L, G);
- if (!V)
+ }, [a]), A = he(
+ (w, $) => {
+ if (w.type === "comment") {
+ const Y = c.deleteCommentOrThread(w, $);
+ if (!Y)
return;
- const { markedComment: ae, index: ce } = V;
- c.addComment(ae, G, ce);
+ const { markedComment: Q, index: Me } = Y;
+ c.addComment(Q, $, Me);
} else {
- c.deleteCommentOrThread(L);
- const V = G !== void 0 ? G.id : L.id, ae = u.get(V);
- ae !== void 0 && setTimeout(() => {
+ c.deleteCommentOrThread(w);
+ const Y = $ !== void 0 ? $.id : w.id, Q = u.get(Y);
+ Q !== void 0 && setTimeout(() => {
a.update(() => {
- for (const ce of ae) {
- const ie = ne(ce);
- _e(ie) && (ie.deleteID(Lr, V), ie.hasNoIDsForEveryType() && Rs(ie));
+ for (const Me of Q) {
+ const re = se(Me);
+ Ce(re) && (re.deleteID(zr, Y), re.hasNoIDsForEveryType() && Ds(re));
}
});
});
}
},
[c, a, u]
- ), x = ge(
- (L, G, V, ae) => {
- c.addComment(L, V), G && (a.update(() => {
- N(ae) && qf(ae, Lr, L.id);
+ ), B = he(
+ (w, $, Y, Q) => {
+ c.addComment(w, Y), $ && (a.update(() => {
+ N(Q) && Hf(Q, zr, w.id);
}), y(!1));
},
[c, a]
);
- K(() => {
- const L = [];
- let G;
- for (const V of p) {
- const ae = u.get(V);
- if (ae !== void 0)
- for (const ce of ae) {
- const ie = a.getElementByKey(ce);
- ie !== null && (ie.classList.add("selected"), L.push(ie), G = window.setTimeout(() => {
- S(!0);
+ z(() => {
+ const w = [];
+ let $;
+ for (const Y of p) {
+ const Q = u.get(Y);
+ if (Q !== void 0)
+ for (const Me of Q) {
+ const re = a.getElementByKey(Me);
+ re !== null && (re.classList.add("selected"), w.push(re), $ = window.setTimeout(() => {
+ _(!0);
}, 0));
}
}
return () => {
- G !== void 0 && window.clearTimeout(G);
- for (const V of L)
- V.classList.remove("selected");
+ $ !== void 0 && window.clearTimeout($);
+ for (const Y of w)
+ Y.classList.remove("selected");
};
- }, [p, a, u]), K(() => {
+ }, [p, a, u]), z(() => {
if (!a.hasNodes([Ze]))
throw new Error("CommentPlugin: TypedMarkNode not registered on editor!");
- const L = /* @__PURE__ */ new Map();
- return Xe(
- lf(
+ const w = /* @__PURE__ */ new Map();
+ return He(
+ Cf(
a,
Ze,
- (G) => zi(G.getTypedIDs()),
- (G, V) => {
- for (const [ae, ce] of Object.entries(G.getTypedIDs()))
- ce.forEach((ie) => {
- V.addID(ae, ie);
+ ($) => Hi($.getTypedIDs()),
+ ($, Y) => {
+ for (const [Q, Me] of Object.entries($.getTypedIDs()))
+ Me.forEach((re) => {
+ Y.addID(Q, re);
});
}
),
a.registerMutationListener(
Ze,
- (G) => {
+ ($) => {
a.getEditorState().read(() => {
- for (const [V, ae] of G) {
- const ce = ne(V);
- let ie = [];
- ae === "destroyed" ? ie = L.get(V) ?? [] : _e(ce) && (ie = ce.getTypedIDs()[Lr] ?? []);
- for (const ve of ie) {
- let Pe = u.get(ve);
- L.set(V, ie), ae === "destroyed" ? Pe !== void 0 && (Pe.delete(V), Pe.size === 0 && u.delete(ve)) : (Pe === void 0 && (Pe = /* @__PURE__ */ new Set(), u.set(ve, Pe)), Pe.has(V) || Pe.add(V));
+ for (const [Y, Q] of $) {
+ const Me = se(Y);
+ let re = [];
+ Q === "destroyed" ? re = w.get(Y) ?? [] : Ce(Me) && (re = Me.getTypedIDs()[zr] ?? []);
+ for (const Oe of re) {
+ let be = u.get(Oe);
+ w.set(Y, re), Q === "destroyed" ? be !== void 0 && (be.delete(Y), be.size === 0 && u.delete(Oe)) : (be === void 0 && (be = /* @__PURE__ */ new Set(), u.set(Oe, be)), be.has(Y) || be.add(Y));
}
}
});
},
{ skipInitialization: !1 }
),
- a.registerUpdateListener(({ editorState: G, tags: V }) => {
- G.read(() => {
- const ae = R();
- let ce = !1, ie = !1;
- if (N(ae)) {
- const ve = ae.anchor.getNode();
- if (M(ve)) {
- const Pe = mb(ve, Lr, ae.anchor.offset) ?? [];
- Pe !== null && (m(Pe), ce = !0), ae.isCollapsed() || (f(ve.getKey()), ie = !0);
+ a.registerUpdateListener(({ editorState: $, tags: Y }) => {
+ $.read(() => {
+ const Q = R();
+ let Me = !1, re = !1;
+ if (N(Q)) {
+ const Oe = Q.anchor.getNode();
+ if (v(Oe)) {
+ const be = $b(Oe, zr, Q.anchor.offset) ?? [];
+ be !== null && (m(be), Me = !0), Q.isCollapsed() || (f(Oe.getKey()), re = !0);
}
}
- ce || m((ve) => ve.length === 0 ? ve : []), ie || f(null), !V.has("collaboration") && N(ae) && y(!1);
+ Me || m((Oe) => Oe.length === 0 ? Oe : []), re || f(null), !Y.has("collaboration") && N(Q) && y(!1);
});
}),
a.registerCommand(
- zd,
+ sf,
() => {
- const G = window.getSelection();
- return G !== null && G.removeAllRanges(), y(!0), !0;
+ const $ = window.getSelection();
+ return $ !== null && $.removeAllRanges(), y(!0), !0;
},
- fn
+ mn
)
);
}, [a, u]);
- const F = () => {
- a.dispatchCommand(zd, void 0);
+ const M = () => {
+ a.dispatchCommand(sf, void 0);
};
- return /* @__PURE__ */ Te(dn, { children: [
- g && un(
+ return /* @__PURE__ */ Te(gn, { children: [
+ g && hn(
/* @__PURE__ */ C(
- O1,
+ C1,
{
editor: a,
- cancelAddComment: E,
- submitAddComment: x
+ cancelAddComment: P,
+ submitAddComment: B
}
),
document.body
),
- d != null && !g && un(
+ d != null && !g && hn(
/* @__PURE__ */ C(
- P1,
+ x1,
{
anchorKey: d,
editor: a,
- showComments: T,
- onAddComment: F
+ showComments: k,
+ onAddComment: M
}
),
document.body
),
- n !== null && un(
+ n !== null && hn(
/* @__PURE__ */ C(
- Vr,
+ Yr,
{
- className: `CommentPlugin_ShowCommentsButton ${T ? "active" : ""}`,
- onClick: () => S(!T),
- title: T ? "Hide Comments" : "Show Comments",
+ className: `CommentPlugin_ShowCommentsButton ${k ? "active" : ""}`,
+ onClick: () => _(!k),
+ title: k ? "Hide Comments" : "Show Comments",
children: /* @__PURE__ */ C("i", { className: "comments" })
}
),
n?.current ?? document.body
),
- T && un(
+ k && hn(
/* @__PURE__ */ C(
- R1,
+ M1,
{
comments: l,
- submitAddComment: x,
+ submitAddComment: B,
deleteCommentOrThread: A,
activeIDs: p,
markNodeMap: u
@@ -26571,13 +26975,13 @@ function $1({
)
] });
}
-function I1() {
- const e = X(void 0), t = ge((r) => {
+function A1() {
+ const e = Z(void 0), t = he((r) => {
e.current = r;
}, []);
return [e, t];
}
-function L1(e, t) {
+function P1(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] : {
@@ -26600,21 +27004,21 @@ function L1(e, t) {
e.includes(s.id) || i.push(s);
}), i && t.current?.setComments(i);
}
-function D1(e, t) {
- K(() => {
+function N1(e, t) {
+ z(() => {
e.options ??= {}, e.options.nodes ??= {}, e.options.nodes.addMissingComments = (r) => {
- L1(r, t);
+ P1(r, t);
};
}, [t, e]);
}
-const MA = vn(function(t, r) {
- const n = X(null), i = X(!0), s = X(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) || Xi(g), [T, S] = I1();
- D1(f, T), K(() => {
+const bP = Mn(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, _] = A1();
+ N1(f, k), z(() => {
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);
}
- }, [p]), cc(r, () => ({
+ }, [p]), dc(r, () => ({
focus() {
n.current?.focus();
},
@@ -26651,11 +27055,11 @@ const MA = vn(function(t, r) {
setUsj(A) {
n.current?.setUsj(A);
},
- applyUpdate(A, x) {
- n.current?.applyUpdate(A, x);
+ applyUpdate(A, B) {
+ n.current?.applyUpdate(A, B);
},
- replaceEmbedUpdate(A, x) {
- return n.current?.replaceEmbedUpdate(A, x);
+ replaceEmbedUpdate(A, B) {
+ return n.current?.replaceEmbedUpdate(A, B);
},
getSelection() {
return n.current?.getSelection();
@@ -26663,11 +27067,11 @@ const MA = vn(function(t, r) {
setSelection(A) {
n.current?.setSelection(A);
},
- setAnnotation(A, x, F, L, G) {
- typeof L == "function" || L === void 0 ? n.current?.setAnnotation(A, x, F, L, G) : n.current?.setAnnotation(A, x, F, L);
+ setAnnotation(A, B, M, w, $) {
+ typeof w == "function" || w === void 0 ? n.current?.setAnnotation(A, B, M, w, $) : n.current?.setAnnotation(A, B, M, w);
},
- removeAnnotation(A, x) {
- n.current?.removeAnnotation(A, x);
+ removeAnnotation(A, B) {
+ n.current?.removeAnnotation(A, B);
},
formatPara(A) {
n.current?.formatPara(A);
@@ -26678,11 +27082,11 @@ const MA = vn(function(t, r) {
removeCharacterMarker(A) {
return n.current?.removeCharacterMarker(A) ?? !1;
},
- replaceCharacterMarker(A, x) {
- return n.current?.replaceCharacterMarker(A, x) ?? !1;
+ replaceCharacterMarker(A, B) {
+ return n.current?.replaceCharacterMarker(A, B) ?? !1;
},
- extendCharacterMarker(A, x) {
- return n.current?.extendCharacterMarker(A, x) ?? !1;
+ extendCharacterMarker(A, B) {
+ return n.current?.extendCharacterMarker(A, B) ?? !1;
},
insertMarker(A) {
return n.current?.insertMarker(A);
@@ -26690,20 +27094,20 @@ const MA = vn(function(t, r) {
getMarkerMenuContext() {
return n.current?.getMarkerMenuContext();
},
- applyMarkerMenuSelection(A, x) {
- return n.current?.applyMarkerMenuSelection(A, x);
+ applyMarkerMenuSelection(A, B) {
+ return n.current?.applyMarkerMenuSelection(A, B);
},
splitParagraphWithMarker(A) {
n.current?.splitParagraphWithMarker(A);
},
- commitTypedMarker(A, x) {
- return n.current?.commitTypedMarker(A, x) ?? !1;
+ commitTypedMarker(A, B) {
+ return n.current?.commitTypedMarker(A, B) ?? !1;
},
commitTypedCloser(A) {
return n.current?.commitTypedCloser(A) ?? !1;
},
- insertNote(A, x, F) {
- n.current?.insertNote(A, x, F);
+ insertNote(A, B, M) {
+ n.current?.insertNote(A, B, M);
},
selectNote(A) {
n.current?.selectNote(A);
@@ -26712,33 +27116,33 @@ const MA = vn(function(t, r) {
return n.current?.getNoteOps(A);
},
setComments(A) {
- T.current?.setComments(A), i.current = !0;
+ k.current?.setComments(A), i.current = !0;
},
get toolbarEndRef() {
return o;
}
}));
- const v = ge(
- (A, x, F, L) => {
+ const S = he(
+ (A, B, M, w) => {
if (!u) return;
- const G = T.current?.getComments();
- u(A, G, x, F, L);
+ const $ = k.current?.getComments();
+ u(A, $, B, M, w);
},
- [T, u]
- ), E = ge(() => {
+ [k, u]
+ ), P = he(() => {
if (!l || i.current) {
i.current = !1;
return;
}
- const A = T.current?.getComments();
+ const A = k.current?.getComments();
l(A);
- }, [T, i, l]);
- return K(() => (a(n.current?.toolbarEndRef ?? null), () => a(null)), []), /* @__PURE__ */ C(xy, { children: /* @__PURE__ */ Te(tm, { ref: n, onUsjChange: v, ...f, children: [
+ }, [k, i, l]);
+ return z(() => (a(n.current?.toolbarEndRef ?? null), () => a(null)), []), /* @__PURE__ */ C(Ky, { children: /* @__PURE__ */ Te(km, { ref: n, onUsjChange: S, ...f, children: [
/* @__PURE__ */ C(
- $1,
+ E1,
{
- setCommentStore: S,
- onChange: E,
+ setCommentStore: _,
+ onChange: P,
showCommentsContainerRef: y ? null : d ?? o,
commentContainerRef: s,
logger: f.logger
@@ -26747,96 +27151,96 @@ const MA = vn(function(t, r) {
/* @__PURE__ */ C("div", { ref: s, className: "comment-container" })
] }) });
});
-function ln(e) {
+function pn(e) {
return e.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
}
-function lm(e) {
+function Em(e) {
return e.replace(/["\\\n\r\f<>]/g, (t) => t === `
` ? "\\a " : t === "\r" ? "\\d " : t === "\f" ? "\\c " : t === "<" ? "\\3C " : t === ">" ? "\\3E " : `\\${t}`);
}
-function U1(e) {
+function O1(e) {
return typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape(e) : e.replace(/[^\w-]/g, (t) => `\\${t}`);
}
-const F1 = /^[#\w().,%/\s-]+$/;
-function ur(e) {
+const w1 = /^[#\w().,%/\s-]+$/;
+function mr(e) {
return e != null;
}
-const z1 = {
+const q1 = {
left: "left",
right: "right",
center: "center",
both: "justify"
-}, K1 = {
+}, R1 = {
left: "right",
right: "left"
-}, ac = ".editor-input.usfm", j1 = /^[\w.#[\]="':()>+~*,\s-]+$/;
-function B1(e) {
- return j1.test(e) ? e : (console.warn(
- `[generateUsjCss] Ignoring unsafe containerSelector "${e}"; using "${ac}".`
- ), ac);
+}, uc = ".editor-input.usfm", $1 = /^[\w.#[\]="':()>+~*,\s-]+$/;
+function I1(e) {
+ return $1.test(e) ? e : (console.warn(
+ `[generateUsjCss] Ignoring unsafe containerSelector "${e}"; using "${uc}".`
+ ), uc);
}
-function V1(e, t, r, n) {
+function L1(e, t, r, n) {
const i = [];
- if (t.fontName && i.push(`font-family: "${lm(t.fontName)}"`), t.bold && i.push("font-weight: bold"), t.italic && i.push("font-style: italic"), t.color && (F1.test(t.color) ? i.push(`color: ${t.color}`) : console.warn(
+ if (t.fontName && i.push(`font-family: "${Em(t.fontName)}"`), t.bold && i.push("font-weight: bold"), t.italic && i.push("font-style: italic"), t.color && (w1.test(t.color) ? i.push(`color: ${t.color}`) : console.warn(
`[generateUsjCss] Skipping unsafe color "${t.color}" for marker "${e}".`
- )), ur(t.fontSize) && t.fontSize > 0 && i.push(`font-size: ${Math.floor(t.fontSize * 100 / 12)}%`), ur(t.firstLineIndent) && i.push(`text-indent: ${ln(t.firstLineIndent * 20 * r)}vw`), ur(t.leftMargin) && t.leftMargin >= 0 && i.push(`margin-${n ? "right" : "left"}: ${ln(t.leftMargin * 20 * r)}vw`), ur(t.rightMargin) && t.rightMargin >= 0 && i.push(
- `margin-${n ? "left" : "right"}: ${ln(t.rightMargin * 20 * r)}vw`
- ), ur(t.spaceBefore) && t.spaceBefore >= 0 && i.push(`margin-top: ${ln(t.spaceBefore * r)}pt`), ur(t.spaceAfter) && t.spaceAfter >= 0 && i.push(`margin-bottom: ${ln(t.spaceAfter * r)}pt`), t.lineSpacing === 1 ? i.push("line-height: 1.5") : t.lineSpacing === 2 && i.push("line-height: 2"), t.subscript ? i.push("vertical-align: text-bottom", "font-size: 66%") : t.superscript && i.push("vertical-align: text-top", "font-size: 66%"), t.underline && i.push("text-decoration: underline"), t.smallCaps && i.push("font-variant: small-caps"), t.justification) {
- const s = z1[n ? K1[t.justification] ?? t.justification : t.justification];
+ )), mr(t.fontSize) && t.fontSize > 0 && i.push(`font-size: ${Math.floor(t.fontSize * 100 / 12)}%`), mr(t.firstLineIndent) && i.push(`text-indent: ${pn(t.firstLineIndent * 20 * r)}vw`), mr(t.leftMargin) && t.leftMargin >= 0 && i.push(`margin-${n ? "right" : "left"}: ${pn(t.leftMargin * 20 * r)}vw`), mr(t.rightMargin) && t.rightMargin >= 0 && i.push(
+ `margin-${n ? "left" : "right"}: ${pn(t.rightMargin * 20 * r)}vw`
+ ), mr(t.spaceBefore) && t.spaceBefore >= 0 && i.push(`margin-top: ${pn(t.spaceBefore * r)}pt`), mr(t.spaceAfter) && t.spaceAfter >= 0 && i.push(`margin-bottom: ${pn(t.spaceAfter * r)}pt`), t.lineSpacing === 1 ? i.push("line-height: 1.5") : t.lineSpacing === 2 && i.push("line-height: 2"), t.subscript ? i.push("vertical-align: text-bottom", "font-size: 66%") : t.superscript && i.push("vertical-align: text-top", "font-size: 66%"), t.underline && i.push("text-decoration: underline"), t.smallCaps && i.push("font-variant: small-caps"), t.justification) {
+ const s = q1[n ? R1[t.justification] ?? t.justification : t.justification];
s && i.push(`text-align: ${s}`);
}
return t.textProperties?.includes("verse") && i.push("white-space: nowrap", "unicode-bidi: embed"), i;
}
-const jd = { c: 150, ca: 133, cp: 150 };
-function Bd(e, t) {
- return e && ur(e.fontSize) && e.fontSize > 0 ? Math.floor(e.fontSize * 100 / 12) : t;
+const af = { c: 150, ca: 133, cp: 150 };
+function cf(e, t) {
+ return e && mr(e.fontSize) && e.fontSize > 0 ? Math.floor(e.fontSize * 100 / 12) : t;
}
-function W1(e, t) {
+function D1(e, t) {
if (["c", "ca", "cp"].filter((i) => {
const s = e.markers[i];
- return s && ur(s.fontSize) && s.fontSize > 0;
+ return s && mr(s.fontSize) && s.fontSize > 0;
}).length === 0) return [];
- const n = Bd(e.markers.c, jd.c);
+ const n = cf(e.markers.c, af.c);
return ["ca", "cp"].map((i) => {
- const s = Bd(
+ const s = cf(
e.markers[i],
- jd[i]
- ), o = ln(s * 100 / n);
+ af[i]
+ ), o = pn(s * 100 / n);
return `${t} .usfm_c .usfm_${i}.usfm_${i} { font-size: ${o}%; }`;
});
}
-function EA(e, t = {}) {
- const { zoom: r = 1, rtl: n = !1, containerSelector: i = ac } = t, s = B1(i), o = [], a = [];
- e.defaultFont && a.push(`font-family: "${lm(e.defaultFont)}"`), ur(e.defaultFontSize) && e.defaultFontSize > 0 && a.push(`font-size: ${ln(e.defaultFontSize * r)}pt`), a.length > 0 && o.push(`${s} { ${a.join("; ")}; }`);
+function kP(e, t = {}) {
+ const { zoom: r = 1, rtl: n = !1, containerSelector: i = uc } = t, s = I1(i), o = [], a = [];
+ e.defaultFont && a.push(`font-family: "${Em(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("; ")}; }`);
for (const [c, l] of Object.entries(e.markers)) {
- const u = V1(c, l, r, n);
- u.length > 0 && o.push(`${s} .usfm_${U1(c)} { ${u.join("; ")}; }`);
+ const u = L1(c, l, r, n);
+ u.length > 0 && o.push(`${s} .usfm_${O1(c)} { ${u.join("; ")}; }`);
}
- return o.push(...W1(e, s)), o.join(`
+ return o.push(...D1(e, s)), o.join(`
`);
}
export {
- dh as BLOCK_VERSE_VIEW_MODE,
- k as CategoryType,
- SA as Editorial,
- Os as GENERATOR_NOTE_CALLER,
- yf as HIDDEN_NOTE_CALLER,
- MA as Marginal,
+ vh as BLOCK_VERSE_VIEW_MODE,
+ T as CategoryType,
+ yP as Editorial,
+ Bi as GENERATOR_NOTE_CALLER,
+ Of as HIDDEN_NOTE_CALLER,
+ bP as Marginal,
b as MarkerType,
- lh as PARAGRAPH_STRUCTURE_VIEW_MODE,
- uh as STANDARD_VIEW_MODE,
- Fs as defaultStyleInfo,
- vA as directionToNames,
- zx as filterAndRankItems,
- EA as generateUsjCss,
- _A as getDefaultViewMode,
- _o as getDefaultViewOptions,
- lM as getEnterMenuItems,
- cM as getMarkerMenuItems,
- CA as getViewMode,
- fh as getViewOptions,
- Xi as isBlockVerseLayout,
- $r as isInsertEmbedOpOfType,
- e_ as viewModeToViewNames
+ Ch as PARAGRAPH_STRUCTURE_VIEW_MODE,
+ Sh as STANDARD_VIEW_MODE,
+ Bs as defaultStyleInfo,
+ mP as directionToNames,
+ s_ as filterAndRankItems,
+ kP as generateUsjCss,
+ hP as getDefaultViewMode,
+ Co as getDefaultViewOptions,
+ wM as getEnterMenuItems,
+ OM as getMarkerMenuItems,
+ gP as getViewMode,
+ Mh as getViewOptions,
+ ns as isBlockVerseLayout,
+ Ur as isInsertEmbedOpOfType,
+ b_ as viewModeToViewNames
};
//# sourceMappingURL=index.js.map
diff --git a/packages/platform/dist/index.js.map b/packages/platform/dist/index.js.map
index 5f48a590..3b2ddaaa 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/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/AttributeRunNode.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/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/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/whitespaceDisplay.plugin.utils.ts","../src/editor/markerEdit/MarkerEditPlugin.tsx","../src/editor/markerEdit/MarkerValidationPlugin.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, and sidebars assemble to their faithful USJ shapes at the\n * assembly level, marker-name driven (they are parser-level structures in\n * ParatextData, independent of stylesheet classification): `\\fig …\\fig*` folds\n * to an inline `figure` object (USFM's `src` attribute renamed to USX/USJ's\n * `file`), `\\tr` plus `t[hc][rc]#(-#)` cell markers build `table` →\n * `table:row` → `table:cell` with name-derived `align`/`colspan`, and\n * `\\esb`…`\\esbe` wraps the following blocks in a `sidebar` (`\\cat` directly\n * after `\\esb` folds to its `category`). Anything off the clean shapes —\n * nested markup or positional (USFM 2.0) attributes in a figure, a missing\n * `\\fig*`, a cell marker with no open row — degrades to the plain char/para\n * output 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\";\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 /** Where top-level blocks (paragraphs, tables) land: an open sidebar's content, else the\n * fragment result. */\n const blockTarget = () => (sidebar ? getContent(sidebar) : result);\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 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 // ---- 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 // ---- 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 (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 closeCharStack();\n const [, alignInfix, spanStart, spanEnd] = cellMatch;\n // The cell keeps only the starting column in its marker (`thc3-4` → `thc3`);\n // the span width becomes `colspan`, a string of columns spanned (`thc3-4` → \"2\").\n const cell = {\n type: \"table:cell\",\n marker: spanEnd ? token.marker.slice(0, token.marker.indexOf(\"-\")) : token.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(tableRow).push(cell);\n // The cell becomes the current content container: text, char spans, notes, and\n // verses flow into it through the ordinary `para`-based container logic.\n para = cell;\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 sidebar = { type: \"sidebar\", marker: SIDEBAR_MARKER, content: [] };\n result.push(sidebar);\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 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).\n endTable();\n closeSidebar(false);\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 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 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, 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 excludeFromCopy(destination) {\n return destination !== \"clone\";\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","/** 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 { $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), marker-trailing-space or\n * attribute text (as a plain TextNode or as an opaque block's folded ImmutableTypedTextNode\n * display run, e.g. an UnknownNode's `\\cat` byte display), and empty or NBSP-only spacer text\n * (which the editor→USJ conversion drops as well; ideally the USJ→editor conversion would\n * 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 // 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 * `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\n // element as \"skip this subtree\" and never walks the children, so the run's glyphs AND its\n // value text (the \"2\" of `\\va 2\\va*`) vanished from the text/html clipboard flavor while\n // getTextContent() kept them on text/plain — and most rich paste targets prefer HTML. 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","/**\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`. 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 */\nfunction 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 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 return {\n jsonPath: usjJsonPathFromIndexes($getJsonPathIndexes(node)),\n };\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 records what a cut would cover\n // and claims nothing, so either order of the two is correct: with no removal, nothing it\n // armed can be reaped.\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 { PASTE_COMMAND } from \"lexical\";\nfunction cleanupText(text) {\n return text.replaceAll(\"\\t\", \" \");\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 { pasteSelection, pasteSelectionAsPlainText } from \"./clipboard.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { IS_APPLE } from \"@lexical/utils\";\nimport { COPY_COMMAND, CUT_COMMAND } from \"lexical\";\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 editor.dispatchCommand(COPY_COMMAND, null);\n }\n else if (!shiftKey && key.toLowerCase() === \"x\") {\n event.preventDefault();\n editor.dispatchCommand(CUT_COMMAND, null);\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 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 { pasteSelection, pasteSelectionAsPlainText } from \"./clipboard.utils\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { COPY_COMMAND, CUT_COMMAND } from \"lexical\";\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 new ContextMenuOption(`Cut`, {\n onSelect: () => {\n editor.dispatchCommand(CUT_COMMAND, null);\n },\n isDisabled: isReadonly,\n }),\n new ContextMenuOption(`Copy`, {\n onSelect: () => {\n editor.dispatchCommand(COPY_COMMAND, null);\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 // 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 { 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 's subtree, cloned by\n // the UA (customizable ) — including any on* handlers — and the\n // engine re-mirrors synchronously whenever a removal changes which\n // option/selectedcontent is current, even inside DOMPurify's inert\n // DOMParser document. Hoisting its children on removal re-inserts a fresh\n // mirror target ahead of the walk, which the engine refills, looping\n // forever (DoS) and amplifying output. Dropping its content on removal\n // (rather than hoisting) breaks that cascade; the content is a duplicate\n // of the option, which is sanitized on its own. See campaign-3 F1/F6.\n 'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);\n /* Tags that are safe for data: URIs */\n let DATA_URI_TAGS = null;\n const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);\n /* Attributes safe for values like \"javascript:\" */\n let URI_SAFE_ATTRIBUTES = null;\n const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);\n const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n /* Document namespace */\n let NAMESPACE = HTML_NAMESPACE;\n let IS_EMPTY_INPUT = false;\n /* Allowed XHTML+XML namespaces */\n let ALLOWED_NAMESPACES = null;\n const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);\n const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);\n let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);\n const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);\n let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);\n // Certain elements are allowed in both SVG and HTML\n // namespace. We need to specify them explicitly\n // so that they don't get erroneously deleted from\n // HTML namespace.\n const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);\n /* Parsing of strict XHTML documents */\n let PARSER_MEDIA_TYPE = null;\n const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n let transformCaseFunc = null;\n /* Keep a reference to config to pass to hooks */\n let CONFIG = null;\n /* Ideally, do not touch anything below this line */\n /* ______________________________________________ */\n const formElement = document.createElement('form');\n const isRegexOrFunction = function isRegexOrFunction(testValue) {\n return testValue instanceof RegExp || testValue instanceof Function;\n };\n /**\n * _parseConfig\n *\n * @param cfg optional config literal\n */\n // eslint-disable-next-line complexity\n const _parseConfig = function _parseConfig() {\n let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n if (CONFIG && CONFIG === cfg) {\n return;\n }\n /* Shield configuration object from tampering */\n if (!cfg || typeof cfg !== 'object') {\n cfg = {};\n }\n /* Shield configuration object from prototype pollution */\n cfg = clone(cfg);\n PARSER_MEDIA_TYPE =\n // eslint-disable-next-line unicorn/prefer-includes\n SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;\n // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;\n /* Set configuration parameters */\n ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {\n transform: transformCaseFunc\n });\n ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {\n transform: transformCaseFunc\n });\n ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {\n transform: stringToString\n });\n URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {\n transform: transformCaseFunc,\n base: DEFAULT_URI_SAFE_ATTRIBUTES\n });\n DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {\n transform: transformCaseFunc,\n base: DEFAULT_DATA_URI_TAGS\n });\n FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {\n transform: transformCaseFunc\n });\n FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {\n transform: transformCaseFunc\n });\n FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {\n transform: transformCaseFunc\n });\n USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;\n ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true\n WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n IN_PLACE = cfg.IN_PLACE || false; // Default false\n IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp\n NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace\n MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map\n HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map\n const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);\n CUSTOM_ELEMENT_HANDLING = create(null);\n if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {\n CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined\n }\n if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined\n }\n if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined\n }\n seal(CUSTOM_ELEMENT_HANDLING);\n if (SAFE_FOR_TEMPLATES) {\n ALLOW_DATA_ATTR = false;\n }\n if (RETURN_DOM_FRAGMENT) {\n RETURN_DOM = true;\n }\n /* Parse profile info */\n if (USE_PROFILES) {\n ALLOWED_TAGS = addToSet({}, text);\n ALLOWED_ATTR = create(null);\n if (USE_PROFILES.html === true) {\n addToSet(ALLOWED_TAGS, html$1);\n addToSet(ALLOWED_ATTR, html);\n }\n if (USE_PROFILES.svg === true) {\n addToSet(ALLOWED_TAGS, svg$1);\n addToSet(ALLOWED_ATTR, svg);\n addToSet(ALLOWED_ATTR, xml);\n }\n if (USE_PROFILES.svgFilters === true) {\n addToSet(ALLOWED_TAGS, svgFilters);\n addToSet(ALLOWED_ATTR, svg);\n addToSet(ALLOWED_ATTR, xml);\n }\n if (USE_PROFILES.mathMl === true) {\n addToSet(ALLOWED_TAGS, mathMl$1);\n addToSet(ALLOWED_ATTR, mathMl);\n addToSet(ALLOWED_ATTR, xml);\n }\n }\n /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent\n * leaking across calls when switching from function to array config */\n EXTRA_ELEMENT_HANDLING.tagCheck = null;\n EXTRA_ELEMENT_HANDLING.attributeCheck = null;\n /* Merge configuration parameters */\n if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {\n if (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else if (arrayIsArray(cfg.ADD_TAGS)) {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else if (arrayIsArray(cfg.ADD_ATTR)) {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\n }\n if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {\n addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n }\n if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n }\n if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);\n }\n /* Add #text in case KEEP_CONTENT is set to true */\n if (KEEP_CONTENT) {\n ALLOWED_TAGS['#text'] = true;\n }\n /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n if (WHOLE_DOCUMENT) {\n addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n }\n /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n if (ALLOWED_TAGS.table) {\n addToSet(ALLOWED_TAGS, ['tbody']);\n delete FORBID_TAGS.tbody;\n }\n // Re-derive the active Trusted Types policy from this configuration on\n // every parse. The active policy must never be sticky closure state that\n // outlives the config that set it: a caller-supplied policy left in place\n // after `clearConfig()` — or after a later call that supplied none, or\n // `TRUSTED_TYPES_POLICY: null` — could sign a subsequent \"default\"\n // `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.\n // See GHSA-vxr8-fq34-vvx9.\n if (cfg.TRUSTED_TYPES_POLICY) {\n if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');\n }\n if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');\n }\n // A caller-supplied policy applies to this configuration only.\n const previousTrustedTypesPolicy = trustedTypesPolicy;\n trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n // Sign local variables required by `sanitize`. If the supplied policy's\n // `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this\n // throws via the re-entrancy guard. Restore the previous policy first so\n // the instance is not left in a poisoned state. See #1422.\n try {\n emptyHTML = _createTrustedHTML('');\n } catch (error) {\n trustedTypesPolicy = previousTrustedTypesPolicy;\n throw error;\n }\n } else if (cfg.TRUSTED_TYPES_POLICY === null) {\n // Explicit opt-out for this call: perform no Trusted Types signing and\n // create nothing (so a strict `trusted-types` CSP that disallows a\n // `dompurify` policy can still call `sanitize` from inside its own\n // policy — see #1422). Resetting to `undefined` rather than a sticky\n // `null` also drops any previously retained caller policy, so it cannot\n // resurface on a later call, while still allowing the next config-less\n // call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.\n trustedTypesPolicy = undefined;\n emptyHTML = '';\n } else {\n // No policy supplied: keep the currently active policy if one is set — a\n // previously supplied policy is intentionally sticky across config-less\n // calls — otherwise fall back to the instance's own internal policy,\n // created at most once. (A policy supplied for a *single* call still\n // lingers by design; what must not linger is a policy whose configuration\n // has been torn down via `clearConfig()`, which restores the default.)\n if (trustedTypesPolicy === undefined) {\n trustedTypesPolicy = _getDefaultTrustedTypesPolicy();\n }\n // Sign internal variables only when a policy is active. A falsy policy\n // (Trusted Types unsupported, creation failed, or an explicit opt-out)\n // leaves `emptyHTML` as a plain string, so we never call `.createHTML` on\n // a non-policy and throw. See #1422.\n if (trustedTypesPolicy && typeof emptyHTML === 'string') {\n emptyHTML = _createTrustedHTML('');\n }\n }\n // Prevent further manipulation of configuration.\n // Not available in IE8, Safari 5, etc.\n if (freeze) {\n freeze(cfg);\n }\n CONFIG = cfg;\n };\n /* Keep track of all possible SVG and MathML tags\n * so that we can perform the namespace checks\n * correctly. */\n const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);\n const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);\n /**\n * Namespace rules for an element in the SVG namespace.\n *\n * @param tagName the element's lowercase tag name\n * @param parent the (possibly simulated) parent node\n * @param parentTagName the parent's lowercase tag name\n * @returns true if a spec-compliant parser could produce this element\n */\n const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {\n // The only way to switch from HTML namespace to SVG\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'svg';\n }\n // The only way to switch from MathML to SVG is via \n // if the parent is either or a MathML\n // text integration point.\n if (parent.namespaceURI === MATHML_NAMESPACE) {\n return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);\n }\n // We only allow elements that are defined in SVG\n // spec. All others are disallowed in SVG namespace.\n return Boolean(ALL_SVG_TAGS[tagName]);\n };\n /**\n * Namespace rules for an element in the MathML namespace.\n *\n * @param tagName the element's lowercase tag name\n * @param parent the (possibly simulated) parent node\n * @param parentTagName the parent's lowercase tag name\n * @returns true if a spec-compliant parser could produce this element\n */\n const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {\n // The only way to switch from HTML namespace to MathML\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'math';\n }\n // The only way to switch from SVG to MathML is via\n // and HTML integration points\n if (parent.namespaceURI === SVG_NAMESPACE) {\n return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];\n }\n // We only allow elements that are defined in MathML\n // spec. All others are disallowed in MathML namespace.\n return Boolean(ALL_MATHML_TAGS[tagName]);\n };\n /**\n * Namespace rules for an element in the HTML namespace.\n *\n * @param tagName the element's lowercase tag name\n * @param parent the (possibly simulated) parent node\n * @param parentTagName the parent's lowercase tag name\n * @returns true if a spec-compliant parser could produce this element\n */\n const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {\n // The only way to switch from SVG to HTML is via\n // HTML integration points, and from MathML to HTML\n // is via MathML text integration points\n if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {\n return false;\n }\n if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {\n return false;\n }\n // We disallow tags that are specific for MathML\n // or SVG and should never appear in HTML namespace\n return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);\n };\n /**\n * @param element a DOM element whose namespace is being checked\n * @returns Return false if the element has a\n * namespace that a spec-compliant parser would never\n * return. Return true otherwise.\n */\n const _checkValidNamespace = function _checkValidNamespace(element) {\n let parent = getParentNode(element);\n // In JSDOM, if we're inside shadow DOM, then parentNode\n // can be null. We just simulate parent in this case.\n if (!parent || !parent.tagName) {\n parent = {\n namespaceURI: NAMESPACE,\n tagName: 'template'\n };\n }\n const tagName = stringToLowerCase(element.tagName);\n const parentTagName = stringToLowerCase(parent.tagName);\n if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n return false;\n }\n if (element.namespaceURI === SVG_NAMESPACE) {\n return _checkSvgNamespace(tagName, parent, parentTagName);\n }\n if (element.namespaceURI === MATHML_NAMESPACE) {\n return _checkMathMlNamespace(tagName, parent, parentTagName);\n }\n if (element.namespaceURI === HTML_NAMESPACE) {\n return _checkHtmlNamespace(tagName, parent, parentTagName);\n }\n // For XHTML and XML documents that support custom namespaces\n if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {\n return true;\n }\n // The code should never reach this place (this means\n // that the element somehow got namespace that is not\n // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).\n // Return false just in case.\n return false;\n };\n /**\n * _forceRemove\n *\n * @param node a DOM node\n */\n const _forceRemove = function _forceRemove(node) {\n arrayPush(DOMPurify.removed, {\n element: node\n });\n try {\n // eslint-disable-next-line unicorn/prefer-dom-node-remove\n getParentNode(node).removeChild(node);\n } catch (_) {\n /* The normal detach failed — this is reached for a parentless node\n (getParentNode() is null, so .removeChild throws). Element.prototype\n .remove() is itself a spec no-op on a parentless node, so a recorded\n \"removal\" would otherwise hand the caller back an intact,\n payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or\n the style-with-element-child rule decided to kill). Fail closed by\n throwing — exactly as a clobbered root does at the IN_PLACE entry —\n rather than trying to \"neutralize\" the node via its own methods.\n Neutralizing would mean calling getAttributeNames()/removeAttribute()\n on the node, both of which a