@stll/folio-core 0.32.2 → 0.33.1
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 +652 -86
- 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 +219 -15
- 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 +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -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 +412 -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 +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -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 +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- 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 +96 -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 +3 -3
- 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);
|
|
@@ -155,14 +261,16 @@ const nextRevisionSeed = (revisionIdCount) => {
|
|
|
155
261
|
* operation is a safe cushion above that. A multi-paragraph
|
|
156
262
|
* `insertAfterBlock` / `insertBeforeBlock` (`text` split on line breaks,
|
|
157
263
|
* see `splitInsertParagraphTexts`) allocates one id per paragraph in
|
|
158
|
-
* tracked-changes mode
|
|
159
|
-
* than four
|
|
160
|
-
* `nextRevisionSeed` call reuse an id this
|
|
161
|
-
* the document.
|
|
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.
|
|
162
268
|
*/
|
|
163
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;
|
|
164
272
|
const estimateRevisionIdReservation = (item) => {
|
|
165
|
-
if (item.operation.type === "insertAfterBlock" || item.operation.type === "insertBeforeBlock") return Math.max(item.insertTexts?.length ?? 1, REVISION_IDS_PER_OPERATION);
|
|
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);
|
|
166
274
|
return REVISION_IDS_PER_OPERATION;
|
|
167
275
|
};
|
|
168
276
|
/**
|
|
@@ -176,7 +284,8 @@ const estimateRevisionIdReservation = (item) => {
|
|
|
176
284
|
const collectLiveBlocksByHash = (doc) => {
|
|
177
285
|
const byHash = /* @__PURE__ */ new Map();
|
|
178
286
|
doc.descendants((node, pos) => {
|
|
179
|
-
if (
|
|
287
|
+
if (isHiddenTableRow(node)) return false;
|
|
288
|
+
if (!node.isTextblock) return true;
|
|
180
289
|
const cleanText = buildCleanBlockText(node, pos).text;
|
|
181
290
|
const hash = hashFolioAIBlockText(normalizeFolioAIBlockText(cleanText));
|
|
182
291
|
const bucket = byHash.get(hash) ?? [];
|
|
@@ -186,6 +295,7 @@ const collectLiveBlocksByHash = (doc) => {
|
|
|
186
295
|
node
|
|
187
296
|
});
|
|
188
297
|
byHash.set(hash, bucket);
|
|
298
|
+
return false;
|
|
189
299
|
});
|
|
190
300
|
return byHash;
|
|
191
301
|
};
|
|
@@ -200,14 +310,16 @@ const collectLiveBlocksByHash = (doc) => {
|
|
|
200
310
|
const collectLiveBlocksByParaId = (doc) => {
|
|
201
311
|
const byParaId = /* @__PURE__ */ new Map();
|
|
202
312
|
doc.descendants((node, pos) => {
|
|
203
|
-
if (
|
|
313
|
+
if (isHiddenTableRow(node)) return false;
|
|
314
|
+
if (!node.isTextblock) return true;
|
|
204
315
|
const paraId = node.attrs["paraId"];
|
|
205
|
-
if (typeof paraId !== "string" || paraId.length === 0) return;
|
|
316
|
+
if (typeof paraId !== "string" || paraId.length === 0) return false;
|
|
206
317
|
if (!byParaId.has(paraId)) byParaId.set(paraId, {
|
|
207
318
|
from: pos,
|
|
208
319
|
to: pos + node.nodeSize,
|
|
209
320
|
node
|
|
210
321
|
});
|
|
322
|
+
return false;
|
|
211
323
|
});
|
|
212
324
|
return byParaId;
|
|
213
325
|
};
|
|
@@ -251,6 +363,21 @@ const ordinalAmongSameHash = (snapshot, blockId) => {
|
|
|
251
363
|
*/
|
|
252
364
|
const SIGNATURE_LINE = "_".repeat(28);
|
|
253
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
|
+
/**
|
|
254
381
|
* Build a borderless PM table mirroring docx-core's
|
|
255
382
|
* `signatureTable` helper: one row, one cell per party, each cell
|
|
256
383
|
* containing party name (bold), two spacer paragraphs, a signature
|
|
@@ -335,7 +462,96 @@ const buildEmphasisInlineContent = (schema, text, baseMarks) => {
|
|
|
335
462
|
}
|
|
336
463
|
return nodes.length > 0 ? nodes : [schema.text(text, [...baseMarks])];
|
|
337
464
|
};
|
|
338
|
-
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 }) => {
|
|
339
555
|
const applied = [];
|
|
340
556
|
const skipped = [];
|
|
341
557
|
const normalizations = [];
|
|
@@ -352,7 +568,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
352
568
|
skipped: operations.map((operation) => ({
|
|
353
569
|
id: operation.id,
|
|
354
570
|
reason: "unsupportedBlock"
|
|
355
|
-
}))
|
|
571
|
+
})),
|
|
572
|
+
nextRevisionId: revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor
|
|
356
573
|
};
|
|
357
574
|
const liveBlocks = collectLiveBlocksByHash(view.state.doc);
|
|
358
575
|
const liveBlocksByParaId = collectLiveBlocksByParaId(view.state.doc);
|
|
@@ -425,14 +642,40 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
425
642
|
if (executableResolved.length === 0) return {
|
|
426
643
|
applied,
|
|
427
644
|
skipped,
|
|
428
|
-
...normalizations.length > 0 && { normalizations }
|
|
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;
|
|
429
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
|
+
}
|
|
430
673
|
let tr = view.state.tr;
|
|
431
674
|
const revisionIdReservation = executableResolved.reduce((total, item) => total + estimateRevisionIdReservation(item), 0);
|
|
432
|
-
let revisionSeed = revisionIdSeed ?? nextRevisionSeed(revisionIdReservation);
|
|
433
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
675
|
+
let revisionSeed = revisionIdSeed ?? revisionStamp?.idSeed ?? nextRevisionSeed(revisionIdReservation);
|
|
676
|
+
const date = revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
434
677
|
const insertedColumnCounts = /* @__PURE__ */ new Map();
|
|
435
|
-
|
|
678
|
+
const executionOrder = executableResolved.toSorted((left, right) => {
|
|
436
679
|
const leftCellShape = left.tableCellMerge ?? left.tableCellSplit;
|
|
437
680
|
const rightCellShape = right.tableCellMerge ?? right.tableCellSplit;
|
|
438
681
|
if (!leftCellShape && rightCellShape) return -1;
|
|
@@ -456,7 +699,55 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
456
699
|
}
|
|
457
700
|
if (left.from !== right.from) return right.from - left.from;
|
|
458
701
|
return right.originalIndex - left.originalIndex;
|
|
459
|
-
})
|
|
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
|
+
}
|
|
460
751
|
const commentMark = item.commentId !== void 0 && commentType ? commentType.create({ commentId: item.commentId }) : null;
|
|
461
752
|
const stepsBefore = tr.steps.length;
|
|
462
753
|
let appliedRevisionIds;
|
|
@@ -511,7 +802,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
511
802
|
revisionIdInsert,
|
|
512
803
|
commentMark,
|
|
513
804
|
suggestionId,
|
|
514
|
-
initials
|
|
805
|
+
initials,
|
|
806
|
+
granularity: wordDiff?.granularity ?? "word"
|
|
515
807
|
});
|
|
516
808
|
if (producesTrackedChanges) appliedRevisionIds = [
|
|
517
809
|
revisionIdDelete,
|
|
@@ -597,7 +889,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
597
889
|
revisionIdInsert,
|
|
598
890
|
commentMark,
|
|
599
891
|
suggestionId,
|
|
600
|
-
initials
|
|
892
|
+
initials,
|
|
893
|
+
granularity: wordDiff?.granularity ?? "word"
|
|
601
894
|
});
|
|
602
895
|
tr = applyReplaceBlockStyleId({
|
|
603
896
|
item,
|
|
@@ -621,50 +914,21 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
621
914
|
});
|
|
622
915
|
continue;
|
|
623
916
|
}
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}));
|
|
640
|
-
insertedBlockRevisionIds.push(paragraphRevisionId);
|
|
641
|
-
}
|
|
642
|
-
if (commentMark) marks.push(commentMark);
|
|
643
|
-
const content = text.length > 0 ? buildEmphasisInlineContent(view.state.schema, text, marks) : null;
|
|
644
|
-
const attrs = isFirstParagraph ? { ...baseAttrs } : {};
|
|
645
|
-
if (isFirstParagraph && operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
|
|
646
|
-
if (isFirstParagraph && operation.styleId !== void 0) {
|
|
647
|
-
attrs["styleId"] = operation.styleId;
|
|
648
|
-
if (operation.inheritFormatting !== false) {
|
|
649
|
-
attrs["listMarker"] = null;
|
|
650
|
-
attrs["listMarkerHidden"] = null;
|
|
651
|
-
attrs["listLevelNumFmts"] = null;
|
|
652
|
-
attrs["listLevelStarts"] = null;
|
|
653
|
-
attrs["listAbstractNumId"] = null;
|
|
654
|
-
attrs["listStartOverride"] = null;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (isSuggested && suggestionId !== null && paragraphRevisionId !== null) attrs["_suggestedInsert"] = {
|
|
658
|
-
suggestionId,
|
|
659
|
-
revisionId: paragraphRevisionId,
|
|
660
|
-
author,
|
|
661
|
-
date,
|
|
662
|
-
...initials ? { initials } : {}
|
|
663
|
-
};
|
|
664
|
-
nodes.push(item.blockNode.type.create(attrs, content));
|
|
665
|
-
}
|
|
666
|
-
if (insertedBlockRevisionIds.length > 0) appliedRevisionIds = insertedBlockRevisionIds;
|
|
667
|
-
tr = tr.insert(item.from, nodes);
|
|
917
|
+
const built = buildInsertedParagraphs({
|
|
918
|
+
item,
|
|
919
|
+
schema: view.state.schema,
|
|
920
|
+
mode,
|
|
921
|
+
author,
|
|
922
|
+
date,
|
|
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);
|
|
668
932
|
break;
|
|
669
933
|
}
|
|
670
934
|
case "insertSignatureTable": {
|
|
@@ -907,21 +1171,220 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
907
1171
|
}
|
|
908
1172
|
case "deleteBlock":
|
|
909
1173
|
if (mode === "direct") {
|
|
910
|
-
|
|
1174
|
+
const at = tr.doc.resolve(item.blockFrom);
|
|
1175
|
+
tr = !(at.index() === at.parent.childCount - 1) || at.nodeBefore?.type.name === item.blockNode.type.name ? tr.delete(item.blockFrom, item.blockTo) : tr.delete(item.blockFrom + 1, item.blockTo - 1);
|
|
911
1176
|
break;
|
|
912
1177
|
}
|
|
913
1178
|
if (deletionType) {
|
|
914
1179
|
const revisionId = revisionSeed++;
|
|
915
|
-
|
|
1180
|
+
const deletionMark = deletionType.create({
|
|
916
1181
|
revisionId,
|
|
917
1182
|
author,
|
|
918
1183
|
date,
|
|
1184
|
+
...isPairedMove(item.operation.moveId) && { moveKind: "moveFrom" },
|
|
919
1185
|
...trackedRevisionExtras
|
|
920
|
-
})
|
|
921
|
-
|
|
1186
|
+
});
|
|
1187
|
+
tr = tr.addMark(item.from, item.to, deletionMark);
|
|
1188
|
+
const atomRanges = undeletedContentAtomRanges(tr, item.blockFrom);
|
|
1189
|
+
for (const { from, to } of atomRanges) tr = tr.addMark(from, to, deletionMark);
|
|
1190
|
+
const inlineRevisionApplied = item.from < item.to || atomRanges.length > 0;
|
|
1191
|
+
appliedRevisionIds = inlineRevisionApplied ? [revisionId] : [];
|
|
1192
|
+
const markPosition = tr.mapping.map(item.blockFrom);
|
|
1193
|
+
const markPlace = tr.doc.resolve(markPosition);
|
|
1194
|
+
if (!(markPlace.index() === markPlace.parent.childCount - 1) && tr.doc.nodeAt(markPosition)?.attrs["pPrMark"] == null) {
|
|
1195
|
+
const markRevisionId = revisionSeed++;
|
|
1196
|
+
tr = tr.setNodeAttribute(markPosition, "pPrMark", {
|
|
1197
|
+
kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
|
|
1198
|
+
info: {
|
|
1199
|
+
id: markRevisionId,
|
|
1200
|
+
author,
|
|
1201
|
+
date,
|
|
1202
|
+
...trackedRevisionExtras
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
appliedRevisionIds = inlineRevisionApplied ? [revisionId, markRevisionId] : [markRevisionId];
|
|
1206
|
+
}
|
|
922
1207
|
}
|
|
923
1208
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
924
1209
|
break;
|
|
1210
|
+
case "insertTable": {
|
|
1211
|
+
const table = buildTableNode({
|
|
1212
|
+
schema: view.state.schema,
|
|
1213
|
+
rows: item.operation.rows,
|
|
1214
|
+
...producesTrackedChanges && { revision: {
|
|
1215
|
+
revisionId: revisionSeed,
|
|
1216
|
+
author,
|
|
1217
|
+
date,
|
|
1218
|
+
...trackedRevisionExtras
|
|
1219
|
+
} }
|
|
1220
|
+
});
|
|
1221
|
+
if (!table) {
|
|
1222
|
+
skipped.push({
|
|
1223
|
+
id: item.operation.id,
|
|
1224
|
+
reason: "unsupportedBlock"
|
|
1225
|
+
});
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (producesTrackedChanges) appliedRevisionIds = [revisionSeed++];
|
|
1229
|
+
tr = tr.insert(item.from, table);
|
|
1230
|
+
markStructuralChange(tr);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case "deleteTable": {
|
|
1234
|
+
const deleted = item.deletedTable;
|
|
1235
|
+
if (!deleted) {
|
|
1236
|
+
skipped.push({
|
|
1237
|
+
id: item.operation.id,
|
|
1238
|
+
reason: "unsupportedBlock"
|
|
1239
|
+
});
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (mode === "direct") {
|
|
1243
|
+
const position = tr.mapping.map(deleted.position, 1);
|
|
1244
|
+
const live = tr.doc.nodeAt(position);
|
|
1245
|
+
if (!live || live.type.spec["tableRole"] !== "table") {
|
|
1246
|
+
skipped.push({
|
|
1247
|
+
id: item.operation.id,
|
|
1248
|
+
reason: "unsupportedBlock"
|
|
1249
|
+
});
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
tr = tr.delete(position, position + live.nodeSize);
|
|
1253
|
+
markStructuralChange(tr);
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
const livePosition = tr.mapping.map(deleted.position, 1);
|
|
1257
|
+
const liveTable = tr.doc.nodeAt(livePosition);
|
|
1258
|
+
if (!liveTable || liveTable.type.spec["tableRole"] !== "table") {
|
|
1259
|
+
skipped.push({
|
|
1260
|
+
id: item.operation.id,
|
|
1261
|
+
reason: "unsupportedBlock"
|
|
1262
|
+
});
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const revision = {
|
|
1266
|
+
revisionId: revisionSeed++,
|
|
1267
|
+
author,
|
|
1268
|
+
date,
|
|
1269
|
+
...trackedRevisionExtras
|
|
1270
|
+
};
|
|
1271
|
+
const rowPositions = [];
|
|
1272
|
+
let rowOffset = livePosition + 1;
|
|
1273
|
+
liveTable.forEach((row) => {
|
|
1274
|
+
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null) rowPositions.push(rowOffset);
|
|
1275
|
+
rowOffset += row.nodeSize;
|
|
1276
|
+
});
|
|
1277
|
+
if (rowPositions.length === 0) {
|
|
1278
|
+
skipped.push({
|
|
1279
|
+
id: item.operation.id,
|
|
1280
|
+
reason: "noopOperation"
|
|
1281
|
+
});
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
for (const rowPosition of rowPositions.toReversed()) tr = tr.setNodeAttribute(rowPosition, "trDel", revision);
|
|
1285
|
+
appliedRevisionIds = [revision.revisionId];
|
|
1286
|
+
markStructuralChange(tr);
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
case "setBlockParagraphProperties": {
|
|
1290
|
+
const patch = paragraphPropertiesPatch(item.blockNode, item.operation.properties);
|
|
1291
|
+
if (patch === null) {
|
|
1292
|
+
skipped.push({
|
|
1293
|
+
id: item.operation.id,
|
|
1294
|
+
reason: "noopOperation"
|
|
1295
|
+
});
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
if (mode === "direct") {
|
|
1299
|
+
tr = tr.setNodeMarkup(item.blockFrom, void 0, {
|
|
1300
|
+
...item.blockNode.attrs,
|
|
1301
|
+
...patch
|
|
1302
|
+
});
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
const revisionId = revisionSeed++;
|
|
1306
|
+
const change = {
|
|
1307
|
+
type: "paragraphPropertyChange",
|
|
1308
|
+
info: {
|
|
1309
|
+
id: revisionId,
|
|
1310
|
+
author,
|
|
1311
|
+
date,
|
|
1312
|
+
...initials ? { initials } : {}
|
|
1313
|
+
},
|
|
1314
|
+
previousFormatting: paragraphPropertiesSnapshot(item.blockNode)
|
|
1315
|
+
};
|
|
1316
|
+
const existing = expectParagraphAttrs(item.blockNode)._propertyChanges;
|
|
1317
|
+
tr = tr.setNodeMarkup(item.blockFrom, void 0, {
|
|
1318
|
+
...item.blockNode.attrs,
|
|
1319
|
+
...patch,
|
|
1320
|
+
_propertyChanges: [...Array.isArray(existing) ? existing : [], change]
|
|
1321
|
+
});
|
|
1322
|
+
appliedRevisionIds = [revisionId];
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
case "splitBlock": {
|
|
1326
|
+
if (mode === "direct") {
|
|
1327
|
+
if (item.to > item.from) tr = tr.delete(item.from, item.to);
|
|
1328
|
+
tr = tr.split(item.from);
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
const revisionIdMark = revisionSeed++;
|
|
1332
|
+
const info = {
|
|
1333
|
+
id: revisionIdMark,
|
|
1334
|
+
author,
|
|
1335
|
+
date,
|
|
1336
|
+
...trackedRevisionExtras
|
|
1337
|
+
};
|
|
1338
|
+
appliedRevisionIds = [revisionIdMark];
|
|
1339
|
+
if (item.to > item.from && deletionType) {
|
|
1340
|
+
const revisionIdSeparator = revisionSeed++;
|
|
1341
|
+
tr = tr.addMark(item.from, item.to, deletionType.create({
|
|
1342
|
+
revisionId: revisionIdSeparator,
|
|
1343
|
+
author,
|
|
1344
|
+
date,
|
|
1345
|
+
...trackedRevisionExtras
|
|
1346
|
+
}));
|
|
1347
|
+
appliedRevisionIds = [revisionIdMark, revisionIdSeparator];
|
|
1348
|
+
}
|
|
1349
|
+
tr = tr.split(item.from);
|
|
1350
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
1351
|
+
kind: "ins",
|
|
1352
|
+
info
|
|
1353
|
+
});
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
case "mergeBlockWithNext": {
|
|
1357
|
+
const separator = item.operation.separator ?? "";
|
|
1358
|
+
const insertAt = item.blockTo - 1;
|
|
1359
|
+
if (mode === "direct") {
|
|
1360
|
+
if (separator.length > 0) tr = tr.insertText(separator, insertAt);
|
|
1361
|
+
tr = tr.join(item.blockTo + separator.length);
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
const revisionIdMark = revisionSeed++;
|
|
1365
|
+
appliedRevisionIds = [revisionIdMark];
|
|
1366
|
+
if (separator.length > 0 && insertionType) {
|
|
1367
|
+
const revisionIdSeparator = revisionSeed++;
|
|
1368
|
+
tr = tr.insertText(separator, insertAt);
|
|
1369
|
+
tr = tr.addMark(insertAt, insertAt + separator.length, insertionType.create({
|
|
1370
|
+
revisionId: revisionIdSeparator,
|
|
1371
|
+
author,
|
|
1372
|
+
date,
|
|
1373
|
+
...trackedRevisionExtras
|
|
1374
|
+
}));
|
|
1375
|
+
appliedRevisionIds = [revisionIdMark, revisionIdSeparator];
|
|
1376
|
+
}
|
|
1377
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
1378
|
+
kind: "del",
|
|
1379
|
+
info: {
|
|
1380
|
+
id: revisionIdMark,
|
|
1381
|
+
author,
|
|
1382
|
+
date,
|
|
1383
|
+
...trackedRevisionExtras
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
925
1388
|
case "commentOnBlock":
|
|
926
1389
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
927
1390
|
break;
|
|
@@ -944,11 +1407,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
944
1407
|
...suggestionId !== null && { suggestionId }
|
|
945
1408
|
});
|
|
946
1409
|
}
|
|
947
|
-
if (tr.docChanged)
|
|
1410
|
+
if (tr.docChanged) {
|
|
1411
|
+
const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
|
|
1412
|
+
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds);
|
|
1413
|
+
if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
|
|
1414
|
+
view.dispatch(tr);
|
|
1415
|
+
}
|
|
948
1416
|
return {
|
|
949
1417
|
applied,
|
|
950
1418
|
skipped,
|
|
951
|
-
...normalizations.length > 0 && { normalizations }
|
|
1419
|
+
...normalizations.length > 0 && { normalizations },
|
|
1420
|
+
nextRevisionId: revisionSeed
|
|
952
1421
|
};
|
|
953
1422
|
};
|
|
954
1423
|
const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
|
|
@@ -970,10 +1439,11 @@ const previewFolioAIEditOperations = (options) => {
|
|
|
970
1439
|
return {
|
|
971
1440
|
applied: result.applied.map(({ id }) => ({ id })),
|
|
972
1441
|
skipped: result.skipped,
|
|
973
|
-
...result.normalizations !== void 0 && { normalizations: result.normalizations }
|
|
1442
|
+
...result.normalizations !== void 0 && { normalizations: result.normalizations },
|
|
1443
|
+
nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
|
|
974
1444
|
};
|
|
975
1445
|
};
|
|
976
|
-
const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark, suggestionId = null, initials }) => {
|
|
1446
|
+
const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark, suggestionId = null, initials, granularity }) => {
|
|
977
1447
|
let nextTr = tr;
|
|
978
1448
|
const replacement = stripInlineEmphasisMarkers((() => {
|
|
979
1449
|
if (item.operation.type === "replaceInBlock" || item.operation.type === "replaceRange") return item.operation.replace;
|
|
@@ -1027,7 +1497,7 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
1027
1497
|
}
|
|
1028
1498
|
}
|
|
1029
1499
|
if (sourceText !== null && cleanBlock !== null) {
|
|
1030
|
-
const segments = diffWordSegments(sourceText, replacement);
|
|
1500
|
+
const segments = diffWordSegments(sourceText, replacement, { granularity });
|
|
1031
1501
|
const offsets = cleanBlock.offsets;
|
|
1032
1502
|
const offsetAt = (cleanOffset) => offsets[cleanOffset] ?? null;
|
|
1033
1503
|
const steps = [];
|
|
@@ -1178,11 +1648,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1178
1648
|
};
|
|
1179
1649
|
}
|
|
1180
1650
|
if (operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") {
|
|
1181
|
-
|
|
1182
|
-
type: "skip",
|
|
1183
|
-
reason: "emptyOperation"
|
|
1184
|
-
};
|
|
1185
|
-
const tableBoundary = findEnclosingTableBoundary(doc, blockFrom);
|
|
1651
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1186
1652
|
const isInsertAfter = operation.type === "insertAfterBlock";
|
|
1187
1653
|
let insertFrom;
|
|
1188
1654
|
if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
|
|
@@ -1207,7 +1673,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1207
1673
|
reason: "emptyOperation"
|
|
1208
1674
|
};
|
|
1209
1675
|
const position = operation.position ?? "after";
|
|
1210
|
-
const tableBoundary =
|
|
1676
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1211
1677
|
let insertFrom;
|
|
1212
1678
|
if (tableBoundary) insertFrom = position === "after" ? tableBoundary.after : tableBoundary.before;
|
|
1213
1679
|
else insertFrom = position === "after" ? blockTo : blockFrom;
|
|
@@ -1229,7 +1695,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1229
1695
|
blockFrom,
|
|
1230
1696
|
position: operation.position ?? "after"
|
|
1231
1697
|
});
|
|
1232
|
-
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.
|
|
1698
|
+
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.columnCount) return {
|
|
1233
1699
|
type: "skip",
|
|
1234
1700
|
reason: "unsupportedBlock"
|
|
1235
1701
|
};
|
|
@@ -1424,11 +1890,115 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1424
1890
|
}
|
|
1425
1891
|
};
|
|
1426
1892
|
}
|
|
1893
|
+
if (operation.type === "insertTable") {
|
|
1894
|
+
const boundary = findOutermostTableBoundary(doc, blockFrom) ?? {
|
|
1895
|
+
before: blockFrom,
|
|
1896
|
+
after: blockTo
|
|
1897
|
+
};
|
|
1898
|
+
const insertFrom = (operation.position ?? "after") === "after" ? boundary.after : boundary.before;
|
|
1899
|
+
return {
|
|
1900
|
+
type: "resolved",
|
|
1901
|
+
operation: {
|
|
1902
|
+
operation,
|
|
1903
|
+
from: insertFrom,
|
|
1904
|
+
to: insertFrom,
|
|
1905
|
+
blockFrom,
|
|
1906
|
+
blockTo,
|
|
1907
|
+
blockNode
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
if (operation.type === "deleteTable") {
|
|
1912
|
+
const target = findEnclosingTableRow(doc, blockFrom);
|
|
1913
|
+
if (!target) return {
|
|
1914
|
+
type: "skip",
|
|
1915
|
+
reason: "unsupportedBlock"
|
|
1916
|
+
};
|
|
1917
|
+
return {
|
|
1918
|
+
type: "resolved",
|
|
1919
|
+
operation: {
|
|
1920
|
+
operation,
|
|
1921
|
+
from: target.tablePosition,
|
|
1922
|
+
to: target.tablePosition + target.table.nodeSize,
|
|
1923
|
+
blockFrom,
|
|
1924
|
+
blockTo,
|
|
1925
|
+
blockNode,
|
|
1926
|
+
deletedTable: {
|
|
1927
|
+
position: target.tablePosition,
|
|
1928
|
+
node: target.table
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
if (operation.type === "setBlockParagraphProperties") return {
|
|
1934
|
+
type: "resolved",
|
|
1935
|
+
operation: {
|
|
1936
|
+
operation,
|
|
1937
|
+
from: blockFrom,
|
|
1938
|
+
to: blockTo,
|
|
1939
|
+
blockFrom,
|
|
1940
|
+
blockTo,
|
|
1941
|
+
blockNode
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
if (operation.type === "splitBlock") {
|
|
1945
|
+
const separator = operation.separator ?? "";
|
|
1946
|
+
const at = cleanBlock.offsets[operation.offset];
|
|
1947
|
+
const after = cleanBlock.offsets[operation.offset + separator.length];
|
|
1948
|
+
if (at === void 0 || after === void 0 || operation.offset === 0 || operation.offset + separator.length >= currentText.length) return {
|
|
1949
|
+
type: "skip",
|
|
1950
|
+
reason: "staleRange"
|
|
1951
|
+
};
|
|
1952
|
+
if (currentText.slice(operation.offset, operation.offset + separator.length) !== separator) return {
|
|
1953
|
+
type: "skip",
|
|
1954
|
+
reason: "staleRange"
|
|
1955
|
+
};
|
|
1956
|
+
if (!canSplit(doc, at)) return {
|
|
1957
|
+
type: "skip",
|
|
1958
|
+
reason: "unsupportedBlock"
|
|
1959
|
+
};
|
|
1960
|
+
return {
|
|
1961
|
+
type: "resolved",
|
|
1962
|
+
operation: {
|
|
1963
|
+
operation,
|
|
1964
|
+
from: at,
|
|
1965
|
+
to: after,
|
|
1966
|
+
blockFrom,
|
|
1967
|
+
blockTo,
|
|
1968
|
+
blockNode
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
if (operation.type === "mergeBlockWithNext") {
|
|
1973
|
+
if (!canJoin(doc, blockTo)) return {
|
|
1974
|
+
type: "skip",
|
|
1975
|
+
reason: "unsupportedBlock"
|
|
1976
|
+
};
|
|
1977
|
+
return {
|
|
1978
|
+
type: "resolved",
|
|
1979
|
+
operation: {
|
|
1980
|
+
operation,
|
|
1981
|
+
from: blockTo,
|
|
1982
|
+
to: blockTo,
|
|
1983
|
+
blockFrom,
|
|
1984
|
+
blockTo,
|
|
1985
|
+
blockNode
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1427
1989
|
if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
|
|
1428
1990
|
const range = getTextRangeFromCleanBlock(cleanBlock);
|
|
1429
1991
|
if (!range) {
|
|
1430
1992
|
const insertionPoint = cleanBlock.offsets.at(0);
|
|
1431
|
-
if (
|
|
1993
|
+
if (insertionPoint === void 0) return {
|
|
1994
|
+
type: "skip",
|
|
1995
|
+
reason: "unsupportedBlock"
|
|
1996
|
+
};
|
|
1997
|
+
if (!(operation.type === "deleteBlock" ? true : currentText.length === 0 && operation.text.length > 0)) return {
|
|
1998
|
+
type: "skip",
|
|
1999
|
+
reason: "unsupportedBlock"
|
|
2000
|
+
};
|
|
2001
|
+
return {
|
|
1432
2002
|
type: "resolved",
|
|
1433
2003
|
operation: {
|
|
1434
2004
|
operation,
|
|
@@ -1439,10 +2009,6 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1439
2009
|
blockNode
|
|
1440
2010
|
}
|
|
1441
2011
|
};
|
|
1442
|
-
return {
|
|
1443
|
-
type: "skip",
|
|
1444
|
-
reason: "unsupportedBlock"
|
|
1445
|
-
};
|
|
1446
2012
|
}
|
|
1447
2013
|
if (operation.type === "replaceBlock" && operation.text === currentText) return {
|
|
1448
2014
|
type: "skip",
|