@stll/folio-core 0.11.0 → 0.13.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/apply.d.ts +2 -1
- package/dist/ai-edits/apply.js +693 -63
- package/dist/ai-edits/block-identity.d.ts +4 -0
- package/dist/ai-edits/block-identity.js +9 -0
- package/dist/ai-edits/blockRange.d.ts +28 -1
- package/dist/ai-edits/blockRange.js +68 -2
- package/dist/ai-edits/clean-text.js +2 -1
- package/dist/ai-edits/headless.d.ts +4 -4
- package/dist/ai-edits/headless.js +9 -9
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.d.ts +7 -7
- package/dist/ai-edits/read.js +110 -3
- package/dist/ai-edits/snapshot.js +21 -3
- package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
- package/dist/ai-edits/table-cell-mutations.js +238 -0
- package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
- package/dist/ai-edits/table-mutation-plan.js +90 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +108 -0
- package/dist/ai-edits/table-row-column-mutations.js +300 -0
- package/dist/ai-edits/table-targets.d.ts +32 -0
- package/dist/ai-edits/table-targets.js +70 -0
- package/dist/ai-edits/types.d.ts +62 -1
- package/dist/ai-edits/word-diff.js +21 -0
- package/dist/compat/eigenpal.d.ts +3 -3
- package/dist/compat/eigenpal.js +3 -3
- package/dist/content-controls/mutateContentControls.js +1 -1
- package/dist/controller/activeEditorStory.d.ts +36 -0
- package/dist/controller/activeEditorStory.js +30 -0
- package/dist/controller/contentControlWidgetController.d.ts +55 -0
- package/dist/controller/contentControlWidgetController.js +141 -0
- package/dist/controller/fontReadiness.js +2 -0
- package/dist/controller/headerFooterEditorManager.js +2 -2
- package/dist/controller/hiddenEditorApi.js +2 -2
- package/dist/controller/hiddenEditorManager.d.ts +12 -1
- package/dist/controller/hiddenEditorManager.js +44 -12
- package/dist/controller/layoutPipeline.d.ts +2 -0
- package/dist/controller/layoutPipeline.js +17 -2
- package/dist/controller/noteEditorManager.d.ts +36 -0
- package/dist/controller/noteEditorManager.js +210 -0
- package/dist/document-operations.d.ts +43 -11
- package/dist/document-operations.js +186 -18
- package/dist/docx/blockContentParser.js +21 -7
- package/dist/docx/commentParser.js +6 -1
- package/dist/docx/commentRangeIntegrity.d.ts +6 -0
- package/dist/docx/commentRangeIntegrity.js +186 -0
- package/dist/docx/conformance.js +62 -1
- package/dist/docx/documentGrid.d.ts +8 -0
- package/dist/docx/documentGrid.js +8 -0
- package/dist/docx/drawingUtils.js +1 -1
- package/dist/docx/encryption/agileDecryption.js +12 -2
- package/dist/docx/encryption/compoundFile.js +6 -1
- package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
- package/dist/docx/encryption/encryptionInfo.js +18 -2
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.js +8 -7
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/groupDrawingParser.js +10 -5
- package/dist/docx/hyperlinkParser.js +1 -1
- package/dist/docx/imageParser.js +10 -6
- package/dist/docx/metadataPrivacy.js +17 -3
- package/dist/docx/notePropertiesParser.js +1 -1
- package/dist/docx/numberingParser.js +1 -1
- package/dist/docx/paragraphParser.js +70 -61
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/parser.js +3 -3
- package/dist/docx/rezip.js +20 -9
- package/dist/docx/runParser.js +17 -6
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sectionParser.js +18 -5
- package/dist/docx/selectiveSave.js +5 -1
- package/dist/docx/selectiveXmlPatch.d.ts +22 -1
- package/dist/docx/selectiveXmlPatch.js +61 -1
- package/dist/docx/serializer/blockSdtSerializer.js +3 -2
- package/dist/docx/serializer/commentSerializer.js +5 -6
- package/dist/docx/serializer/documentSerializer.js +2 -2
- package/dist/docx/serializer/headerFooterSerializer.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +2 -2
- package/dist/docx/serializer/paragraphSerializer.js +23 -15
- package/dist/docx/serializer/runSerializer.js +25 -21
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/serializer/tableSerializer.d.ts +5 -4
- package/dist/docx/serializer/tableSerializer.js +26 -20
- package/dist/docx/serializer/xmlUtils.d.ts +13 -1
- package/dist/docx/serializer/xmlUtils.js +27 -1
- package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
- package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
- package/dist/docx/server/boundedArchive.d.ts +14 -3
- package/dist/docx/server/boundedArchive.js +56 -11
- package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
- package/dist/docx/server/extractDocxText.js +55 -8
- package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
- package/dist/docx/server/inspectDocxPackage.js +217 -0
- package/dist/docx/server/validateDocxConformance.d.ts +41 -0
- package/dist/docx/server/validateDocxConformance.js +336 -0
- package/dist/docx/settingsParser.js +26 -3
- package/dist/docx/shapeParser.js +1 -1
- package/dist/docx/styleParser.js +4 -2
- package/dist/docx/tableParser.d.ts +8 -1
- package/dist/docx/tableParser.js +31 -10
- package/dist/docx/textBoxParser.d.ts +3 -3
- package/dist/docx/textBoxParser.js +16 -12
- package/dist/docx/unzip.js +1 -0
- package/dist/docx/vmlImageParser.js +11 -3
- package/dist/docx/xmlParser.js +11 -1
- package/dist/docx/xmlSafety.d.ts +5 -0
- package/dist/docx/xmlSafety.js +9 -0
- package/dist/fields/resolveFieldValues.js +1 -3
- package/dist/fonts/embeddedFonts.d.ts +33 -5
- package/dist/fonts/embeddedFonts.js +37 -8
- package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
- package/dist/i18n/messages/catalogs.gen.js +17 -0
- package/dist/i18n/messages/messages.gen.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
- package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +151 -20
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
- package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
- package/dist/layout-bridge/engine/clickToPosition.js +2 -2
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +58 -30
- package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
- package/dist/layout-engine/justificationCompatibility.js +8 -0
- package/dist/layout-engine/keep-together.js +11 -1
- package/dist/layout-engine/measure/cache.js +23 -5
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +56 -37
- package/dist/layout-engine/measure/measureBlocks.js +15 -4
- package/dist/layout-engine/measure/measureContainer.js +1 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +11 -2
- package/dist/layout-engine/measure/measureHelpers.js +15 -4
- package/dist/layout-engine/measure/measureParagraph.js +85 -25
- package/dist/layout-engine/measure/measureProvider.js +4 -3
- package/dist/layout-engine/measure/tableCellFlow.js +5 -2
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
- package/dist/layout-engine/paragraphSpacing.js +107 -13
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +48 -15
- package/dist/layout-painter/registry/modules/textBox.js +5 -1
- package/dist/layout-painter/renderImage.d.ts +12 -1
- package/dist/layout-painter/renderImage.js +21 -4
- package/dist/layout-painter/renderPage.d.ts +5 -2
- package/dist/layout-painter/renderPage.js +59 -14
- package/dist/layout-painter/renderParagraph.js +55 -15
- package/dist/layout-painter/renderTable.d.ts +5 -1
- package/dist/layout-painter/renderTable.js +7 -3
- package/dist/layout-painter/renderTextBox.d.ts +2 -1
- package/dist/layout-painter/renderTextBox.js +25 -14
- package/dist/layout-painter/renderWatermark.d.ts +1 -1
- package/dist/layout-painter/renderWatermark.js +2 -1
- package/dist/managers/DocumentLoaderManager.js +1 -1
- package/dist/markdown/fromMarkdown.js +1 -1
- package/dist/model.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +175 -18
- package/dist/prosemirror/commands/comments.d.ts +79 -4
- package/dist/prosemirror/commands/comments.js +622 -13
- package/dist/prosemirror/commands/contentControls.js +1 -1
- package/dist/prosemirror/commands/hyperlink.js +15 -1
- package/dist/prosemirror/commands/image.js +7 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
- package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
- package/dist/prosemirror/commentIdAllocator.js +13 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +404 -62
- package/dist/prosemirror/conversion/index.js +1 -1
- package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
- package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/toProseDoc.js +364 -103
- package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
- package/dist/prosemirror/extensions/StarterKit.js +6 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +6 -2
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +70 -11
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +14 -6
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
- package/dist/prosemirror/index.d.ts +1 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +36 -10
- package/dist/prosemirror/plugins/contentControlWidgets.js +128 -94
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
- package/dist/prosemirror/plugins/revisionIds.js +57 -0
- package/dist/prosemirror/plugins/selectionTracker.js +1 -0
- package/dist/prosemirror/plugins/suggestionMode.js +5 -4
- package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
- package/dist/prosemirror/runFormattingMarkNames.js +40 -0
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +30 -3
- package/dist/prosemirror/schema/marks.js +15 -0
- package/dist/prosemirror/schema/nodes.d.ts +111 -6
- package/dist/prosemirror/selectionState.js +1 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
- package/dist/prosemirror/tableCellMergeRevision.js +22 -0
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
- package/dist/prosemirror/validation.js +9 -1
- package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
- package/dist/server.d.ts +7 -3
- package/dist/server.js +7 -3
- package/dist/utils/clipboard.d.ts +10 -1
- package/dist/utils/clipboard.js +33 -6
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +35 -5
- package/dist/utils/fontFamilyMerge.js +9 -11
- package/dist/utils/fontLoader.js +3 -2
- package/dist/utils/fontResolver.d.ts +2 -1
- package/dist/utils/fontResolver.js +31 -7
- package/dist/utils/fontWeights.d.ts +1 -1
- package/dist/utils/fontWeights.js +1 -1
- package/dist/utils/hexId.d.ts +11 -1
- package/dist/utils/hexId.js +11 -1
- package/dist/utils/paragraphFormattingMerge.js +1 -0
- package/dist/utils/sanitizeImageSrc.d.ts +21 -0
- package/dist/utils/sanitizeImageSrc.js +30 -0
- package/dist/utils/stripXmlDeclarations.d.ts +11 -0
- package/dist/utils/stripXmlDeclarations.js +35 -0
- package/dist/version-comparison.d.ts +22 -2
- package/dist/version-comparison.js +41 -12
- package/dist/watermark/index.d.ts +11 -1
- package/dist/watermark/index.js +22 -1
- package/package.json +3 -2
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
|
|
2
|
+
import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
|
|
3
|
+
import { markStructuralChange } from "../extensions/features/ParagraphChangeTrackerExtension.js";
|
|
1
4
|
import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
|
|
5
|
+
import { textFormattingToMarks } from "../conversion/toProseDoc.js";
|
|
6
|
+
import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
|
|
7
|
+
import { hasMatchingCollapsedTableCellMerge, resolveCollapsedTableCellMerge, resolveVisibleTableCellMerge } from "./tableCellMergeResolution.js";
|
|
8
|
+
import { TableMap, removeRow } from "prosemirror-tables";
|
|
2
9
|
//#region src/prosemirror/commands/comments.ts
|
|
3
10
|
/**
|
|
4
11
|
* Add a comment mark to the current selection.
|
|
@@ -36,18 +43,18 @@ function removeCommentMark(commentId) {
|
|
|
36
43
|
* Resolve a tracked change: accept or reject.
|
|
37
44
|
* - Accept: keep insertions (remove mark), delete deletions (remove text)
|
|
38
45
|
* - Reject: keep deletions (remove mark), delete insertions (remove text)
|
|
46
|
+
* - Run formatting: accept the current formatting, or restore the previous formatting
|
|
39
47
|
*
|
|
40
48
|
* Pass `revisionId` to scope the operation to one specific
|
|
41
49
|
* revision — otherwise overlapping marks for other revisions get
|
|
42
50
|
* processed too, silently consuming pending work. Without an id
|
|
43
|
-
* the operation matches every
|
|
51
|
+
* the operation matches every revision mark or property-change entry in the
|
|
44
52
|
* range (the bulk accept-all/reject-all path).
|
|
45
53
|
*/
|
|
46
54
|
function resolveChange(from, to, mode, revisionIds) {
|
|
47
55
|
return (state, dispatch) => {
|
|
48
56
|
const insertionType = state.schema.marks["insertion"];
|
|
49
57
|
const deletionType = state.schema.marks["deletion"];
|
|
50
|
-
if (!insertionType && !deletionType) return false;
|
|
51
58
|
const keepType = mode === "accept" ? insertionType : deletionType;
|
|
52
59
|
const removeType = mode === "accept" ? deletionType : insertionType;
|
|
53
60
|
const revisionSet = revisionIds === void 0 ? null : new Set(revisionIds);
|
|
@@ -56,6 +63,8 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
56
63
|
const tr = state.tr;
|
|
57
64
|
const deleteRanges = [];
|
|
58
65
|
const pPrMarkOps = [];
|
|
66
|
+
const tableRowStructuralOps = [];
|
|
67
|
+
const tableCellStructuralOps = [];
|
|
59
68
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
60
69
|
if (node.type.name === "paragraph") {
|
|
61
70
|
const op = collectPPrMarkOp(node, pos, from, to, mode, revisionSet);
|
|
@@ -95,6 +104,11 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
95
104
|
if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
|
|
96
105
|
return true;
|
|
97
106
|
}
|
|
107
|
+
if (node.type.name === "tableRow" && rangeCoversNode(from, to, pos, node)) {
|
|
108
|
+
const op = collectTableRowStructuralOp(node, pos, mode, revisionSet);
|
|
109
|
+
if (op) tableRowStructuralOps.push(op);
|
|
110
|
+
}
|
|
111
|
+
if ((node.type.name === "tableCell" || node.type.name === "tableHeader") && rangeCoversNode(from, to, pos, node)) tableCellStructuralOps.push(...collectTableCellStructuralOps(node, pos, mode, revisionSet));
|
|
98
112
|
const tableChangeAttrName = TABLE_PROPERTY_CHANGE_ATTR_BY_NODE[node.type.name];
|
|
99
113
|
if (tableChangeAttrName !== void 0) {
|
|
100
114
|
if (rangeCoversNode(from, to, pos, node)) {
|
|
@@ -107,6 +121,16 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
107
121
|
const nodeEnd = pos + node.nodeSize;
|
|
108
122
|
const rangeFrom = Math.max(from, pos);
|
|
109
123
|
const rangeTo = Math.min(to, nodeEnd);
|
|
124
|
+
const runPropertyChangeMark = node.marks.find((mark) => mark.type.name === "runPropertyChange");
|
|
125
|
+
if (runPropertyChangeMark) resolveRunPropertyChange({
|
|
126
|
+
tr,
|
|
127
|
+
node,
|
|
128
|
+
from: rangeFrom,
|
|
129
|
+
to: rangeTo,
|
|
130
|
+
mark: runPropertyChangeMark,
|
|
131
|
+
mode,
|
|
132
|
+
revisionSet
|
|
133
|
+
});
|
|
110
134
|
if (removeType && node.marks.some((m) => m.type === removeType && matchesRevision(m))) deleteRanges.push({
|
|
111
135
|
from: rangeFrom,
|
|
112
136
|
to: rangeTo
|
|
@@ -131,11 +155,162 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
131
155
|
tr.setNodeAttribute(mappedPos, "pPrMark", null);
|
|
132
156
|
} catch {}
|
|
133
157
|
}
|
|
158
|
+
tableRowStructuralOps.sort((left, right) => right.rowPos - left.rowPos);
|
|
159
|
+
let resolvedTableRowStructure = false;
|
|
160
|
+
for (const op of tableRowStructuralOps) {
|
|
161
|
+
const mappedPos = tr.mapping.map(op.rowPos);
|
|
162
|
+
const row = tr.doc.nodeAt(mappedPos);
|
|
163
|
+
if (!row || row.type.name !== "tableRow") continue;
|
|
164
|
+
if (op.action === "clear") {
|
|
165
|
+
tr.setNodeAttribute(mappedPos, op.attrName, null);
|
|
166
|
+
resolvedTableRowStructure = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
deleteTableRowAt(tr, mappedPos);
|
|
170
|
+
resolvedTableRowStructure = true;
|
|
171
|
+
}
|
|
172
|
+
if (resolvedTableRowStructure) markStructuralChange(tr);
|
|
173
|
+
tableCellStructuralOps.sort((left, right) => right.cellPos - left.cellPos);
|
|
174
|
+
let resolvedTableCellStructure = false;
|
|
175
|
+
let failedTableCellMergeResolution = false;
|
|
176
|
+
for (const op of tableCellStructuralOps) {
|
|
177
|
+
const mappedPos = tr.mapping.map(op.cellPos);
|
|
178
|
+
const cell = tr.doc.nodeAt(mappedPos);
|
|
179
|
+
if (!cell || cell.type.name !== "tableCell" && cell.type.name !== "tableHeader") continue;
|
|
180
|
+
if (op.type === "merge") {
|
|
181
|
+
const resolved = op.source === "collapsed" ? resolveCollapsedTableCellMerge(tr, mappedPos, op.mode, op.revisionSet) : resolveVisibleTableCellMerge(tr, mappedPos, op.mode);
|
|
182
|
+
if (!resolved) {
|
|
183
|
+
failedTableCellMergeResolution = true;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
resolvedTableCellStructure ||= resolved;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (op.action === "clear") {
|
|
190
|
+
tr.setNodeAttribute(mappedPos, "cellMarker", null);
|
|
191
|
+
resolvedTableCellStructure = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
deleteTableCellAt(tr, mappedPos);
|
|
195
|
+
resolvedTableCellStructure = true;
|
|
196
|
+
}
|
|
197
|
+
if (failedTableCellMergeResolution) return false;
|
|
198
|
+
if (resolvedTableCellStructure) markStructuralChange(tr);
|
|
134
199
|
if (tr.steps.length > 0) dispatch(tr);
|
|
135
200
|
}
|
|
136
201
|
return true;
|
|
137
202
|
};
|
|
138
203
|
}
|
|
204
|
+
const resolveRunPropertyChange = ({ tr, node, from, to, mark, mode, revisionSet }) => {
|
|
205
|
+
const { changes } = expectRunPropertyChangeMarkAttrs(mark);
|
|
206
|
+
const matches = changes.filter((change) => revisionSet === null || revisionSet.has(change.info.id));
|
|
207
|
+
if (matches.length === 0) return;
|
|
208
|
+
tr.removeMark(from, to, mark);
|
|
209
|
+
const remaining = changes.filter((change) => revisionSet !== null && !revisionSet.has(change.info.id));
|
|
210
|
+
if (remaining.length > 0) tr.addMark(from, to, mark.type.create({ changes: remaining }));
|
|
211
|
+
if (mode === "accept") return;
|
|
212
|
+
const previousFormatting = matches.at(0)?.previousFormatting;
|
|
213
|
+
for (const currentMark of node.marks) if (RUN_FORMATTING_MARK_NAMES.has(currentMark.type.name)) tr.removeMark(from, to, currentMark.type);
|
|
214
|
+
for (const previousMark of textFormattingToMarks(previousFormatting)) tr.addMark(from, to, previousMark);
|
|
215
|
+
if (previousFormatting?.styleId) {
|
|
216
|
+
const characterStyle = node.type.schema.marks["characterStyle"];
|
|
217
|
+
if (characterStyle) tr.addMark(from, to, characterStyle.create({
|
|
218
|
+
styleId: previousFormatting.styleId,
|
|
219
|
+
_styleRPr: null
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function collectTableRowStructuralOp(node, rowPos, mode, revisionSet) {
|
|
224
|
+
for (const attrName of ["trIns", "trDel"]) {
|
|
225
|
+
const marker = node.attrs[attrName];
|
|
226
|
+
if (!isTableRowRevisionAttr(marker)) continue;
|
|
227
|
+
if (revisionSet !== null && !revisionSet.has(marker.revisionId)) continue;
|
|
228
|
+
return {
|
|
229
|
+
rowPos,
|
|
230
|
+
attrName,
|
|
231
|
+
action: attrName === "trIns" === (mode === "accept") ? "clear" : "remove"
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function isTableRowRevisionAttr(value) {
|
|
237
|
+
return typeof value === "object" && value !== null && "revisionId" in value && typeof value.revisionId === "number";
|
|
238
|
+
}
|
|
239
|
+
function collectTableCellStructuralOps(node, cellPos, mode, revisionSet) {
|
|
240
|
+
const operations = [];
|
|
241
|
+
const marker = node.attrs["cellMarker"];
|
|
242
|
+
if (isTableCellRevisionAttr(marker) && (revisionSet === null || revisionSet.has(marker.info.revisionId))) if (marker.kind === "merge") operations.push({
|
|
243
|
+
type: "merge",
|
|
244
|
+
cellPos,
|
|
245
|
+
source: "visible",
|
|
246
|
+
mode,
|
|
247
|
+
revisionSet
|
|
248
|
+
});
|
|
249
|
+
else {
|
|
250
|
+
const keepsCell = marker.kind === "ins" === (mode === "accept");
|
|
251
|
+
operations.push({
|
|
252
|
+
type: "membership",
|
|
253
|
+
cellPos,
|
|
254
|
+
action: keepsCell ? "clear" : "remove"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (hasMatchingCollapsedTableCellMerge(node, revisionSet)) operations.push({
|
|
258
|
+
type: "merge",
|
|
259
|
+
cellPos,
|
|
260
|
+
source: "collapsed",
|
|
261
|
+
mode,
|
|
262
|
+
revisionSet
|
|
263
|
+
});
|
|
264
|
+
return operations;
|
|
265
|
+
}
|
|
266
|
+
function isTableCellRevisionAttr(value) {
|
|
267
|
+
if (typeof value !== "object" || value === null || !("kind" in value) || !("info" in value)) return false;
|
|
268
|
+
if (value.kind !== "ins" && value.kind !== "del" && value.kind !== "merge") return false;
|
|
269
|
+
const info = value.info;
|
|
270
|
+
return typeof info === "object" && info !== null && "revisionId" in info && typeof info.revisionId === "number";
|
|
271
|
+
}
|
|
272
|
+
function deleteTableCellAt(tr, cellPos) {
|
|
273
|
+
const cell = tr.doc.nodeAt(cellPos);
|
|
274
|
+
if (!cell || cell.type.name !== "tableCell" && cell.type.name !== "tableHeader") return;
|
|
275
|
+
const resolved = tr.doc.resolve(cellPos);
|
|
276
|
+
const row = resolved.parent;
|
|
277
|
+
if (row.type.spec["tableRole"] !== "row") return;
|
|
278
|
+
if (row.childCount > 1) {
|
|
279
|
+
tr.delete(cellPos, cellPos + cell.nodeSize);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
deleteTableRowAt(tr, resolved.start() - 1);
|
|
283
|
+
}
|
|
284
|
+
function deleteTableRowAt(tr, rowPos) {
|
|
285
|
+
const resolved = tr.doc.resolve(rowPos);
|
|
286
|
+
const table = resolved.parent;
|
|
287
|
+
if (table.type.spec["tableRole"] !== "table") return;
|
|
288
|
+
const rowIndex = resolved.index();
|
|
289
|
+
if (table.childCount > 1) {
|
|
290
|
+
const map = TableMap.get(table);
|
|
291
|
+
removeRow(tr, {
|
|
292
|
+
map,
|
|
293
|
+
table,
|
|
294
|
+
tableStart: resolved.start(),
|
|
295
|
+
left: 0,
|
|
296
|
+
top: rowIndex,
|
|
297
|
+
right: map.width,
|
|
298
|
+
bottom: rowIndex + 1
|
|
299
|
+
}, rowIndex);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const tablePosition = resolved.start() - 1;
|
|
303
|
+
const tableEnd = tablePosition + table.nodeSize;
|
|
304
|
+
const outerResolved = tr.doc.resolve(tablePosition);
|
|
305
|
+
const parent = outerResolved.parent;
|
|
306
|
+
const tableIndex = outerResolved.index();
|
|
307
|
+
if (parent.canReplace(tableIndex, tableIndex + 1)) {
|
|
308
|
+
tr.delete(tablePosition, tableEnd);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const emptyParagraph = tr.doc.type.schema.nodes["paragraph"]?.createAndFill();
|
|
312
|
+
if (emptyParagraph && parent.canReplaceWith(tableIndex, tableIndex + 1, emptyParagraph.type, emptyParagraph.marks)) tr.replaceWith(tablePosition, tableEnd, emptyParagraph);
|
|
313
|
+
}
|
|
139
314
|
function collectPPrMarkOp(node, pos, from, to, mode, revisionSet) {
|
|
140
315
|
if (!rangeCoversParagraphBoundary(from, to, pos, node)) return null;
|
|
141
316
|
const pPrMark = node.attrs["pPrMark"];
|
|
@@ -279,30 +454,65 @@ function rejectAllChanges() {
|
|
|
279
454
|
return (state, dispatch) => rejectChange(0, state.doc.content.size)(state, dispatch);
|
|
280
455
|
}
|
|
281
456
|
/**
|
|
282
|
-
* Find the document range covered by all
|
|
457
|
+
* Find the document range covered by all inline revision marks
|
|
283
458
|
* carrying any of the given AI-edit `revisionIds`. Returns null when
|
|
284
459
|
* none of those marks are present (already accepted/rejected, or
|
|
285
460
|
* never existed). A replace operation typically passes two ids (one
|
|
286
461
|
* for its deletion side, one for its insertion side); inserts and
|
|
287
|
-
* standalone deletions pass a single id.
|
|
462
|
+
* standalone deletions and formatting changes pass a single id.
|
|
288
463
|
*/
|
|
289
464
|
function findAIEditRevisionRange(state, revisionIds) {
|
|
290
465
|
const insertionType = state.schema.marks["insertion"];
|
|
291
466
|
const deletionType = state.schema.marks["deletion"];
|
|
292
|
-
|
|
467
|
+
const runPropertyChangeType = state.schema.marks["runPropertyChange"];
|
|
293
468
|
const idSet = new Set(typeof revisionIds === "number" ? [revisionIds] : revisionIds);
|
|
294
469
|
const range = {
|
|
295
470
|
from: null,
|
|
296
471
|
to: null
|
|
297
472
|
};
|
|
298
473
|
state.doc.descendants((node, pos) => {
|
|
474
|
+
if (node.type.name === "tableRow") for (const attrName of ["trIns", "trDel"]) {
|
|
475
|
+
const marker = node.attrs[attrName];
|
|
476
|
+
if (isTableRowRevisionAttr(marker) && idSet.has(marker.revisionId)) {
|
|
477
|
+
const start = pos;
|
|
478
|
+
const end = pos + node.nodeSize;
|
|
479
|
+
if (range.from === null || start < range.from) range.from = start;
|
|
480
|
+
if (range.to === null || end > range.to) range.to = end;
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
485
|
+
const marker = node.attrs["cellMarker"];
|
|
486
|
+
const hasDirectRevision = isTableCellRevisionAttr(marker) && idSet.has(marker.info.revisionId);
|
|
487
|
+
const continuationCells = node.attrs["_docxVMergeContinuationCells"];
|
|
488
|
+
const hasCollapsedRevision = Array.isArray(continuationCells) && continuationCells.some((cell) => {
|
|
489
|
+
const change = getTableCellMergeChange(cell);
|
|
490
|
+
return change !== null && idSet.has(change.info.id);
|
|
491
|
+
});
|
|
492
|
+
if (hasDirectRevision || hasCollapsedRevision) {
|
|
493
|
+
const start = pos;
|
|
494
|
+
const end = pos + node.nodeSize;
|
|
495
|
+
if (range.from === null || start < range.from) range.from = start;
|
|
496
|
+
if (range.to === null || end > range.to) range.to = end;
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
299
500
|
if (!node.isInline) return;
|
|
300
|
-
for (const mark of node.marks)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
501
|
+
for (const mark of node.marks) {
|
|
502
|
+
if (mark.type === runPropertyChangeType && expectRunPropertyChangeMarkAttrs(mark).changes.some((change) => idSet.has(change.info.id))) {
|
|
503
|
+
const start = pos;
|
|
504
|
+
const end = pos + node.nodeSize;
|
|
505
|
+
if (range.from === null || start < range.from) range.from = start;
|
|
506
|
+
if (range.to === null || end > range.to) range.to = end;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
if ((mark.type === insertionType || mark.type === deletionType) && typeof mark.attrs["revisionId"] === "number" && idSet.has(mark.attrs["revisionId"])) {
|
|
510
|
+
const start = pos;
|
|
511
|
+
const end = pos + node.nodeSize;
|
|
512
|
+
if (range.from === null || start < range.from) range.from = start;
|
|
513
|
+
if (range.to === null || end > range.to) range.to = end;
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
306
516
|
}
|
|
307
517
|
});
|
|
308
518
|
if (range.from === null || range.to === null) return null;
|
|
@@ -313,7 +523,7 @@ function findAIEditRevisionRange(state, revisionIds) {
|
|
|
313
523
|
}
|
|
314
524
|
/**
|
|
315
525
|
* Accept the tracked-change marks belonging to an AI-edit operation.
|
|
316
|
-
* Pass a single revisionId for inserts
|
|
526
|
+
* Pass a single revisionId for inserts, standalone deletions, or formatting changes; or the
|
|
317
527
|
* full id list for a replace (one id per side). Returns false when
|
|
318
528
|
* none of the ids match anything in the doc.
|
|
319
529
|
*/
|
|
@@ -338,6 +548,405 @@ function rejectAIEditRevision(revisionIds) {
|
|
|
338
548
|
};
|
|
339
549
|
}
|
|
340
550
|
/**
|
|
551
|
+
* Suggestion (AI-proposed tracked change) commands.
|
|
552
|
+
*
|
|
553
|
+
* A suggestion is a set of `insertion` / `deletion` / `runPropertyChange` marks
|
|
554
|
+
* sharing one `suggestionId` and carrying `provenance: "suggested"`. Accepting
|
|
555
|
+
* rewrites those marks to normal (`"user"`) tracked changes authored by the
|
|
556
|
+
* accepting user; rejecting inverse-applies them like `rejectChange`, reusing
|
|
557
|
+
* the same {@link resolveChange} machinery scoped to the suggestion's revision
|
|
558
|
+
* ids. All three operate as ordinary PM transactions, so they sync via collab
|
|
559
|
+
* and are undoable.
|
|
560
|
+
*/
|
|
561
|
+
const SUGGESTION_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
562
|
+
"insertion",
|
|
563
|
+
"deletion",
|
|
564
|
+
"runPropertyChange"
|
|
565
|
+
]);
|
|
566
|
+
const isSuggestionMark = (mark) => mark.attrs["provenance"] === "suggested" && SUGGESTION_MARK_NAMES.has(mark.type.name);
|
|
567
|
+
const suggestionKindOf = (mark) => {
|
|
568
|
+
if (mark.type.name === "insertion") return "insertion";
|
|
569
|
+
if (mark.type.name === "deletion") return "deletion";
|
|
570
|
+
return "formatting";
|
|
571
|
+
};
|
|
572
|
+
const readSuggestedMarker = (marker) => {
|
|
573
|
+
if (typeof marker !== "object" || marker === null || marker.provenance !== "suggested") return null;
|
|
574
|
+
const suggestionId = marker.suggestionId;
|
|
575
|
+
const revisionId = marker.revisionId;
|
|
576
|
+
if (typeof suggestionId !== "string" || typeof revisionId !== "number") return null;
|
|
577
|
+
return {
|
|
578
|
+
suggestionId,
|
|
579
|
+
revisionId
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
const readStructuralSuggestion = (node) => {
|
|
583
|
+
const attrs = node.attrs;
|
|
584
|
+
const name = node.type.name;
|
|
585
|
+
const insertMarker = attrs["_suggestedInsert"];
|
|
586
|
+
if ((name === "paragraph" || name === "table") && typeof insertMarker === "object" && insertMarker !== null) {
|
|
587
|
+
const suggestionId = insertMarker.suggestionId;
|
|
588
|
+
const revisionId = insertMarker.revisionId;
|
|
589
|
+
if (typeof suggestionId === "string" && typeof revisionId === "number") return {
|
|
590
|
+
suggestionId,
|
|
591
|
+
revisionId,
|
|
592
|
+
kind: name === "table" ? "insertTable" : "insertBlock",
|
|
593
|
+
isNodeInsert: true
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (node.type.name === "tableRow") {
|
|
597
|
+
const ins = readSuggestedMarker(attrs["trIns"]);
|
|
598
|
+
if (ins) return {
|
|
599
|
+
...ins,
|
|
600
|
+
kind: "insertRow",
|
|
601
|
+
isNodeInsert: false
|
|
602
|
+
};
|
|
603
|
+
const del = readSuggestedMarker(attrs["trDel"]);
|
|
604
|
+
if (del) return {
|
|
605
|
+
...del,
|
|
606
|
+
kind: "deleteRow",
|
|
607
|
+
isNodeInsert: false
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
611
|
+
const cellMarker = attrs["cellMarker"];
|
|
612
|
+
if (typeof cellMarker === "object" && cellMarker !== null) {
|
|
613
|
+
const markerKind = cellMarker.kind;
|
|
614
|
+
if (markerKind === "ins" || markerKind === "del") {
|
|
615
|
+
const marker = readSuggestedMarker(cellMarker.info);
|
|
616
|
+
if (marker) {
|
|
617
|
+
const kind = markerKind === "del" ? "deleteColumn" : "insertColumn";
|
|
618
|
+
return {
|
|
619
|
+
...marker,
|
|
620
|
+
kind,
|
|
621
|
+
isNodeInsert: false
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
};
|
|
629
|
+
const collectSuggestions = (state) => {
|
|
630
|
+
const runPropertyChangeType = state.schema.marks["runPropertyChange"];
|
|
631
|
+
const bySuggestion = /* @__PURE__ */ new Map();
|
|
632
|
+
const entryFor = (suggestionId) => {
|
|
633
|
+
const existing = bySuggestion.get(suggestionId);
|
|
634
|
+
if (existing) return existing;
|
|
635
|
+
const created = {
|
|
636
|
+
segments: [],
|
|
637
|
+
kinds: /* @__PURE__ */ new Set(),
|
|
638
|
+
revisionIds: /* @__PURE__ */ new Set()
|
|
639
|
+
};
|
|
640
|
+
bySuggestion.set(suggestionId, created);
|
|
641
|
+
return created;
|
|
642
|
+
};
|
|
643
|
+
state.doc.descendants((node, pos) => {
|
|
644
|
+
const structural = readStructuralSuggestion(node);
|
|
645
|
+
if (structural) {
|
|
646
|
+
const entry = entryFor(structural.suggestionId);
|
|
647
|
+
entry.segments.push({
|
|
648
|
+
from: pos,
|
|
649
|
+
to: pos + node.nodeSize
|
|
650
|
+
});
|
|
651
|
+
entry.kinds.add(structural.kind);
|
|
652
|
+
entry.revisionIds.add(structural.revisionId);
|
|
653
|
+
}
|
|
654
|
+
if (!node.isInline) return;
|
|
655
|
+
for (const mark of node.marks) {
|
|
656
|
+
if (!isSuggestionMark(mark)) continue;
|
|
657
|
+
const suggestionId = mark.attrs["suggestionId"];
|
|
658
|
+
if (typeof suggestionId !== "string" || suggestionId.length === 0) continue;
|
|
659
|
+
const entry = entryFor(suggestionId);
|
|
660
|
+
entry.segments.push({
|
|
661
|
+
from: pos,
|
|
662
|
+
to: pos + node.nodeSize
|
|
663
|
+
});
|
|
664
|
+
entry.kinds.add(suggestionKindOf(mark));
|
|
665
|
+
if (mark.type === runPropertyChangeType) for (const change of expectRunPropertyChangeMarkAttrs(mark).changes) entry.revisionIds.add(change.info.id);
|
|
666
|
+
else if (typeof mark.attrs["revisionId"] === "number") entry.revisionIds.add(mark.attrs["revisionId"]);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
return bySuggestion;
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* A whole inserted table (`insertTable`) has no OOXML tracked representation and
|
|
673
|
+
* accepts directly; every other kind accepts as a tracked change. A group that
|
|
674
|
+
* mixes a table insert with any other kind (a caller stamping heterogeneous
|
|
675
|
+
* operations with one `suggestionId`) is `"mixed"`.
|
|
676
|
+
*/
|
|
677
|
+
const suggestionAppliedAs = (kinds) => {
|
|
678
|
+
if (!kinds.has("insertTable")) return "tracked";
|
|
679
|
+
return [...kinds].some((kind) => kind !== "insertTable") ? "mixed" : "direct";
|
|
680
|
+
};
|
|
681
|
+
/** Merge sorted, possibly adjacent/overlapping segments into contiguous ranges. */
|
|
682
|
+
const mergeSegments = (segments) => {
|
|
683
|
+
const sorted = [...segments].toSorted((a, b) => a.from - b.from || a.to - b.to);
|
|
684
|
+
const merged = [];
|
|
685
|
+
for (const segment of sorted) {
|
|
686
|
+
const last = merged.at(-1);
|
|
687
|
+
if (last && segment.from <= last.to) {
|
|
688
|
+
last.to = Math.max(last.to, segment.to);
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
merged.push({ ...segment });
|
|
692
|
+
}
|
|
693
|
+
return merged;
|
|
694
|
+
};
|
|
695
|
+
const SUGGESTION_KIND_ORDER = [
|
|
696
|
+
"insertion",
|
|
697
|
+
"deletion",
|
|
698
|
+
"formatting",
|
|
699
|
+
"insertBlock",
|
|
700
|
+
"insertTable",
|
|
701
|
+
"insertRow",
|
|
702
|
+
"deleteRow",
|
|
703
|
+
"insertColumn",
|
|
704
|
+
"deleteColumn"
|
|
705
|
+
];
|
|
706
|
+
/**
|
|
707
|
+
* List every suggestion in the document for host consumption: its id, the
|
|
708
|
+
* contiguous ranges it covers, which kinds of change it contains, and how
|
|
709
|
+
* accepting it applies (tracked vs direct).
|
|
710
|
+
*/
|
|
711
|
+
function getSuggestions(state) {
|
|
712
|
+
const bySuggestion = collectSuggestions(state);
|
|
713
|
+
const suggestions = [];
|
|
714
|
+
for (const [suggestionId, entry] of bySuggestion) suggestions.push({
|
|
715
|
+
suggestionId,
|
|
716
|
+
ranges: mergeSegments(entry.segments),
|
|
717
|
+
kinds: SUGGESTION_KIND_ORDER.filter((kind) => entry.kinds.has(kind)),
|
|
718
|
+
appliedAs: suggestionAppliedAs(entry.kinds)
|
|
719
|
+
});
|
|
720
|
+
return suggestions.toSorted((a, b) => (a.ranges[0]?.from ?? 0) - (b.ranges[0]?.from ?? 0));
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* The document range covering every mark belonging to `suggestionId`, plus the
|
|
724
|
+
* revision ids those marks carry. Returns null when the suggestion is absent
|
|
725
|
+
* (already accepted/rejected, or never existed).
|
|
726
|
+
*/
|
|
727
|
+
function findSuggestionRange(state, suggestionId) {
|
|
728
|
+
const entry = collectSuggestions(state).get(suggestionId);
|
|
729
|
+
if (!entry || entry.segments.length === 0) return null;
|
|
730
|
+
const ranges = mergeSegments(entry.segments);
|
|
731
|
+
const from = ranges[0]?.from ?? 0;
|
|
732
|
+
return {
|
|
733
|
+
from,
|
|
734
|
+
to: ranges.at(-1)?.to ?? from,
|
|
735
|
+
revisionIds: [...entry.revisionIds]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Normalize an optional acceptance date to a canonical ISO 8601 string. Absent
|
|
740
|
+
* or unparseable input falls back to the current time — a malformed `w:date`
|
|
741
|
+
* must never reach the serialized document (fail-safe boundary validation).
|
|
742
|
+
*/
|
|
743
|
+
const normalizeAcceptDate = (date) => {
|
|
744
|
+
if (date === void 0) return (/* @__PURE__ */ new Date()).toISOString();
|
|
745
|
+
const parsed = new Date(date);
|
|
746
|
+
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : parsed.toISOString();
|
|
747
|
+
};
|
|
748
|
+
/**
|
|
749
|
+
* The node-attr patch that converts a suggested block/table revision into a
|
|
750
|
+
* normal (`"user"`) one authored by the accepting user. Returns `null` when the
|
|
751
|
+
* node carries no matching suggested revision. Position-stable (attr writes
|
|
752
|
+
* only), so callers can apply it during a single descendants walk.
|
|
753
|
+
*/
|
|
754
|
+
const convertStructuralSuggestionAttrs = (node, author, date) => {
|
|
755
|
+
const attrs = node.attrs;
|
|
756
|
+
const userInfo = (marker) => {
|
|
757
|
+
const source = marker.info ?? marker;
|
|
758
|
+
return {
|
|
759
|
+
revisionId: source.revisionId,
|
|
760
|
+
author,
|
|
761
|
+
date,
|
|
762
|
+
...typeof source.initials === "string" ? { initials: source.initials } : {}
|
|
763
|
+
};
|
|
764
|
+
};
|
|
765
|
+
const name = node.type.name;
|
|
766
|
+
const insertMarker = attrs["_suggestedInsert"];
|
|
767
|
+
if ((name === "paragraph" || name === "table") && typeof insertMarker === "object" && insertMarker !== null) {
|
|
768
|
+
const marker = insertMarker;
|
|
769
|
+
if (name === "table") return {
|
|
770
|
+
...attrs,
|
|
771
|
+
_suggestedInsert: null
|
|
772
|
+
};
|
|
773
|
+
if (typeof marker.revisionId === "number") return {
|
|
774
|
+
...attrs,
|
|
775
|
+
_suggestedInsert: null,
|
|
776
|
+
pPrMark: {
|
|
777
|
+
kind: "ins",
|
|
778
|
+
info: {
|
|
779
|
+
id: marker.revisionId,
|
|
780
|
+
author,
|
|
781
|
+
date,
|
|
782
|
+
...typeof marker.initials === "string" ? { initials: marker.initials } : {}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
if (node.type.name === "tableRow") {
|
|
788
|
+
if (readSuggestedMarker(attrs["trIns"])) return {
|
|
789
|
+
...attrs,
|
|
790
|
+
trIns: userInfo(attrs["trIns"])
|
|
791
|
+
};
|
|
792
|
+
if (readSuggestedMarker(attrs["trDel"])) return {
|
|
793
|
+
...attrs,
|
|
794
|
+
trDel: userInfo(attrs["trDel"])
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
798
|
+
const cellMarker = attrs["cellMarker"];
|
|
799
|
+
if (cellMarker && (cellMarker.kind === "ins" || cellMarker.kind === "del") && readSuggestedMarker(cellMarker.info)) return {
|
|
800
|
+
...attrs,
|
|
801
|
+
cellMarker: {
|
|
802
|
+
kind: cellMarker.kind,
|
|
803
|
+
info: userInfo(cellMarker)
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return null;
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Convert suggested marks AND block/table node revisions to normal (`"user"`)
|
|
811
|
+
* tracked changes. `matchesSuggestion(id)` selects which suggestion to convert
|
|
812
|
+
* (one id, or all).
|
|
813
|
+
*/
|
|
814
|
+
const acceptSuggestions = (matchesSuggestion, options) => {
|
|
815
|
+
return (state, dispatch) => {
|
|
816
|
+
const insertionType = state.schema.marks["insertion"];
|
|
817
|
+
const deletionType = state.schema.marks["deletion"];
|
|
818
|
+
const runPropertyChangeType = state.schema.marks["runPropertyChange"];
|
|
819
|
+
const date = normalizeAcceptDate(options.date);
|
|
820
|
+
const tr = state.tr;
|
|
821
|
+
let changed = false;
|
|
822
|
+
state.doc.descendants((node, pos) => {
|
|
823
|
+
const structural = readStructuralSuggestion(node);
|
|
824
|
+
if (structural && matchesSuggestion(structural.suggestionId)) {
|
|
825
|
+
const nextAttrs = convertStructuralSuggestionAttrs(node, options.author, date);
|
|
826
|
+
if (nextAttrs) {
|
|
827
|
+
tr.setNodeMarkup(pos, void 0, nextAttrs);
|
|
828
|
+
changed = true;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (!node.isInline) return;
|
|
832
|
+
const from = pos;
|
|
833
|
+
const to = pos + node.nodeSize;
|
|
834
|
+
for (const mark of node.marks) {
|
|
835
|
+
if (!isSuggestionMark(mark) || typeof mark.attrs["suggestionId"] !== "string" || !matchesSuggestion(mark.attrs["suggestionId"])) continue;
|
|
836
|
+
changed = true;
|
|
837
|
+
tr.removeMark(from, to, mark);
|
|
838
|
+
if (mark.type === runPropertyChangeType) {
|
|
839
|
+
const nextChanges = [];
|
|
840
|
+
for (const change of expectRunPropertyChangeMarkAttrs(mark).changes) nextChanges.push({
|
|
841
|
+
...change,
|
|
842
|
+
info: {
|
|
843
|
+
...change.info,
|
|
844
|
+
author: options.author,
|
|
845
|
+
date
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
tr.addMark(from, to, mark.type.create({
|
|
849
|
+
changes: nextChanges,
|
|
850
|
+
provenance: "user",
|
|
851
|
+
suggestionId: null
|
|
852
|
+
}));
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (mark.type === insertionType || mark.type === deletionType) tr.addMark(from, to, mark.type.create({
|
|
856
|
+
...mark.attrs,
|
|
857
|
+
author: options.author,
|
|
858
|
+
date,
|
|
859
|
+
provenance: "user",
|
|
860
|
+
suggestionId: null
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
if (!changed) return false;
|
|
865
|
+
if (dispatch) dispatch(tr);
|
|
866
|
+
return true;
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* Accept one suggestion: convert its inline marks and block/table node
|
|
871
|
+
* revisions into normal tracked changes authored by `author`, keeping revision
|
|
872
|
+
* ids. A whole inserted table (which OOXML cannot track) is applied directly;
|
|
873
|
+
* everything else becomes a tracked change. See {@link getSuggestions} for the
|
|
874
|
+
* per-suggestion `appliedAs`.
|
|
875
|
+
*/
|
|
876
|
+
function acceptSuggestion(suggestionId, options) {
|
|
877
|
+
return acceptSuggestions((id) => id === suggestionId, options);
|
|
878
|
+
}
|
|
879
|
+
/** Accept every suggestion in the document. */
|
|
880
|
+
function acceptAllSuggestions(options) {
|
|
881
|
+
return acceptSuggestions(() => true, options);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Reject one suggestion: inverse-apply it in a single transaction. Runs BOTH
|
|
885
|
+
* phases so a heterogeneous group (one `suggestionId` spanning whole-node
|
|
886
|
+
* inserts AND other edits) is fully resolved:
|
|
887
|
+
* 1. inline marks and suggested `trIns`/`trDel`/`cellMarker` revisions are
|
|
888
|
+
* inverse-applied through {@link resolveChange} (delete suggested-inserted
|
|
889
|
+
* text/rows/cells, drop suggested deletions/formatting);
|
|
890
|
+
* 2. any remaining whole-node inserts (paragraph/table) are then deleted,
|
|
891
|
+
* mapped through the accumulated transaction so positions stay valid.
|
|
892
|
+
*
|
|
893
|
+
* A paragraph whole-node insert carries inline marks with the same revision id,
|
|
894
|
+
* so phase 1 empties its text and phase 2 removes the now-empty node; a whole
|
|
895
|
+
* inserted table has no matching revision, so only phase 2 removes it.
|
|
896
|
+
*/
|
|
897
|
+
function rejectSuggestion(suggestionId) {
|
|
898
|
+
return (state, dispatch) => {
|
|
899
|
+
const entry = collectSuggestions(state).get(suggestionId);
|
|
900
|
+
if (!entry) return false;
|
|
901
|
+
let tr = null;
|
|
902
|
+
if (entry.revisionIds.size > 0) {
|
|
903
|
+
const ranges = mergeSegments(entry.segments);
|
|
904
|
+
const from = ranges[0]?.from ?? 0;
|
|
905
|
+
resolveChange(from, ranges.at(-1)?.to ?? from, "reject", [...entry.revisionIds])(state, (resolved) => {
|
|
906
|
+
tr = resolved;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
const workingTr = tr ?? state.tr;
|
|
910
|
+
const positions = [];
|
|
911
|
+
workingTr.doc.descendants((node, pos) => {
|
|
912
|
+
const structural = readStructuralSuggestion(node);
|
|
913
|
+
if (structural?.isNodeInsert && structural.suggestionId === suggestionId) positions.push(pos);
|
|
914
|
+
});
|
|
915
|
+
for (const pos of positions.toSorted((a, b) => b - a)) {
|
|
916
|
+
const node = workingTr.doc.nodeAt(pos);
|
|
917
|
+
if (node) workingTr.delete(pos, pos + node.nodeSize);
|
|
918
|
+
}
|
|
919
|
+
if (workingTr.steps.length === 0) return false;
|
|
920
|
+
if (dispatch) dispatch(workingTr);
|
|
921
|
+
return true;
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
/** Reject every suggestion in the document. */
|
|
925
|
+
function rejectAllSuggestions() {
|
|
926
|
+
return (state, dispatch) => {
|
|
927
|
+
const bySuggestion = collectSuggestions(state);
|
|
928
|
+
if (bySuggestion.size === 0) return false;
|
|
929
|
+
const revisionIds = /* @__PURE__ */ new Set();
|
|
930
|
+
for (const entry of bySuggestion.values()) for (const id of entry.revisionIds) revisionIds.add(id);
|
|
931
|
+
let tr = null;
|
|
932
|
+
if (revisionIds.size > 0) resolveChange(0, state.doc.content.size, "reject", [...revisionIds])(state, (resolved) => {
|
|
933
|
+
tr = resolved;
|
|
934
|
+
});
|
|
935
|
+
const workingTr = tr ?? state.tr;
|
|
936
|
+
const positions = [];
|
|
937
|
+
workingTr.doc.descendants((node, pos) => {
|
|
938
|
+
if (readStructuralSuggestion(node)?.isNodeInsert) positions.push(pos);
|
|
939
|
+
});
|
|
940
|
+
for (const pos of positions.toSorted((a, b) => b - a)) {
|
|
941
|
+
const node = workingTr.doc.nodeAt(pos);
|
|
942
|
+
if (node) workingTr.delete(pos, pos + node.nodeSize);
|
|
943
|
+
}
|
|
944
|
+
if (workingTr.steps.length === 0) return false;
|
|
945
|
+
if (dispatch) dispatch(workingTr);
|
|
946
|
+
return true;
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
341
950
|
* Find the tracked change mark range at a given cursor position.
|
|
342
951
|
* If the cursor is inside a tracked change, returns the full extent
|
|
343
952
|
* of that mark (expanding to cover all adjacent nodes with the same
|
|
@@ -502,4 +1111,4 @@ function findPreviousChange(state, startPos) {
|
|
|
502
1111
|
return result.value;
|
|
503
1112
|
}
|
|
504
1113
|
//#endregion
|
|
505
|
-
export { acceptAIEditRevision, acceptAllChanges, acceptChange, addCommentMark, findAIEditRevisionRange, findChangeAtPosition, findNextChange, findParagraphBoundaryChangeAtPosition, findPreviousChange, rejectAIEditRevision, rejectAllChanges, rejectChange, removeCommentMark };
|
|
1114
|
+
export { acceptAIEditRevision, acceptAllChanges, acceptAllSuggestions, acceptChange, acceptSuggestion, addCommentMark, findAIEditRevisionRange, findChangeAtPosition, findNextChange, findParagraphBoundaryChangeAtPosition, findPreviousChange, findSuggestionRange, getSuggestions, rejectAIEditRevision, rejectAllChanges, rejectAllSuggestions, rejectChange, rejectSuggestion, removeCommentMark };
|