@stll/folio-core 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/ai-edits/apply.d.ts +26 -1
- package/dist/ai-edits/apply.js +1519 -105
- package/dist/ai-edits/batch-claims.d.ts +130 -0
- package/dist/ai-edits/batch-claims.js +244 -0
- package/dist/ai-edits/blockRange.js +9 -3
- package/dist/ai-edits/character-boundaries.d.ts +43 -0
- package/dist/ai-edits/character-boundaries.js +95 -0
- package/dist/ai-edits/clean-text.d.ts +33 -3
- package/dist/ai-edits/clean-text.js +52 -6
- package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
- package/dist/ai-edits/comment-lifecycle.js +52 -0
- package/dist/ai-edits/headless.d.ts +71 -7
- package/dist/ai-edits/headless.js +381 -41
- package/dist/ai-edits/index.d.ts +2 -1
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/minimal-replacement.d.ts +21 -6
- package/dist/ai-edits/minimal-replacement.js +134 -15
- package/dist/ai-edits/newListNumbering.d.ts +23 -0
- package/dist/ai-edits/newListNumbering.js +94 -0
- package/dist/ai-edits/note-references.d.ts +96 -0
- package/dist/ai-edits/note-references.js +139 -0
- package/dist/ai-edits/pending-suggestions.d.ts +79 -0
- package/dist/ai-edits/pending-suggestions.js +306 -0
- package/dist/ai-edits/read.d.ts +1 -1
- package/dist/ai-edits/read.js +56 -43
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +177 -23
- package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
- package/dist/ai-edits/table-cell-mutations.js +4 -1
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
- package/dist/ai-edits/table-row-column-mutations.js +61 -26
- package/dist/ai-edits/types.d.ts +155 -16
- package/dist/compare/compare.d.ts +1 -1
- package/dist/compare/compare.js +65 -16
- package/dist/compare/content-alignment.js +13 -2
- package/dist/compare/content-types.d.ts +7 -0
- package/dist/compare/content.d.ts +4 -1
- package/dist/compare/content.js +6 -1
- package/dist/compare/formatting.js +2 -1
- package/dist/compare/inline-atoms.d.ts +21 -4
- package/dist/compare/inline-atoms.js +138 -16
- package/dist/compare/plan.js +10 -5
- package/dist/compare/scenario.js +14 -3
- package/dist/compare/section-boundary-properties.js +1 -1
- package/dist/compare/types.d.ts +4 -2
- package/dist/compare/types.js +2 -1
- package/dist/compare/verification.d.ts +1 -1
- package/dist/compare/verification.js +15 -3
- package/dist/controller/committedLayoutDiff.d.ts +25 -0
- package/dist/controller/committedLayoutDiff.js +71 -0
- package/dist/controller/contentControlWidgetController.d.ts +1 -1
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/fontReadiness.d.ts +70 -3
- package/dist/controller/fontReadiness.js +100 -10
- package/dist/controller/headerFooterEditorManager.js +6 -1
- package/dist/controller/layoutPipeline.d.ts +12 -10
- package/dist/controller/layoutPipeline.js +62 -9
- package/dist/controller/layoutRunOptions.d.ts +24 -0
- package/dist/controller/layoutRunOptions.js +17 -0
- package/dist/controller/layoutScheduler.d.ts +19 -14
- package/dist/controller/layoutScheduler.js +15 -12
- package/dist/controller/layoutSession.d.ts +26 -2
- package/dist/controller/layoutSession.js +1 -1
- package/dist/controller/noteEditorManager.js +8 -3
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +230 -21
- package/dist/docx/altChunk.d.ts +8 -0
- package/dist/docx/altChunk.js +17 -0
- package/dist/docx/attributeRemainder.d.ts +1 -1
- package/dist/docx/blockContentParser.d.ts +64 -1
- package/dist/docx/blockContentParser.js +17 -4
- package/dist/docx/blockCustomXmlShell.d.ts +7 -0
- package/dist/docx/blockCustomXmlShell.js +14 -0
- package/dist/docx/blockPlainText.js +7 -0
- package/dist/docx/commentAnchorIndex.js +1 -1
- package/dist/docx/commentRangeIntegrity.js +8 -3
- package/dist/docx/commentReferenceNormalization.js +1 -1
- package/dist/docx/commentReplyMarkers.js +20 -1
- package/dist/docx/compatibility.d.ts +1 -1
- package/dist/docx/compatibility.js +34 -13
- package/dist/docx/containerChildren.gen.d.ts +1 -1
- package/dist/docx/containerChildren.gen.js +1 -0
- package/dist/docx/documentParser.js +12 -12
- package/dist/docx/drawingIdNormalization.d.ts +3 -1
- package/dist/docx/drawingIdNormalization.js +6 -1
- package/dist/docx/ensureParaIds.js +119 -97
- package/dist/docx/footnoteParser.js +8 -4
- package/dist/docx/headerFooterParser.js +3 -3
- package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
- package/dist/docx/headerFooterReferenceNormalization.js +52 -27
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/listNumberingInstances.d.ts +59 -0
- package/dist/docx/listNumberingInstances.js +249 -0
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/opaqueCarrier.d.ts +7 -0
- package/dist/docx/opaqueCarrier.js +38 -0
- package/dist/docx/packageParts.js +4 -1
- package/dist/docx/paragraphParser.js +91 -24
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +7 -1
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +173 -20
- package/dist/docx/rezip.js +27 -5
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sdtPropertiesPatch.js +3 -0
- package/dist/docx/selectiveSave.js +16 -9
- package/dist/docx/selectiveXmlPatch.js +106 -37
- package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
- package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
- package/dist/docx/serializer/documentSerializer.js +2 -0
- package/dist/docx/serializer/headerFooterSerializer.js +2 -0
- package/dist/docx/serializer/noteSerializer.js +3 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -9
- package/dist/docx/serializer/tableSerializer.js +25 -38
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/tableParser.d.ts +157 -1
- package/dist/docx/tableParser.js +89 -10
- package/dist/docx/textBoxParser.js +1 -1
- package/dist/docx/unzip.d.ts +2 -1
- package/dist/docx/unzip.js +1 -1
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
- package/dist/i18n/messages/catalogs.gen.js +153 -17
- package/dist/i18n/messages/messages.gen.d.ts +8 -0
- package/dist/internal/compare/inline-presentation.d.ts +2 -1
- package/dist/internal/compare/inline-presentation.js +11 -10
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
- package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
- package/dist/internal/indexedPositionMap.d.ts +11 -0
- package/dist/internal/indexedPositionMap.js +42 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
- package/dist/internal/revisionResolutionEdits.d.ts +13 -0
- package/dist/internal/revisionResolutionEdits.js +19 -0
- package/dist/internal/revisionResolutionInline.d.ts +24 -0
- package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
- package/dist/internal/revisionResolutionStep.d.ts +34 -0
- package/dist/internal/revisionResolutionStep.js +282 -0
- package/dist/internal/revisionResolutionTracking.d.ts +11 -0
- package/dist/internal/revisionResolutionTracking.js +124 -0
- package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
- package/dist/internal/wholeStoryRevisionResolution.js +530 -0
- package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
- package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
- package/dist/layout-bridge/convert/tableConversion.js +1 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
- package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
- package/dist/layout-engine/layoutInstrumentation.js +4 -1
- package/dist/layout-engine/measure/cache.d.ts +18 -1
- package/dist/layout-engine/measure/cache.js +31 -1
- package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
- package/dist/layout-engine/measure/measureContainer.js +7 -47
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
- package/dist/layout-engine/measure/measureHelpers.js +22 -2
- package/dist/layout-engine/measure/measureWorker.js +2 -3
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
- package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/managers/ContextMenuManager.js +5 -2
- package/dist/managers/types.d.ts +3 -0
- package/dist/markdown/fromMarkdown.js +2 -1
- package/dist/markdown/internals.d.ts +11 -1
- package/dist/markdown/internals.js +24 -3
- package/dist/markdown/renderBlock.js +39 -9
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.js +3 -10
- package/dist/markdown/renderTable.js +24 -12
- package/dist/markdown/trailers.js +2 -2
- package/dist/markdown/types.d.ts +29 -7
- package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
- package/dist/paged-layout/incrementalMeasure.js +1 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
- package/dist/paged-layout/transactionDirtyRange.js +29 -41
- package/dist/panel-layout.d.ts +83 -0
- package/dist/panel-layout.js +96 -0
- package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
- package/dist/prosemirror/anchoredTextBoxes.js +156 -0
- package/dist/prosemirror/attrs/index.d.ts +6 -2
- package/dist/prosemirror/attrs/index.js +172 -1
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +241 -207
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/paragraph.d.ts +7 -1
- package/dist/prosemirror/commands/paragraph.js +9 -1
- package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
- package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
- package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
- package/dist/prosemirror/conversion/toProseDoc.js +39 -4
- package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
- package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
- package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
- package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
- package/dist/prosemirror/extensions/StarterKit.js +3 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
- package/dist/prosemirror/extensions/types.d.ts +4 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/index.d.ts +3 -2
- package/dist/prosemirror/index.js +3 -2
- package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
- package/dist/prosemirror/indexedNodeLookup.js +103 -0
- package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
- package/dist/prosemirror/listAutoformatMarkers.js +98 -0
- package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
- package/dist/prosemirror/listInstanceReferences.js +72 -0
- package/dist/prosemirror/listLabels.d.ts +24 -0
- package/dist/prosemirror/listLabels.js +111 -0
- package/dist/prosemirror/listNumbering.d.ts +110 -0
- package/dist/prosemirror/listNumbering.js +345 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewProjection.d.ts +95 -0
- package/dist/prosemirror/markupViewProjection.js +83 -0
- package/dist/prosemirror/numberingAttr.d.ts +9 -1
- package/dist/prosemirror/numberingAttr.js +9 -1
- package/dist/prosemirror/outlineLevelAttr.js +1 -1
- package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
- package/dist/prosemirror/paragraphIndentation.js +29 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
- package/dist/prosemirror/paragraphMarkJoin.js +24 -0
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
- package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
- package/dist/prosemirror/plugins/documentNumbering.js +97 -11
- package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
- package/dist/prosemirror/plugins/documentStyles.js +14 -1
- package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
- package/dist/prosemirror/plugins/suggestionMode.js +116 -52
- package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
- package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
- package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
- package/dist/prosemirror/positionSweep.d.ts +24 -0
- package/dist/prosemirror/positionSweep.js +222 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
- package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
- package/dist/prosemirror/replacedAnnotations.js +170 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +42 -11
- package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
- package/dist/prosemirror/sectionMarkEdits.js +59 -0
- package/dist/prosemirror/storyListNumbering.d.ts +17 -0
- package/dist/prosemirror/storyListNumbering.js +102 -0
- package/dist/prosemirror/tableGridMutation.d.ts +48 -2
- package/dist/prosemirror/tableGridMutation.js +106 -1
- package/dist/prosemirror/validation.js +5 -1
- package/dist/redline.js +93 -9
- package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
- package/dist/render-dom/BodySelectionOverlay.js +3 -2
- package/dist/utils/findReplace.js +2 -2
- package/dist/utils/mergeDocumentContent.js +1 -1
- package/dist/utils/noteReferenceLabels.d.ts +36 -0
- package/dist/utils/noteReferenceLabels.js +32 -0
- package/dist/utils/replaceText.js +1 -1
- package/dist/version-comparison.d.ts +1 -0
- package/dist/version-comparison.js +1 -0
- package/package.json +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/ai-edits/batch-claims.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Which operations of one batch may not share their targets.
|
|
4
|
+
*
|
|
5
|
+
* A batch resolves every operation against the document as it was read and
|
|
6
|
+
* applies them from the end of the document backwards, so an operation never
|
|
7
|
+
* moves the positions an earlier-placed one resolved to. That holds only while
|
|
8
|
+
* the operations' targets are disjoint. Two operations on the same stretch of
|
|
9
|
+
* text, or an operation inside a paragraph another one deletes, rewrites,
|
|
10
|
+
* splits or merges away, are applied against coordinates the first one has
|
|
11
|
+
* already invalidated — and the second then edits whatever now sits there,
|
|
12
|
+
* which can be a paragraph neither operation named.
|
|
13
|
+
*
|
|
14
|
+
* Each operation therefore states what it claims before anything is applied,
|
|
15
|
+
* and the later of two operations whose claims conflict is refused
|
|
16
|
+
* (`overlappingOperation`). The caller re-reads the document after the batch
|
|
17
|
+
* and sends the refused operation again against the result. What remains
|
|
18
|
+
* applies exactly as the same operations applied one at a time, from the end
|
|
19
|
+
* of the document backwards, would.
|
|
20
|
+
*
|
|
21
|
+
* Positions are the ones the batch resolved against. A block is keyed by the
|
|
22
|
+
* position before its node.
|
|
23
|
+
*/
|
|
24
|
+
type PositionRange = {
|
|
25
|
+
from: number;
|
|
26
|
+
to: number;
|
|
27
|
+
};
|
|
28
|
+
/** The text an operation rewrites (`text`), only marks (`annotation`), or breaks (`split`). */
|
|
29
|
+
type InlineClaim = {
|
|
30
|
+
type: "text" | "annotation" | "split";
|
|
31
|
+
block: number;
|
|
32
|
+
from: number;
|
|
33
|
+
to: number;
|
|
34
|
+
};
|
|
35
|
+
type BatchClaim = InlineClaim |
|
|
36
|
+
/** Every character of the block: `replaceBlock` changing its text. */
|
|
37
|
+
{
|
|
38
|
+
type: "rewriteBlock";
|
|
39
|
+
block: number;
|
|
40
|
+
} |
|
|
41
|
+
/** The block's paragraph properties, and nothing of its text. */
|
|
42
|
+
{
|
|
43
|
+
type: "paragraphProperties";
|
|
44
|
+
block: number;
|
|
45
|
+
} | {
|
|
46
|
+
type: "deleteBlock";
|
|
47
|
+
block: number;
|
|
48
|
+
/** Where the node ends. */
|
|
49
|
+
end: number;
|
|
50
|
+
/**
|
|
51
|
+
* The paragraph survives, emptied: it ends its container. A block the
|
|
52
|
+
* batch inserts at `end` lands first (the batch runs backwards), so the
|
|
53
|
+
* paragraph no longer ends anything when the deletion runs and goes as
|
|
54
|
+
* any other does — see `followedByInsertion`.
|
|
55
|
+
*/
|
|
56
|
+
keepsParagraph: boolean;
|
|
57
|
+
/** The node leaves the document while the batch is still applying. */
|
|
58
|
+
removesNode: boolean;
|
|
59
|
+
/** Applied directly rather than tracked. */
|
|
60
|
+
direct: boolean;
|
|
61
|
+
} |
|
|
62
|
+
/**
|
|
63
|
+
* Joins `block` with the block starting at `next`, where `block` ends.
|
|
64
|
+
* Applied directly the two nodes become one while the batch is still
|
|
65
|
+
* applying (`joinsNow`); tracked, only `block`'s paragraph mark is marked
|
|
66
|
+
* deleted and both paragraphs stay as they are until the change is accepted.
|
|
67
|
+
*/
|
|
68
|
+
{
|
|
69
|
+
type: "merge";
|
|
70
|
+
block: number;
|
|
71
|
+
next: number;
|
|
72
|
+
joinsNow: boolean;
|
|
73
|
+
} |
|
|
74
|
+
/** A new block between two others, at `at`. */
|
|
75
|
+
{
|
|
76
|
+
type: "insertion";
|
|
77
|
+
at: number;
|
|
78
|
+
} |
|
|
79
|
+
/**
|
|
80
|
+
* A table, a row or a column's cells, removed. A removed row or column
|
|
81
|
+
* leaves the table to the other structural operations of the batch; a
|
|
82
|
+
* removed table leaves nothing to them.
|
|
83
|
+
*/
|
|
84
|
+
{
|
|
85
|
+
type: "tableRemoval";
|
|
86
|
+
table: number;
|
|
87
|
+
wholeTable: boolean;
|
|
88
|
+
axis: "row" | "column" | "table";
|
|
89
|
+
ranges: readonly PositionRange[];
|
|
90
|
+
} |
|
|
91
|
+
/** Any other change to a table's grid: rows or columns added, cells merged or split. */
|
|
92
|
+
{
|
|
93
|
+
type: "tableStructure";
|
|
94
|
+
table: number;
|
|
95
|
+
axis: "row" | "column" | "cell";
|
|
96
|
+
} | {
|
|
97
|
+
type: "unclaimed";
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The claims of the operations a batch has accepted so far, indexed so a
|
|
101
|
+
* batch of thousands of operations checks each one against its neighbours
|
|
102
|
+
* rather than against every other operation.
|
|
103
|
+
*/
|
|
104
|
+
declare class BatchClaims {
|
|
105
|
+
private readonly blocks;
|
|
106
|
+
private readonly insertions;
|
|
107
|
+
private readonly joins;
|
|
108
|
+
private readonly removals;
|
|
109
|
+
private readonly tables;
|
|
110
|
+
private readonly rowInsertions;
|
|
111
|
+
private readonly columnInsertions;
|
|
112
|
+
/** Accepted deletions that keep their paragraph, by where the paragraph ends. */
|
|
113
|
+
private readonly keptParagraphs;
|
|
114
|
+
/** `claim`'s roles, given the insertions accepted so far. */
|
|
115
|
+
private rolesOf;
|
|
116
|
+
/**
|
|
117
|
+
* The operation an insertion at `at` conflicts with through a deletion it
|
|
118
|
+
* would stop keeping its paragraph: one the deleted paragraph then no
|
|
119
|
+
* longer allows.
|
|
120
|
+
*/
|
|
121
|
+
private conflictOfFollowing;
|
|
122
|
+
/**
|
|
123
|
+
* The id of an accepted operation `claim` conflicts with, or `null` when it
|
|
124
|
+
* conflicts with none.
|
|
125
|
+
*/
|
|
126
|
+
conflictOf(claim: BatchClaim): string | null;
|
|
127
|
+
add(operationId: string, claim: BatchClaim): void;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { BatchClaim, BatchClaims };
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
//#region src/ai-edits/batch-claims.ts
|
|
2
|
+
const blockRolesOf = (claim) => {
|
|
3
|
+
switch (claim.type) {
|
|
4
|
+
case "text":
|
|
5
|
+
case "annotation":
|
|
6
|
+
case "split": return [{
|
|
7
|
+
block: claim.block,
|
|
8
|
+
role: {
|
|
9
|
+
kind: claim.type,
|
|
10
|
+
claim
|
|
11
|
+
}
|
|
12
|
+
}];
|
|
13
|
+
case "rewriteBlock":
|
|
14
|
+
case "paragraphProperties": return [{
|
|
15
|
+
block: claim.block,
|
|
16
|
+
role: { kind: claim.type }
|
|
17
|
+
}];
|
|
18
|
+
case "deleteBlock": return [{
|
|
19
|
+
block: claim.block,
|
|
20
|
+
role: {
|
|
21
|
+
kind: "deleteBlock",
|
|
22
|
+
keepsParagraph: claim.keepsParagraph,
|
|
23
|
+
removesNode: claim.removesNode,
|
|
24
|
+
direct: claim.direct
|
|
25
|
+
}
|
|
26
|
+
}];
|
|
27
|
+
case "merge": return [{
|
|
28
|
+
block: claim.block,
|
|
29
|
+
role: {
|
|
30
|
+
kind: "mergeOwn",
|
|
31
|
+
joinsNow: claim.joinsNow
|
|
32
|
+
}
|
|
33
|
+
}, {
|
|
34
|
+
block: claim.next,
|
|
35
|
+
role: {
|
|
36
|
+
kind: "mergeNext",
|
|
37
|
+
joinsNow: claim.joinsNow
|
|
38
|
+
}
|
|
39
|
+
}];
|
|
40
|
+
default: return [];
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A deletion of the paragraph that ends its container, once the batch inserts
|
|
45
|
+
* a block after it: the insertion lands first, the paragraph then ends
|
|
46
|
+
* nothing, and it goes as any other — tracked, its mark is deleted into the
|
|
47
|
+
* inserted block and its properties with it; directly, the node goes.
|
|
48
|
+
*/
|
|
49
|
+
const followedByInsertion = (role) => ({
|
|
50
|
+
...role,
|
|
51
|
+
keepsParagraph: false,
|
|
52
|
+
removesNode: role.direct
|
|
53
|
+
});
|
|
54
|
+
const isPoint = ({ from, to }) => from === to;
|
|
55
|
+
/**
|
|
56
|
+
* Two stretches of one block conflict when they overlap, or start at the same
|
|
57
|
+
* position: applied from the end backwards, the one applied second would then
|
|
58
|
+
* find the first one's text where its own range begins. Two insertions at one
|
|
59
|
+
* point are the exception — each writes new text and keeps the other's order.
|
|
60
|
+
*/
|
|
61
|
+
const inlineRangesConflict = (left, right) => left.from < right.to && right.from < left.to || left.from === right.from && !(isPoint(left) && isPoint(right));
|
|
62
|
+
const contains = (outer, inner) => outer.from <= inner.from && inner.to <= outer.to;
|
|
63
|
+
/** Whether two claims on the SAME block conflict; symmetric. */
|
|
64
|
+
/**
|
|
65
|
+
* Which of two roles the rules below are written from: the one ranked
|
|
66
|
+
* higher is put on the left, so each pair is decided in one place.
|
|
67
|
+
*/
|
|
68
|
+
const ROLE_RANK = {
|
|
69
|
+
deleteBlock: 4,
|
|
70
|
+
rewriteBlock: 3,
|
|
71
|
+
mergeOwn: 2,
|
|
72
|
+
mergeNext: 2,
|
|
73
|
+
paragraphProperties: 1,
|
|
74
|
+
split: 0,
|
|
75
|
+
text: 0,
|
|
76
|
+
annotation: 0
|
|
77
|
+
};
|
|
78
|
+
const rolesConflict = (left, right) => {
|
|
79
|
+
if (ROLE_RANK[right.kind] > ROLE_RANK[left.kind]) return rolesConflict(right, left);
|
|
80
|
+
switch (left.kind) {
|
|
81
|
+
case "deleteBlock": switch (right.kind) {
|
|
82
|
+
case "paragraphProperties": return !left.keepsParagraph;
|
|
83
|
+
case "mergeNext": return left.removesNode;
|
|
84
|
+
default: return true;
|
|
85
|
+
}
|
|
86
|
+
case "rewriteBlock": switch (right.kind) {
|
|
87
|
+
case "paragraphProperties":
|
|
88
|
+
case "mergeNext": return false;
|
|
89
|
+
case "mergeOwn": return right.joinsNow;
|
|
90
|
+
default: return true;
|
|
91
|
+
}
|
|
92
|
+
case "mergeOwn": switch (right.kind) {
|
|
93
|
+
case "text":
|
|
94
|
+
case "annotation":
|
|
95
|
+
case "paragraphProperties":
|
|
96
|
+
case "mergeNext": return false;
|
|
97
|
+
default: return true;
|
|
98
|
+
}
|
|
99
|
+
case "mergeNext": return right.kind === "paragraphProperties" && left.joinsNow;
|
|
100
|
+
case "paragraphProperties": return right.kind === "split" || right.kind === "paragraphProperties";
|
|
101
|
+
case "split":
|
|
102
|
+
case "text":
|
|
103
|
+
case "annotation":
|
|
104
|
+
if (right.kind === "paragraphProperties") return left.kind === "split";
|
|
105
|
+
if (right.kind !== "split" && right.kind !== "text" && right.kind !== "annotation") return true;
|
|
106
|
+
if (left.kind === "split" && right.kind === "split") return true;
|
|
107
|
+
if (left.kind === "annotation" && right.kind === "annotation") return false;
|
|
108
|
+
if (left.kind === "annotation" && contains(left.claim, right.claim)) return false;
|
|
109
|
+
if (right.kind === "annotation" && contains(right.claim, left.claim)) return false;
|
|
110
|
+
return inlineRangesConflict(left.claim, right.claim);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const insideAny = (ranges, position) => ranges.some(({ from, to }) => from <= position && position < to);
|
|
114
|
+
const strictlyInsideAny = (ranges, position) => ranges.some(({ from, to }) => from < position && position < to);
|
|
115
|
+
/**
|
|
116
|
+
* The claims of the operations a batch has accepted so far, indexed so a
|
|
117
|
+
* batch of thousands of operations checks each one against its neighbours
|
|
118
|
+
* rather than against every other operation.
|
|
119
|
+
*/
|
|
120
|
+
var BatchClaims = class {
|
|
121
|
+
blocks = /* @__PURE__ */ new Map();
|
|
122
|
+
insertions = /* @__PURE__ */ new Map();
|
|
123
|
+
joins = /* @__PURE__ */ new Map();
|
|
124
|
+
removals = [];
|
|
125
|
+
tables = /* @__PURE__ */ new Map();
|
|
126
|
+
rowInsertions = /* @__PURE__ */ new Map();
|
|
127
|
+
columnInsertions = /* @__PURE__ */ new Map();
|
|
128
|
+
/** Accepted deletions that keep their paragraph, by where the paragraph ends. */
|
|
129
|
+
keptParagraphs = /* @__PURE__ */ new Map();
|
|
130
|
+
/** `claim`'s roles, given the insertions accepted so far. */
|
|
131
|
+
rolesOf(claim) {
|
|
132
|
+
return blockRolesOf(claim).map(({ block, role }) => role.kind === "deleteBlock" && role.keepsParagraph && claim.type === "deleteBlock" && this.insertions.has(claim.end) ? {
|
|
133
|
+
block,
|
|
134
|
+
role: followedByInsertion(role)
|
|
135
|
+
} : {
|
|
136
|
+
block,
|
|
137
|
+
role
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The operation an insertion at `at` conflicts with through a deletion it
|
|
142
|
+
* would stop keeping its paragraph: one the deleted paragraph then no
|
|
143
|
+
* longer allows.
|
|
144
|
+
*/
|
|
145
|
+
conflictOfFollowing(at) {
|
|
146
|
+
const kept = this.keptParagraphs.get(at);
|
|
147
|
+
if (kept === void 0) return null;
|
|
148
|
+
const entries = this.blocks.get(kept.block) ?? [];
|
|
149
|
+
const deletion = entries.find(({ operationId }) => operationId === kept.operationId);
|
|
150
|
+
if (deletion?.role.kind !== "deleteBlock") return null;
|
|
151
|
+
const followed = followedByInsertion(deletion.role);
|
|
152
|
+
return entries.find(({ operationId, role }) => operationId !== kept.operationId && rolesConflict(followed, role))?.operationId ?? null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The id of an accepted operation `claim` conflicts with, or `null` when it
|
|
156
|
+
* conflicts with none.
|
|
157
|
+
*/
|
|
158
|
+
conflictOf(claim) {
|
|
159
|
+
for (const { block, role } of this.rolesOf(claim)) {
|
|
160
|
+
for (const entry of this.blocks.get(block) ?? []) if (rolesConflict(entry.role, role)) return entry.operationId;
|
|
161
|
+
if (role.kind === "deleteBlock") continue;
|
|
162
|
+
for (const removal of this.removals) if (insideAny(removal.claim.ranges, block)) return removal.operationId;
|
|
163
|
+
}
|
|
164
|
+
switch (claim.type) {
|
|
165
|
+
case "merge": return this.insertions.get(claim.next) ?? null;
|
|
166
|
+
case "insertion": {
|
|
167
|
+
const join = this.joins.get(claim.at);
|
|
168
|
+
if (join !== void 0) return join;
|
|
169
|
+
const following = this.conflictOfFollowing(claim.at);
|
|
170
|
+
if (following !== null) return following;
|
|
171
|
+
return this.removals.find(({ claim: removed }) => strictlyInsideAny(removed.ranges, claim.at))?.operationId ?? null;
|
|
172
|
+
}
|
|
173
|
+
case "tableStructure": return this.removals.find(({ claim: removed }) => insideAny(removed.ranges, claim.table) || removed.table === claim.table && removed.axis !== claim.axis && (removed.axis === "row" || removed.axis === "column"))?.operationId ?? this.removals.find(({ claim: removed }) => removed.wholeTable && removed.table === claim.table)?.operationId ?? null;
|
|
174
|
+
case "tableRemoval":
|
|
175
|
+
if (claim.axis === "row") {
|
|
176
|
+
const columnInsertion = this.columnInsertions.get(claim.table);
|
|
177
|
+
if (columnInsertion !== void 0) return columnInsertion;
|
|
178
|
+
}
|
|
179
|
+
if (claim.axis === "column") {
|
|
180
|
+
const rowInsertion = this.rowInsertions.get(claim.table);
|
|
181
|
+
if (rowInsertion !== void 0) return rowInsertion;
|
|
182
|
+
}
|
|
183
|
+
if (claim.wholeTable) {
|
|
184
|
+
const structural = this.tables.get(claim.table);
|
|
185
|
+
if (structural !== void 0) return structural;
|
|
186
|
+
}
|
|
187
|
+
for (const removal of this.removals) if (insideAny(removal.claim.ranges, claim.table) || insideAny(claim.ranges, removal.claim.table) || removal.claim.table === claim.table && (removal.claim.wholeTable || claim.wholeTable || removal.claim.axis !== claim.axis)) return removal.operationId;
|
|
188
|
+
for (const [table, operationId] of this.tables) if (strictlyInsideAny(claim.ranges, table)) return operationId;
|
|
189
|
+
for (const [block, entries] of this.blocks) {
|
|
190
|
+
const editor = entries.find(({ role }) => role.kind !== "deleteBlock");
|
|
191
|
+
if (editor !== void 0 && insideAny(claim.ranges, block)) return editor.operationId;
|
|
192
|
+
}
|
|
193
|
+
for (const [at, operationId] of this.insertions) if (strictlyInsideAny(claim.ranges, at)) return operationId;
|
|
194
|
+
return null;
|
|
195
|
+
default: return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
add(operationId, claim) {
|
|
199
|
+
for (const { block, role } of this.rolesOf(claim)) {
|
|
200
|
+
const entries = this.blocks.get(block);
|
|
201
|
+
if (entries) entries.push({
|
|
202
|
+
operationId,
|
|
203
|
+
role
|
|
204
|
+
});
|
|
205
|
+
else this.blocks.set(block, [{
|
|
206
|
+
operationId,
|
|
207
|
+
role
|
|
208
|
+
}]);
|
|
209
|
+
if (claim.type === "deleteBlock" && role.kind === "deleteBlock" && role.keepsParagraph) this.keptParagraphs.set(claim.end, {
|
|
210
|
+
block,
|
|
211
|
+
operationId
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
switch (claim.type) {
|
|
215
|
+
case "merge":
|
|
216
|
+
this.joins.set(claim.next, operationId);
|
|
217
|
+
break;
|
|
218
|
+
case "insertion": {
|
|
219
|
+
if (!this.insertions.has(claim.at)) this.insertions.set(claim.at, operationId);
|
|
220
|
+
const kept = this.keptParagraphs.get(claim.at);
|
|
221
|
+
if (kept !== void 0) {
|
|
222
|
+
this.keptParagraphs.delete(claim.at);
|
|
223
|
+
for (const entry of this.blocks.get(kept.block) ?? []) if (entry.operationId === kept.operationId && entry.role.kind === "deleteBlock") entry.role = followedByInsertion(entry.role);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "tableStructure":
|
|
228
|
+
if (!this.tables.has(claim.table)) this.tables.set(claim.table, operationId);
|
|
229
|
+
if (claim.axis === "row" && !this.rowInsertions.has(claim.table)) this.rowInsertions.set(claim.table, operationId);
|
|
230
|
+
if (claim.axis === "column" && !this.columnInsertions.has(claim.table)) this.columnInsertions.set(claim.table, operationId);
|
|
231
|
+
break;
|
|
232
|
+
case "tableRemoval":
|
|
233
|
+
this.removals.push({
|
|
234
|
+
operationId,
|
|
235
|
+
claim
|
|
236
|
+
});
|
|
237
|
+
if (!this.tables.has(claim.table)) this.tables.set(claim.table, operationId);
|
|
238
|
+
break;
|
|
239
|
+
default: break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
//#endregion
|
|
244
|
+
export { BatchClaims };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findParagraphByParaId } from "../prosemirror/utils/findParagraphByParaId.js";
|
|
2
2
|
import { getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex } from "../types/block-id.js";
|
|
3
3
|
import { buildCleanBlockText, resolveCleanTextRange } from "./clean-text.js";
|
|
4
|
-
import { createFolioAIEditSnapshot, hashFolioAIBlockText, isFolioAIContentBlock } from "./snapshot.js";
|
|
4
|
+
import { collectNoteReferenceLabels, createFolioAIEditSnapshot, hashFolioAIBlockText, isFolioAIContentBlock } from "./snapshot.js";
|
|
5
5
|
//#region src/ai-edits/blockRange.ts
|
|
6
6
|
const resolveFolioAIBlockRange = ({ blockId, doc, snapshot }) => {
|
|
7
7
|
const paraId = getFolioParaIdFromBlockId(blockId);
|
|
@@ -27,7 +27,10 @@ const resolveFolioAITextRange = ({ range, doc, snapshot }) => {
|
|
|
27
27
|
if (blockRange === null) return null;
|
|
28
28
|
const blockNode = doc.nodeAt(blockRange.from);
|
|
29
29
|
if (!blockNode?.isTextblock) return null;
|
|
30
|
-
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from
|
|
30
|
+
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from, {
|
|
31
|
+
fieldResults: "text",
|
|
32
|
+
noteReferences: collectNoteReferenceLabels(doc)
|
|
33
|
+
});
|
|
31
34
|
const resolved = resolveCleanTextRange({
|
|
32
35
|
cleanBlock,
|
|
33
36
|
startOffset: range.startOffset,
|
|
@@ -63,7 +66,10 @@ const resolvePassageRange = ({ blockId, text, doc, snapshot }) => {
|
|
|
63
66
|
if (!blockNode?.isTextblock) return null;
|
|
64
67
|
const needleSource = buildPassageNeedleSource(text);
|
|
65
68
|
if (needleSource === null) return null;
|
|
66
|
-
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from
|
|
69
|
+
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from, {
|
|
70
|
+
fieldResults: "text",
|
|
71
|
+
noteReferences: collectNoteReferenceLabels(doc)
|
|
72
|
+
});
|
|
67
73
|
const match = matchPassage(cleanBlock.text, needleSource);
|
|
68
74
|
if (match === null) return null;
|
|
69
75
|
const resolved = resolveCleanTextRange({
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/ai-edits/character-boundaries.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Where an offset into a block's clean text may fall.
|
|
4
|
+
*
|
|
5
|
+
* Offsets count UTF-16 code units, as JavaScript strings do. One character a
|
|
6
|
+
* reader sees can span several: a character outside the Basic Multilingual
|
|
7
|
+
* Plane is two (a surrogate pair), and a grapheme cluster — a letter and its
|
|
8
|
+
* combining marks, an emoji joined with zero-width joiners, a flag's two
|
|
9
|
+
* regional indicators — is several code points. An editing caret stands
|
|
10
|
+
* only between two clusters.
|
|
11
|
+
*
|
|
12
|
+
* An offset between the two halves of a surrogate pair names no character at
|
|
13
|
+
* all, and text cut there cannot be written: each half becomes a lone
|
|
14
|
+
* surrogate, which XML cannot hold, so the save drops both and the character
|
|
15
|
+
* disappears. Every offset an operation takes is refused there.
|
|
16
|
+
*
|
|
17
|
+
* An offset inside a grapheme cluster names a code point, and marks may start
|
|
18
|
+
* there — a document can itself format a combining mark apart from its base,
|
|
19
|
+
* and a comparison reproducing that formatting needs the same boundary — but
|
|
20
|
+
* text written or broken there changes a character nobody named: the accent
|
|
21
|
+
* of `é` moves onto the replacement, a family emoji falls apart into people.
|
|
22
|
+
* Operations that change text or break a paragraph are refused there as well.
|
|
23
|
+
*/
|
|
24
|
+
/** Whether `offset` falls between the two halves of one surrogate pair of `text`. */
|
|
25
|
+
declare const splitsSurrogatePair: (text: string, offset: number) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether `offset` falls inside one grapheme cluster of `text`, or between
|
|
28
|
+
* the halves of a surrogate pair. Without `Intl.Segmenter` only the surrogate
|
|
29
|
+
* pair is recognised.
|
|
30
|
+
*/
|
|
31
|
+
declare const splitsGraphemeCluster: (text: string, offset: number) => boolean;
|
|
32
|
+
type CharacterBoundaryStrictness =
|
|
33
|
+
/** Refuse only an offset inside a surrogate pair: annotations. */
|
|
34
|
+
"codePoint" |
|
|
35
|
+
/** Refuse any offset inside a grapheme cluster: edits of text and breaks. */
|
|
36
|
+
"grapheme";
|
|
37
|
+
/**
|
|
38
|
+
* Why `offsets` do not all stand on a character boundary of `text`, naming
|
|
39
|
+
* the first that does not and the character it cuts; `null` when they all do.
|
|
40
|
+
*/
|
|
41
|
+
declare const describeCharacterSplit: (text: string, offsets: readonly number[], strictness: CharacterBoundaryStrictness) => string | null;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { CharacterBoundaryStrictness, describeCharacterSplit, splitsGraphemeCluster, splitsSurrogatePair };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/ai-edits/character-boundaries.ts
|
|
2
|
+
/**
|
|
3
|
+
* Where an offset into a block's clean text may fall.
|
|
4
|
+
*
|
|
5
|
+
* Offsets count UTF-16 code units, as JavaScript strings do. One character a
|
|
6
|
+
* reader sees can span several: a character outside the Basic Multilingual
|
|
7
|
+
* Plane is two (a surrogate pair), and a grapheme cluster — a letter and its
|
|
8
|
+
* combining marks, an emoji joined with zero-width joiners, a flag's two
|
|
9
|
+
* regional indicators — is several code points. An editing caret stands
|
|
10
|
+
* only between two clusters.
|
|
11
|
+
*
|
|
12
|
+
* An offset between the two halves of a surrogate pair names no character at
|
|
13
|
+
* all, and text cut there cannot be written: each half becomes a lone
|
|
14
|
+
* surrogate, which XML cannot hold, so the save drops both and the character
|
|
15
|
+
* disappears. Every offset an operation takes is refused there.
|
|
16
|
+
*
|
|
17
|
+
* An offset inside a grapheme cluster names a code point, and marks may start
|
|
18
|
+
* there — a document can itself format a combining mark apart from its base,
|
|
19
|
+
* and a comparison reproducing that formatting needs the same boundary — but
|
|
20
|
+
* text written or broken there changes a character nobody named: the accent
|
|
21
|
+
* of `é` moves onto the replacement, a family emoji falls apart into people.
|
|
22
|
+
* Operations that change text or break a paragraph are refused there as well.
|
|
23
|
+
*/
|
|
24
|
+
const isHighSurrogate = (code) => code >= 55296 && code <= 56319;
|
|
25
|
+
const isLowSurrogate = (code) => code >= 56320 && code <= 57343;
|
|
26
|
+
/** Whether `offset` falls between the two halves of one surrogate pair of `text`. */
|
|
27
|
+
const splitsSurrogatePair = (text, offset) => offset > 0 && offset < text.length && isHighSurrogate(text.charCodeAt(offset - 1)) && isLowSurrogate(text.charCodeAt(offset));
|
|
28
|
+
let graphemeSegmenter;
|
|
29
|
+
const getGraphemeSegmenter = () => {
|
|
30
|
+
if (graphemeSegmenter === void 0) graphemeSegmenter = typeof Intl === "object" && typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
31
|
+
return graphemeSegmenter;
|
|
32
|
+
};
|
|
33
|
+
const MAX_CACHED_TEXTS = 16;
|
|
34
|
+
const graphemeStartsByText = /* @__PURE__ */ new Map();
|
|
35
|
+
/**
|
|
36
|
+
* The offsets at which a grapheme cluster of `text` starts. A batch asks about
|
|
37
|
+
* the same block for each of its operations, so the last few answers are kept.
|
|
38
|
+
*/
|
|
39
|
+
const graphemeStarts = (text, segmenter) => {
|
|
40
|
+
const cached = graphemeStartsByText.get(text);
|
|
41
|
+
if (cached !== void 0) return cached;
|
|
42
|
+
const starts = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const { index } of segmenter.segment(text)) starts.add(index);
|
|
44
|
+
if (graphemeStartsByText.size >= MAX_CACHED_TEXTS) {
|
|
45
|
+
const oldest = graphemeStartsByText.keys().next();
|
|
46
|
+
if (oldest.done !== true) graphemeStartsByText.delete(oldest.value);
|
|
47
|
+
}
|
|
48
|
+
graphemeStartsByText.set(text, starts);
|
|
49
|
+
return starts;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Whether `offset` falls inside one grapheme cluster of `text`, or between
|
|
53
|
+
* the halves of a surrogate pair. Without `Intl.Segmenter` only the surrogate
|
|
54
|
+
* pair is recognised.
|
|
55
|
+
*/
|
|
56
|
+
const splitsGraphemeCluster = (text, offset) => {
|
|
57
|
+
if (offset <= 0 || offset >= text.length) return false;
|
|
58
|
+
if (splitsSurrogatePair(text, offset)) return true;
|
|
59
|
+
const segmenter = getGraphemeSegmenter();
|
|
60
|
+
return segmenter !== null && !graphemeStarts(text, segmenter).has(offset);
|
|
61
|
+
};
|
|
62
|
+
const splitsCharacter = (text, offset, strictness) => strictness === "grapheme" ? splitsGraphemeCluster(text, offset) : splitsSurrogatePair(text, offset);
|
|
63
|
+
/**
|
|
64
|
+
* The character a split `offset` falls inside, as the offsets of its ends: the
|
|
65
|
+
* whole grapheme cluster, or — without `Intl.Segmenter`, which leaves only a
|
|
66
|
+
* surrogate pair recognisable — the pair.
|
|
67
|
+
*/
|
|
68
|
+
const enclosingCharacter = (text, offset) => {
|
|
69
|
+
const segmenter = getGraphemeSegmenter();
|
|
70
|
+
if (segmenter === null) return {
|
|
71
|
+
start: offset - 1,
|
|
72
|
+
end: offset + 1
|
|
73
|
+
};
|
|
74
|
+
const starts = graphemeStarts(text, segmenter);
|
|
75
|
+
let start = offset;
|
|
76
|
+
while (start > 0 && !starts.has(start)) start--;
|
|
77
|
+
let end = offset;
|
|
78
|
+
while (end < text.length && !starts.has(end)) end++;
|
|
79
|
+
return {
|
|
80
|
+
start,
|
|
81
|
+
end
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Why `offsets` do not all stand on a character boundary of `text`, naming
|
|
86
|
+
* the first that does not and the character it cuts; `null` when they all do.
|
|
87
|
+
*/
|
|
88
|
+
const describeCharacterSplit = (text, offsets, strictness) => {
|
|
89
|
+
const offset = offsets.find((candidate) => splitsCharacter(text, candidate, strictness));
|
|
90
|
+
if (offset === void 0) return null;
|
|
91
|
+
const { start, end } = enclosingCharacter(text, offset);
|
|
92
|
+
return `offset ${String(offset)} falls inside ${JSON.stringify(text.slice(start, end))}, which spans offsets ${String(start)} to ${String(end)}; use ${String(start)} or ${String(end)}.`;
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { describeCharacterSplit, splitsGraphemeCluster, splitsSurrogatePair };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PageBreakRunAttrs } from "../prosemirror/schema/nodes.js";
|
|
2
|
+
import { NoteReferenceKind } from "../utils/noteReferenceLabels.js";
|
|
3
|
+
import { NoteReferenceLabels } from "./note-references.js";
|
|
2
4
|
import { Node } from "prosemirror-model";
|
|
3
5
|
//#region src/ai-edits/clean-text.d.ts
|
|
4
6
|
/**
|
|
@@ -56,6 +58,20 @@ type CleanTextStructuralBoundary = {
|
|
|
56
58
|
length: number;
|
|
57
59
|
from: number;
|
|
58
60
|
to: number;
|
|
61
|
+
} | {
|
|
62
|
+
/**
|
|
63
|
+
* A footnote or endnote reference, read as its marker (`[^1]`). The
|
|
64
|
+
* marker is not text: a range may start or end at its edges but never
|
|
65
|
+
* inside it, and no text change may rewrite or remove it.
|
|
66
|
+
*/
|
|
67
|
+
type: "noteReference";
|
|
68
|
+
offset: number;
|
|
69
|
+
/** Characters the marker contributes. */
|
|
70
|
+
length: number;
|
|
71
|
+
from: number;
|
|
72
|
+
to: number;
|
|
73
|
+
noteType: NoteReferenceKind;
|
|
74
|
+
noteId: string;
|
|
59
75
|
};
|
|
60
76
|
type ResolveCleanTextRangeOptions = {
|
|
61
77
|
cleanBlock: CleanBlockText;
|
|
@@ -82,8 +98,16 @@ type BuildCleanBlockTextOptions = {
|
|
|
82
98
|
* offsets that locate it.
|
|
83
99
|
*/
|
|
84
100
|
fieldResults: "text" | "omitted";
|
|
101
|
+
/**
|
|
102
|
+
* How a footnote/endnote reference reads. Labels numbered over the whole
|
|
103
|
+
* story (`collectNoteReferenceLabels`) give the marker every other reader
|
|
104
|
+
* shows; without them a block numbers its own references. `"sourceText"` is
|
|
105
|
+
* the editor's own text for the reference, which only a comparison of two
|
|
106
|
+
* documents' editor text wants.
|
|
107
|
+
*/
|
|
108
|
+
noteReferences?: NoteReferenceLabels | "sourceText";
|
|
85
109
|
};
|
|
86
|
-
declare const buildCleanBlockText: (blockNode: Node, blockFrom: number, { fieldResults }?: BuildCleanBlockTextOptions) => CleanBlockText;
|
|
110
|
+
declare const buildCleanBlockText: (blockNode: Node, blockFrom: number, { fieldResults, noteReferences }?: BuildCleanBlockTextOptions) => CleanBlockText;
|
|
87
111
|
/**
|
|
88
112
|
* A "redline-aware" view of a textblock: the same left-to-right traversal as
|
|
89
113
|
* {@link buildCleanBlockText}, but every tracked change and comment anchor is
|
|
@@ -101,8 +125,14 @@ declare const buildCleanBlockText: (blockNode: Node, blockFrom: number, { fieldR
|
|
|
101
125
|
*
|
|
102
126
|
* This is the view a consumer embeds when it wants the model to reason about
|
|
103
127
|
* the redline itself, in contrast to {@link buildCleanBlockText}'s
|
|
104
|
-
* post-tracked-changes view.
|
|
128
|
+
* post-tracked-changes view. A footnote or endnote reference reads as its
|
|
129
|
+
* marker (`[^1]`), numbered within the block.
|
|
105
130
|
*/
|
|
106
131
|
declare const buildAnnotatedBlockText: (blockNode: Node) => string;
|
|
132
|
+
/**
|
|
133
|
+
* {@link buildAnnotatedBlockText} with a story's reference numbering, so a
|
|
134
|
+
* reference reads as the marker every other reader of the story shows.
|
|
135
|
+
*/
|
|
136
|
+
declare const buildAnnotatedBlockTextWithNoteReferences: (blockNode: Node, noteReferences: NoteReferenceLabels | undefined) => string;
|
|
107
137
|
//#endregion
|
|
108
|
-
export { BuildCleanBlockTextOptions, CleanBlockText, CleanTextStructuralBoundary, buildAnnotatedBlockText, buildCleanBlockText, resolveCleanTextRange };
|
|
138
|
+
export { BuildCleanBlockTextOptions, CleanBlockText, CleanTextStructuralBoundary, buildAnnotatedBlockText, buildAnnotatedBlockTextWithNoteReferences, buildCleanBlockText, resolveCleanTextRange };
|