@stll/folio-core 0.32.2 → 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 +649 -85
- 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 +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 +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/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 +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": {
|
|
@@ -912,16 +1176,213 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
912
1176
|
}
|
|
913
1177
|
if (deletionType) {
|
|
914
1178
|
const revisionId = revisionSeed++;
|
|
915
|
-
|
|
1179
|
+
const deletionMark = deletionType.create({
|
|
916
1180
|
revisionId,
|
|
917
1181
|
author,
|
|
918
1182
|
date,
|
|
1183
|
+
...isPairedMove(item.operation.moveId) && { moveKind: "moveFrom" },
|
|
919
1184
|
...trackedRevisionExtras
|
|
920
|
-
})
|
|
921
|
-
|
|
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
|
+
}
|
|
922
1205
|
}
|
|
923
1206
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
924
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
|
+
}
|
|
925
1386
|
case "commentOnBlock":
|
|
926
1387
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
927
1388
|
break;
|
|
@@ -944,11 +1405,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
944
1405
|
...suggestionId !== null && { suggestionId }
|
|
945
1406
|
});
|
|
946
1407
|
}
|
|
947
|
-
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
|
+
}
|
|
948
1414
|
return {
|
|
949
1415
|
applied,
|
|
950
1416
|
skipped,
|
|
951
|
-
...normalizations.length > 0 && { normalizations }
|
|
1417
|
+
...normalizations.length > 0 && { normalizations },
|
|
1418
|
+
nextRevisionId: revisionSeed
|
|
952
1419
|
};
|
|
953
1420
|
};
|
|
954
1421
|
const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
|
|
@@ -970,10 +1437,11 @@ const previewFolioAIEditOperations = (options) => {
|
|
|
970
1437
|
return {
|
|
971
1438
|
applied: result.applied.map(({ id }) => ({ id })),
|
|
972
1439
|
skipped: result.skipped,
|
|
973
|
-
...result.normalizations !== void 0 && { normalizations: result.normalizations }
|
|
1440
|
+
...result.normalizations !== void 0 && { normalizations: result.normalizations },
|
|
1441
|
+
nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
|
|
974
1442
|
};
|
|
975
1443
|
};
|
|
976
|
-
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 }) => {
|
|
977
1445
|
let nextTr = tr;
|
|
978
1446
|
const replacement = stripInlineEmphasisMarkers((() => {
|
|
979
1447
|
if (item.operation.type === "replaceInBlock" || item.operation.type === "replaceRange") return item.operation.replace;
|
|
@@ -1027,7 +1495,7 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
1027
1495
|
}
|
|
1028
1496
|
}
|
|
1029
1497
|
if (sourceText !== null && cleanBlock !== null) {
|
|
1030
|
-
const segments = diffWordSegments(sourceText, replacement);
|
|
1498
|
+
const segments = diffWordSegments(sourceText, replacement, { granularity });
|
|
1031
1499
|
const offsets = cleanBlock.offsets;
|
|
1032
1500
|
const offsetAt = (cleanOffset) => offsets[cleanOffset] ?? null;
|
|
1033
1501
|
const steps = [];
|
|
@@ -1178,11 +1646,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1178
1646
|
};
|
|
1179
1647
|
}
|
|
1180
1648
|
if (operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") {
|
|
1181
|
-
|
|
1182
|
-
type: "skip",
|
|
1183
|
-
reason: "emptyOperation"
|
|
1184
|
-
};
|
|
1185
|
-
const tableBoundary = findEnclosingTableBoundary(doc, blockFrom);
|
|
1649
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1186
1650
|
const isInsertAfter = operation.type === "insertAfterBlock";
|
|
1187
1651
|
let insertFrom;
|
|
1188
1652
|
if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
|
|
@@ -1207,7 +1671,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1207
1671
|
reason: "emptyOperation"
|
|
1208
1672
|
};
|
|
1209
1673
|
const position = operation.position ?? "after";
|
|
1210
|
-
const tableBoundary =
|
|
1674
|
+
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
1211
1675
|
let insertFrom;
|
|
1212
1676
|
if (tableBoundary) insertFrom = position === "after" ? tableBoundary.after : tableBoundary.before;
|
|
1213
1677
|
else insertFrom = position === "after" ? blockTo : blockFrom;
|
|
@@ -1229,7 +1693,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1229
1693
|
blockFrom,
|
|
1230
1694
|
position: operation.position ?? "after"
|
|
1231
1695
|
});
|
|
1232
|
-
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.
|
|
1696
|
+
if (!insertion || (operation.cellTexts?.length ?? 0) > insertion.columnCount) return {
|
|
1233
1697
|
type: "skip",
|
|
1234
1698
|
reason: "unsupportedBlock"
|
|
1235
1699
|
};
|
|
@@ -1424,11 +1888,115 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1424
1888
|
}
|
|
1425
1889
|
};
|
|
1426
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
|
+
}
|
|
1427
1987
|
if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
|
|
1428
1988
|
const range = getTextRangeFromCleanBlock(cleanBlock);
|
|
1429
1989
|
if (!range) {
|
|
1430
1990
|
const insertionPoint = cleanBlock.offsets.at(0);
|
|
1431
|
-
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 {
|
|
1432
2000
|
type: "resolved",
|
|
1433
2001
|
operation: {
|
|
1434
2002
|
operation,
|
|
@@ -1439,10 +2007,6 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
1439
2007
|
blockNode
|
|
1440
2008
|
}
|
|
1441
2009
|
};
|
|
1442
|
-
return {
|
|
1443
|
-
type: "skip",
|
|
1444
|
-
reason: "unsupportedBlock"
|
|
1445
|
-
};
|
|
1446
2010
|
}
|
|
1447
2011
|
if (operation.type === "replaceBlock" && operation.text === currentText) return {
|
|
1448
2012
|
type: "skip",
|