@stll/folio-core 0.32.1 → 0.33.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/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +692 -75
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +235 -7
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +33 -0
- package/dist/compare/__fixtures__/body-sequence.js +67 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +406 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1059 -0
- package/dist/compare/reproducible-package.d.ts +17 -0
- package/dist/compare/reproducible-package.js +30 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +282 -0
- package/dist/compare/types.js +32 -0
- package/dist/compare/verification.d.ts +55 -0
- package/dist/compare/verification.js +146 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +42 -9
- package/dist/document-operations.js +171 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paragraphParser.js +22 -22
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +13 -0
- package/dist/docx/revisionIdNormalization.js +103 -0
- package/dist/docx/rezip.js +26 -17
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -4
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +2 -1
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +91 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
package/dist/ai-edits/apply.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
1
|
+
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
2
|
+
import { PPR_CHANGE_SCOPED_ATTR_KEYS } from "../prosemirror/commands/propertyChangeScope.js";
|
|
2
3
|
import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
|
|
4
|
+
import { requestDeterministicParaIds } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
|
|
5
|
+
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
6
|
+
import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
|
|
3
7
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
4
8
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
5
9
|
import { buildCleanBlockText } from "./clean-text.js";
|
|
6
10
|
import { hasInlineEmphasis, parseInlineEmphasisRuns, stripInlineEmphasisMarkers } from "./inline-emphasis.js";
|
|
7
|
-
import { hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
11
|
+
import { hashFolioAIBlockText, isHiddenTableRow, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
8
12
|
import { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
|
|
9
13
|
import { planTableMutations } from "./table-mutation-plan.js";
|
|
10
|
-
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey } from "./table-row-column-mutations.js";
|
|
11
|
-
import {
|
|
14
|
+
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, splitCellParagraphTexts } from "./table-row-column-mutations.js";
|
|
15
|
+
import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBoundary, tableRectangleCutsMergedCell } from "./table-targets.js";
|
|
12
16
|
import { diffWordSegments } from "./word-diff.js";
|
|
17
|
+
import { panic } from "better-result";
|
|
13
18
|
import { TableMap } from "prosemirror-tables";
|
|
19
|
+
import { canJoin, canSplit } from "prosemirror-transform";
|
|
14
20
|
//#region src/ai-edits/apply.ts
|
|
15
21
|
/**
|
|
16
22
|
* Operation types applied in `"suggested"` mode. Every produced revision — an
|
|
@@ -33,6 +39,106 @@ const SUGGESTED_SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
33
39
|
"insertTableColumn",
|
|
34
40
|
"deleteTableColumn"
|
|
35
41
|
]);
|
|
42
|
+
/**
|
|
43
|
+
* The attrs that render a list label, cleared together whenever a paragraph
|
|
44
|
+
* stops being a list item. `w:numPr` alone is not enough: the marker attrs the
|
|
45
|
+
* editor caches would keep drawing a label on a paragraph that is no longer in
|
|
46
|
+
* the list. One frozen record rather than a list repeated at each call site,
|
|
47
|
+
* because a list repeated is a list that drifts.
|
|
48
|
+
*/
|
|
49
|
+
const CLEARED_LIST_MARKER_ATTRS = Object.freeze({
|
|
50
|
+
listMarker: null,
|
|
51
|
+
listMarkerHidden: null,
|
|
52
|
+
listLevelNumFmts: null,
|
|
53
|
+
listLevelStarts: null,
|
|
54
|
+
listAbstractNumId: null,
|
|
55
|
+
listStartOverride: null
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* The attrs one `setBlockParagraphProperties` writes, or `null` when the
|
|
59
|
+
* block already holds them. `styleId: null` clears the style; `listLevel`
|
|
60
|
+
* moves `w:numPr/w:ilvl` and leaves `w:numId` alone, because a demoted item
|
|
61
|
+
* stays in the same list; `listLevel: null` drops `w:numPr` entirely, which
|
|
62
|
+
* is a paragraph that stopped being a list item.
|
|
63
|
+
*/
|
|
64
|
+
const paragraphPropertiesPatch = (node, properties) => {
|
|
65
|
+
const patch = {};
|
|
66
|
+
if (properties.styleId !== void 0 && (node.attrs["styleId"] ?? null) !== properties.styleId) patch["styleId"] = properties.styleId;
|
|
67
|
+
if (properties.listLevel !== void 0) {
|
|
68
|
+
const numPr = node.attrs["numPr"];
|
|
69
|
+
const current = typeof numPr === "object" && numPr !== null && "ilvl" in numPr ? numPr.ilvl : void 0;
|
|
70
|
+
if (properties.listLevel === null) {
|
|
71
|
+
if (numPr !== null && numPr !== void 0) {
|
|
72
|
+
patch["numPr"] = null;
|
|
73
|
+
Object.assign(patch, CLEARED_LIST_MARKER_ATTRS);
|
|
74
|
+
}
|
|
75
|
+
} else if (current !== properties.listLevel) {
|
|
76
|
+
const numId = typeof numPr === "object" && numPr !== null && "numId" in numPr ? numPr.numId : void 0;
|
|
77
|
+
patch["numPr"] = {
|
|
78
|
+
...typeof numId === "number" && { numId },
|
|
79
|
+
ilvl: properties.listLevel
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
84
|
+
};
|
|
85
|
+
const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
86
|
+
/**
|
|
87
|
+
* Strip insertion and deletion marks from the zero-width anchors the batch
|
|
88
|
+
* touched.
|
|
89
|
+
*
|
|
90
|
+
* An operation marks a RANGE, and a zero-width anchor between two words is
|
|
91
|
+
* inside it. The operation did not create or delete that anchor, so the anchor
|
|
92
|
+
* must keep its existing revision ownership. Marking is by range everywhere;
|
|
93
|
+
* the guard lives here once rather than at every call site.
|
|
94
|
+
*/
|
|
95
|
+
const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds) => {
|
|
96
|
+
const anchors = [];
|
|
97
|
+
tr.doc.descendants((node, pos) => {
|
|
98
|
+
if (!isZeroWidthAnchor(node)) return true;
|
|
99
|
+
const marks = node.marks.filter(({ attrs, type }) => REVISION_MARK_NAMES.has(type.name) && batchRevisionIds.has(attrs["revisionId"]));
|
|
100
|
+
if (marks.length > 0) anchors.push({
|
|
101
|
+
from: pos,
|
|
102
|
+
to: pos + node.nodeSize,
|
|
103
|
+
marks
|
|
104
|
+
});
|
|
105
|
+
return false;
|
|
106
|
+
});
|
|
107
|
+
for (const { from, to, marks } of anchors) for (const mark of marks) tr = tr.removeMark(from, to, mark);
|
|
108
|
+
return tr;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* The block's non-text inline children a block deletion still has to mark, as
|
|
112
|
+
* positions in `tr.doc`.
|
|
113
|
+
*
|
|
114
|
+
* Skips what is already deleted, so an earlier revision's run is not marked
|
|
115
|
+
* twice, and skips zero-width anchors because the operation did not create or
|
|
116
|
+
* delete them; their existing revision ownership must remain unchanged.
|
|
117
|
+
*/
|
|
118
|
+
const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
119
|
+
const position = tr.mapping.map(blockFrom);
|
|
120
|
+
const block = tr.doc.nodeAt(position);
|
|
121
|
+
if (!block?.isTextblock) return [];
|
|
122
|
+
const ranges = [];
|
|
123
|
+
block.forEach((child, offset) => {
|
|
124
|
+
if (child.isText || isZeroWidthAnchor(child) || child.marks.some(({ type }) => type.name === "deletion")) return;
|
|
125
|
+
const from = position + 1 + offset;
|
|
126
|
+
ranges.push({
|
|
127
|
+
from,
|
|
128
|
+
to: from + child.nodeSize
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
return ranges;
|
|
132
|
+
};
|
|
133
|
+
/** The in-scope paragraph properties as they stand, for a `w:pPrChange` record. */
|
|
134
|
+
const paragraphPropertiesSnapshot = (node) => {
|
|
135
|
+
const snapshot = {};
|
|
136
|
+
for (const key of PPR_CHANGE_SCOPED_ATTR_KEYS) {
|
|
137
|
+
const value = node.attrs[key];
|
|
138
|
+
if (value !== null && value !== void 0) snapshot[key] = value;
|
|
139
|
+
}
|
|
140
|
+
return snapshot;
|
|
141
|
+
};
|
|
36
142
|
const applyReplaceBlockStyleId = ({ item, tr }) => {
|
|
37
143
|
if (item.operation.type !== "replaceBlock" || item.operation.styleId === void 0) return tr;
|
|
38
144
|
const block = tr.doc.nodeAt(item.blockFrom);
|
|
@@ -143,12 +249,31 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
|
|
|
143
249
|
* uniqueness across overlapping calls in the same JS realm.
|
|
144
250
|
*/
|
|
145
251
|
let revisionIdCursor = Date.now() * 1e3;
|
|
146
|
-
const nextRevisionSeed = (
|
|
252
|
+
const nextRevisionSeed = (revisionIdCount) => {
|
|
147
253
|
const start = revisionIdCursor;
|
|
148
|
-
revisionIdCursor += Math.max(
|
|
254
|
+
revisionIdCursor += Math.max(revisionIdCount, 1);
|
|
149
255
|
return start;
|
|
150
256
|
};
|
|
151
257
|
/**
|
|
258
|
+
* Ids one resolved operation may allocate from the shared revision-id range
|
|
259
|
+
* reserved by `nextRevisionSeed`. Most operation types allocate at most
|
|
260
|
+
* three (delete + insert + background-format clearing); reserving four per
|
|
261
|
+
* operation is a safe cushion above that. A multi-paragraph
|
|
262
|
+
* `insertAfterBlock` / `insertBeforeBlock` (`text` split on line breaks,
|
|
263
|
+
* see `splitInsertParagraphTexts`) allocates one id per paragraph in
|
|
264
|
+
* tracked-changes mode plus one for each paragraph MARK it brings, so it
|
|
265
|
+
* needs more than four once split into more than two paragraphs — reserving
|
|
266
|
+
* less than that would let a later `nextRevisionSeed` call reuse an id this
|
|
267
|
+
* operation already stamped on the document.
|
|
268
|
+
*/
|
|
269
|
+
const REVISION_IDS_PER_OPERATION = 4;
|
|
270
|
+
/** Ids one inserted paragraph allocates: its runs, and its paragraph mark. */
|
|
271
|
+
const REVISION_IDS_PER_INSERTED_PARAGRAPH = 2;
|
|
272
|
+
const estimateRevisionIdReservation = (item) => {
|
|
273
|
+
if (item.operation.type === "insertAfterBlock" || item.operation.type === "insertBeforeBlock") return Math.max((item.insertTexts?.length ?? 1) * REVISION_IDS_PER_INSERTED_PARAGRAPH, REVISION_IDS_PER_OPERATION);
|
|
274
|
+
return REVISION_IDS_PER_OPERATION;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
152
277
|
* Walk the live doc once and bucket every textblock by its
|
|
153
278
|
* normalised text hash. Resolution then maps each snapshot anchor
|
|
154
279
|
* to the live block at the same ordinal among same-hash siblings —
|
|
@@ -159,7 +284,8 @@ const nextRevisionSeed = (count) => {
|
|
|
159
284
|
const collectLiveBlocksByHash = (doc) => {
|
|
160
285
|
const byHash = /* @__PURE__ */ new Map();
|
|
161
286
|
doc.descendants((node, pos) => {
|
|
162
|
-
if (
|
|
287
|
+
if (isHiddenTableRow(node)) return false;
|
|
288
|
+
if (!node.isTextblock) return true;
|
|
163
289
|
const cleanText = buildCleanBlockText(node, pos).text;
|
|
164
290
|
const hash = hashFolioAIBlockText(normalizeFolioAIBlockText(cleanText));
|
|
165
291
|
const bucket = byHash.get(hash) ?? [];
|
|
@@ -169,6 +295,7 @@ const collectLiveBlocksByHash = (doc) => {
|
|
|
169
295
|
node
|
|
170
296
|
});
|
|
171
297
|
byHash.set(hash, bucket);
|
|
298
|
+
return false;
|
|
172
299
|
});
|
|
173
300
|
return byHash;
|
|
174
301
|
};
|
|
@@ -183,14 +310,16 @@ const collectLiveBlocksByHash = (doc) => {
|
|
|
183
310
|
const collectLiveBlocksByParaId = (doc) => {
|
|
184
311
|
const byParaId = /* @__PURE__ */ new Map();
|
|
185
312
|
doc.descendants((node, pos) => {
|
|
186
|
-
if (
|
|
313
|
+
if (isHiddenTableRow(node)) return false;
|
|
314
|
+
if (!node.isTextblock) return true;
|
|
187
315
|
const paraId = node.attrs["paraId"];
|
|
188
|
-
if (typeof paraId !== "string" || paraId.length === 0) return;
|
|
316
|
+
if (typeof paraId !== "string" || paraId.length === 0) return false;
|
|
189
317
|
if (!byParaId.has(paraId)) byParaId.set(paraId, {
|
|
190
318
|
from: pos,
|
|
191
319
|
to: pos + node.nodeSize,
|
|
192
320
|
node
|
|
193
321
|
});
|
|
322
|
+
return false;
|
|
194
323
|
});
|
|
195
324
|
return byParaId;
|
|
196
325
|
};
|
|
@@ -234,6 +363,21 @@ const ordinalAmongSameHash = (snapshot, blockId) => {
|
|
|
234
363
|
*/
|
|
235
364
|
const SIGNATURE_LINE = "_".repeat(28);
|
|
236
365
|
/**
|
|
366
|
+
* A plain table from a grid of cell texts, `null` when the schema has no
|
|
367
|
+
* tables. In tracked mode every row carries `trIns`, which is how Word says
|
|
368
|
+
* "this table is new": there is no whole-table insertion element, only rows
|
|
369
|
+
* that were inserted.
|
|
370
|
+
*/
|
|
371
|
+
const buildTableNode = ({ schema, rows, revision }) => {
|
|
372
|
+
const paragraphType = schema.nodes["paragraph"];
|
|
373
|
+
const cellType = schema.nodes["tableCell"];
|
|
374
|
+
const rowType = schema.nodes["tableRow"];
|
|
375
|
+
const tableType = schema.nodes["table"];
|
|
376
|
+
if (!paragraphType || !cellType || !rowType || !tableType || rows.length === 0) return null;
|
|
377
|
+
const rowNodes = rows.map((cells) => rowType.create(revision ? { trIns: revision } : null, cells.map((text) => cellType.create(null, splitCellParagraphTexts(text).map((line) => paragraphType.create(null, line.length > 0 ? schema.text(line) : null))))));
|
|
378
|
+
return tableType.create(null, rowNodes);
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
237
381
|
* Build a borderless PM table mirroring docx-core's
|
|
238
382
|
* `signatureTable` helper: one row, one cell per party, each cell
|
|
239
383
|
* containing party name (bold), two spacer paragraphs, a signature
|
|
@@ -279,6 +423,22 @@ const buildSignatureTableNode = ({ schema, parties }) => {
|
|
|
279
423
|
const row = rowType.create({}, cells);
|
|
280
424
|
return tableType.create({}, row);
|
|
281
425
|
};
|
|
426
|
+
const LINE_BREAK_PATTERN = /\r\n|\r|\n/;
|
|
427
|
+
/**
|
|
428
|
+
* Split `insertAfterBlock` / `insertBeforeBlock` text on line breaks into one
|
|
429
|
+
* string per paragraph the operation should produce. A model routinely sends
|
|
430
|
+
* a whole clause — heading plus body — as one `text` with embedded newlines;
|
|
431
|
+
* Word has no such thing as a paragraph containing a line break, so each line
|
|
432
|
+
* becomes its own paragraph instead of literal newline characters inside one.
|
|
433
|
+
* Blank (whitespace-only) lines are dropped rather than becoming empty
|
|
434
|
+
* paragraphs, so a trailing/leading newline collapses away. Falls back to a
|
|
435
|
+
* single empty string when every line is blank, so callers never get zero
|
|
436
|
+
* paragraphs out of non-empty input.
|
|
437
|
+
*/
|
|
438
|
+
const splitInsertParagraphTexts = (text) => {
|
|
439
|
+
const nonBlankLines = text.split(LINE_BREAK_PATTERN).filter((line) => line.trim().length > 0);
|
|
440
|
+
return nonBlankLines.length > 0 ? nonBlankLines : [""];
|
|
441
|
+
};
|
|
282
442
|
/**
|
|
283
443
|
* Build the inline content for an inserted or replaced block, promoting the
|
|
284
444
|
* model's `**bold**` / `***bold italic***` markdown into real Word marks (the
|
|
@@ -302,9 +462,99 @@ const buildEmphasisInlineContent = (schema, text, baseMarks) => {
|
|
|
302
462
|
}
|
|
303
463
|
return nodes.length > 0 ? nodes : [schema.text(text, [...baseMarks])];
|
|
304
464
|
};
|
|
305
|
-
const
|
|
465
|
+
const isBatchableParagraphInsertion = (item, mode) => {
|
|
466
|
+
if (item.operation.type !== "insertAfterBlock" && item.operation.type !== "insertBeforeBlock") return false;
|
|
467
|
+
const insertTexts = item.insertTexts ?? [""];
|
|
468
|
+
const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
|
|
469
|
+
return !(mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert);
|
|
470
|
+
};
|
|
471
|
+
const buildInsertedParagraphs = ({ item, schema, mode, author, date, initials, commentMark, suggestionId, revisionSeed, isPairedMove }) => {
|
|
472
|
+
const operation = item.operation;
|
|
473
|
+
if (operation.type !== "insertAfterBlock" && operation.type !== "insertBeforeBlock") panic("Only paragraph insertions can build inserted paragraphs", { type: operation.type });
|
|
474
|
+
const insertionType = schema.marks["insertion"];
|
|
475
|
+
const producesTrackedChanges = mode !== "direct";
|
|
476
|
+
const isSuggested = mode === "suggested";
|
|
477
|
+
const trackedRevisionExtras = {
|
|
478
|
+
...initials ? { initials } : {},
|
|
479
|
+
...suggestionId !== null ? {
|
|
480
|
+
provenance: "suggested",
|
|
481
|
+
suggestionId
|
|
482
|
+
} : {}
|
|
483
|
+
};
|
|
484
|
+
const baseAttrs = operation.inheritFormatting === false ? {} : stripBlockIdentityAttrs(item.blockNode.attrs);
|
|
485
|
+
const insertTexts = item.insertTexts ?? [""];
|
|
486
|
+
const revisionIds = [];
|
|
487
|
+
const nodes = [];
|
|
488
|
+
let nextRevisionId = revisionSeed;
|
|
489
|
+
for (const [paragraphIndex, text] of insertTexts.entries()) {
|
|
490
|
+
const isFirstParagraph = paragraphIndex === 0;
|
|
491
|
+
const marks = [];
|
|
492
|
+
let paragraphRevisionId = null;
|
|
493
|
+
if (producesTrackedChanges && insertionType) {
|
|
494
|
+
paragraphRevisionId = nextRevisionId++;
|
|
495
|
+
marks.push(insertionType.create({
|
|
496
|
+
revisionId: paragraphRevisionId,
|
|
497
|
+
author,
|
|
498
|
+
date,
|
|
499
|
+
...isPairedMove(operation.moveId) && { moveKind: "moveTo" },
|
|
500
|
+
...trackedRevisionExtras
|
|
501
|
+
}));
|
|
502
|
+
revisionIds.push(paragraphRevisionId);
|
|
503
|
+
}
|
|
504
|
+
if (commentMark) marks.push(commentMark);
|
|
505
|
+
const content = text.length > 0 ? buildEmphasisInlineContent(schema, text, marks) : null;
|
|
506
|
+
const attrs = isFirstParagraph ? { ...baseAttrs } : {};
|
|
507
|
+
if (isFirstParagraph && operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
|
|
508
|
+
if (isFirstParagraph && operation.listLevel === null) {
|
|
509
|
+
attrs["numPr"] = null;
|
|
510
|
+
Object.assign(attrs, CLEARED_LIST_MARKER_ATTRS);
|
|
511
|
+
} else if (isFirstParagraph && operation.listLevel !== void 0) {
|
|
512
|
+
const anchorNumPr = baseAttrs["numPr"];
|
|
513
|
+
const numId = typeof anchorNumPr === "object" && anchorNumPr !== null && "numId" in anchorNumPr ? anchorNumPr.numId : void 0;
|
|
514
|
+
attrs["numPr"] = {
|
|
515
|
+
...typeof numId === "number" && { numId },
|
|
516
|
+
ilvl: operation.listLevel
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (isFirstParagraph && operation.styleId !== void 0) {
|
|
520
|
+
attrs["styleId"] = operation.styleId;
|
|
521
|
+
if (operation.inheritFormatting !== false && operation.styleId !== null) Object.assign(attrs, CLEARED_LIST_MARKER_ATTRS);
|
|
522
|
+
}
|
|
523
|
+
if (isSuggested && suggestionId !== null && paragraphRevisionId !== null) attrs["_suggestedInsert"] = {
|
|
524
|
+
suggestionId,
|
|
525
|
+
revisionId: paragraphRevisionId,
|
|
526
|
+
author,
|
|
527
|
+
date,
|
|
528
|
+
...initials ? { initials } : {}
|
|
529
|
+
};
|
|
530
|
+
nodes.push(item.blockNode.type.create(attrs, content));
|
|
531
|
+
}
|
|
532
|
+
if (producesTrackedChanges && !isSuggested) for (const [index, node] of nodes.entries()) {
|
|
533
|
+
const revisionId = nextRevisionId++;
|
|
534
|
+
nodes[index] = node.type.create({
|
|
535
|
+
...node.attrs,
|
|
536
|
+
pPrMark: {
|
|
537
|
+
kind: isPairedMove(operation.moveId) ? "moveTo" : "ins",
|
|
538
|
+
info: {
|
|
539
|
+
id: revisionId,
|
|
540
|
+
author,
|
|
541
|
+
date,
|
|
542
|
+
...trackedRevisionExtras
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}, node.content);
|
|
546
|
+
revisionIds.push(revisionId);
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
nodes,
|
|
550
|
+
revisionIds,
|
|
551
|
+
nextRevisionId
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff }) => {
|
|
306
555
|
const applied = [];
|
|
307
556
|
const skipped = [];
|
|
557
|
+
const normalizations = [];
|
|
308
558
|
const resolved = [];
|
|
309
559
|
const insertionType = view.state.schema.marks["insertion"];
|
|
310
560
|
const deletionType = view.state.schema.marks["deletion"];
|
|
@@ -318,7 +568,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
318
568
|
skipped: operations.map((operation) => ({
|
|
319
569
|
id: operation.id,
|
|
320
570
|
reason: "unsupportedBlock"
|
|
321
|
-
}))
|
|
571
|
+
})),
|
|
572
|
+
nextRevisionId: revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor
|
|
322
573
|
};
|
|
323
574
|
const liveBlocks = collectLiveBlocksByHash(view.state.doc);
|
|
324
575
|
const liveBlocksByParaId = collectLiveBlocksByParaId(view.state.doc);
|
|
@@ -369,6 +620,11 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
369
620
|
}
|
|
370
621
|
claimedTableColumns.add(columnKey);
|
|
371
622
|
}
|
|
623
|
+
if ((operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") && LINE_BREAK_PATTERN.test(operation.text)) normalizations.push({
|
|
624
|
+
id: operation.id,
|
|
625
|
+
code: "splitMultilineText",
|
|
626
|
+
paragraphCount: resolution.operation.insertTexts?.length ?? 1
|
|
627
|
+
});
|
|
372
628
|
const commentId = commentText !== void 0 ? createCommentId?.(commentText) : void 0;
|
|
373
629
|
resolved.push({
|
|
374
630
|
...resolution.operation,
|
|
@@ -385,13 +641,41 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
385
641
|
const executableResolved = tablePlan.executable;
|
|
386
642
|
if (executableResolved.length === 0) return {
|
|
387
643
|
applied,
|
|
388
|
-
skipped
|
|
644
|
+
skipped,
|
|
645
|
+
...normalizations.length > 0 && { normalizations },
|
|
646
|
+
nextRevisionId: revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor
|
|
647
|
+
};
|
|
648
|
+
const moveSideCounts = /* @__PURE__ */ new Map();
|
|
649
|
+
for (const { operation } of executableResolved) {
|
|
650
|
+
const moveId = operation.type === "deleteBlock" || operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock" ? operation.moveId : void 0;
|
|
651
|
+
if (moveId === void 0) continue;
|
|
652
|
+
const counts = moveSideCounts.get(moveId) ?? {
|
|
653
|
+
from: 0,
|
|
654
|
+
to: 0
|
|
655
|
+
};
|
|
656
|
+
if (operation.type === "deleteBlock") counts.from += 1;
|
|
657
|
+
else counts.to += 1;
|
|
658
|
+
moveSideCounts.set(moveId, counts);
|
|
659
|
+
}
|
|
660
|
+
const isPairedMove = (moveId) => {
|
|
661
|
+
if (moveId === void 0) return false;
|
|
662
|
+
const counts = moveSideCounts.get(moveId);
|
|
663
|
+
return counts?.from === 1 && counts.to === 1;
|
|
389
664
|
};
|
|
665
|
+
for (const { operation } of executableResolved) {
|
|
666
|
+
const moveId = operation.type === "deleteBlock" || operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock" ? operation.moveId : void 0;
|
|
667
|
+
if (moveId !== void 0 && !isPairedMove(moveId)) normalizations.push({
|
|
668
|
+
id: operation.id,
|
|
669
|
+
code: "unpairedMove",
|
|
670
|
+
moveId
|
|
671
|
+
});
|
|
672
|
+
}
|
|
390
673
|
let tr = view.state.tr;
|
|
391
|
-
|
|
392
|
-
|
|
674
|
+
const revisionIdReservation = executableResolved.reduce((total, item) => total + estimateRevisionIdReservation(item), 0);
|
|
675
|
+
let revisionSeed = revisionIdSeed ?? revisionStamp?.idSeed ?? nextRevisionSeed(revisionIdReservation);
|
|
676
|
+
const date = revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
393
677
|
const insertedColumnCounts = /* @__PURE__ */ new Map();
|
|
394
|
-
|
|
678
|
+
const executionOrder = executableResolved.toSorted((left, right) => {
|
|
395
679
|
const leftCellShape = left.tableCellMerge ?? left.tableCellSplit;
|
|
396
680
|
const rightCellShape = right.tableCellMerge ?? right.tableCellSplit;
|
|
397
681
|
if (!leftCellShape && rightCellShape) return -1;
|
|
@@ -415,7 +699,55 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
415
699
|
}
|
|
416
700
|
if (left.from !== right.from) return right.from - left.from;
|
|
417
701
|
return right.originalIndex - left.originalIndex;
|
|
418
|
-
})
|
|
702
|
+
});
|
|
703
|
+
for (let executionIndex = 0; executionIndex < executionOrder.length; executionIndex++) {
|
|
704
|
+
const item = executionOrder[executionIndex];
|
|
705
|
+
if (!item) panic("The operation execution index exceeded the resolved plan", { executionIndex });
|
|
706
|
+
if (isBatchableParagraphInsertion(item, mode)) {
|
|
707
|
+
const insertionRun = [item];
|
|
708
|
+
for (let lookahead = executionIndex + 1; lookahead < executionOrder.length; lookahead++) {
|
|
709
|
+
const candidate = executionOrder[lookahead];
|
|
710
|
+
if (!candidate || candidate.from !== item.from || !isBatchableParagraphInsertion(candidate, mode)) break;
|
|
711
|
+
insertionRun.push(candidate);
|
|
712
|
+
}
|
|
713
|
+
if (insertionRun.length > 1) {
|
|
714
|
+
const nodeGroups = [];
|
|
715
|
+
const runApplied = [];
|
|
716
|
+
for (const insertion of insertionRun) {
|
|
717
|
+
const insertionSuggestionId = isSuggested ? insertion.operation.suggestionId ?? insertion.operation.id : null;
|
|
718
|
+
const insertionCommentMark = insertion.commentId !== void 0 && commentType ? commentType.create({ commentId: insertion.commentId }) : null;
|
|
719
|
+
const built = buildInsertedParagraphs({
|
|
720
|
+
item: insertion,
|
|
721
|
+
schema: view.state.schema,
|
|
722
|
+
mode,
|
|
723
|
+
author,
|
|
724
|
+
date,
|
|
725
|
+
initials,
|
|
726
|
+
commentMark: insertionCommentMark,
|
|
727
|
+
suggestionId: insertionSuggestionId,
|
|
728
|
+
revisionSeed,
|
|
729
|
+
isPairedMove
|
|
730
|
+
});
|
|
731
|
+
revisionSeed = built.nextRevisionId;
|
|
732
|
+
nodeGroups.push(built.nodes);
|
|
733
|
+
runApplied.push({
|
|
734
|
+
id: insertion.operation.id,
|
|
735
|
+
...insertion.commentId !== void 0 && { commentId: insertion.commentId },
|
|
736
|
+
...built.revisionIds[0] !== void 0 && {
|
|
737
|
+
revisionId: built.revisionIds[0],
|
|
738
|
+
revisionIds: built.revisionIds
|
|
739
|
+
},
|
|
740
|
+
...insertionSuggestionId !== null && { suggestionId: insertionSuggestionId }
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
const nodes = [];
|
|
744
|
+
for (const group of nodeGroups.toReversed()) for (const node of group) nodes.push(node);
|
|
745
|
+
tr = tr.insert(item.from, nodes);
|
|
746
|
+
applied.push(...runApplied);
|
|
747
|
+
executionIndex += insertionRun.length - 1;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
419
751
|
const commentMark = item.commentId !== void 0 && commentType ? commentType.create({ commentId: item.commentId }) : null;
|
|
420
752
|
const stepsBefore = tr.steps.length;
|
|
421
753
|
let appliedRevisionIds;
|
|
@@ -470,7 +802,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
470
802
|
revisionIdInsert,
|
|
471
803
|
commentMark,
|
|
472
804
|
suggestionId,
|
|
473
|
-
initials
|
|
805
|
+
initials,
|
|
806
|
+
granularity: wordDiff?.granularity ?? "word"
|
|
474
807
|
});
|
|
475
808
|
if (producesTrackedChanges) appliedRevisionIds = [
|
|
476
809
|
revisionIdDelete,
|
|
@@ -556,7 +889,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
556
889
|
revisionIdInsert,
|
|
557
890
|
commentMark,
|
|
558
891
|
suggestionId,
|
|
559
|
-
initials
|
|
892
|
+
initials,
|
|
893
|
+
granularity: wordDiff?.granularity ?? "word"
|
|
560
894
|
});
|
|
561
895
|
tr = applyReplaceBlockStyleId({
|
|
562
896
|
item,
|
|
@@ -571,50 +905,30 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
571
905
|
}
|
|
572
906
|
case "insertAfterBlock":
|
|
573
907
|
case "insertBeforeBlock": {
|
|
574
|
-
|
|
908
|
+
const insertTexts = item.insertTexts ?? [""];
|
|
909
|
+
const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
|
|
910
|
+
if (mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert) {
|
|
575
911
|
skipped.push({
|
|
576
912
|
id: item.operation.id,
|
|
577
913
|
reason: "unsupportedMode"
|
|
578
914
|
});
|
|
579
915
|
continue;
|
|
580
916
|
}
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
insertedBlockRevisionId = revisionId;
|
|
586
|
-
marks.push(insertionType.create({
|
|
587
|
-
revisionId,
|
|
588
|
-
author,
|
|
589
|
-
date,
|
|
590
|
-
...trackedRevisionExtras
|
|
591
|
-
}));
|
|
592
|
-
appliedRevisionIds = [revisionId];
|
|
593
|
-
}
|
|
594
|
-
if (commentMark) marks.push(commentMark);
|
|
595
|
-
const content = item.insertText && item.insertText.length > 0 ? buildEmphasisInlineContent(view.state.schema, item.insertText, marks) : null;
|
|
596
|
-
const attrs = { ...item.operation.inheritFormatting === false ? {} : stripBlockIdentityAttrs(item.blockNode.attrs) };
|
|
597
|
-
if (item.operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
|
|
598
|
-
if (item.operation.styleId !== void 0) {
|
|
599
|
-
attrs["styleId"] = item.operation.styleId;
|
|
600
|
-
if (item.operation.inheritFormatting !== false) {
|
|
601
|
-
attrs["listMarker"] = null;
|
|
602
|
-
attrs["listMarkerHidden"] = null;
|
|
603
|
-
attrs["listLevelNumFmts"] = null;
|
|
604
|
-
attrs["listLevelStarts"] = null;
|
|
605
|
-
attrs["listAbstractNumId"] = null;
|
|
606
|
-
attrs["listStartOverride"] = null;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
if (isSuggested && suggestionId !== null && insertedBlockRevisionId !== null) attrs["_suggestedInsert"] = {
|
|
610
|
-
suggestionId,
|
|
611
|
-
revisionId: insertedBlockRevisionId,
|
|
917
|
+
const built = buildInsertedParagraphs({
|
|
918
|
+
item,
|
|
919
|
+
schema: view.state.schema,
|
|
920
|
+
mode,
|
|
612
921
|
author,
|
|
613
922
|
date,
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
923
|
+
initials,
|
|
924
|
+
commentMark,
|
|
925
|
+
suggestionId,
|
|
926
|
+
revisionSeed,
|
|
927
|
+
isPairedMove
|
|
928
|
+
});
|
|
929
|
+
revisionSeed = built.nextRevisionId;
|
|
930
|
+
if (built.revisionIds.length > 0) appliedRevisionIds = built.revisionIds;
|
|
931
|
+
tr = tr.insert(item.from, built.nodes);
|
|
618
932
|
break;
|
|
619
933
|
}
|
|
620
934
|
case "insertSignatureTable": {
|
|
@@ -862,16 +1176,213 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
862
1176
|
}
|
|
863
1177
|
if (deletionType) {
|
|
864
1178
|
const revisionId = revisionSeed++;
|
|
865
|
-
|
|
1179
|
+
const deletionMark = deletionType.create({
|
|
866
1180
|
revisionId,
|
|
867
1181
|
author,
|
|
868
1182
|
date,
|
|
1183
|
+
...isPairedMove(item.operation.moveId) && { moveKind: "moveFrom" },
|
|
869
1184
|
...trackedRevisionExtras
|
|
870
|
-
})
|
|
871
|
-
|
|
1185
|
+
});
|
|
1186
|
+
tr = tr.addMark(item.from, item.to, deletionMark);
|
|
1187
|
+
const atomRanges = undeletedContentAtomRanges(tr, item.blockFrom);
|
|
1188
|
+
for (const { from, to } of atomRanges) tr = tr.addMark(from, to, deletionMark);
|
|
1189
|
+
const inlineRevisionApplied = item.from < item.to || atomRanges.length > 0;
|
|
1190
|
+
appliedRevisionIds = inlineRevisionApplied ? [revisionId] : [];
|
|
1191
|
+
const markPosition = tr.mapping.map(item.blockFrom);
|
|
1192
|
+
if (tr.doc.resolve(markPosition).parent.childCount > 1 && tr.doc.nodeAt(markPosition)?.attrs["pPrMark"] == null) {
|
|
1193
|
+
const markRevisionId = revisionSeed++;
|
|
1194
|
+
tr = tr.setNodeAttribute(markPosition, "pPrMark", {
|
|
1195
|
+
kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
|
|
1196
|
+
info: {
|
|
1197
|
+
id: markRevisionId,
|
|
1198
|
+
author,
|
|
1199
|
+
date,
|
|
1200
|
+
...trackedRevisionExtras
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
appliedRevisionIds = inlineRevisionApplied ? [revisionId, markRevisionId] : [markRevisionId];
|
|
1204
|
+
}
|
|
872
1205
|
}
|
|
873
1206
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
874
1207
|
break;
|
|
1208
|
+
case "insertTable": {
|
|
1209
|
+
const table = buildTableNode({
|
|
1210
|
+
schema: view.state.schema,
|
|
1211
|
+
rows: item.operation.rows,
|
|
1212
|
+
...producesTrackedChanges && { revision: {
|
|
1213
|
+
revisionId: revisionSeed,
|
|
1214
|
+
author,
|
|
1215
|
+
date,
|
|
1216
|
+
...trackedRevisionExtras
|
|
1217
|
+
} }
|
|
1218
|
+
});
|
|
1219
|
+
if (!table) {
|
|
1220
|
+
skipped.push({
|
|
1221
|
+
id: item.operation.id,
|
|
1222
|
+
reason: "unsupportedBlock"
|
|
1223
|
+
});
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (producesTrackedChanges) appliedRevisionIds = [revisionSeed++];
|
|
1227
|
+
tr = tr.insert(item.from, table);
|
|
1228
|
+
markStructuralChange(tr);
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
case "deleteTable": {
|
|
1232
|
+
const deleted = item.deletedTable;
|
|
1233
|
+
if (!deleted) {
|
|
1234
|
+
skipped.push({
|
|
1235
|
+
id: item.operation.id,
|
|
1236
|
+
reason: "unsupportedBlock"
|
|
1237
|
+
});
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (mode === "direct") {
|
|
1241
|
+
const position = tr.mapping.map(deleted.position, 1);
|
|
1242
|
+
const live = tr.doc.nodeAt(position);
|
|
1243
|
+
if (!live || live.type.spec["tableRole"] !== "table") {
|
|
1244
|
+
skipped.push({
|
|
1245
|
+
id: item.operation.id,
|
|
1246
|
+
reason: "unsupportedBlock"
|
|
1247
|
+
});
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
tr = tr.delete(position, position + live.nodeSize);
|
|
1251
|
+
markStructuralChange(tr);
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1254
|
+
const livePosition = tr.mapping.map(deleted.position, 1);
|
|
1255
|
+
const liveTable = tr.doc.nodeAt(livePosition);
|
|
1256
|
+
if (!liveTable || liveTable.type.spec["tableRole"] !== "table") {
|
|
1257
|
+
skipped.push({
|
|
1258
|
+
id: item.operation.id,
|
|
1259
|
+
reason: "unsupportedBlock"
|
|
1260
|
+
});
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
const revision = {
|
|
1264
|
+
revisionId: revisionSeed++,
|
|
1265
|
+
author,
|
|
1266
|
+
date,
|
|
1267
|
+
...trackedRevisionExtras
|
|
1268
|
+
};
|
|
1269
|
+
const rowPositions = [];
|
|
1270
|
+
let rowOffset = livePosition + 1;
|
|
1271
|
+
liveTable.forEach((row) => {
|
|
1272
|
+
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null) rowPositions.push(rowOffset);
|
|
1273
|
+
rowOffset += row.nodeSize;
|
|
1274
|
+
});
|
|
1275
|
+
if (rowPositions.length === 0) {
|
|
1276
|
+
skipped.push({
|
|
1277
|
+
id: item.operation.id,
|
|
1278
|
+
reason: "noopOperation"
|
|
1279
|
+
});
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
for (const rowPosition of rowPositions.toReversed()) tr = tr.setNodeAttribute(rowPosition, "trDel", revision);
|
|
1283
|
+
appliedRevisionIds = [revision.revisionId];
|
|
1284
|
+
markStructuralChange(tr);
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
case "setBlockParagraphProperties": {
|
|
1288
|
+
const patch = paragraphPropertiesPatch(item.blockNode, item.operation.properties);
|
|
1289
|
+
if (patch === null) {
|
|
1290
|
+
skipped.push({
|
|
1291
|
+
id: item.operation.id,
|
|
1292
|
+
reason: "noopOperation"
|
|
1293
|
+
});
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
if (mode === "direct") {
|
|
1297
|
+
tr = tr.setNodeMarkup(item.blockFrom, void 0, {
|
|
1298
|
+
...item.blockNode.attrs,
|
|
1299
|
+
...patch
|
|
1300
|
+
});
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
const revisionId = revisionSeed++;
|
|
1304
|
+
const change = {
|
|
1305
|
+
type: "paragraphPropertyChange",
|
|
1306
|
+
info: {
|
|
1307
|
+
id: revisionId,
|
|
1308
|
+
author,
|
|
1309
|
+
date,
|
|
1310
|
+
...initials ? { initials } : {}
|
|
1311
|
+
},
|
|
1312
|
+
previousFormatting: paragraphPropertiesSnapshot(item.blockNode)
|
|
1313
|
+
};
|
|
1314
|
+
const existing = expectParagraphAttrs(item.blockNode)._propertyChanges;
|
|
1315
|
+
tr = tr.setNodeMarkup(item.blockFrom, void 0, {
|
|
1316
|
+
...item.blockNode.attrs,
|
|
1317
|
+
...patch,
|
|
1318
|
+
_propertyChanges: [...Array.isArray(existing) ? existing : [], change]
|
|
1319
|
+
});
|
|
1320
|
+
appliedRevisionIds = [revisionId];
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
case "splitBlock": {
|
|
1324
|
+
if (mode === "direct") {
|
|
1325
|
+
if (item.to > item.from) tr = tr.delete(item.from, item.to);
|
|
1326
|
+
tr = tr.split(item.from);
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
const revisionIdMark = revisionSeed++;
|
|
1330
|
+
const info = {
|
|
1331
|
+
id: revisionIdMark,
|
|
1332
|
+
author,
|
|
1333
|
+
date,
|
|
1334
|
+
...trackedRevisionExtras
|
|
1335
|
+
};
|
|
1336
|
+
appliedRevisionIds = [revisionIdMark];
|
|
1337
|
+
if (item.to > item.from && deletionType) {
|
|
1338
|
+
const revisionIdSeparator = revisionSeed++;
|
|
1339
|
+
tr = tr.addMark(item.from, item.to, deletionType.create({
|
|
1340
|
+
revisionId: revisionIdSeparator,
|
|
1341
|
+
author,
|
|
1342
|
+
date,
|
|
1343
|
+
...trackedRevisionExtras
|
|
1344
|
+
}));
|
|
1345
|
+
appliedRevisionIds = [revisionIdMark, revisionIdSeparator];
|
|
1346
|
+
}
|
|
1347
|
+
tr = tr.split(item.from);
|
|
1348
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
1349
|
+
kind: "ins",
|
|
1350
|
+
info
|
|
1351
|
+
});
|
|
1352
|
+
break;
|
|
1353
|
+
}
|
|
1354
|
+
case "mergeBlockWithNext": {
|
|
1355
|
+
const separator = item.operation.separator ?? "";
|
|
1356
|
+
const insertAt = item.blockTo - 1;
|
|
1357
|
+
if (mode === "direct") {
|
|
1358
|
+
if (separator.length > 0) tr = tr.insertText(separator, insertAt);
|
|
1359
|
+
tr = tr.join(item.blockTo + separator.length);
|
|
1360
|
+
break;
|
|
1361
|
+
}
|
|
1362
|
+
const revisionIdMark = revisionSeed++;
|
|
1363
|
+
appliedRevisionIds = [revisionIdMark];
|
|
1364
|
+
if (separator.length > 0 && insertionType) {
|
|
1365
|
+
const revisionIdSeparator = revisionSeed++;
|
|
1366
|
+
tr = tr.insertText(separator, insertAt);
|
|
1367
|
+
tr = tr.addMark(insertAt, insertAt + separator.length, insertionType.create({
|
|
1368
|
+
revisionId: revisionIdSeparator,
|
|
1369
|
+
author,
|
|
1370
|
+
date,
|
|
1371
|
+
...trackedRevisionExtras
|
|
1372
|
+
}));
|
|
1373
|
+
appliedRevisionIds = [revisionIdMark, revisionIdSeparator];
|
|
1374
|
+
}
|
|
1375
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
1376
|
+
kind: "del",
|
|
1377
|
+
info: {
|
|
1378
|
+
id: revisionIdMark,
|
|
1379
|
+
author,
|
|
1380
|
+
date,
|
|
1381
|
+
...trackedRevisionExtras
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
875
1386
|
case "commentOnBlock":
|
|
876
1387
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
877
1388
|
break;
|
|
@@ -894,10 +1405,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
894
1405
|
...suggestionId !== null && { suggestionId }
|
|
895
1406
|
});
|
|
896
1407
|
}
|
|
897
|
-
if (tr.docChanged)
|
|
1408
|
+
if (tr.docChanged) {
|
|
1409
|
+
const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
|
|
1410
|
+
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds);
|
|
1411
|
+
if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
|
|
1412
|
+
view.dispatch(tr);
|
|
1413
|
+
}
|
|
898
1414
|
return {
|
|
899
1415
|
applied,
|
|
900
|
-
skipped
|
|
1416
|
+
skipped,
|
|
1417
|
+
...normalizations.length > 0 && { normalizations },
|
|
1418
|
+
nextRevisionId: revisionSeed
|
|
901
1419
|
};
|
|
902
1420
|
};
|
|
903
1421
|
const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
|
|
@@ -918,10 +1436,12 @@ const previewFolioAIEditOperations = (options) => {
|
|
|
918
1436
|
});
|
|
919
1437
|
return {
|
|
920
1438
|
applied: result.applied.map(({ id }) => ({ id })),
|
|
921
|
-
skipped: result.skipped
|
|
1439
|
+
skipped: result.skipped,
|
|
1440
|
+
...result.normalizations !== void 0 && { normalizations: result.normalizations },
|
|
1441
|
+
nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
|
|
922
1442
|
};
|
|
923
1443
|
};
|
|
924
|
-
const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark, suggestionId = null, initials }) => {
|
|
1444
|
+
const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark, suggestionId = null, initials, granularity }) => {
|
|
925
1445
|
let nextTr = tr;
|
|
926
1446
|
const replacement = stripInlineEmphasisMarkers((() => {
|
|
927
1447
|
if (item.operation.type === "replaceInBlock" || item.operation.type === "replaceRange") return item.operation.replace;
|
|
@@ -975,7 +1495,7 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
975
1495
|
}
|
|
976
1496
|
}
|
|
977
1497
|
if (sourceText !== null && cleanBlock !== null) {
|
|
978
|
-
const segments = diffWordSegments(sourceText, replacement);
|
|
1498
|
+
const segments = diffWordSegments(sourceText, replacement, { granularity });
|
|
979
1499
|
const offsets = cleanBlock.offsets;
|
|
980
1500
|
const offsetAt = (cleanOffset) => offsets[cleanOffset] ?? null;
|
|
981
1501
|
const steps = [];
|
|
@@ -1126,15 +1646,12 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1126
1646
|
};
|
|
1127
1647
|
}
|
|
1128
1648
|
if (operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") {
|
|
1129
|
-
|
|
1130
|
-
type: "skip",
|
|
1131
|
-
reason: "emptyOperation"
|
|
1132
|
-
};
|
|
1133
|
-
const tableBoundary = findEnclosingTableBoundary(doc, blockFrom);
|
|
1649
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1134
1650
|
const isInsertAfter = operation.type === "insertAfterBlock";
|
|
1135
1651
|
let insertFrom;
|
|
1136
1652
|
if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
|
|
1137
1653
|
else insertFrom = isInsertAfter ? blockTo : blockFrom;
|
|
1654
|
+
const insertTexts = LINE_BREAK_PATTERN.test(operation.text) ? splitInsertParagraphTexts(operation.text) : [operation.text];
|
|
1138
1655
|
return {
|
|
1139
1656
|
type: "resolved",
|
|
1140
1657
|
operation: {
|
|
@@ -1144,7 +1661,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1144
1661
|
blockFrom,
|
|
1145
1662
|
blockTo,
|
|
1146
1663
|
blockNode,
|
|
1147
|
-
|
|
1664
|
+
insertTexts
|
|
1148
1665
|
}
|
|
1149
1666
|
};
|
|
1150
1667
|
}
|
|
@@ -1154,7 +1671,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1154
1671
|
reason: "emptyOperation"
|
|
1155
1672
|
};
|
|
1156
1673
|
const position = operation.position ?? "after";
|
|
1157
|
-
const tableBoundary =
|
|
1674
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1158
1675
|
let insertFrom;
|
|
1159
1676
|
if (tableBoundary) insertFrom = position === "after" ? tableBoundary.after : tableBoundary.before;
|
|
1160
1677
|
else insertFrom = position === "after" ? blockTo : blockFrom;
|
|
@@ -1176,7 +1693,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1176
1693
|
blockFrom,
|
|
1177
1694
|
position: operation.position ?? "after"
|
|
1178
1695
|
});
|
|
1179
|
-
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.
|
|
1696
|
+
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.columnCount) return {
|
|
1180
1697
|
type: "skip",
|
|
1181
1698
|
reason: "unsupportedBlock"
|
|
1182
1699
|
};
|
|
@@ -1371,11 +1888,115 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1371
1888
|
}
|
|
1372
1889
|
};
|
|
1373
1890
|
}
|
|
1891
|
+
if (operation.type === "insertTable") {
|
|
1892
|
+
const boundary = findOutermostTableBoundary(doc, blockFrom) ?? {
|
|
1893
|
+
before: blockFrom,
|
|
1894
|
+
after: blockTo
|
|
1895
|
+
};
|
|
1896
|
+
const insertFrom = (operation.position ?? "after") === "after" ? boundary.after : boundary.before;
|
|
1897
|
+
return {
|
|
1898
|
+
type: "resolved",
|
|
1899
|
+
operation: {
|
|
1900
|
+
operation,
|
|
1901
|
+
from: insertFrom,
|
|
1902
|
+
to: insertFrom,
|
|
1903
|
+
blockFrom,
|
|
1904
|
+
blockTo,
|
|
1905
|
+
blockNode
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
if (operation.type === "deleteTable") {
|
|
1910
|
+
const target = findEnclosingTableRow(doc, blockFrom);
|
|
1911
|
+
if (!target) return {
|
|
1912
|
+
type: "skip",
|
|
1913
|
+
reason: "unsupportedBlock"
|
|
1914
|
+
};
|
|
1915
|
+
return {
|
|
1916
|
+
type: "resolved",
|
|
1917
|
+
operation: {
|
|
1918
|
+
operation,
|
|
1919
|
+
from: target.tablePosition,
|
|
1920
|
+
to: target.tablePosition + target.table.nodeSize,
|
|
1921
|
+
blockFrom,
|
|
1922
|
+
blockTo,
|
|
1923
|
+
blockNode,
|
|
1924
|
+
deletedTable: {
|
|
1925
|
+
position: target.tablePosition,
|
|
1926
|
+
node: target.table
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
if (operation.type === "setBlockParagraphProperties") return {
|
|
1932
|
+
type: "resolved",
|
|
1933
|
+
operation: {
|
|
1934
|
+
operation,
|
|
1935
|
+
from: blockFrom,
|
|
1936
|
+
to: blockTo,
|
|
1937
|
+
blockFrom,
|
|
1938
|
+
blockTo,
|
|
1939
|
+
blockNode
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
if (operation.type === "splitBlock") {
|
|
1943
|
+
const separator = operation.separator ?? "";
|
|
1944
|
+
const at = cleanBlock.offsets[operation.offset];
|
|
1945
|
+
const after = cleanBlock.offsets[operation.offset + separator.length];
|
|
1946
|
+
if (at === void 0 || after === void 0 || operation.offset === 0 || operation.offset + separator.length >= currentText.length) return {
|
|
1947
|
+
type: "skip",
|
|
1948
|
+
reason: "staleRange"
|
|
1949
|
+
};
|
|
1950
|
+
if (currentText.slice(operation.offset, operation.offset + separator.length) !== separator) return {
|
|
1951
|
+
type: "skip",
|
|
1952
|
+
reason: "staleRange"
|
|
1953
|
+
};
|
|
1954
|
+
if (!canSplit(doc, at)) return {
|
|
1955
|
+
type: "skip",
|
|
1956
|
+
reason: "unsupportedBlock"
|
|
1957
|
+
};
|
|
1958
|
+
return {
|
|
1959
|
+
type: "resolved",
|
|
1960
|
+
operation: {
|
|
1961
|
+
operation,
|
|
1962
|
+
from: at,
|
|
1963
|
+
to: after,
|
|
1964
|
+
blockFrom,
|
|
1965
|
+
blockTo,
|
|
1966
|
+
blockNode
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
if (operation.type === "mergeBlockWithNext") {
|
|
1971
|
+
if (!canJoin(doc, blockTo)) return {
|
|
1972
|
+
type: "skip",
|
|
1973
|
+
reason: "unsupportedBlock"
|
|
1974
|
+
};
|
|
1975
|
+
return {
|
|
1976
|
+
type: "resolved",
|
|
1977
|
+
operation: {
|
|
1978
|
+
operation,
|
|
1979
|
+
from: blockTo,
|
|
1980
|
+
to: blockTo,
|
|
1981
|
+
blockFrom,
|
|
1982
|
+
blockTo,
|
|
1983
|
+
blockNode
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1374
1987
|
if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
|
|
1375
1988
|
const range = getTextRangeFromCleanBlock(cleanBlock);
|
|
1376
1989
|
if (!range) {
|
|
1377
1990
|
const insertionPoint = cleanBlock.offsets.at(0);
|
|
1378
|
-
if (
|
|
1991
|
+
if (insertionPoint === void 0) return {
|
|
1992
|
+
type: "skip",
|
|
1993
|
+
reason: "unsupportedBlock"
|
|
1994
|
+
};
|
|
1995
|
+
if (!(operation.type === "deleteBlock" ? true : currentText.length === 0 && operation.text.length > 0)) return {
|
|
1996
|
+
type: "skip",
|
|
1997
|
+
reason: "unsupportedBlock"
|
|
1998
|
+
};
|
|
1999
|
+
return {
|
|
1379
2000
|
type: "resolved",
|
|
1380
2001
|
operation: {
|
|
1381
2002
|
operation,
|
|
@@ -1386,10 +2007,6 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1386
2007
|
blockNode
|
|
1387
2008
|
}
|
|
1388
2009
|
};
|
|
1389
|
-
return {
|
|
1390
|
-
type: "skip",
|
|
1391
|
-
reason: "unsupportedBlock"
|
|
1392
|
-
};
|
|
1393
2010
|
}
|
|
1394
2011
|
if (operation.type === "replaceBlock" && operation.text === currentText) return {
|
|
1395
2012
|
type: "skip",
|