@stll/folio-core 0.51.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/ai-edits/apply.d.ts +26 -1
- package/dist/ai-edits/apply.js +1519 -105
- package/dist/ai-edits/batch-claims.d.ts +130 -0
- package/dist/ai-edits/batch-claims.js +244 -0
- package/dist/ai-edits/blockRange.js +9 -3
- package/dist/ai-edits/character-boundaries.d.ts +43 -0
- package/dist/ai-edits/character-boundaries.js +95 -0
- package/dist/ai-edits/clean-text.d.ts +33 -3
- package/dist/ai-edits/clean-text.js +52 -6
- package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
- package/dist/ai-edits/comment-lifecycle.js +52 -0
- package/dist/ai-edits/headless.d.ts +71 -7
- package/dist/ai-edits/headless.js +381 -41
- package/dist/ai-edits/index.d.ts +2 -1
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/minimal-replacement.d.ts +21 -6
- package/dist/ai-edits/minimal-replacement.js +134 -15
- package/dist/ai-edits/newListNumbering.d.ts +23 -0
- package/dist/ai-edits/newListNumbering.js +94 -0
- package/dist/ai-edits/note-references.d.ts +96 -0
- package/dist/ai-edits/note-references.js +139 -0
- package/dist/ai-edits/pending-suggestions.d.ts +79 -0
- package/dist/ai-edits/pending-suggestions.js +306 -0
- package/dist/ai-edits/read.d.ts +1 -1
- package/dist/ai-edits/read.js +56 -43
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +177 -23
- package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
- package/dist/ai-edits/table-cell-mutations.js +4 -1
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
- package/dist/ai-edits/table-row-column-mutations.js +61 -26
- package/dist/ai-edits/types.d.ts +155 -16
- package/dist/compare/compare.d.ts +1 -1
- package/dist/compare/compare.js +65 -16
- package/dist/compare/content-alignment.js +13 -2
- package/dist/compare/content-types.d.ts +7 -0
- package/dist/compare/content.d.ts +4 -1
- package/dist/compare/content.js +6 -1
- package/dist/compare/formatting.js +2 -1
- package/dist/compare/inline-atoms.d.ts +21 -4
- package/dist/compare/inline-atoms.js +138 -16
- package/dist/compare/inline-provenance.js +18 -22
- package/dist/compare/plan.js +10 -5
- package/dist/compare/scenario.js +14 -3
- package/dist/compare/section-boundary-properties.js +14 -1
- package/dist/compare/types.d.ts +4 -2
- package/dist/compare/types.js +2 -1
- package/dist/compare/verification.d.ts +1 -1
- package/dist/compare/verification.js +15 -3
- package/dist/controller/committedLayoutDiff.d.ts +25 -0
- package/dist/controller/committedLayoutDiff.js +71 -0
- package/dist/controller/contentControlWidgetController.d.ts +1 -1
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/fontReadiness.d.ts +70 -3
- package/dist/controller/fontReadiness.js +100 -10
- package/dist/controller/headerFooterEditorManager.js +6 -1
- package/dist/controller/layoutPipeline.d.ts +12 -10
- package/dist/controller/layoutPipeline.js +62 -9
- package/dist/controller/layoutRunOptions.d.ts +24 -0
- package/dist/controller/layoutRunOptions.js +17 -0
- package/dist/controller/layoutScheduler.d.ts +19 -14
- package/dist/controller/layoutScheduler.js +15 -12
- package/dist/controller/layoutSession.d.ts +26 -2
- package/dist/controller/layoutSession.js +1 -1
- package/dist/controller/noteEditorManager.js +8 -3
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +230 -21
- package/dist/docx/altChunk.d.ts +8 -0
- package/dist/docx/altChunk.js +17 -0
- package/dist/docx/attributeRemainder.d.ts +1 -1
- package/dist/docx/blockContentParser.d.ts +64 -1
- package/dist/docx/blockContentParser.js +17 -4
- package/dist/docx/blockCustomXmlShell.d.ts +7 -0
- package/dist/docx/blockCustomXmlShell.js +14 -0
- package/dist/docx/blockPlainText.js +7 -0
- package/dist/docx/commentAnchorIndex.js +1 -1
- package/dist/docx/commentRangeIntegrity.js +8 -3
- package/dist/docx/commentReferenceNormalization.js +1 -1
- package/dist/docx/commentReplyMarkers.js +20 -1
- package/dist/docx/compatibility.d.ts +1 -1
- package/dist/docx/compatibility.js +34 -13
- package/dist/docx/containerChildren.gen.d.ts +1 -1
- package/dist/docx/containerChildren.gen.js +1 -0
- package/dist/docx/documentParser.js +12 -12
- package/dist/docx/drawingIdNormalization.d.ts +3 -1
- package/dist/docx/drawingIdNormalization.js +6 -1
- package/dist/docx/ensureParaIds.js +119 -97
- package/dist/docx/footnoteParser.js +8 -4
- package/dist/docx/headerFooterParser.js +3 -3
- package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
- package/dist/docx/headerFooterReferenceNormalization.js +52 -27
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/listNumberingInstances.d.ts +59 -0
- package/dist/docx/listNumberingInstances.js +249 -0
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/opaqueCarrier.d.ts +7 -0
- package/dist/docx/opaqueCarrier.js +38 -0
- package/dist/docx/packageParts.js +4 -1
- package/dist/docx/paragraphParser.js +91 -24
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +7 -1
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +173 -20
- package/dist/docx/rezip.js +27 -5
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sdtPropertiesPatch.js +3 -0
- package/dist/docx/selectiveSave.js +16 -9
- package/dist/docx/selectiveXmlPatch.js +106 -37
- package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
- package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
- package/dist/docx/serializer/documentSerializer.js +2 -0
- package/dist/docx/serializer/headerFooterSerializer.js +2 -0
- package/dist/docx/serializer/noteSerializer.js +3 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -9
- package/dist/docx/serializer/tableSerializer.js +25 -38
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/tableParser.d.ts +157 -1
- package/dist/docx/tableParser.js +89 -10
- package/dist/docx/textBoxParser.js +1 -1
- package/dist/docx/unzip.d.ts +2 -1
- package/dist/docx/unzip.js +1 -1
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/generated/text_shaper.js +26 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
- package/dist/i18n/messages/catalogs.gen.js +153 -17
- package/dist/i18n/messages/messages.gen.d.ts +8 -0
- package/dist/internal/compare/inline-presentation.d.ts +2 -1
- package/dist/internal/compare/inline-presentation.js +11 -10
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
- package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
- package/dist/internal/indexedPositionMap.d.ts +11 -0
- package/dist/internal/indexedPositionMap.js +42 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
- package/dist/internal/revisionResolutionEdits.d.ts +13 -0
- package/dist/internal/revisionResolutionEdits.js +19 -0
- package/dist/internal/revisionResolutionInline.d.ts +24 -0
- package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
- package/dist/internal/revisionResolutionStep.d.ts +34 -0
- package/dist/internal/revisionResolutionStep.js +282 -0
- package/dist/internal/revisionResolutionTracking.d.ts +11 -0
- package/dist/internal/revisionResolutionTracking.js +124 -0
- package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
- package/dist/internal/wholeStoryRevisionResolution.js +530 -0
- package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
- package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
- package/dist/layout-bridge/convert/tableConversion.js +1 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
- package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
- package/dist/layout-engine/layoutInstrumentation.js +4 -1
- package/dist/layout-engine/measure/cache.d.ts +18 -1
- package/dist/layout-engine/measure/cache.js +31 -1
- package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
- package/dist/layout-engine/measure/measureContainer.js +7 -47
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
- package/dist/layout-engine/measure/measureHelpers.js +22 -2
- package/dist/layout-engine/measure/measureWorker.js +2 -3
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
- package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/managers/ContextMenuManager.js +5 -2
- package/dist/managers/types.d.ts +3 -0
- package/dist/markdown/fromMarkdown.js +2 -1
- package/dist/markdown/internals.d.ts +11 -1
- package/dist/markdown/internals.js +24 -3
- package/dist/markdown/renderBlock.js +39 -9
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.js +3 -10
- package/dist/markdown/renderTable.js +24 -12
- package/dist/markdown/trailers.js +2 -2
- package/dist/markdown/types.d.ts +29 -7
- package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
- package/dist/paged-layout/incrementalMeasure.js +1 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
- package/dist/paged-layout/transactionDirtyRange.js +29 -41
- package/dist/panel-layout.d.ts +83 -0
- package/dist/panel-layout.js +96 -0
- package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
- package/dist/prosemirror/anchoredTextBoxes.js +156 -0
- package/dist/prosemirror/attrs/index.d.ts +6 -2
- package/dist/prosemirror/attrs/index.js +172 -1
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +241 -207
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/paragraph.d.ts +7 -1
- package/dist/prosemirror/commands/paragraph.js +9 -1
- package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
- package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
- package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
- package/dist/prosemirror/conversion/toProseDoc.js +39 -4
- package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
- package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
- package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
- package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
- package/dist/prosemirror/extensions/StarterKit.js +3 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
- package/dist/prosemirror/extensions/types.d.ts +4 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/index.d.ts +3 -2
- package/dist/prosemirror/index.js +3 -2
- package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
- package/dist/prosemirror/indexedNodeLookup.js +103 -0
- package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
- package/dist/prosemirror/listAutoformatMarkers.js +98 -0
- package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
- package/dist/prosemirror/listInstanceReferences.js +72 -0
- package/dist/prosemirror/listLabels.d.ts +24 -0
- package/dist/prosemirror/listLabels.js +111 -0
- package/dist/prosemirror/listNumbering.d.ts +110 -0
- package/dist/prosemirror/listNumbering.js +345 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewProjection.d.ts +95 -0
- package/dist/prosemirror/markupViewProjection.js +83 -0
- package/dist/prosemirror/numberingAttr.d.ts +9 -1
- package/dist/prosemirror/numberingAttr.js +9 -1
- package/dist/prosemirror/outlineLevelAttr.js +1 -1
- package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
- package/dist/prosemirror/paragraphIndentation.js +29 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
- package/dist/prosemirror/paragraphMarkJoin.js +24 -0
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
- package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
- package/dist/prosemirror/plugins/documentNumbering.js +97 -11
- package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
- package/dist/prosemirror/plugins/documentStyles.js +14 -1
- package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
- package/dist/prosemirror/plugins/suggestionMode.js +116 -52
- package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
- package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
- package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
- package/dist/prosemirror/positionSweep.d.ts +24 -0
- package/dist/prosemirror/positionSweep.js +222 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
- package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
- package/dist/prosemirror/replacedAnnotations.js +170 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/runStyleFormatting.d.ts +7 -1
- package/dist/prosemirror/runStyleFormatting.js +13 -7
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +42 -11
- package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
- package/dist/prosemirror/sectionMarkEdits.js +59 -0
- package/dist/prosemirror/storyListNumbering.d.ts +17 -0
- package/dist/prosemirror/storyListNumbering.js +102 -0
- package/dist/prosemirror/tableGridMutation.d.ts +48 -2
- package/dist/prosemirror/tableGridMutation.js +106 -1
- package/dist/prosemirror/validation.js +5 -1
- package/dist/redline.js +93 -9
- package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
- package/dist/render-dom/BodySelectionOverlay.js +3 -2
- package/dist/shaping/shaper.d.ts +47 -4
- package/dist/shaping/shaper.js +46 -15
- package/dist/text-shaping.d.ts +4 -0
- package/dist/text-shaping.js +4 -0
- package/dist/utils/findReplace.js +2 -2
- package/dist/utils/mergeDocumentContent.js +1 -1
- package/dist/utils/noteReferenceLabels.d.ts +36 -0
- package/dist/utils/noteReferenceLabels.js +32 -0
- package/dist/utils/replaceText.js +1 -1
- package/dist/version-comparison.d.ts +1 -0
- package/dist/version-comparison.js +1 -0
- package/package.json +8 -2
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
package/dist/ai-edits/apply.js
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
import { paragraphNumberingReference, paragraphNumberingReferenceId } from "../docx/numberingReference.js";
|
|
2
2
|
import { formattingEquals } from "../docx/runConsolidator.js";
|
|
3
|
+
import { isStyleSourcedParagraphNumbering } from "../internal/paragraphFormattingSerialization.js";
|
|
4
|
+
import { deleteTextBoxesAnchoredAt, droppedTextBoxAnchorIds, endOfParagraphWithItsTextBoxes, gatherTextBoxesAfterParagraph, textBoxAnchorIdOf, textBoxAnchorIdsBetween } from "../prosemirror/anchoredTextBoxes.js";
|
|
3
5
|
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
4
6
|
import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
|
|
5
7
|
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
|
|
6
8
|
import { encloseWholeControls } from "../prosemirror/contentControlRevisions.js";
|
|
7
9
|
import { requestDeterministicParaIds } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
|
|
8
10
|
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
11
|
+
import "../prosemirror/extensions/nodes/TextBoxAnchorExtension.js";
|
|
9
12
|
import { CLEARED_LIST_RENDERING_ATTRS } from "../prosemirror/listMarker.js";
|
|
10
|
-
import { paragraphNumberingAttr, readParagraphNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
13
|
+
import { paragraphNumberingAttr, readParagraphNumberingAttr, removedNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
11
14
|
import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
|
|
15
|
+
import { styleResolvedParagraphFormatting } from "../prosemirror/paragraphFormattingProvenance.js";
|
|
12
16
|
import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
|
|
17
|
+
import { joinAtParagraphMark } from "../prosemirror/paragraphMarkJoin.js";
|
|
13
18
|
import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
|
|
14
|
-
import { getDocumentNumbering } from "../prosemirror/plugins/documentNumbering.js";
|
|
15
|
-
import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
|
|
16
|
-
import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, hasReplacedAnnotations, inheritedReplacementMarks, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
|
|
19
|
+
import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosemirror/plugins/documentNumbering.js";
|
|
20
|
+
import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
|
|
21
|
+
import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, placeTrackedInsertion, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
|
|
17
22
|
import { applyMarksToRunFormattingRepresentation, runFormattingInlineControlNodeName, selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
23
|
+
import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
|
|
18
24
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
19
|
-
import { paragraphRunStyleContext, paragraphRunStyleContextAt } from "../prosemirror/runStyleFormatting.js";
|
|
20
|
-
import {
|
|
25
|
+
import { paragraphRunStyleContext, paragraphRunStyleContextAt, resolveParagraphBodyRunFormatting } from "../prosemirror/runStyleFormatting.js";
|
|
26
|
+
import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
|
|
27
|
+
import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
|
|
21
28
|
import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
|
|
22
29
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
30
|
+
import { BatchClaims } from "./batch-claims.js";
|
|
23
31
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
32
|
+
import { describeCharacterSplit } from "./character-boundaries.js";
|
|
24
33
|
import { buildCleanBlockText, resolveCleanTextRange } from "./clean-text.js";
|
|
25
34
|
import { hasInlineEmphasis, parseInlineEmphasisRuns, stripInlineEmphasisMarkers } from "./inline-emphasis.js";
|
|
26
|
-
import { applyTextChanges, changesFromSegments, planTextChanges, widenChangesToAtomicSpans } from "./minimal-replacement.js";
|
|
27
|
-
import {
|
|
35
|
+
import { applyTextChanges, changesFromSegments, keepAtomicSpans, planTextChanges, widenChangesToAtomicSpans } from "./minimal-replacement.js";
|
|
36
|
+
import { concreteListReference, resolveNewListOperations } from "./newListNumbering.js";
|
|
37
|
+
import { cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, segmentsAroundNoteReferences } from "./note-references.js";
|
|
38
|
+
import { collectNoteReferenceLabels, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isHiddenTableRow, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
28
39
|
import { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
|
|
29
40
|
import { planTableMutations } from "./table-mutation-plan.js";
|
|
30
|
-
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
|
|
41
|
+
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, describeUnplacedCellTexts, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
|
|
31
42
|
import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBoundary, tableRectangleCutsMergedCell } from "./table-targets.js";
|
|
32
43
|
import { tableFromTemplate } from "./table-template.js";
|
|
33
44
|
import { wordDiffSessionFromOptions } from "./word-diff.js";
|
|
34
45
|
import { panic } from "better-result";
|
|
35
|
-
import { Fragment } from "prosemirror-model";
|
|
46
|
+
import { Fragment, Mark } from "prosemirror-model";
|
|
36
47
|
import { TableMap } from "prosemirror-tables";
|
|
37
48
|
import { canJoin, canSplit } from "prosemirror-transform";
|
|
38
49
|
//#region src/ai-edits/apply.ts
|
|
@@ -109,6 +120,12 @@ const SUGGESTED_SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
109
120
|
"insertTableColumn",
|
|
110
121
|
"deleteTableColumn"
|
|
111
122
|
]);
|
|
123
|
+
/** Operations that only add marks: they move no position of the document. */
|
|
124
|
+
const ANNOTATION_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
125
|
+
"formatRange",
|
|
126
|
+
"commentOnRange",
|
|
127
|
+
"commentOnBlock"
|
|
128
|
+
]);
|
|
112
129
|
const REPLACE_BLOCK_IMPACT = {
|
|
113
130
|
text: {
|
|
114
131
|
changesStyle: false,
|
|
@@ -167,6 +184,7 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
167
184
|
const styleChanged = nextStyleId !== void 0 && (attrs.styleId ?? null) !== nextStyleId;
|
|
168
185
|
if (styleChanged) {
|
|
169
186
|
patch["styleId"] = nextStyleId;
|
|
187
|
+
patch["_resolvedFormatting"] = styleResolvedParagraphFormatting(resolvedFormattingFromStyle);
|
|
170
188
|
patch["alignmentFromStyle"] = resolvedFormattingFromStyle?.alignment;
|
|
171
189
|
if (properties.alignment === void 0) patch["alignment"] = currentDirectAlignment ?? resolvedFormattingFromStyle?.alignment ?? null;
|
|
172
190
|
if (properties.spacing === void 0) Object.assign(patch, paragraphSpacingAttrPatch({
|
|
@@ -182,25 +200,42 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
182
200
|
else originalFormatting.styleId = nextStyleId;
|
|
183
201
|
if (currentDirectAlignment !== void 0) originalFormatting.alignment = currentDirectAlignment;
|
|
184
202
|
originalFormattingChanged = true;
|
|
203
|
+
patch["outlineLevel"] = attrs._originalFormatting?.outlineLevel ?? resolvedFormattingFromStyle?.outlineLevel ?? null;
|
|
204
|
+
Object.assign(patch, restyledListAttrs({
|
|
205
|
+
attrs: { ...attrs },
|
|
206
|
+
styleNumbering: resolvedFormattingFromStyle?.numPr,
|
|
207
|
+
removesNumbering: false,
|
|
208
|
+
numbering
|
|
209
|
+
}));
|
|
185
210
|
}
|
|
211
|
+
if (properties.outlineLevel !== void 0) {
|
|
212
|
+
const directOutlineLevel = properties.outlineLevel ?? void 0;
|
|
213
|
+
originalFormatting ??= {};
|
|
214
|
+
if (directOutlineLevel === void 0) Reflect.deleteProperty(originalFormatting, "outlineLevel");
|
|
215
|
+
else originalFormatting.outlineLevel = directOutlineLevel;
|
|
216
|
+
patch["outlineLevel"] = directOutlineLevel ?? resolvedFormattingFromStyle?.outlineLevel ?? null;
|
|
217
|
+
originalFormattingChanged = true;
|
|
218
|
+
}
|
|
219
|
+
const numPrFromStyle = styleChanged ? readParagraphNumberingAttr(patch["numPrFromStyle"]) : attrs.numPrFromStyle;
|
|
186
220
|
if (properties.numbering !== void 0) if (properties.numbering === null) {
|
|
187
|
-
patch["numPr"] =
|
|
221
|
+
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
188
222
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
189
223
|
} else {
|
|
224
|
+
const listReference = concreteListReference(properties.numbering);
|
|
190
225
|
Object.assign(patch, listLevelAttrPatch(attrs, {
|
|
191
|
-
numId:
|
|
192
|
-
ilvl:
|
|
226
|
+
numId: listReference.numId,
|
|
227
|
+
ilvl: listReference.level
|
|
193
228
|
}, numbering));
|
|
194
229
|
if (properties.listLevel === null) {
|
|
195
|
-
patch["numPr"] = paragraphNumberingAttr(paragraphNumberingReference({ numId:
|
|
230
|
+
patch["numPr"] = paragraphNumberingAttr(paragraphNumberingReference({ numId: listReference.numId }));
|
|
196
231
|
patch["numPrFromStyle"] = null;
|
|
197
232
|
}
|
|
198
233
|
}
|
|
199
234
|
else if (properties.listLevel !== void 0) if (properties.listLevel === null) {
|
|
200
|
-
patch["numPr"] =
|
|
235
|
+
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
201
236
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
202
237
|
} else {
|
|
203
|
-
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(node.attrs["numPr"]) ?? void 0);
|
|
238
|
+
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(styleChanged ? patch["numPr"] : node.attrs["numPr"]) ?? void 0);
|
|
204
239
|
if (numId === void 0) {
|
|
205
240
|
patch["numPr"] = paragraphNumberingAttr({
|
|
206
241
|
kind: "levelOnly",
|
|
@@ -242,6 +277,24 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
242
277
|
return Object.keys(patch).length > 0 ? patch : null;
|
|
243
278
|
};
|
|
244
279
|
const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
280
|
+
const hasOrdinaryTextRevision = (block) => {
|
|
281
|
+
let found = false;
|
|
282
|
+
block.descendants((node) => {
|
|
283
|
+
if (node.marks.some((mark) => REVISION_MARK_NAMES.has(mark.type.name) && mark.attrs["provenance"] !== "suggested")) {
|
|
284
|
+
found = true;
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
});
|
|
289
|
+
return found;
|
|
290
|
+
};
|
|
291
|
+
const hasOrdinaryParagraphMarkRevision = (block) => {
|
|
292
|
+
const mark = block.attrs["pPrMark"];
|
|
293
|
+
if (typeof mark !== "object" || mark === null || !("info" in mark)) return false;
|
|
294
|
+
const info = mark.info;
|
|
295
|
+
if (typeof info !== "object" || info === null) return false;
|
|
296
|
+
return !("provenance" in info) || info.provenance !== "suggested";
|
|
297
|
+
};
|
|
245
298
|
/**
|
|
246
299
|
* Strip insertion and deletion marks from the zero-width anchors the batch
|
|
247
300
|
* touched.
|
|
@@ -251,10 +304,12 @@ const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
|
251
304
|
* must keep its existing revision ownership. Marking is by range everywhere;
|
|
252
305
|
* the guard lives here once rather than at every call site.
|
|
253
306
|
*/
|
|
254
|
-
const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds) => {
|
|
307
|
+
const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds, deletedTextBoxAnchorIds) => {
|
|
255
308
|
const anchors = [];
|
|
256
309
|
tr.doc.descendants((node, pos) => {
|
|
257
310
|
if (!isZeroWidthAnchor(node)) return true;
|
|
311
|
+
const textBoxAnchorId = textBoxAnchorIdOf(node);
|
|
312
|
+
if (textBoxAnchorId !== void 0 && deletedTextBoxAnchorIds.has(textBoxAnchorId)) return false;
|
|
258
313
|
const marks = node.marks.filter(({ attrs, type }) => REVISION_MARK_NAMES.has(type.name) && batchRevisionIds.has(attrs["revisionId"]));
|
|
259
314
|
if (marks.length > 0) anchors.push({
|
|
260
315
|
from: pos,
|
|
@@ -272,7 +327,9 @@ const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds) => {
|
|
|
272
327
|
*
|
|
273
328
|
* Skips what is already deleted, so an earlier revision's run is not marked
|
|
274
329
|
* twice, and skips zero-width anchors because the operation did not create or
|
|
275
|
-
* delete them; their existing revision ownership must remain unchanged.
|
|
330
|
+
* delete them; their existing revision ownership must remain unchanged. A
|
|
331
|
+
* text box's anchor is the exception: it stands for the box's drawing run,
|
|
332
|
+
* which is the paragraph's content, and the box follows it.
|
|
276
333
|
*/
|
|
277
334
|
const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
278
335
|
const position = tr.mapping.map(blockFrom);
|
|
@@ -280,7 +337,7 @@ const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
|
280
337
|
if (!block?.isTextblock) return [];
|
|
281
338
|
const ranges = [];
|
|
282
339
|
block.forEach((child, offset) => {
|
|
283
|
-
if (child.isText || isZeroWidthAnchor(child) || child.marks.some(({ type }) => type.name === "deletion")) return;
|
|
340
|
+
if (child.isText || isZeroWidthAnchor(child) && child.type.name !== "textBoxAnchor" || child.marks.some(({ type }) => type.name === "deletion")) return;
|
|
284
341
|
const from = position + 1 + offset;
|
|
285
342
|
ranges.push({
|
|
286
343
|
from,
|
|
@@ -390,6 +447,12 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
|
|
|
390
447
|
revisionId: null,
|
|
391
448
|
revisionIds: []
|
|
392
449
|
};
|
|
450
|
+
const nextStyleId = properties.styleId === void 0 ? attrs.styleId : properties.styleId;
|
|
451
|
+
if ((attrs.styleId ?? null) !== (nextStyleId ?? null)) patch["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
452
|
+
styleId: properties.styleId ?? void 0,
|
|
453
|
+
tableRunFormatting: attrs._tableRunFormatting,
|
|
454
|
+
styleResolver
|
|
455
|
+
}).defaultFormatting ?? null;
|
|
393
456
|
const changeInfo = revisionInfo?.();
|
|
394
457
|
const change = changeInfo ? {
|
|
395
458
|
type: "paragraphPropertyChange",
|
|
@@ -413,13 +476,46 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
|
|
|
413
476
|
tr,
|
|
414
477
|
revisionIds: []
|
|
415
478
|
};
|
|
479
|
+
const styled = bridgeResult.tr.setNodeMarkup(position, void 0, nextAttrs);
|
|
416
480
|
return {
|
|
417
|
-
tr:
|
|
481
|
+
tr: (attrs.styleId ?? null) === (nextStyleId ?? null) ? styled : rebaseParagraphRuns({
|
|
482
|
+
tr: styled,
|
|
483
|
+
position,
|
|
484
|
+
previous: node,
|
|
485
|
+
styleResolver
|
|
486
|
+
}),
|
|
418
487
|
changed: true,
|
|
419
488
|
revisionId: change?.info.id ?? null,
|
|
420
489
|
revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
|
|
421
490
|
};
|
|
422
491
|
};
|
|
492
|
+
/** Rebuild rendered marks after the paragraph's style cascade changes. */
|
|
493
|
+
const rebaseParagraphRuns = ({ tr, position, previous, target, styleResolver }) => {
|
|
494
|
+
const paragraph = tr.doc.nodeAt(position);
|
|
495
|
+
if (!paragraph) return tr;
|
|
496
|
+
const sourceContext = paragraphRunStyleContext(previous, styleResolver);
|
|
497
|
+
const targetContext = paragraphRunStyleContext(target ?? paragraph, styleResolver);
|
|
498
|
+
const representations = selectRunFormattingCarrierRepresentations({
|
|
499
|
+
doc: tr.doc,
|
|
500
|
+
from: position + 1,
|
|
501
|
+
to: position + paragraph.nodeSize - 1
|
|
502
|
+
});
|
|
503
|
+
for (const representation of representations) applyMarksToRunFormattingRepresentation({
|
|
504
|
+
tr,
|
|
505
|
+
representation,
|
|
506
|
+
marks: reconcileRunFormattingMarks({
|
|
507
|
+
authoredFormatting: readAuthoredRunFormatting({
|
|
508
|
+
context: sourceContext,
|
|
509
|
+
marks: representation.node.marks,
|
|
510
|
+
styleResolver
|
|
511
|
+
}),
|
|
512
|
+
context: targetContext,
|
|
513
|
+
node: representation.node,
|
|
514
|
+
styleResolver
|
|
515
|
+
})
|
|
516
|
+
});
|
|
517
|
+
return tr;
|
|
518
|
+
};
|
|
423
519
|
const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) => {
|
|
424
520
|
if (item.operation.type !== "replaceBlock" || item.operation.styleId === void 0) return {
|
|
425
521
|
tr,
|
|
@@ -449,6 +545,11 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
|
|
|
449
545
|
revisionId: null,
|
|
450
546
|
revisionIds: []
|
|
451
547
|
};
|
|
548
|
+
if ((attrs.styleId ?? null) !== item.operation.styleId) patch["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
549
|
+
styleId: item.operation.styleId ?? void 0,
|
|
550
|
+
tableRunFormatting: attrs._tableRunFormatting,
|
|
551
|
+
styleResolver
|
|
552
|
+
}).defaultFormatting ?? null;
|
|
452
553
|
const existing = attrs._propertyChanges;
|
|
453
554
|
const changeInfo = revisionInfo?.();
|
|
454
555
|
const change = changeInfo ? {
|
|
@@ -472,8 +573,14 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
|
|
|
472
573
|
tr,
|
|
473
574
|
revisionIds: []
|
|
474
575
|
};
|
|
576
|
+
const styled = bridgeResult.tr.setNodeMarkup(blockPosition, void 0, nextAttrs);
|
|
475
577
|
return {
|
|
476
|
-
tr:
|
|
578
|
+
tr: (attrs.styleId ?? null) === item.operation.styleId ? styled : rebaseParagraphRuns({
|
|
579
|
+
tr: styled,
|
|
580
|
+
position: blockPosition,
|
|
581
|
+
previous: block,
|
|
582
|
+
styleResolver
|
|
583
|
+
}),
|
|
477
584
|
revisionId: change?.info.id ?? null,
|
|
478
585
|
revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
|
|
479
586
|
};
|
|
@@ -747,10 +854,14 @@ let revisionIdCursor = Date.now() * 1e3;
|
|
|
747
854
|
*/
|
|
748
855
|
const collectLiveBlocksByHash = (doc) => {
|
|
749
856
|
const byHash = /* @__PURE__ */ new Map();
|
|
857
|
+
const noteReferences = collectNoteReferenceLabels(doc);
|
|
750
858
|
doc.descendants((node, pos) => {
|
|
751
859
|
if (isHiddenTableRow(node)) return false;
|
|
752
860
|
if (!node.isTextblock) return true;
|
|
753
|
-
const cleanText = buildCleanBlockText(node, pos
|
|
861
|
+
const cleanText = buildCleanBlockText(node, pos, {
|
|
862
|
+
fieldResults: "text",
|
|
863
|
+
noteReferences
|
|
864
|
+
}).text;
|
|
754
865
|
const hash = hashFolioAIBlockText(normalizeFolioAIBlockText(cleanText));
|
|
755
866
|
const bucket = byHash.get(hash) ?? [];
|
|
756
867
|
bucket.push({
|
|
@@ -804,6 +915,190 @@ const getTableMutationPlanTarget = (item) => {
|
|
|
804
915
|
return { type: "none" };
|
|
805
916
|
};
|
|
806
917
|
/**
|
|
918
|
+
* `insertion`, resolved against the document as it was read, placed through
|
|
919
|
+
* what `tr` has already changed. The batch runs from the end backwards, so
|
|
920
|
+
* nothing it has applied yet sits before the new row; mapped all the same, so
|
|
921
|
+
* that stays true of the positions the row is written at and not only of the
|
|
922
|
+
* order they are visited in. A row another operation of the batch inserted
|
|
923
|
+
* at the same boundary is the later one in input order, so the new row goes
|
|
924
|
+
* before it.
|
|
925
|
+
*/
|
|
926
|
+
const mapTableRowInsertion = (tr, insertion) => {
|
|
927
|
+
const tableStart = tr.mapping.map(insertion.tableStart, -1);
|
|
928
|
+
return {
|
|
929
|
+
...insertion,
|
|
930
|
+
tableStart,
|
|
931
|
+
rowPosition: tr.mapping.map(insertion.rowPosition, -1),
|
|
932
|
+
rowspanUpdates: insertion.rowspanUpdates.map((update) => tr.mapping.map(insertion.tableStart + update, 1) - tableStart)
|
|
933
|
+
};
|
|
934
|
+
};
|
|
935
|
+
/** The cells a column deletion removes: every cell standing in that column alone. */
|
|
936
|
+
const deletedColumnCellRanges = (doc, { tablePosition, columnIndex }) => {
|
|
937
|
+
const table = doc.nodeAt(tablePosition);
|
|
938
|
+
if (!table || table.type.spec["tableRole"] !== "table") return [];
|
|
939
|
+
const map = TableMap.get(table);
|
|
940
|
+
const tableStart = tablePosition + 1;
|
|
941
|
+
const ranges = [];
|
|
942
|
+
const seen = /* @__PURE__ */ new Set();
|
|
943
|
+
for (let row = 0; row < map.height; row++) {
|
|
944
|
+
const relative = map.map[row * map.width + columnIndex];
|
|
945
|
+
if (relative === void 0 || seen.has(relative)) continue;
|
|
946
|
+
seen.add(relative);
|
|
947
|
+
const rectangle = map.findCell(relative);
|
|
948
|
+
const cell = table.nodeAt(relative);
|
|
949
|
+
if (!cell || rectangle.left !== columnIndex || rectangle.right !== columnIndex + 1) continue;
|
|
950
|
+
ranges.push({
|
|
951
|
+
from: tableStart + relative,
|
|
952
|
+
to: tableStart + relative + cell.nodeSize
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
return ranges;
|
|
956
|
+
};
|
|
957
|
+
/** A row deletion also changes cells whose vertical merge began above it. */
|
|
958
|
+
const deletedRowAffectedRanges = ({ doc, deletion, row }) => {
|
|
959
|
+
const ranges = [{
|
|
960
|
+
from: deletion.rowPosition,
|
|
961
|
+
to: deletion.rowPosition + row.nodeSize
|
|
962
|
+
}];
|
|
963
|
+
const table = doc.nodeAt(deletion.tablePosition);
|
|
964
|
+
if (table?.type.spec["tableRole"] !== "table") return panic("A resolved row deletion lost its table", { position: deletion.tablePosition });
|
|
965
|
+
const map = TableMap.get(table);
|
|
966
|
+
const seen = /* @__PURE__ */ new Set();
|
|
967
|
+
for (let column = 0; column < map.width; column++) {
|
|
968
|
+
const relative = map.map[deletion.rowIndex * map.width + column];
|
|
969
|
+
if (relative === void 0 || seen.has(relative)) continue;
|
|
970
|
+
seen.add(relative);
|
|
971
|
+
const position = deletion.tableStart + relative;
|
|
972
|
+
if (position >= deletion.rowPosition) continue;
|
|
973
|
+
const cell = table.nodeAt(relative);
|
|
974
|
+
if (cell) ranges.push({
|
|
975
|
+
from: position,
|
|
976
|
+
to: position + cell.nodeSize
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
return ranges;
|
|
980
|
+
};
|
|
981
|
+
/** What `item` claims of the document its batch resolved against; see `batch-claims.ts`. */
|
|
982
|
+
const batchClaimOf = ({ item, doc, producesTrackedChanges }) => {
|
|
983
|
+
const block = item.blockFrom;
|
|
984
|
+
switch (item.operation.type) {
|
|
985
|
+
case "replaceInBlock":
|
|
986
|
+
case "replaceRange": return {
|
|
987
|
+
type: "text",
|
|
988
|
+
block,
|
|
989
|
+
from: item.from,
|
|
990
|
+
to: item.to
|
|
991
|
+
};
|
|
992
|
+
case "formatRange":
|
|
993
|
+
case "commentOnRange":
|
|
994
|
+
case "commentOnBlock": return {
|
|
995
|
+
type: "annotation",
|
|
996
|
+
block,
|
|
997
|
+
from: item.from,
|
|
998
|
+
to: item.to
|
|
999
|
+
};
|
|
1000
|
+
case "splitBlock": return {
|
|
1001
|
+
type: "split",
|
|
1002
|
+
block,
|
|
1003
|
+
from: item.from,
|
|
1004
|
+
to: item.to
|
|
1005
|
+
};
|
|
1006
|
+
case "replaceBlock": return item.replaceBlockImpact === "style" ? {
|
|
1007
|
+
type: "paragraphProperties",
|
|
1008
|
+
block
|
|
1009
|
+
} : {
|
|
1010
|
+
type: "rewriteBlock",
|
|
1011
|
+
block
|
|
1012
|
+
};
|
|
1013
|
+
case "setBlockParagraphProperties": return {
|
|
1014
|
+
type: "paragraphProperties",
|
|
1015
|
+
block
|
|
1016
|
+
};
|
|
1017
|
+
case "mergeBlockWithNext": return {
|
|
1018
|
+
type: "merge",
|
|
1019
|
+
block,
|
|
1020
|
+
next: item.blockTo,
|
|
1021
|
+
joinsNow: !producesTrackedChanges
|
|
1022
|
+
};
|
|
1023
|
+
case "deleteBlock": {
|
|
1024
|
+
const at = doc.resolve(block);
|
|
1025
|
+
const endsItsContainer = paragraphEndsItsContainer(at, item.blockNode.type.name);
|
|
1026
|
+
const keepsParagraph = producesTrackedChanges ? endsItsContainer : endsItsContainer && at.nodeBefore?.type.name !== item.blockNode.type.name;
|
|
1027
|
+
return {
|
|
1028
|
+
type: "deleteBlock",
|
|
1029
|
+
block,
|
|
1030
|
+
end: item.blockTo,
|
|
1031
|
+
keepsParagraph,
|
|
1032
|
+
removesNode: !producesTrackedChanges && !keepsParagraph,
|
|
1033
|
+
direct: !producesTrackedChanges
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
case "insertAfterBlock":
|
|
1037
|
+
case "insertBeforeBlock":
|
|
1038
|
+
case "insertSignatureTable":
|
|
1039
|
+
case "insertTable": return {
|
|
1040
|
+
type: "insertion",
|
|
1041
|
+
at: item.from
|
|
1042
|
+
};
|
|
1043
|
+
case "deleteTable": {
|
|
1044
|
+
const deleted = item.deletedTable;
|
|
1045
|
+
return deleted ? {
|
|
1046
|
+
type: "tableRemoval",
|
|
1047
|
+
table: deleted.position,
|
|
1048
|
+
wholeTable: true,
|
|
1049
|
+
axis: "table",
|
|
1050
|
+
ranges: [{
|
|
1051
|
+
from: deleted.position,
|
|
1052
|
+
to: deleted.position + deleted.node.nodeSize
|
|
1053
|
+
}]
|
|
1054
|
+
} : { type: "unclaimed" };
|
|
1055
|
+
}
|
|
1056
|
+
case "deleteTableRow": {
|
|
1057
|
+
const deletion = item.tableRowDeletion;
|
|
1058
|
+
const row = deletion ? doc.nodeAt(deletion.rowPosition) : null;
|
|
1059
|
+
const table = deletion ? doc.nodeAt(deletion.tablePosition) : null;
|
|
1060
|
+
return deletion && row && table?.type.spec["tableRole"] === "table" ? {
|
|
1061
|
+
type: "tableRemoval",
|
|
1062
|
+
table: deletion.tablePosition,
|
|
1063
|
+
wholeTable: table.childCount === 1,
|
|
1064
|
+
axis: "row",
|
|
1065
|
+
ranges: table.childCount === 1 ? [{
|
|
1066
|
+
from: deletion.tablePosition,
|
|
1067
|
+
to: deletion.tablePosition + table.nodeSize
|
|
1068
|
+
}] : deletedRowAffectedRanges({
|
|
1069
|
+
doc,
|
|
1070
|
+
deletion,
|
|
1071
|
+
row
|
|
1072
|
+
})
|
|
1073
|
+
} : { type: "unclaimed" };
|
|
1074
|
+
}
|
|
1075
|
+
case "deleteTableColumn": {
|
|
1076
|
+
const deletion = item.tableColumnDeletion;
|
|
1077
|
+
return deletion ? {
|
|
1078
|
+
type: "tableRemoval",
|
|
1079
|
+
table: deletion.tablePosition,
|
|
1080
|
+
wholeTable: false,
|
|
1081
|
+
axis: "column",
|
|
1082
|
+
ranges: deletedColumnCellRanges(doc, deletion)
|
|
1083
|
+
} : { type: "unclaimed" };
|
|
1084
|
+
}
|
|
1085
|
+
case "insertTableRow":
|
|
1086
|
+
case "insertTableColumn":
|
|
1087
|
+
case "mergeTableCells":
|
|
1088
|
+
case "splitTableCell": {
|
|
1089
|
+
const target = getTableMutationPlanTarget(item);
|
|
1090
|
+
let axis = "cell";
|
|
1091
|
+
if (item.operation.type === "insertTableRow") axis = "row";
|
|
1092
|
+
if (item.operation.type === "insertTableColumn") axis = "column";
|
|
1093
|
+
return target.type === "none" ? { type: "unclaimed" } : {
|
|
1094
|
+
type: "tableStructure",
|
|
1095
|
+
table: target.tablePosition,
|
|
1096
|
+
axis
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
/**
|
|
807
1102
|
* The snapshot recorded an `hashOccurrenceCount` per anchor but
|
|
808
1103
|
* not which ordinal within that bucket the block was — recompute
|
|
809
1104
|
* on demand from the snapshot's anchor map. Stable iteration
|
|
@@ -956,6 +1251,79 @@ const isBatchableParagraphInsertion = (item, mode) => {
|
|
|
956
1251
|
const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
|
|
957
1252
|
return !(mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert);
|
|
958
1253
|
};
|
|
1254
|
+
const insertBlocksInsideComments = ({ tr, at, blocks, carriedComments }) => {
|
|
1255
|
+
const spanning = carriedComments ?? commentsAcrossBlockBoundary(tr.doc, at);
|
|
1256
|
+
const fragment = Fragment.from(blocks);
|
|
1257
|
+
let next = tr.insert(at, fragment);
|
|
1258
|
+
for (const comment of spanning) next = next.addMark(at, at + fragment.size, comment);
|
|
1259
|
+
return next;
|
|
1260
|
+
};
|
|
1261
|
+
/** A paragraph mark that is a pending insertion (not a relocation's end). */
|
|
1262
|
+
const isInsertedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "ins";
|
|
1263
|
+
const isSuggestedParagraphInsert = (paragraph) => typeof paragraph.attrs["_suggestedInsert"] === "object" && paragraph.attrs["_suggestedInsert"] !== null;
|
|
1264
|
+
/** A paragraph mark that is a pending deletion, or a relocation's source. */
|
|
1265
|
+
const isDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && (value.kind === "del" || value.kind === "moveFrom");
|
|
1266
|
+
const hasRevisionMarker = (value) => typeof value === "object" && value !== null && "revisionId" in value;
|
|
1267
|
+
const isDeletedCell = (cell) => {
|
|
1268
|
+
const marker = cell.attrs["cellMarker"];
|
|
1269
|
+
return typeof marker === "object" && marker !== null && "kind" in marker && marker.kind === "del";
|
|
1270
|
+
};
|
|
1271
|
+
/**
|
|
1272
|
+
* Whether accepting every change removes the paragraph at `blockFrom` whole:
|
|
1273
|
+
* its paragraph mark and all of its text pending deletion (`cleanText`, the
|
|
1274
|
+
* accepted reading, is empty), or the table row or cell holding it pending
|
|
1275
|
+
* deletion. A reader lists such a paragraph as a blank block.
|
|
1276
|
+
*
|
|
1277
|
+
* Word keeps a paragraph whose mark is deleted joined to the next one: text
|
|
1278
|
+
* typed into it stays inserted there, and accepting runs it into the next
|
|
1279
|
+
* paragraph (or, in a deleted row, takes it away with the row). An operation
|
|
1280
|
+
* that asks for the paragraph to read something cannot get that without
|
|
1281
|
+
* rejecting the deletion, which is the reviewer's call, not a side effect.
|
|
1282
|
+
*/
|
|
1283
|
+
const isPendingDeletion = (doc, blockFrom, paragraph, cleanText) => {
|
|
1284
|
+
if (cleanText.length === 0 && isDeletedPPrMark(paragraph.attrs["pPrMark"])) return true;
|
|
1285
|
+
const $block = doc.resolve(blockFrom);
|
|
1286
|
+
for (let depth = $block.depth; depth > 0; depth -= 1) {
|
|
1287
|
+
const ancestor = $block.node(depth);
|
|
1288
|
+
const name = ancestor.type.name;
|
|
1289
|
+
if (name === "tableRow" && hasRevisionMarker(ancestor.attrs["trDel"])) return true;
|
|
1290
|
+
if ((name === "tableCell" || name === "tableHeader") && isDeletedCell(ancestor)) return true;
|
|
1291
|
+
}
|
|
1292
|
+
return false;
|
|
1293
|
+
};
|
|
1294
|
+
/** Whether every piece of the paragraph's content is pending inserted text. */
|
|
1295
|
+
const holdsOnlyInsertedContent = (paragraph) => {
|
|
1296
|
+
let whollyInserted = true;
|
|
1297
|
+
paragraph.descendants((node) => {
|
|
1298
|
+
if (!whollyInserted) return false;
|
|
1299
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
1300
|
+
whollyInserted = node.marks.some((mark) => mark.type.name === "insertion" && mark.attrs["moveKind"] !== "moveTo");
|
|
1301
|
+
return false;
|
|
1302
|
+
});
|
|
1303
|
+
return whollyInserted;
|
|
1304
|
+
};
|
|
1305
|
+
/**
|
|
1306
|
+
* How to take back the paragraph at `position` when it is wholly a pending
|
|
1307
|
+
* insertion (the break that introduced it inserted, and everything in it
|
|
1308
|
+
* inserted text), or `null` when it is not. Such a paragraph goes outright:
|
|
1309
|
+
* accepting and rejecting both leave it out.
|
|
1310
|
+
*
|
|
1311
|
+
* The inserted break is the paragraph's own (an `ins` mark, or a suggested
|
|
1312
|
+
* whole-paragraph insert), except on the paragraph that ends its container:
|
|
1313
|
+
* that one keeps the container's original mark and the inserted break was
|
|
1314
|
+
* rotated onto the paragraph before it, whose `ins` mark then goes too.
|
|
1315
|
+
*/
|
|
1316
|
+
const pendingParagraphRetraction = (doc, position) => {
|
|
1317
|
+
const paragraph = doc.nodeAt(position);
|
|
1318
|
+
if (paragraph?.type.name !== "paragraph" || !holdsOnlyInsertedContent(paragraph)) return null;
|
|
1319
|
+
const at = doc.resolve(position);
|
|
1320
|
+
const before = at.nodeBefore;
|
|
1321
|
+
const endsItsContainer = paragraphEndsItsContainer(at, paragraph.type.name);
|
|
1322
|
+
if (at.parent.childCount < 2 || endsItsContainer && before?.type.name !== paragraph.type.name) return null;
|
|
1323
|
+
if (isInsertedPPrMark(paragraph.attrs["pPrMark"]) || isSuggestedParagraphInsert(paragraph)) return { clearBreakAt: null };
|
|
1324
|
+
if (endsItsContainer && before && paragraph.attrs["pPrMark"] == null) return isInsertedPPrMark(before.attrs["pPrMark"]) ? { clearBreakAt: position - before.nodeSize } : null;
|
|
1325
|
+
return null;
|
|
1326
|
+
};
|
|
959
1327
|
const addedBreakRevisionId = (value) => {
|
|
960
1328
|
if (typeof value !== "object" || value === null || !("kind" in value) || !("info" in value)) return null;
|
|
961
1329
|
if (value.kind !== "ins" && value.kind !== "moveTo") return null;
|
|
@@ -963,6 +1331,198 @@ const addedBreakRevisionId = (value) => {
|
|
|
963
1331
|
if (typeof info !== "object" || info === null || !("id" in info)) return null;
|
|
964
1332
|
return typeof info.id === "number" ? info.id : null;
|
|
965
1333
|
};
|
|
1334
|
+
/** The revision id of a paragraph mark that says its break was added or deleted. */
|
|
1335
|
+
const addedOrDeletedBreakRevisionId = (value) => {
|
|
1336
|
+
if (typeof value !== "object" || value === null || !("kind" in value) || !("info" in value)) return null;
|
|
1337
|
+
if (value.kind !== "del") return addedBreakRevisionId(value);
|
|
1338
|
+
const info = value.info;
|
|
1339
|
+
if (typeof info !== "object" || info === null || !("id" in info)) return null;
|
|
1340
|
+
return typeof info.id === "number" ? info.id : null;
|
|
1341
|
+
};
|
|
1342
|
+
/** Whether every piece of the paragraph's content is pending deleted text (or it has none). */
|
|
1343
|
+
const holdsOnlyDeletedContent = (paragraph) => {
|
|
1344
|
+
let whollyDeleted = true;
|
|
1345
|
+
paragraph.descendants((node) => {
|
|
1346
|
+
if (!whollyDeleted) return false;
|
|
1347
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
1348
|
+
whollyDeleted = node.marks.some((mark) => mark.type.name === "deletion");
|
|
1349
|
+
return false;
|
|
1350
|
+
});
|
|
1351
|
+
return whollyDeleted;
|
|
1352
|
+
};
|
|
1353
|
+
/** A paragraph mark that is a plain pending deletion (not a relocation's source). */
|
|
1354
|
+
const isPlainDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "del";
|
|
1355
|
+
/** An emptied paragraph still carries deleted content, rather than being an original blank. */
|
|
1356
|
+
const hasDeletedContent = (paragraph) => {
|
|
1357
|
+
let found = false;
|
|
1358
|
+
paragraph.descendants((node) => {
|
|
1359
|
+
if (node.isInline && node.marks.some((mark) => mark.type.name === "deletion")) {
|
|
1360
|
+
found = true;
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
return !found;
|
|
1364
|
+
});
|
|
1365
|
+
return found;
|
|
1366
|
+
};
|
|
1367
|
+
/** The final paragraph reached by a chain of wholly deleted paragraphs. */
|
|
1368
|
+
const deletedFinalParagraphAfter = (at, paragraphTypeName) => {
|
|
1369
|
+
let position = at.pos;
|
|
1370
|
+
for (let index = at.index(); index < at.parent.childCount; index++) {
|
|
1371
|
+
const paragraph = at.parent.child(index);
|
|
1372
|
+
if (paragraph.type.name !== paragraphTypeName || !holdsOnlyDeletedContent(paragraph)) return null;
|
|
1373
|
+
if (index === at.parent.childCount - 1) return paragraph.attrs["pPrMark"] == null && hasDeletedContent(paragraph) ? position : null;
|
|
1374
|
+
if (!isPlainDeletedPPrMark(paragraph.attrs["pPrMark"])) return null;
|
|
1375
|
+
position += paragraph.nodeSize;
|
|
1376
|
+
}
|
|
1377
|
+
return null;
|
|
1378
|
+
};
|
|
1379
|
+
/**
|
|
1380
|
+
* Where the added break of the final paragraph at `at` rotates to: the free
|
|
1381
|
+
* mark `addedBreakCarrierBefore` finds, or else a paragraph the batch deletes
|
|
1382
|
+
* whole, whose deleted break the added one cancels (`cancelsDeletedBreak`).
|
|
1383
|
+
* The cancelling paragraph must be followed directly by the paragraph its
|
|
1384
|
+
* words run into.
|
|
1385
|
+
*/
|
|
1386
|
+
const finalBreakCarrierBefore = (at, paragraphTypeName, batchRevisionIds) => {
|
|
1387
|
+
const free = addedBreakCarrierBefore(at, paragraphTypeName);
|
|
1388
|
+
if (free) return {
|
|
1389
|
+
...free,
|
|
1390
|
+
cancelsDeletedBreak: false
|
|
1391
|
+
};
|
|
1392
|
+
let position = at.pos;
|
|
1393
|
+
for (let index = at.index() - 1; index >= 0; index--) {
|
|
1394
|
+
const sibling = at.parent.child(index);
|
|
1395
|
+
position -= sibling.nodeSize;
|
|
1396
|
+
if (sibling.type.name !== paragraphTypeName) {
|
|
1397
|
+
if (sibling.type.spec["tableRole"] === "table") return null;
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
const mark = sibling.attrs["pPrMark"];
|
|
1401
|
+
const revisionId = addedBreakRevisionId(mark);
|
|
1402
|
+
if (revisionId !== null && batchRevisionIds.has(revisionId)) continue;
|
|
1403
|
+
const deletedBreakId = isPlainDeletedPPrMark(mark) ? addedOrDeletedBreakRevisionId(mark) : null;
|
|
1404
|
+
return deletedBreakId !== null && batchRevisionIds.has(deletedBreakId) && holdsOnlyDeletedContent(sibling) && at.parent.maybeChild(index + 1)?.type.name === paragraphTypeName ? {
|
|
1405
|
+
position,
|
|
1406
|
+
node: sibling,
|
|
1407
|
+
cancelsDeletedBreak: true
|
|
1408
|
+
} : null;
|
|
1409
|
+
}
|
|
1410
|
+
return null;
|
|
1411
|
+
};
|
|
1412
|
+
/**
|
|
1413
|
+
* Finish the tracked deletion of every paragraph a container ends with.
|
|
1414
|
+
*
|
|
1415
|
+
* Its own mark cannot be deleted (see `paragraphEndsItsContainer`), so the
|
|
1416
|
+
* deletion emptied it and left the mark. Applied directly, the paragraph goes
|
|
1417
|
+
* whenever a paragraph precedes it, which becomes the container's last; only
|
|
1418
|
+
* a container that would be left empty or ending in a table keeps an empty
|
|
1419
|
+
* paragraph. Tracked resolves to the same: the preceding paragraph's mark is
|
|
1420
|
+
* deleted, and accepting runs that paragraph into the emptied one, keeping
|
|
1421
|
+
* its own properties since it holds the words.
|
|
1422
|
+
*
|
|
1423
|
+
* A paragraph with a break pending deletion already runs into this one. If
|
|
1424
|
+
* its content is also wholly deleted, look past it for the surviving carrier;
|
|
1425
|
+
* otherwise another final-break revision would depend on batch boundaries.
|
|
1426
|
+
* A preceding break that is itself a pending insertion is
|
|
1427
|
+
* retracted instead of deleted: the paragraph joins the emptied one now, and
|
|
1428
|
+
* records the emptied one's properties as `w:pPrChange` where they differ,
|
|
1429
|
+
* which is what rejecting reads to put them back.
|
|
1430
|
+
*
|
|
1431
|
+
* This runs once over the finished batch: which paragraph precedes the
|
|
1432
|
+
* emptied one is settled only then, since the batch runs backwards and an
|
|
1433
|
+
* insertion or deletion before it lands after it.
|
|
1434
|
+
*/
|
|
1435
|
+
const withRetiredFinalParagraphs = ({ tr, deletions, appliedIds, batchRevisionIds, revisionSeed, author, date }) => {
|
|
1436
|
+
let nextRevisionId = revisionSeed;
|
|
1437
|
+
const addedRevisions = [];
|
|
1438
|
+
const retractedRevisionIds = [];
|
|
1439
|
+
const resolvedOperationIds = /* @__PURE__ */ new Set();
|
|
1440
|
+
const located = deletions.filter(({ operationId }) => appliedIds.has(operationId)).map((deletion) => ({
|
|
1441
|
+
deletion,
|
|
1442
|
+
at: tr.mapping.slice(deletion.mappedFrom).map(deletion.position)
|
|
1443
|
+
})).toSorted((left, right) => right.at - left.at);
|
|
1444
|
+
for (const { deletion: { operationId, revisionExtras }, at } of located) {
|
|
1445
|
+
const emptied = tr.doc.nodeAt(at);
|
|
1446
|
+
if (emptied?.type.name !== "paragraph") continue;
|
|
1447
|
+
const $emptied = tr.doc.resolve(at);
|
|
1448
|
+
if (!paragraphEndsItsContainer($emptied, emptied.type.name)) continue;
|
|
1449
|
+
let position = at;
|
|
1450
|
+
for (let index = $emptied.index() - 1; index >= 0; index--) {
|
|
1451
|
+
const previous = $emptied.parent.child(index);
|
|
1452
|
+
position -= previous.nodeSize;
|
|
1453
|
+
if (previous.type.name !== emptied.type.name) break;
|
|
1454
|
+
const mark = previous.attrs["pPrMark"];
|
|
1455
|
+
if (mark == null) {
|
|
1456
|
+
const revisionId = nextRevisionId++;
|
|
1457
|
+
tr.setNodeAttribute(position, "pPrMark", {
|
|
1458
|
+
kind: "del",
|
|
1459
|
+
info: {
|
|
1460
|
+
id: revisionId,
|
|
1461
|
+
author,
|
|
1462
|
+
date,
|
|
1463
|
+
...revisionExtras
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
resolvedOperationIds.add(operationId);
|
|
1467
|
+
addedRevisions.push({
|
|
1468
|
+
operationId,
|
|
1469
|
+
revisionId
|
|
1470
|
+
});
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
if (isPlainDeletedPPrMark(mark)) {
|
|
1474
|
+
if (holdsOnlyDeletedContent(previous)) continue;
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
if (isInsertedPPrMark(mark)) {
|
|
1478
|
+
const following = tr.doc.nodeAt(position + previous.nodeSize);
|
|
1479
|
+
if (following?.type.name !== emptied.type.name) break;
|
|
1480
|
+
const previousFormatting = paragraphPropertiesBeforeBatch(following, batchRevisionIds);
|
|
1481
|
+
const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
|
|
1482
|
+
const existing = expectParagraphAttrs(previous)._propertyChanges;
|
|
1483
|
+
if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) break;
|
|
1484
|
+
joinAtParagraphMark({
|
|
1485
|
+
tr,
|
|
1486
|
+
paragraphPos: position,
|
|
1487
|
+
paragraph: previous,
|
|
1488
|
+
next: following
|
|
1489
|
+
});
|
|
1490
|
+
resolvedOperationIds.add(operationId);
|
|
1491
|
+
const retracted = addedBreakRevisionId(mark);
|
|
1492
|
+
if (retracted !== null) retractedRevisionIds.push(retracted);
|
|
1493
|
+
const joined = tr.doc.nodeAt(position);
|
|
1494
|
+
if (formattingChanges && joined) {
|
|
1495
|
+
const revisionId = nextRevisionId++;
|
|
1496
|
+
tr.setNodeMarkup(position, void 0, {
|
|
1497
|
+
...joined.attrs,
|
|
1498
|
+
_propertyChanges: [...Array.isArray(existing) ? existing : [], {
|
|
1499
|
+
type: "paragraphPropertyChange",
|
|
1500
|
+
info: {
|
|
1501
|
+
id: revisionId,
|
|
1502
|
+
author,
|
|
1503
|
+
date,
|
|
1504
|
+
...revisionExtras
|
|
1505
|
+
},
|
|
1506
|
+
previousFormatting
|
|
1507
|
+
}]
|
|
1508
|
+
});
|
|
1509
|
+
addedRevisions.push({
|
|
1510
|
+
operationId,
|
|
1511
|
+
revisionId
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return {
|
|
1520
|
+
nextRevisionId,
|
|
1521
|
+
resolvedOperationIds,
|
|
1522
|
+
addedRevisions,
|
|
1523
|
+
retractedRevisionIds
|
|
1524
|
+
};
|
|
1525
|
+
};
|
|
966
1526
|
/**
|
|
967
1527
|
* Move an ADDED paragraph break off every paragraph its container ends with.
|
|
968
1528
|
*
|
|
@@ -985,8 +1545,50 @@ const addedBreakRevisionId = (value) => {
|
|
|
985
1545
|
* which paragraph ends a container is only settled when the batch is, and an
|
|
986
1546
|
* insertion that looked final was undone by the next operation writing a table
|
|
987
1547
|
* after it.
|
|
1548
|
+
*
|
|
1549
|
+
* The paragraph the run was appended after may be one the same batch deletes:
|
|
1550
|
+
* the insertion landed first, so the deletion took that paragraph's mark as
|
|
1551
|
+
* well. The break rotating onto it then cancels the deleted one, as deleting a
|
|
1552
|
+
* paragraph whose break is inserted does one operation at a time: the deleted
|
|
1553
|
+
* paragraph's words run into the one after it, which ends with the free mark,
|
|
1554
|
+
* and neither break is left to resolve.
|
|
988
1555
|
*/
|
|
989
1556
|
const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials }) => {
|
|
1557
|
+
const supersededDeletedBreaks = [];
|
|
1558
|
+
const collectSupersededDeletedBreaks = (parent, contentStart) => {
|
|
1559
|
+
let previous = null;
|
|
1560
|
+
parent.forEach((node, offset) => {
|
|
1561
|
+
const position = contentStart + offset;
|
|
1562
|
+
if (node.type.name !== "paragraph") {
|
|
1563
|
+
previous = null;
|
|
1564
|
+
if (!node.isTextblock && node.childCount > 0) collectSupersededDeletedBreaks(node, position + 1);
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (previous) {
|
|
1568
|
+
const deletedMark = previous.node.attrs["pPrMark"];
|
|
1569
|
+
const insertedMark = node.attrs["pPrMark"];
|
|
1570
|
+
const insertedRevisionId = addedBreakRevisionId(insertedMark);
|
|
1571
|
+
const trackedInsertion = insertedRevisionId !== null && batchRevisionIds.has(insertedRevisionId);
|
|
1572
|
+
const suggestedInsertion = isSuggestedParagraphInsert(node);
|
|
1573
|
+
const deletedBreakRevisionId = addedOrDeletedBreakRevisionId(deletedMark);
|
|
1574
|
+
if (isPlainDeletedPPrMark(deletedMark) && deletedBreakRevisionId !== null && !batchRevisionIds.has(deletedBreakRevisionId) && !holdsOnlyDeletedContent(previous.node) && (trackedInsertion || suggestedInsertion) && holdsOnlyInsertedContent(node)) supersededDeletedBreaks.push({
|
|
1575
|
+
preceding: previous.position,
|
|
1576
|
+
inserted: position,
|
|
1577
|
+
deletedMark,
|
|
1578
|
+
precedingMark: trackedInsertion ? insertedMark : null
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
previous = {
|
|
1582
|
+
node,
|
|
1583
|
+
position
|
|
1584
|
+
};
|
|
1585
|
+
});
|
|
1586
|
+
};
|
|
1587
|
+
collectSupersededDeletedBreaks(tr.doc, 0);
|
|
1588
|
+
for (const { preceding, inserted, deletedMark, precedingMark } of supersededDeletedBreaks) {
|
|
1589
|
+
tr.setNodeAttribute(preceding, "pPrMark", precedingMark);
|
|
1590
|
+
tr.setNodeAttribute(inserted, "pPrMark", deletedMark);
|
|
1591
|
+
}
|
|
990
1592
|
const paragraphTypeName = tr.doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
|
|
991
1593
|
const rotations = finalParagraphsOf(tr.doc, paragraphTypeName).flatMap(({ node, position }) => {
|
|
992
1594
|
const mark = node.attrs["pPrMark"];
|
|
@@ -997,13 +1599,14 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
997
1599
|
ownerRevisionId: revisionId,
|
|
998
1600
|
position
|
|
999
1601
|
}] : [];
|
|
1000
|
-
});
|
|
1602
|
+
}).toSorted((left, right) => right.position - left.position);
|
|
1001
1603
|
let next = tr;
|
|
1002
1604
|
let nextRevisionId = revisionSeed;
|
|
1003
1605
|
const synthesizedRevisions = [];
|
|
1606
|
+
const retractedRevisionIds = [];
|
|
1004
1607
|
for (const { position: finalPosition, node: final, mark: finalMark } of rotations) {
|
|
1005
1608
|
const resolved = next.doc.resolve(finalPosition);
|
|
1006
|
-
const carrier =
|
|
1609
|
+
const carrier = finalBreakCarrierBefore(resolved, final.type.name, batchRevisionIds);
|
|
1007
1610
|
if (!carrier) {
|
|
1008
1611
|
next = next.setNodeAttribute(finalPosition, "pPrMark", null);
|
|
1009
1612
|
continue;
|
|
@@ -1041,7 +1644,8 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1041
1644
|
const ownerRevisionId = addedBreakRevisionId(carriedMark);
|
|
1042
1645
|
if (ownerRevisionId === null || !batchRevisionIds.has(ownerRevisionId)) panic("A final-mark rotation crossed a break outside its operation batch", { ownerRevisionId });
|
|
1043
1646
|
const displacedMark = previous.node.attrs["pPrMark"];
|
|
1044
|
-
|
|
1647
|
+
const cancels = carrier.cancelsDeletedBreak && previous.position === carrier.position;
|
|
1648
|
+
if (!cancels) next = next.setNodeAttribute(previous.position, "pPrMark", carriedMark);
|
|
1045
1649
|
const currentFormatting = paragraphPropertiesSnapshot(current.node);
|
|
1046
1650
|
if (JSON.stringify(previousFormatting) !== JSON.stringify(currentFormatting)) {
|
|
1047
1651
|
const liveCurrent = next.doc.nodeAt(current.position);
|
|
@@ -1067,6 +1671,22 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1067
1671
|
revisionId
|
|
1068
1672
|
});
|
|
1069
1673
|
}
|
|
1674
|
+
if (cancels) {
|
|
1675
|
+
const deleted = next.doc.nodeAt(previous.position);
|
|
1676
|
+
const following = next.doc.nodeAt(current.position);
|
|
1677
|
+
if (!deleted || !following) panic("A cancelled final-mark rotation lost its paragraphs", { position: previous.position });
|
|
1678
|
+
joinAtParagraphMark({
|
|
1679
|
+
tr: next,
|
|
1680
|
+
paragraphPos: previous.position,
|
|
1681
|
+
paragraph: deleted,
|
|
1682
|
+
next: following,
|
|
1683
|
+
firstIsGoing: true
|
|
1684
|
+
});
|
|
1685
|
+
retractedRevisionIds.push(ownerRevisionId);
|
|
1686
|
+
const deletedBreakId = addedOrDeletedBreakRevisionId(displacedMark);
|
|
1687
|
+
if (deletedBreakId !== null) retractedRevisionIds.push(deletedBreakId);
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1070
1690
|
if (displacedMark == null) break;
|
|
1071
1691
|
carriedMark = displacedMark;
|
|
1072
1692
|
}
|
|
@@ -1074,10 +1694,26 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1074
1694
|
return {
|
|
1075
1695
|
transaction: next,
|
|
1076
1696
|
nextRevisionId,
|
|
1077
|
-
synthesizedRevisions
|
|
1697
|
+
synthesizedRevisions,
|
|
1698
|
+
retractedRevisionIds
|
|
1699
|
+
};
|
|
1700
|
+
};
|
|
1701
|
+
const restyledListAttrs = ({ attrs, styleNumbering, removesNumbering, numbering }) => {
|
|
1702
|
+
const stated = readParagraphNumberingAttr(attrs["numPr"]);
|
|
1703
|
+
const statedByOldStyle = isStyleSourcedParagraphNumbering(stated, readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1704
|
+
const fromStyle = styleNumbering === void 0 ? null : paragraphNumberingAttr(styleNumbering);
|
|
1705
|
+
const numPr = removesNumbering ? removedNumberingAttr(fromStyle) : stated !== null && !statedByOldStyle ? stated : fromStyle;
|
|
1706
|
+
return {
|
|
1707
|
+
...CLEARED_LIST_RENDERING_ATTRS,
|
|
1708
|
+
...numPr?.kind === "reference" && listAttrsFromNumbering({
|
|
1709
|
+
numId: numPr.numId,
|
|
1710
|
+
ilvl: numPr.ilvl ?? 0
|
|
1711
|
+
}, numbering),
|
|
1712
|
+
numPr,
|
|
1713
|
+
numPrFromStyle: fromStyle
|
|
1078
1714
|
};
|
|
1079
1715
|
};
|
|
1080
|
-
const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, author, date, initials, commentMark, suggestionId, revisionSeed, isPairedMove, numbering }) => {
|
|
1716
|
+
const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, author, date, initials, commentMark, suggestionId, revisionSeed, isPairedMove, numbering, styleResolver }) => {
|
|
1081
1717
|
const operation = item.operation;
|
|
1082
1718
|
if (operation.type !== "insertAfterBlock" && operation.type !== "insertBeforeBlock") panic("Only paragraph insertions can build inserted paragraphs", { type: operation.type });
|
|
1083
1719
|
const insertionType = schema.marks["insertion"];
|
|
@@ -1131,11 +1767,11 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1131
1767
|
}) : buildEmphasisInlineContent(schema, text, marks);
|
|
1132
1768
|
const attrs = formatsParagraph ? { ...baseAttrs } : {};
|
|
1133
1769
|
if (isFirstParagraph && operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
|
|
1134
|
-
const explicitNumbering = operation.numbering;
|
|
1770
|
+
const explicitNumbering = operation.numbering === void 0 || operation.numbering === null ? operation.numbering : concreteListReference(operation.numbering);
|
|
1135
1771
|
const listLevel = operation.listLevel;
|
|
1136
1772
|
if (formatsParagraph) {
|
|
1137
1773
|
if (explicitNumbering === null) {
|
|
1138
|
-
attrs["numPr"] =
|
|
1774
|
+
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1139
1775
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
1140
1776
|
} else if (explicitNumbering !== void 0) {
|
|
1141
1777
|
Object.assign(attrs, listLevelAttrPatch(operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode), {
|
|
@@ -1147,7 +1783,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1147
1783
|
attrs["numPrFromStyle"] = null;
|
|
1148
1784
|
}
|
|
1149
1785
|
} else if (listLevel === null) {
|
|
1150
|
-
attrs["numPr"] =
|
|
1786
|
+
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1151
1787
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
1152
1788
|
} else if (typeof listLevel === "number") {
|
|
1153
1789
|
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(Reflect.get(baseAttrs, "numPr")) ?? void 0);
|
|
@@ -1165,9 +1801,24 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1165
1801
|
}
|
|
1166
1802
|
if (formatsParagraph && operation.styleId !== void 0) {
|
|
1167
1803
|
attrs["styleId"] = operation.styleId;
|
|
1168
|
-
|
|
1804
|
+
attrs["_resolvedFormatting"] = styleResolvedParagraphFormatting(formattingFromStyle);
|
|
1805
|
+
attrs["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
1806
|
+
styleId: operation.styleId ?? void 0,
|
|
1807
|
+
tableRunFormatting: expectParagraphAttrs(item.blockNode)._tableRunFormatting,
|
|
1808
|
+
styleResolver
|
|
1809
|
+
}).defaultFormatting ?? null;
|
|
1810
|
+
if (operation.inheritFormatting !== false && operation.styleId !== null) {
|
|
1811
|
+
Object.assign(attrs, restyledListAttrs({
|
|
1812
|
+
attrs,
|
|
1813
|
+
styleNumbering: formattingFromStyle?.numPr,
|
|
1814
|
+
removesNumbering: explicitNumbering === null || explicitNumbering === void 0 && listLevel === null,
|
|
1815
|
+
numbering
|
|
1816
|
+
}));
|
|
1817
|
+
attrs["outlineLevel"] = attrs["_originalFormatting"]?.outlineLevel ?? formattingFromStyle?.outlineLevel ?? null;
|
|
1818
|
+
}
|
|
1169
1819
|
}
|
|
1170
|
-
if (formatsParagraph &&
|
|
1820
|
+
if (formatsParagraph && operation.outlineLevel !== void 0) attrs["outlineLevel"] = operation.outlineLevel ?? formattingFromStyle?.outlineLevel ?? null;
|
|
1821
|
+
if (formatsParagraph && (operation.styleId !== void 0 || operation.outlineLevel !== void 0 || operation.alignment !== void 0 || operation.spacing !== void 0 || operation.indentation !== void 0)) {
|
|
1171
1822
|
const inheritedDirectAlignment = operation.inheritFormatting === false ? void 0 : directParagraphAlignment(expectParagraphAttrs(item.blockNode));
|
|
1172
1823
|
const inheritedDirectSpacing = operation.inheritFormatting === false ? void 0 : directParagraphSpacing(expectParagraphAttrs(item.blockNode));
|
|
1173
1824
|
const inheritedDirectIndentation = operation.inheritFormatting === false ? void 0 : directParagraphIndentation(expectParagraphAttrs(item.blockNode));
|
|
@@ -1183,6 +1834,11 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1183
1834
|
if (operation.styleId === null) Reflect.deleteProperty(originalFormatting, "styleId");
|
|
1184
1835
|
else originalFormatting.styleId = operation.styleId;
|
|
1185
1836
|
}
|
|
1837
|
+
if (operation.outlineLevel !== void 0) {
|
|
1838
|
+
originalFormatting ??= {};
|
|
1839
|
+
if (operation.outlineLevel === null) Reflect.deleteProperty(originalFormatting, "outlineLevel");
|
|
1840
|
+
else originalFormatting.outlineLevel = operation.outlineLevel;
|
|
1841
|
+
}
|
|
1186
1842
|
if (operation.alignment !== void 0 || inheritedDirectAlignment !== void 0) {
|
|
1187
1843
|
originalFormatting ??= {};
|
|
1188
1844
|
if (directAlignment === void 0) Reflect.deleteProperty(originalFormatting, "alignment");
|
|
@@ -1200,6 +1856,17 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1200
1856
|
}));
|
|
1201
1857
|
attrs["_originalFormatting"] = originalFormatting && Object.keys(originalFormatting).length > 0 ? originalFormatting : null;
|
|
1202
1858
|
}
|
|
1859
|
+
if (formatsParagraph && operation.styleId !== void 0 && formattingFromStyle?.numPr?.kind === "reference" && operation.numbering === void 0 && operation.listLevel === void 0) {
|
|
1860
|
+
const { numId, ilvl = 0 } = formattingFromStyle.numPr;
|
|
1861
|
+
const levelIndentation = numbering?.getLevel(numId, ilvl)?.pPr;
|
|
1862
|
+
if (levelIndentation && operation.indentation === void 0) {
|
|
1863
|
+
const direct = attrs["_originalFormatting"];
|
|
1864
|
+
const directIndentLeft = typeof direct === "object" && direct !== null && "indentLeft" in direct ? direct.indentLeft : void 0;
|
|
1865
|
+
const directIndentFirstLine = typeof direct === "object" && direct !== null && "indentFirstLine" in direct ? direct.indentFirstLine : void 0;
|
|
1866
|
+
if (directIndentLeft === void 0 && formattingFromStyle.indentLeft === void 0) attrs["indentLeft"] = levelIndentation.indentLeft ?? attrs["indentLeft"];
|
|
1867
|
+
if (directIndentFirstLine === void 0 && formattingFromStyle.indentFirstLine === void 0) attrs["indentFirstLine"] = levelIndentation.indentFirstLine ?? attrs["indentFirstLine"];
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1203
1870
|
if (isSuggested && suggestionId !== null && paragraphRevisionId !== null) attrs["_suggestedInsert"] = {
|
|
1204
1871
|
suggestionId,
|
|
1205
1872
|
revisionId: paragraphRevisionId,
|
|
@@ -1207,7 +1874,28 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1207
1874
|
date,
|
|
1208
1875
|
...initials ? { initials } : {}
|
|
1209
1876
|
};
|
|
1210
|
-
|
|
1877
|
+
const paragraph = item.blockNode.type.create(attrs, content);
|
|
1878
|
+
const sourceContext = paragraphRunStyleContext(operation.inheritFormatting === false ? item.blockNode.type.create({}) : item.blockNode, styleResolver);
|
|
1879
|
+
const targetContext = paragraphRunStyleContext(paragraph, styleResolver);
|
|
1880
|
+
const reconciled = [];
|
|
1881
|
+
paragraph.forEach((child) => {
|
|
1882
|
+
if (!child.isInline) {
|
|
1883
|
+
reconciled.push(child);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const reconciledMarks = reconcileRunFormattingMarks({
|
|
1887
|
+
authoredFormatting: readAuthoredRunFormatting({
|
|
1888
|
+
context: sourceContext,
|
|
1889
|
+
marks: child.marks,
|
|
1890
|
+
styleResolver
|
|
1891
|
+
}),
|
|
1892
|
+
context: targetContext,
|
|
1893
|
+
node: child,
|
|
1894
|
+
styleResolver
|
|
1895
|
+
});
|
|
1896
|
+
reconciled.push(child.mark(reconciledMarks));
|
|
1897
|
+
});
|
|
1898
|
+
nodes.push(paragraph.copy(Fragment.fromArray(reconciled)));
|
|
1211
1899
|
}
|
|
1212
1900
|
if (producesTrackedChanges && !isSuggested) for (const [index, node] of nodes.entries()) {
|
|
1213
1901
|
const revisionId = nextRevisionId++;
|
|
@@ -1231,7 +1919,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1231
1919
|
nextRevisionId
|
|
1232
1920
|
};
|
|
1233
1921
|
};
|
|
1234
|
-
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates, replacementBackground = "clear" }) => {
|
|
1922
|
+
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: requestedOperations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates, replacementBackground = "clear", undefinedStyles = "refuse" }) => {
|
|
1235
1923
|
const applied = [];
|
|
1236
1924
|
const skipped = [];
|
|
1237
1925
|
const normalizations = [];
|
|
@@ -1263,7 +1951,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1263
1951
|
return id;
|
|
1264
1952
|
};
|
|
1265
1953
|
const styleResolver = getDocumentStyleResolver(view.state);
|
|
1266
|
-
const
|
|
1954
|
+
const styleDefinitions = undefinedStyles === "keep" ? null : getDocumentStyleDefinitions(view.state);
|
|
1955
|
+
const { operations, numbering } = resolveNewListOperations(requestedOperations, getDocumentNumbering(view.state));
|
|
1267
1956
|
const formattingFromStyleForInsertion = (item) => {
|
|
1268
1957
|
if (item.operation.type !== "insertAfterBlock" && item.operation.type !== "insertBeforeBlock") return;
|
|
1269
1958
|
const anchorAttrs = expectParagraphAttrs(item.blockNode);
|
|
@@ -1277,6 +1966,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1277
1966
|
};
|
|
1278
1967
|
const claimedTableRows = /* @__PURE__ */ new Set();
|
|
1279
1968
|
const claimedTableColumns = /* @__PURE__ */ new Set();
|
|
1969
|
+
const batchClaims = new BatchClaims();
|
|
1280
1970
|
const diffText = wordDiffMode === "coarse" ? coarseWordDiff : wordDiffSessionFromOptions(wordDiff).diff;
|
|
1281
1971
|
const producesTrackedChanges = mode !== "direct";
|
|
1282
1972
|
const isSuggested = mode === "suggested";
|
|
@@ -1290,6 +1980,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1290
1980
|
};
|
|
1291
1981
|
const liveBlocks = collectLiveBlocksByHash(view.state.doc);
|
|
1292
1982
|
const liveBlocksByParaId = collectLiveBlocksByParaId(view.state.doc);
|
|
1983
|
+
const noteReferences = collectNoteReferenceLabels(view.state.doc);
|
|
1984
|
+
const numberingInstanceIds = getDocumentNumberingInstanceIds(view.state) === null ? null : new Set(numbering?.definitions.nums.map(({ numId }) => numId) ?? []);
|
|
1293
1985
|
for (const [index, operation] of operations.entries()) {
|
|
1294
1986
|
const commentText = getOperationCommentText(operation);
|
|
1295
1987
|
if (commentText !== void 0 && (!commentType || createCommentId === void 0)) {
|
|
@@ -1304,12 +1996,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1304
1996
|
operation,
|
|
1305
1997
|
liveBlocks,
|
|
1306
1998
|
liveBlocksByParaId,
|
|
1999
|
+
noteReferences,
|
|
1307
2000
|
doc: view.state.doc
|
|
1308
2001
|
});
|
|
1309
2002
|
if (resolution.type === "skip") {
|
|
1310
2003
|
skipped.push({
|
|
1311
2004
|
id: operation.id,
|
|
1312
|
-
reason: resolution.reason
|
|
2005
|
+
reason: resolution.reason,
|
|
2006
|
+
...resolution.message !== void 0 && { message: resolution.message }
|
|
1313
2007
|
});
|
|
1314
2008
|
continue;
|
|
1315
2009
|
}
|
|
@@ -1320,6 +2014,24 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1320
2014
|
});
|
|
1321
2015
|
continue;
|
|
1322
2016
|
}
|
|
2017
|
+
const undefinedStyle = findUndefinedParagraphStyleReference(operation, styleDefinitions);
|
|
2018
|
+
if (undefinedStyle !== void 0) {
|
|
2019
|
+
skipped.push({
|
|
2020
|
+
id: operation.id,
|
|
2021
|
+
reason: "missingStyle",
|
|
2022
|
+
message: describeUndefinedParagraphStyle(undefinedStyle, styleDefinitions)
|
|
2023
|
+
});
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
const undefinedNumbering = findUndefinedNumberingReference(operation, numberingInstanceIds);
|
|
2027
|
+
if (undefinedNumbering !== void 0) {
|
|
2028
|
+
skipped.push({
|
|
2029
|
+
id: operation.id,
|
|
2030
|
+
reason: "missingNumbering",
|
|
2031
|
+
message: describeUndefinedNumbering(undefinedNumbering, numberingInstanceIds)
|
|
2032
|
+
});
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
1323
2035
|
const deletion = resolution.operation.tableRowDeletion;
|
|
1324
2036
|
if (deletion) {
|
|
1325
2037
|
const rowKey = `${deletion.tablePosition}:${deletion.rowIndex}`;
|
|
@@ -1330,7 +2042,6 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1330
2042
|
});
|
|
1331
2043
|
continue;
|
|
1332
2044
|
}
|
|
1333
|
-
claimedTableRows.add(rowKey);
|
|
1334
2045
|
}
|
|
1335
2046
|
const columnDeletion = resolution.operation.tableColumnDeletion;
|
|
1336
2047
|
if (columnDeletion) {
|
|
@@ -1342,8 +2053,24 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1342
2053
|
});
|
|
1343
2054
|
continue;
|
|
1344
2055
|
}
|
|
1345
|
-
claimedTableColumns.add(columnKey);
|
|
1346
2056
|
}
|
|
2057
|
+
const claim = batchClaimOf({
|
|
2058
|
+
item: resolution.operation,
|
|
2059
|
+
doc: view.state.doc,
|
|
2060
|
+
producesTrackedChanges
|
|
2061
|
+
});
|
|
2062
|
+
const claimedBy = batchClaims.conflictOf(claim);
|
|
2063
|
+
if (claimedBy !== null) {
|
|
2064
|
+
skipped.push({
|
|
2065
|
+
id: operation.id,
|
|
2066
|
+
reason: "overlappingOperation",
|
|
2067
|
+
message: `operation ${JSON.stringify(claimedBy)}, earlier in this batch, already claims its target.`
|
|
2068
|
+
});
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
batchClaims.add(operation.id, claim);
|
|
2072
|
+
if (deletion) claimedTableRows.add(`${deletion.tablePosition}:${deletion.rowIndex}`);
|
|
2073
|
+
if (columnDeletion) claimedTableColumns.add(getTableColumnCoordinateKey(columnDeletion));
|
|
1347
2074
|
if ((operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") && operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text)) normalizations.push({
|
|
1348
2075
|
id: operation.id,
|
|
1349
2076
|
code: "splitMultilineText",
|
|
@@ -1394,6 +2121,11 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1394
2121
|
});
|
|
1395
2122
|
}
|
|
1396
2123
|
let tr = view.state.tr;
|
|
2124
|
+
/** Text boxes a tracked block deletion deletes, by anchor. */
|
|
2125
|
+
const deletedTextBoxAnchorIds = /* @__PURE__ */ new Set();
|
|
2126
|
+
/** Container-ending paragraphs a tracked `deleteBlock` emptied: see `withRetiredFinalParagraphs`. */
|
|
2127
|
+
const deletedFinalParagraphs = [];
|
|
2128
|
+
const deferredNoopFinalDeletions = /* @__PURE__ */ new Set();
|
|
1397
2129
|
const ownsSharedRevisionIdCursor = revisionIdSeed === void 0 && revisionStamp === void 0;
|
|
1398
2130
|
let revisionSeed = revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor;
|
|
1399
2131
|
/**
|
|
@@ -1409,7 +2141,30 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1409
2141
|
};
|
|
1410
2142
|
const date = revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1411
2143
|
const insertedColumnCounts = /* @__PURE__ */ new Map();
|
|
2144
|
+
const tableAfterDeletedSplit = /* @__PURE__ */ new Map();
|
|
2145
|
+
if (mode === "tracked-changes") {
|
|
2146
|
+
const afterTables = /* @__PURE__ */ new Map();
|
|
2147
|
+
for (const item of executableResolved) {
|
|
2148
|
+
if (item.operation.type !== "insertTable" || item.operation.position === "before") continue;
|
|
2149
|
+
const boundaries = afterTables.get(item.blockFrom) ?? /* @__PURE__ */ new Set();
|
|
2150
|
+
boundaries.add(item.from);
|
|
2151
|
+
afterTables.set(item.blockFrom, boundaries);
|
|
2152
|
+
}
|
|
2153
|
+
for (const item of executableResolved) {
|
|
2154
|
+
if (item.operation.type !== "deleteBlock") continue;
|
|
2155
|
+
if (!isInsertedPPrMark(item.blockNode.attrs["pPrMark"])) continue;
|
|
2156
|
+
const at = view.state.doc.resolve(item.blockFrom);
|
|
2157
|
+
if (at.parent.maybeChild(at.index() + 1)?.type !== item.blockNode.type) continue;
|
|
2158
|
+
if (afterTables.get(item.blockFrom)?.has(item.blockTo)) tableAfterDeletedSplit.set(item.blockFrom, commentsAcrossBlockBoundary(view.state.doc, item.blockTo));
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
1412
2161
|
const executionOrder = executableResolved.toSorted((left, right) => {
|
|
2162
|
+
if (left.blockFrom === right.blockFrom && tableAfterDeletedSplit.has(left.blockFrom)) {
|
|
2163
|
+
if (left.operation.type === "deleteBlock" && right.operation.type === "insertTable" && right.from === left.blockTo) return -1;
|
|
2164
|
+
if (left.operation.type === "insertTable" && right.operation.type === "deleteBlock" && left.from === right.blockTo) return 1;
|
|
2165
|
+
}
|
|
2166
|
+
const leftAnnotates = ANNOTATION_OPERATION_TYPES.has(left.operation.type);
|
|
2167
|
+
if (leftAnnotates !== ANNOTATION_OPERATION_TYPES.has(right.operation.type)) return leftAnnotates ? -1 : 1;
|
|
1413
2168
|
const leftCellShape = left.tableCellMerge ?? left.tableCellSplit;
|
|
1414
2169
|
const rightCellShape = right.tableCellMerge ?? right.tableCellSplit;
|
|
1415
2170
|
if (!leftCellShape && rightCellShape) return -1;
|
|
@@ -1473,7 +2228,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1473
2228
|
suggestionId: insertionSuggestionId,
|
|
1474
2229
|
revisionSeed,
|
|
1475
2230
|
isPairedMove,
|
|
1476
|
-
numbering
|
|
2231
|
+
numbering,
|
|
2232
|
+
styleResolver
|
|
1477
2233
|
});
|
|
1478
2234
|
revisionSeed = built.nextRevisionId;
|
|
1479
2235
|
claimSharedRevisionIds(revisionSeed);
|
|
@@ -1499,6 +2255,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1499
2255
|
const commentMark = provisionalCommentId !== void 0 && commentType ? commentType.create({ commentId: provisionalCommentId }) : null;
|
|
1500
2256
|
const stepsBefore = tr.steps.length;
|
|
1501
2257
|
let appliedRevisionIds = [];
|
|
2258
|
+
let unifiedFormatting = false;
|
|
1502
2259
|
if (isSuggested && !SUGGESTED_SUPPORTED_OPERATION_TYPES.has(item.operation.type)) {
|
|
1503
2260
|
skipped.push({
|
|
1504
2261
|
id: item.operation.id,
|
|
@@ -1528,13 +2285,15 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1528
2285
|
const minimal = applyMinimalDirectReplacement({
|
|
1529
2286
|
tr,
|
|
1530
2287
|
item,
|
|
2288
|
+
noteReferences,
|
|
1531
2289
|
replacement: item.operation.replace,
|
|
1532
2290
|
commentMark,
|
|
1533
2291
|
replacementBackground,
|
|
1534
2292
|
...styleResolver !== void 0 ? { styleResolver } : {}
|
|
1535
2293
|
});
|
|
1536
2294
|
if (minimal !== null) {
|
|
1537
|
-
tr = minimal;
|
|
2295
|
+
tr = minimal.transaction;
|
|
2296
|
+
unifiedFormatting = minimal.unifiesFormatting;
|
|
1538
2297
|
break;
|
|
1539
2298
|
}
|
|
1540
2299
|
}
|
|
@@ -1545,6 +2304,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1545
2304
|
const tracked = applyMinimalTrackedReplacement({
|
|
1546
2305
|
tr,
|
|
1547
2306
|
item,
|
|
2307
|
+
noteReferences,
|
|
1548
2308
|
replacement: stripInlineEmphasisMarkers(item.operation.replace),
|
|
1549
2309
|
commentMark,
|
|
1550
2310
|
replacementBackground,
|
|
@@ -1568,6 +2328,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1568
2328
|
if (tracked) {
|
|
1569
2329
|
tr = tracked.transaction;
|
|
1570
2330
|
operationRevisionSeed = tracked.nextRevisionId;
|
|
2331
|
+
unifiedFormatting = tracked.unifiesFormatting;
|
|
1571
2332
|
appliedRevisionIds = [
|
|
1572
2333
|
revisionIdDelete,
|
|
1573
2334
|
revisionIdInsert,
|
|
@@ -1576,6 +2337,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1576
2337
|
break;
|
|
1577
2338
|
}
|
|
1578
2339
|
}
|
|
2340
|
+
if (rangeHoldsNoteReference(item)) {
|
|
2341
|
+
skipped.push({
|
|
2342
|
+
id: item.operation.id,
|
|
2343
|
+
reason: "protectedReference"
|
|
2344
|
+
});
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
1579
2347
|
const stepsBeforeBackgroundClear = tr.steps.length;
|
|
1580
2348
|
const backgroundResult = clearReplacementBackground({
|
|
1581
2349
|
tr,
|
|
@@ -1705,6 +2473,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1705
2473
|
const minimal = changesText && mode === "direct" ? applyMinimalDirectReplacement({
|
|
1706
2474
|
tr,
|
|
1707
2475
|
item,
|
|
2476
|
+
noteReferences,
|
|
1708
2477
|
replacement: item.operation.text,
|
|
1709
2478
|
commentMark,
|
|
1710
2479
|
replacementBackground,
|
|
@@ -1713,6 +2482,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1713
2482
|
const tracked = changesText && producesTrackedChanges ? applyMinimalTrackedReplacement({
|
|
1714
2483
|
tr,
|
|
1715
2484
|
item,
|
|
2485
|
+
noteReferences,
|
|
1716
2486
|
replacement: stripInlineEmphasisMarkers(item.operation.text),
|
|
1717
2487
|
commentMark,
|
|
1718
2488
|
replacementBackground,
|
|
@@ -1733,13 +2503,30 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1733
2503
|
});
|
|
1734
2504
|
continue;
|
|
1735
2505
|
}
|
|
1736
|
-
if (minimal !== null)
|
|
1737
|
-
|
|
2506
|
+
if (minimal !== null) {
|
|
2507
|
+
tr = minimal.transaction;
|
|
2508
|
+
unifiedFormatting = minimal.unifiesFormatting;
|
|
2509
|
+
} else if (tracked !== null) {
|
|
1738
2510
|
tr = tracked.transaction;
|
|
1739
2511
|
operationRevisionSeed = tracked.nextRevisionId;
|
|
2512
|
+
unifiedFormatting = tracked.unifiesFormatting;
|
|
1740
2513
|
clearedBackground = tracked.backgroundRevisionIds.length > 0;
|
|
1741
2514
|
backgroundRevisionIds = tracked.backgroundRevisionIds;
|
|
1742
2515
|
} else if (changesText) {
|
|
2516
|
+
if (rangeHoldsNoteReference(item)) {
|
|
2517
|
+
skipped.push({
|
|
2518
|
+
id: item.operation.id,
|
|
2519
|
+
reason: "protectedReference"
|
|
2520
|
+
});
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
if (buildCleanBlockText(item.blockNode, item.blockFrom).structuralBoundaries.length > 0) {
|
|
2524
|
+
skipped.push({
|
|
2525
|
+
id: item.operation.id,
|
|
2526
|
+
reason: "unsupportedBlock"
|
|
2527
|
+
});
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
1743
2530
|
const stepsBeforeBackgroundClear = tr.steps.length;
|
|
1744
2531
|
const backgroundResult = clearReplacementBackground({
|
|
1745
2532
|
tr,
|
|
@@ -1802,6 +2589,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1802
2589
|
}
|
|
1803
2590
|
case "insertAfterBlock":
|
|
1804
2591
|
case "insertBeforeBlock": {
|
|
2592
|
+
if (isSuggested && isPendingDeletion(tr.doc, item.blockFrom, item.blockNode, buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
2593
|
+
fieldResults: "text",
|
|
2594
|
+
noteReferences
|
|
2595
|
+
}).text)) {
|
|
2596
|
+
skipped.push({
|
|
2597
|
+
id: item.operation.id,
|
|
2598
|
+
reason: "pendingDeletion"
|
|
2599
|
+
});
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
1805
2602
|
const built = buildInsertedParagraphs({
|
|
1806
2603
|
item,
|
|
1807
2604
|
schema: view.state.schema,
|
|
@@ -1814,11 +2611,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1814
2611
|
suggestionId,
|
|
1815
2612
|
revisionSeed: operationRevisionSeed,
|
|
1816
2613
|
isPairedMove,
|
|
1817
|
-
numbering
|
|
2614
|
+
numbering,
|
|
2615
|
+
styleResolver
|
|
1818
2616
|
});
|
|
1819
2617
|
operationRevisionSeed = built.nextRevisionId;
|
|
1820
2618
|
if (built.revisionIds.length > 0) appliedRevisionIds = built.revisionIds;
|
|
1821
|
-
tr =
|
|
2619
|
+
tr = insertBlocksInsideComments({
|
|
2620
|
+
tr,
|
|
2621
|
+
at: item.from,
|
|
2622
|
+
blocks: built.nodes
|
|
2623
|
+
});
|
|
1822
2624
|
break;
|
|
1823
2625
|
}
|
|
1824
2626
|
case "insertSignatureTable": {
|
|
@@ -1871,7 +2673,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1871
2673
|
const template = tableTemplates?.get(item.operation.id);
|
|
1872
2674
|
const result = applyTableRowInsertion({
|
|
1873
2675
|
tr,
|
|
1874
|
-
insertion,
|
|
2676
|
+
insertion: mapTableRowInsertion(tr, insertion),
|
|
1875
2677
|
cellTexts: item.operation.cellTexts,
|
|
1876
2678
|
revision,
|
|
1877
2679
|
...template !== void 0 && { template }
|
|
@@ -1906,6 +2708,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1906
2708
|
cellTexts: item.operation.cellTexts,
|
|
1907
2709
|
revision
|
|
1908
2710
|
});
|
|
2711
|
+
if (result.type === "payloadDoesNotFit") {
|
|
2712
|
+
skipped.push({
|
|
2713
|
+
id: item.operation.id,
|
|
2714
|
+
reason: "payloadDoesNotFit",
|
|
2715
|
+
message: result.message
|
|
2716
|
+
});
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
1909
2719
|
if (result.type === "unsupported") {
|
|
1910
2720
|
skipped.push({
|
|
1911
2721
|
id: item.operation.id,
|
|
@@ -2061,12 +2871,32 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2061
2871
|
}
|
|
2062
2872
|
break;
|
|
2063
2873
|
}
|
|
2064
|
-
case "deleteBlock":
|
|
2874
|
+
case "deleteBlock": {
|
|
2875
|
+
if (isSuggested && (hasOrdinaryTextRevision(item.blockNode) || hasOrdinaryParagraphMarkRevision(item.blockNode))) {
|
|
2876
|
+
skipped.push({
|
|
2877
|
+
id: item.operation.id,
|
|
2878
|
+
reason: "unsupportedMode"
|
|
2879
|
+
});
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2065
2882
|
if (mode === "direct") {
|
|
2066
2883
|
const at = tr.doc.resolve(item.blockFrom);
|
|
2067
2884
|
tr = !paragraphEndsItsContainer(at, item.blockNode.type.name) || at.nodeBefore?.type.name === item.blockNode.type.name ? tr.delete(item.blockFrom, item.blockTo) : tr.delete(item.blockFrom + 1, item.blockTo - 1);
|
|
2068
2885
|
break;
|
|
2069
2886
|
}
|
|
2887
|
+
const retraction = isPairedMove(item.operation.moveId) ? null : pendingParagraphRetraction(tr.doc, item.blockFrom);
|
|
2888
|
+
if (retraction) {
|
|
2889
|
+
if (isSuggested && !isSuggestedParagraphInsert(item.blockNode)) {
|
|
2890
|
+
skipped.push({
|
|
2891
|
+
id: item.operation.id,
|
|
2892
|
+
reason: "unsupportedMode"
|
|
2893
|
+
});
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
tr = tr.delete(item.blockFrom, item.blockTo);
|
|
2897
|
+
if (retraction.clearBreakAt !== null) tr = tr.setNodeAttribute(retraction.clearBreakAt, "pPrMark", null);
|
|
2898
|
+
break;
|
|
2899
|
+
}
|
|
2070
2900
|
if (deletionType) {
|
|
2071
2901
|
const revisionId = operationRevisionSeed++;
|
|
2072
2902
|
const deletionMark = deletionType.create({
|
|
@@ -2079,6 +2909,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2079
2909
|
tr = tr.addMark(item.from, item.to, deletionMark);
|
|
2080
2910
|
const atomRanges = undeletedContentAtomRanges(tr, item.blockFrom);
|
|
2081
2911
|
for (const { from, to } of atomRanges) tr = tr.addMark(from, to, deletionMark);
|
|
2912
|
+
for (const anchorId of textBoxAnchorIdsBetween(tr.doc, tr.mapping.map(item.blockFrom), tr.mapping.map(item.blockTo))) deletedTextBoxAnchorIds.add(anchorId);
|
|
2082
2913
|
encloseWholeControls({
|
|
2083
2914
|
tr,
|
|
2084
2915
|
from: item.blockFrom,
|
|
@@ -2088,7 +2919,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2088
2919
|
const inlineRevisionApplied = item.from < item.to || atomRanges.length > 0;
|
|
2089
2920
|
appliedRevisionIds = inlineRevisionApplied ? [revisionId] : [];
|
|
2090
2921
|
const markPosition = tr.mapping.map(item.blockFrom);
|
|
2091
|
-
|
|
2922
|
+
const endsItsContainer = paragraphEndsItsContainer(tr.doc.resolve(markPosition), item.blockNode.type.name);
|
|
2923
|
+
const deleted = tr.doc.nodeAt(markPosition);
|
|
2924
|
+
const following = deleted && tr.doc.nodeAt(markPosition + deleted.nodeSize);
|
|
2925
|
+
if (deleted && following?.type === deleted.type && !isPairedMove(item.operation.moveId) && isInsertedPPrMark(deleted.attrs["pPrMark"])) joinAtParagraphMark({
|
|
2926
|
+
tr,
|
|
2927
|
+
paragraphPos: markPosition,
|
|
2928
|
+
paragraph: deleted,
|
|
2929
|
+
next: following,
|
|
2930
|
+
firstIsGoing: true
|
|
2931
|
+
});
|
|
2932
|
+
else if (!endsItsContainer && deleted?.attrs["pPrMark"] == null) {
|
|
2092
2933
|
const markRevisionId = operationRevisionSeed++;
|
|
2093
2934
|
tr = tr.setNodeAttribute(markPosition, "pPrMark", {
|
|
2094
2935
|
kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
|
|
@@ -2100,10 +2941,25 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2100
2941
|
}
|
|
2101
2942
|
});
|
|
2102
2943
|
appliedRevisionIds = inlineRevisionApplied ? [revisionId, markRevisionId] : [markRevisionId];
|
|
2944
|
+
} else if (endsItsContainer && !isPairedMove(item.operation.moveId)) deletedFinalParagraphs.push({
|
|
2945
|
+
operationId: item.operation.id,
|
|
2946
|
+
position: markPosition,
|
|
2947
|
+
mappedFrom: tr.mapping.maps.length,
|
|
2948
|
+
revisionExtras: trackedRevisionExtras
|
|
2949
|
+
});
|
|
2950
|
+
else if (deleted && !isPairedMove(item.operation.moveId) && isPlainDeletedPPrMark(deleted.attrs["pPrMark"]) && holdsOnlyDeletedContent(deleted)) {
|
|
2951
|
+
const finalPosition = deletedFinalParagraphAfter(tr.doc.resolve(markPosition), deleted.type.name);
|
|
2952
|
+
if (finalPosition !== null) deletedFinalParagraphs.push({
|
|
2953
|
+
operationId: item.operation.id,
|
|
2954
|
+
position: finalPosition,
|
|
2955
|
+
mappedFrom: tr.mapping.maps.length,
|
|
2956
|
+
revisionExtras: trackedRevisionExtras
|
|
2957
|
+
});
|
|
2103
2958
|
}
|
|
2104
2959
|
}
|
|
2105
2960
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
2106
2961
|
break;
|
|
2962
|
+
}
|
|
2107
2963
|
case "insertTable": {
|
|
2108
2964
|
const revision = {
|
|
2109
2965
|
revisionId: operationRevisionSeed,
|
|
@@ -2129,7 +2985,55 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2129
2985
|
continue;
|
|
2130
2986
|
}
|
|
2131
2987
|
if (producesTrackedChanges) appliedRevisionIds = [operationRevisionSeed++];
|
|
2132
|
-
|
|
2988
|
+
const carriedComments = tableAfterDeletedSplit.get(item.blockFrom);
|
|
2989
|
+
const usesRetractedSplit = carriedComments !== void 0 && item.from === item.blockTo;
|
|
2990
|
+
let at = usesRetractedSplit ? tr.mapping.map(item.blockFrom, -1) : item.from;
|
|
2991
|
+
let insertionComments = carriedComments;
|
|
2992
|
+
if (!usesRetractedSplit) {
|
|
2993
|
+
const boundary = tr.doc.resolve(at);
|
|
2994
|
+
const before = boundary.nodeBefore;
|
|
2995
|
+
const finalDeleted = deletedFinalParagraphAfter(boundary, "paragraph");
|
|
2996
|
+
const mapping = tr.mapping;
|
|
2997
|
+
const pendingIndex = finalDeleted === null ? -1 : deletedFinalParagraphs.findIndex(({ position, mappedFrom }) => mapping.slice(mappedFrom).map(position) === finalDeleted);
|
|
2998
|
+
const pending = pendingIndex < 0 ? void 0 : deletedFinalParagraphs.at(pendingIndex);
|
|
2999
|
+
if (before?.type.name === "paragraph" && finalDeleted !== null && (pending !== void 0 || isPlainDeletedPPrMark(before.attrs["pPrMark"]) && !holdsOnlyDeletedContent(before))) {
|
|
3000
|
+
insertionComments = commentsAcrossBlockBoundary(tr.doc, at);
|
|
3001
|
+
const final = tr.doc.nodeAt(finalDeleted);
|
|
3002
|
+
if (!final) panic("A deleted final paragraph vanished before table insertion", { finalDeleted });
|
|
3003
|
+
if (pending) {
|
|
3004
|
+
const receiptIndex = applied.findIndex(({ id }) => id === pending.operationId);
|
|
3005
|
+
const receipt = receiptIndex < 0 ? void 0 : applied.at(receiptIndex);
|
|
3006
|
+
if (receipt === void 0) panic("A deleted final paragraph lost its operation receipt", { operationId: pending.operationId });
|
|
3007
|
+
const revisionId = operationRevisionSeed++;
|
|
3008
|
+
tr.setNodeAttribute(finalDeleted, "pPrMark", {
|
|
3009
|
+
kind: "del",
|
|
3010
|
+
info: {
|
|
3011
|
+
id: revisionId,
|
|
3012
|
+
author,
|
|
3013
|
+
date,
|
|
3014
|
+
...pending.revisionExtras
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
3017
|
+
applied[receiptIndex] = {
|
|
3018
|
+
...receipt,
|
|
3019
|
+
revisionId: receipt.revisionId ?? revisionId,
|
|
3020
|
+
revisionIds: [...receipt.revisionIds ?? [], revisionId]
|
|
3021
|
+
};
|
|
3022
|
+
deletedFinalParagraphs.splice(pendingIndex, 1);
|
|
3023
|
+
deferredNoopFinalDeletions.delete(pending.operationId);
|
|
3024
|
+
} else {
|
|
3025
|
+
tr.setNodeAttribute(at - before.nodeSize, "pPrMark", null);
|
|
3026
|
+
tr.setNodeAttribute(finalDeleted, "pPrMark", before.attrs["pPrMark"]);
|
|
3027
|
+
}
|
|
3028
|
+
at = finalDeleted + final.nodeSize;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
tr = insertBlocksInsideComments({
|
|
3032
|
+
tr,
|
|
3033
|
+
at,
|
|
3034
|
+
blocks: table,
|
|
3035
|
+
...insertionComments !== void 0 && { carriedComments: insertionComments }
|
|
3036
|
+
});
|
|
2133
3037
|
markStructuralChange(tr);
|
|
2134
3038
|
break;
|
|
2135
3039
|
}
|
|
@@ -2173,10 +3077,21 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2173
3077
|
};
|
|
2174
3078
|
const rowPositions = [];
|
|
2175
3079
|
let rowOffset = livePosition + 1;
|
|
3080
|
+
let holdsPendingInsertedRow = false;
|
|
2176
3081
|
liveTable.forEach((row) => {
|
|
2177
|
-
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null)
|
|
3082
|
+
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null) {
|
|
3083
|
+
rowPositions.push(rowOffset);
|
|
3084
|
+
holdsPendingInsertedRow ||= row.attrs["trIns"] != null;
|
|
3085
|
+
}
|
|
2178
3086
|
rowOffset += row.nodeSize;
|
|
2179
3087
|
});
|
|
3088
|
+
if (holdsPendingInsertedRow) {
|
|
3089
|
+
skipped.push({
|
|
3090
|
+
id: item.operation.id,
|
|
3091
|
+
reason: "unsupportedBlock"
|
|
3092
|
+
});
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
2180
3095
|
if (rowPositions.length === 0) {
|
|
2181
3096
|
skipped.push({
|
|
2182
3097
|
id: item.operation.id,
|
|
@@ -2198,8 +3113,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2198
3113
|
break;
|
|
2199
3114
|
}
|
|
2200
3115
|
case "setBlockParagraphProperties": {
|
|
2201
|
-
const
|
|
2202
|
-
const liveBlock = tr.doc.nodeAt(
|
|
3116
|
+
const mapped = tr.mapping.mapResult(item.blockFrom);
|
|
3117
|
+
const liveBlock = mapped.deletedAcross ? null : tr.doc.nodeAt(mapped.pos);
|
|
3118
|
+
const paraId = item.blockNode.attrs["paraId"];
|
|
3119
|
+
if (!liveBlock || liveBlock.type !== item.blockNode.type || typeof paraId === "string" && liveBlock.attrs["paraId"] !== paraId) {
|
|
3120
|
+
skipped.push({
|
|
3121
|
+
id: item.operation.id,
|
|
3122
|
+
reason: "missingBlock"
|
|
3123
|
+
});
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
const blockPosition = mapped.pos;
|
|
2203
3127
|
const appliedProperties = applyBlockParagraphProperties({
|
|
2204
3128
|
tr,
|
|
2205
3129
|
position: blockPosition,
|
|
@@ -2226,9 +3150,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2226
3150
|
break;
|
|
2227
3151
|
}
|
|
2228
3152
|
case "splitBlock": {
|
|
3153
|
+
const splitsFromItsTextBoxes = textBoxAnchorIdsBetween(tr.doc, item.blockFrom, item.from).size > 0 && endOfParagraphWithItsTextBoxes(tr.doc, item.blockFrom) > item.blockTo;
|
|
2229
3154
|
if (mode === "direct") {
|
|
2230
3155
|
if (item.to > item.from) tr = tr.delete(item.from, item.to);
|
|
2231
3156
|
tr = tr.split(item.from);
|
|
3157
|
+
if (splitsFromItsTextBoxes) gatherTextBoxesAfterParagraph(tr, item.blockFrom);
|
|
3158
|
+
} else if (splitsFromItsTextBoxes) {
|
|
3159
|
+
skipped.push({
|
|
3160
|
+
id: item.operation.id,
|
|
3161
|
+
reason: "unsupportedBlock"
|
|
3162
|
+
});
|
|
3163
|
+
continue;
|
|
2232
3164
|
} else {
|
|
2233
3165
|
const revisionIdMark = operationRevisionSeed++;
|
|
2234
3166
|
const info = {
|
|
@@ -2288,15 +3220,49 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2288
3220
|
case "mergeBlockWithNext": {
|
|
2289
3221
|
const separator = item.operation.separator ?? "";
|
|
2290
3222
|
const insertAt = item.blockTo - 1;
|
|
3223
|
+
const second = tr.doc.nodeAt(item.blockTo);
|
|
3224
|
+
const spanningComments = commentsAcrossBlockBoundary(tr.doc, item.blockTo);
|
|
3225
|
+
const withSeparator = (transaction) => {
|
|
3226
|
+
const paragraph = transaction.doc.nodeAt(item.blockFrom);
|
|
3227
|
+
const sourceMarks = paragraph?.lastChild?.marks.filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name)) ?? [];
|
|
3228
|
+
const sourceNode = view.state.schema.text(separator, sourceMarks);
|
|
3229
|
+
const context = paragraph && paragraphRunStyleContext(paragraph, styleResolver);
|
|
3230
|
+
const marks = context ? reconcileRunFormattingMarks({
|
|
3231
|
+
authoredFormatting: readAuthoredRunFormatting({
|
|
3232
|
+
context,
|
|
3233
|
+
marks: sourceMarks,
|
|
3234
|
+
styleResolver
|
|
3235
|
+
}),
|
|
3236
|
+
context,
|
|
3237
|
+
node: sourceNode,
|
|
3238
|
+
styleResolver
|
|
3239
|
+
}) : sourceMarks;
|
|
3240
|
+
let next = transaction.replaceWith(insertAt, insertAt, view.state.schema.text(separator, marks));
|
|
3241
|
+
for (const comment of spanningComments) next = next.addMark(insertAt, insertAt + separator.length, comment);
|
|
3242
|
+
return next;
|
|
3243
|
+
};
|
|
2291
3244
|
if (mode === "direct") {
|
|
2292
|
-
if (separator.length > 0) tr = tr
|
|
3245
|
+
if (separator.length > 0) tr = withSeparator(tr);
|
|
3246
|
+
const nextPosition = item.blockTo + separator.length;
|
|
3247
|
+
const next = tr.doc.nodeAt(nextPosition);
|
|
3248
|
+
const paragraph = tr.doc.nodeAt(item.blockFrom);
|
|
3249
|
+
if (next && paragraph) tr = rebaseParagraphRuns({
|
|
3250
|
+
tr,
|
|
3251
|
+
position: nextPosition,
|
|
3252
|
+
previous: next,
|
|
3253
|
+
target: paragraph,
|
|
3254
|
+
styleResolver
|
|
3255
|
+
});
|
|
2293
3256
|
tr = tr.join(item.blockTo + separator.length);
|
|
3257
|
+
const secondSection = second ? sectionPropertiesOf(second) : null;
|
|
3258
|
+
const joined = tr.doc.nodeAt(item.blockFrom);
|
|
3259
|
+
if (joined && sectionPropertiesOf(joined) !== secondSection) tr = tr.setNodeAttribute(item.blockFrom, "_sectionProperties", secondSection);
|
|
2294
3260
|
} else {
|
|
2295
3261
|
const revisionIdMark = operationRevisionSeed++;
|
|
2296
3262
|
appliedRevisionIds = [revisionIdMark];
|
|
2297
3263
|
if (separator.length > 0 && insertionType) {
|
|
2298
3264
|
const revisionIdSeparator = operationRevisionSeed++;
|
|
2299
|
-
tr = tr
|
|
3265
|
+
tr = withSeparator(tr);
|
|
2300
3266
|
tr = tr.addMark(insertAt, insertAt + separator.length, insertionType.create({
|
|
2301
3267
|
revisionId: revisionIdSeparator,
|
|
2302
3268
|
author,
|
|
@@ -2305,7 +3271,31 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2305
3271
|
}));
|
|
2306
3272
|
appliedRevisionIds.push(revisionIdSeparator);
|
|
2307
3273
|
}
|
|
2308
|
-
|
|
3274
|
+
const paragraph = tr.doc.nodeAt(item.blockFrom);
|
|
3275
|
+
const joinPos = item.blockTo + separator.length;
|
|
3276
|
+
const next = tr.doc.nodeAt(joinPos);
|
|
3277
|
+
if (!paragraph || !next) panic("A resolved merge lost its paragraphs");
|
|
3278
|
+
if (isInsertedPPrMark(paragraph.attrs["pPrMark"])) {
|
|
3279
|
+
joinAtParagraphMark({
|
|
3280
|
+
tr,
|
|
3281
|
+
paragraphPos: item.blockFrom,
|
|
3282
|
+
paragraph,
|
|
3283
|
+
next
|
|
3284
|
+
});
|
|
3285
|
+
const previousFormatting = paragraphPropertiesSnapshot(next);
|
|
3286
|
+
const existing = expectParagraphAttrs(paragraph)._propertyChanges;
|
|
3287
|
+
if (holdsOnlyInsertedContent(paragraph) && !hasSerializableParagraphPropertyChange(existing) && JSON.stringify(paragraphPropertiesSnapshot(paragraph)) !== JSON.stringify(previousFormatting)) tr = tr.setNodeAttribute(item.blockFrom, "_propertyChanges", [...Array.isArray(existing) ? existing : [], {
|
|
3288
|
+
type: "paragraphPropertyChange",
|
|
3289
|
+
info: {
|
|
3290
|
+
id: revisionIdMark,
|
|
3291
|
+
author,
|
|
3292
|
+
date,
|
|
3293
|
+
...trackedRevisionExtras
|
|
3294
|
+
},
|
|
3295
|
+
previousFormatting
|
|
3296
|
+
}]);
|
|
3297
|
+
else appliedRevisionIds = appliedRevisionIds.filter((id) => id !== revisionIdMark);
|
|
3298
|
+
} else tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
2309
3299
|
kind: "del",
|
|
2310
3300
|
info: {
|
|
2311
3301
|
id: revisionIdMark,
|
|
@@ -2343,11 +3333,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2343
3333
|
default: break;
|
|
2344
3334
|
}
|
|
2345
3335
|
if (tr.steps.length === stepsBefore) {
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
3336
|
+
if (!deletedFinalParagraphs.some(({ operationId }) => operationId === item.operation.id)) {
|
|
3337
|
+
skipped.push({
|
|
3338
|
+
id: item.operation.id,
|
|
3339
|
+
reason: "noopOperation"
|
|
3340
|
+
});
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
deferredNoopFinalDeletions.add(item.operation.id);
|
|
2351
3344
|
}
|
|
2352
3345
|
let committedCommentId;
|
|
2353
3346
|
if (item.commentText !== void 0) {
|
|
@@ -2366,6 +3359,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2366
3359
|
}
|
|
2367
3360
|
revisionSeed = operationRevisionSeed;
|
|
2368
3361
|
claimSharedRevisionIds(revisionSeed);
|
|
3362
|
+
if (unifiedFormatting) normalizations.push({
|
|
3363
|
+
id: item.operation.id,
|
|
3364
|
+
code: "uniformReplacementFormatting"
|
|
3365
|
+
});
|
|
2369
3366
|
applied.push({
|
|
2370
3367
|
id: item.operation.id,
|
|
2371
3368
|
...committedCommentId !== void 0 && { commentId: committedCommentId },
|
|
@@ -2376,9 +3373,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2376
3373
|
...suggestionId !== null && { suggestionId }
|
|
2377
3374
|
});
|
|
2378
3375
|
}
|
|
2379
|
-
if (tr.docChanged) {
|
|
3376
|
+
if (tr.docChanged || deferredNoopFinalDeletions.size > 0) {
|
|
2380
3377
|
const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
|
|
2381
|
-
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds);
|
|
3378
|
+
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds, deletedTextBoxAnchorIds);
|
|
3379
|
+
if (deleteTextBoxesAnchoredAt(tr, droppedTextBoxAnchorIds(view.state.doc, tr.doc))) markStructuralChange(tr);
|
|
2382
3380
|
continueSharedRevisionIds();
|
|
2383
3381
|
const rotated = withRotatedAddedFinalBreaks({
|
|
2384
3382
|
tr,
|
|
@@ -2390,6 +3388,25 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2390
3388
|
});
|
|
2391
3389
|
tr = rotated.transaction;
|
|
2392
3390
|
revisionSeed = rotated.nextRevisionId;
|
|
3391
|
+
const retired = withRetiredFinalParagraphs({
|
|
3392
|
+
tr,
|
|
3393
|
+
deletions: deletedFinalParagraphs,
|
|
3394
|
+
appliedIds: new Set(applied.map(({ id }) => id)),
|
|
3395
|
+
batchRevisionIds,
|
|
3396
|
+
revisionSeed,
|
|
3397
|
+
author,
|
|
3398
|
+
date
|
|
3399
|
+
});
|
|
3400
|
+
revisionSeed = retired.nextRevisionId;
|
|
3401
|
+
for (const operationId of deferredNoopFinalDeletions) {
|
|
3402
|
+
if (retired.resolvedOperationIds.has(operationId)) continue;
|
|
3403
|
+
const receiptIndex = applied.findIndex(({ id }) => id === operationId);
|
|
3404
|
+
if (receiptIndex >= 0) applied.splice(receiptIndex, 1);
|
|
3405
|
+
skipped.push({
|
|
3406
|
+
id: operationId,
|
|
3407
|
+
reason: "noopOperation"
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
2393
3410
|
const receiptIndexByRevisionId = /* @__PURE__ */ new Map();
|
|
2394
3411
|
for (const [receiptIndex, receipt] of applied.entries()) for (const revisionId of receipt.revisionIds ?? []) receiptIndexByRevisionId.set(revisionId, receiptIndex);
|
|
2395
3412
|
for (const { ownerRevisionId, revisionId } of rotated.synthesizedRevisions) {
|
|
@@ -2404,9 +3421,35 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2404
3421
|
revisionIds: [...receipt.revisionIds, revisionId]
|
|
2405
3422
|
};
|
|
2406
3423
|
}
|
|
3424
|
+
for (const { operationId, revisionId } of retired.addedRevisions) {
|
|
3425
|
+
const receiptIndex = applied.findIndex(({ id }) => id === operationId);
|
|
3426
|
+
const receipt = applied.at(receiptIndex);
|
|
3427
|
+
if (receiptIndex < 0 || !receipt) panic("A retired final-mark revision lost its operation receipt", {
|
|
3428
|
+
operationId,
|
|
3429
|
+
revisionId
|
|
3430
|
+
});
|
|
3431
|
+
const revisionIds = [...receipt.revisionIds ?? [], revisionId];
|
|
3432
|
+
applied[receiptIndex] = {
|
|
3433
|
+
...receipt,
|
|
3434
|
+
revisionId: receipt.revisionId ?? revisionId,
|
|
3435
|
+
revisionIds
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
const retractedRevisionIds = /* @__PURE__ */ new Set([...rotated.retractedRevisionIds, ...retired.retractedRevisionIds]);
|
|
3439
|
+
if (retractedRevisionIds.size > 0) for (const [receiptIndex, receipt] of applied.entries()) {
|
|
3440
|
+
const revisionIds = receipt.revisionIds?.filter((id) => !retractedRevisionIds.has(id));
|
|
3441
|
+
if (revisionIds === void 0) continue;
|
|
3442
|
+
const { revisionId: _oldRevisionId, revisionIds: _oldRevisionIds, ...withoutRevisions } = receipt;
|
|
3443
|
+
const firstRevisionId = revisionIds.at(0);
|
|
3444
|
+
applied[receiptIndex] = firstRevisionId === void 0 ? withoutRevisions : {
|
|
3445
|
+
...withoutRevisions,
|
|
3446
|
+
revisionId: firstRevisionId,
|
|
3447
|
+
revisionIds
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
2407
3450
|
claimSharedRevisionIds(revisionSeed);
|
|
2408
3451
|
if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
|
|
2409
|
-
view.dispatch(tr);
|
|
3452
|
+
if (tr.docChanged) view.dispatch(tr);
|
|
2410
3453
|
}
|
|
2411
3454
|
return {
|
|
2412
3455
|
applied,
|
|
@@ -2416,9 +3459,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2416
3459
|
};
|
|
2417
3460
|
};
|
|
2418
3461
|
const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
|
|
2419
|
-
const previewFolioAIEditOperations = (options) =>
|
|
3462
|
+
const previewFolioAIEditOperations = (options) => previewFolioAIEditOperationsWithResult(options).outcome;
|
|
3463
|
+
/**
|
|
3464
|
+
* A preview together with the document it would leave and the provisional
|
|
3465
|
+
* comment ids it placed, for a caller that checks the result before reporting
|
|
3466
|
+
* it. Internal: not re-exported from the package entry points.
|
|
3467
|
+
*/
|
|
3468
|
+
const previewFolioAIEditOperationsWithResult = (options) => {
|
|
2420
3469
|
const { view, createCommentId, ...applyOptions } = options;
|
|
2421
3470
|
let previewCommentId = -1;
|
|
3471
|
+
const commentIds = [];
|
|
2422
3472
|
const previewView = {
|
|
2423
3473
|
state: view.state,
|
|
2424
3474
|
dispatch: (transaction) => {
|
|
@@ -2428,15 +3478,23 @@ const previewFolioAIEditOperations = (options) => {
|
|
|
2428
3478
|
const result = applyFolioAIEditOperationsInternal({
|
|
2429
3479
|
...applyOptions,
|
|
2430
3480
|
view: previewView,
|
|
2431
|
-
...createCommentId !== void 0 && { createCommentId: () =>
|
|
3481
|
+
...createCommentId !== void 0 && { createCommentId: () => {
|
|
3482
|
+
const id = previewCommentId--;
|
|
3483
|
+
commentIds.push(id);
|
|
3484
|
+
return id;
|
|
3485
|
+
} },
|
|
2432
3486
|
revisionIdSeed: -1e9,
|
|
2433
3487
|
wordDiffMode: "coarse"
|
|
2434
3488
|
});
|
|
2435
3489
|
return {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
3490
|
+
outcome: {
|
|
3491
|
+
applied: result.applied.map(({ id }) => ({ id })),
|
|
3492
|
+
skipped: result.skipped,
|
|
3493
|
+
...result.normalizations !== void 0 && { normalizations: result.normalizations },
|
|
3494
|
+
nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
|
|
3495
|
+
},
|
|
3496
|
+
doc: previewView.state.doc,
|
|
3497
|
+
commentIds
|
|
2440
3498
|
};
|
|
2441
3499
|
};
|
|
2442
3500
|
const CLEAN_TEXT_CONTROL_PATTERN = /[\t\n]/gu;
|
|
@@ -2460,9 +3518,10 @@ const cleanTextInlineNodes = ({ schema, text, marks = [] }) => {
|
|
|
2460
3518
|
if (trailing.length > 0) nodes.push(schema.text(trailing, marks));
|
|
2461
3519
|
return nodes;
|
|
2462
3520
|
};
|
|
2463
|
-
const insertCleanText = ({ tr, from, to, text, standsInFor }) => {
|
|
3521
|
+
const insertCleanText = ({ tr, from, to, text, standsInFor, keepsReplacedText = false }) => {
|
|
2464
3522
|
const annotations = surveyReplacedAnnotations(tr.doc, from, to);
|
|
2465
|
-
const
|
|
3523
|
+
const carried = standsInFor ? surveyReplacedAnnotations(tr.doc, standsInFor.from, standsInFor.to).carried : [];
|
|
3524
|
+
const replaced = keepsReplacedText ? commentsForTrackedInsertion(tr.doc, from, [carried])[0] ?? [] : carried;
|
|
2466
3525
|
if (!hasReplacedAnnotations(annotations) && replaced.length === 0 && !hasCleanTextControls(text)) return {
|
|
2467
3526
|
transaction: tr.insertText(text, from, to),
|
|
2468
3527
|
start: from,
|
|
@@ -2539,7 +3598,8 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
2539
3598
|
standsInFor: {
|
|
2540
3599
|
from: item.from,
|
|
2541
3600
|
to: item.to
|
|
2542
|
-
}
|
|
3601
|
+
},
|
|
3602
|
+
keepsReplacedText: true
|
|
2543
3603
|
});
|
|
2544
3604
|
nextTr = inserted.transaction;
|
|
2545
3605
|
nextTr = nextTr.addMark(inserted.start, inserted.end, insertionType.create(insAttrs));
|
|
@@ -2551,6 +3611,17 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
2551
3611
|
}
|
|
2552
3612
|
return nextTr;
|
|
2553
3613
|
};
|
|
3614
|
+
/** Whether the resolved range covers a footnote or endnote reference. */
|
|
3615
|
+
const rangeHoldsNoteReference = ({ blockNode, blockFrom, from, to }) => {
|
|
3616
|
+
let holds = false;
|
|
3617
|
+
blockNode.descendants((node, pos) => {
|
|
3618
|
+
if (holds) return false;
|
|
3619
|
+
const start = blockFrom + 1 + pos;
|
|
3620
|
+
if (noteReferenceOf(node) !== null && start < to && start + node.nodeSize > from) holds = true;
|
|
3621
|
+
return !holds;
|
|
3622
|
+
});
|
|
3623
|
+
return holds;
|
|
3624
|
+
};
|
|
2554
3625
|
/**
|
|
2555
3626
|
* The clean-text span a text replacement matched: where it starts in the
|
|
2556
3627
|
* block's clean text and what it says. `null` when the operation does not
|
|
@@ -2592,17 +3663,27 @@ const isCarriedMark = (mark) => NON_INCLUSIVE_MARK_DISPOSITION[mark.type.name] =
|
|
|
2592
3663
|
* document character by character, or the changes do not produce the
|
|
2593
3664
|
* replacement.
|
|
2594
3665
|
*/
|
|
2595
|
-
const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
2596
|
-
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom
|
|
3666
|
+
const planDocumentReplacement = ({ doc, item, replacement, noteReferences, planChanges, keepFieldsOf }) => {
|
|
3667
|
+
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
3668
|
+
fieldResults: "text",
|
|
3669
|
+
noteReferences
|
|
3670
|
+
});
|
|
2597
3671
|
const span = replacedCleanSpan(item, cleanBlock);
|
|
2598
3672
|
if (span === null) return null;
|
|
2599
3673
|
const spanStart = span.start;
|
|
2600
3674
|
const spanEnd = span.start + span.text.length;
|
|
2601
|
-
const
|
|
3675
|
+
const noteReferenceSpans = noteReferenceSpansWithin(cleanBlock, spanStart, spanEnd);
|
|
3676
|
+
const editsReferences = !(segmentsAroundNoteReferences(span.text, replacement, noteReferenceSpans) !== null) && noteReferenceEditsAllowed();
|
|
3677
|
+
const atomicSpans = cleanBlock.structuralBoundaries.flatMap((boundary) => boundary.type === "field" || editsReferences && boundary.type === "noteReference" ? [{
|
|
2602
3678
|
offset: boundary.offset - spanStart,
|
|
2603
3679
|
length: boundary.length
|
|
2604
3680
|
}] : []);
|
|
2605
|
-
const
|
|
3681
|
+
const planPieces = (plan) => editsReferences ? [...plan(span.text, replacement)] : planChangesAroundNoteReferences(span.text, replacement, noteReferenceSpans, plan);
|
|
3682
|
+
const pieceChanges = planPieces(planChanges);
|
|
3683
|
+
if (pieceChanges === null) return null;
|
|
3684
|
+
const widened = widenChangesToAtomicSpans(span.text, pieceChanges, atomicSpans);
|
|
3685
|
+
const keptPieces = keepFieldsOf === void 0 ? null : planPieces(keepFieldsOf);
|
|
3686
|
+
const changes = keptPieces === null ? widened : keepAtomicSpans(span.text, replacement, widened, widenChangesToAtomicSpans(span.text, keptPieces, atomicSpans), atomicSpans);
|
|
2606
3687
|
if (applyTextChanges(span.text, changes) !== replacement) return null;
|
|
2607
3688
|
const unitAt = (index) => {
|
|
2608
3689
|
const from = cleanBlock.offsets[index];
|
|
@@ -2610,7 +3691,7 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2610
3691
|
if (from === void 0 || !node) return null;
|
|
2611
3692
|
return {
|
|
2612
3693
|
from,
|
|
2613
|
-
to: node.isText ? from +
|
|
3694
|
+
to: !node.isText || noteReferenceOf(node) !== null ? from + node.nodeSize : from + 1
|
|
2614
3695
|
};
|
|
2615
3696
|
};
|
|
2616
3697
|
const matchedFrom = item.from;
|
|
@@ -2730,6 +3811,72 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2730
3811
|
replacementStyleSource
|
|
2731
3812
|
};
|
|
2732
3813
|
};
|
|
3814
|
+
/** The marks that present a character: its formatting, and the links and comments over it. */
|
|
3815
|
+
const presentationMarks = (marks) => marks.filter((mark) => isFormattingMark(mark) || isCarriedMark(mark));
|
|
3816
|
+
/**
|
|
3817
|
+
* Whether a change of `plan` replaces characters that do not all present
|
|
3818
|
+
* alike. Its new text takes one formatting, the first replaced character's,
|
|
3819
|
+
* and every link and comment over the stretch, so the others' formatting ends
|
|
3820
|
+
* where the replaced text did. That is the rule for every mode, and the
|
|
3821
|
+
* receipt says it was applied (`uniformReplacementFormatting`).
|
|
3822
|
+
*/
|
|
3823
|
+
const replacementUnifiesFormatting = (doc, plan) => plan.planned.some(({ change, pieces }) => {
|
|
3824
|
+
if (pieces.length === 0 || change.text.length === 0) return false;
|
|
3825
|
+
let first = null;
|
|
3826
|
+
for (let index = change.start; index < change.end; index++) {
|
|
3827
|
+
const unit = plan.unitAt(plan.spanStart + index);
|
|
3828
|
+
const marks = presentationMarks((unit ? doc.nodeAt(unit.from)?.marks : null) ?? Mark.none);
|
|
3829
|
+
if (first === null) first = marks;
|
|
3830
|
+
else if (!Mark.sameSet(first, marks)) return true;
|
|
3831
|
+
}
|
|
3832
|
+
return false;
|
|
3833
|
+
});
|
|
3834
|
+
/**
|
|
3835
|
+
* The marks each UTF-16 unit of `replacement` carries once a direct
|
|
3836
|
+
* replacement has written it (see {@link applyMinimalDirectReplacement}): a
|
|
3837
|
+
* character the direct plan keeps keeps its own, and one it writes takes the
|
|
3838
|
+
* marks that plan gives its change. Background cleared from a touched stretch
|
|
3839
|
+
* is left off either way.
|
|
3840
|
+
*
|
|
3841
|
+
* A tracked replacement writes its insertions with these marks, so accepting
|
|
3842
|
+
* it leaves exactly the text and formatting the direct edit writes, although
|
|
3843
|
+
* its redline cuts the text into coarser, readable changes than the direct
|
|
3844
|
+
* edit does.
|
|
3845
|
+
*/
|
|
3846
|
+
const directReplacementMarks = ({ doc, plan, replacementBackground }) => {
|
|
3847
|
+
const clearsBackground = replacementBackground === "clear";
|
|
3848
|
+
const touched = clearsBackground ? touchedBackgroundIndices({
|
|
3849
|
+
doc,
|
|
3850
|
+
plan
|
|
3851
|
+
}) : /* @__PURE__ */ new Set();
|
|
3852
|
+
const withoutBackground = (marks) => marks.filter((mark) => !BACKGROUND_MARK_NAMES.has(mark.type.name));
|
|
3853
|
+
const allocated = [];
|
|
3854
|
+
let cursor = 0;
|
|
3855
|
+
const keepUntil = (end) => {
|
|
3856
|
+
for (; cursor < end; cursor++) {
|
|
3857
|
+
const index = plan.spanStart + cursor;
|
|
3858
|
+
const unit = plan.unitAt(index);
|
|
3859
|
+
const marks = (unit ? doc.nodeAt(unit.from)?.marks : null) ?? Mark.none;
|
|
3860
|
+
allocated.push(touched.has(index) ? withoutBackground(marks) : marks);
|
|
3861
|
+
}
|
|
3862
|
+
};
|
|
3863
|
+
for (const { change, pieces, insertion } of plan.planned) {
|
|
3864
|
+
keepUntil(change.start);
|
|
3865
|
+
cursor = change.end;
|
|
3866
|
+
let marks;
|
|
3867
|
+
if (insertion !== void 0) marks = insertion.marks(doc);
|
|
3868
|
+
else {
|
|
3869
|
+
const first = pieces.at(0);
|
|
3870
|
+
const last = pieces.at(-1);
|
|
3871
|
+
if (first === void 0 || last === void 0) panic("A planned replacement lost the characters it removes");
|
|
3872
|
+
marks = addCarriedMarks(inheritedReplacementMarks(doc, first.from, first.to), surveyReplacedAnnotations(doc, first.from, last.to).carried);
|
|
3873
|
+
}
|
|
3874
|
+
const written = clearsBackground ? withoutBackground(marks) : marks;
|
|
3875
|
+
for (let offset = 0; offset < change.text.length; offset++) allocated.push(written);
|
|
3876
|
+
}
|
|
3877
|
+
keepUntil(plan.spanEnd - plan.spanStart);
|
|
3878
|
+
return allocated;
|
|
3879
|
+
};
|
|
2733
3880
|
/**
|
|
2734
3881
|
* Apply a direct-mode text replacement as the difference between the matched
|
|
2735
3882
|
* text and its replacement; `minimal-replacement.ts` states the contract.
|
|
@@ -2738,15 +3885,17 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2738
3885
|
* cannot be mapped onto the document character by character. The caller then
|
|
2739
3886
|
* replaces the matched span whole, as it always did.
|
|
2740
3887
|
*/
|
|
2741
|
-
const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, replacementBackground, styleResolver }) => {
|
|
3888
|
+
const applyMinimalDirectReplacement = ({ tr, item, noteReferences, replacement, commentMark, replacementBackground, styleResolver }) => {
|
|
2742
3889
|
const plan = planDocumentReplacement({
|
|
2743
3890
|
doc: tr.doc,
|
|
2744
3891
|
item,
|
|
2745
3892
|
replacement,
|
|
2746
|
-
|
|
3893
|
+
noteReferences,
|
|
3894
|
+
planChanges: planTextChanges
|
|
2747
3895
|
});
|
|
2748
3896
|
if (plan === null || plan.changes.length === 0) return null;
|
|
2749
3897
|
const { planned, matchedFrom, matchedTo } = plan;
|
|
3898
|
+
const unifiesFormatting = replacementUnifiesFormatting(tr.doc, plan);
|
|
2750
3899
|
let nextTr = tr;
|
|
2751
3900
|
if (replacementBackground === "clear") nextTr = clearTouchedBackground({
|
|
2752
3901
|
tr: nextTr,
|
|
@@ -2810,7 +3959,10 @@ const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, rep
|
|
|
2810
3959
|
}
|
|
2811
3960
|
if (to > from) nextTr = nextTr.addMark(from, to, commentMark);
|
|
2812
3961
|
}
|
|
2813
|
-
return
|
|
3962
|
+
return {
|
|
3963
|
+
transaction: nextTr,
|
|
3964
|
+
unifiesFormatting
|
|
3965
|
+
};
|
|
2814
3966
|
};
|
|
2815
3967
|
/**
|
|
2816
3968
|
* Apply a tracked-changes or suggested text replacement as its redline: each
|
|
@@ -2825,7 +3977,7 @@ const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, rep
|
|
|
2825
3977
|
* cannot be mapped onto the document character by character; the caller then
|
|
2826
3978
|
* marks the whole match as replaced, as it always did.
|
|
2827
3979
|
*/
|
|
2828
|
-
const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, replacementBackground, styleResolver, diffText, revisionIdDelete, revisionIdInsert, revisionIdBackgroundSeed, author, date, initials, suggestionId }) => {
|
|
3980
|
+
const applyMinimalTrackedReplacement = ({ tr, item, noteReferences, replacement, commentMark, replacementBackground, styleResolver, diffText, revisionIdDelete, revisionIdInsert, revisionIdBackgroundSeed, author, date, initials, suggestionId }) => {
|
|
2829
3981
|
const schema = tr.doc.type.schema;
|
|
2830
3982
|
const insertionType = schema.marks["insertion"];
|
|
2831
3983
|
const deletionType = schema.marks["deletion"];
|
|
@@ -2834,9 +3986,24 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2834
3986
|
doc: tr.doc,
|
|
2835
3987
|
item,
|
|
2836
3988
|
replacement,
|
|
2837
|
-
|
|
3989
|
+
noteReferences,
|
|
3990
|
+
planChanges: (source, target) => changesFromSegments(diffText(source, target)),
|
|
3991
|
+
keepFieldsOf: planTextChanges
|
|
2838
3992
|
});
|
|
2839
3993
|
if (plan === null) return null;
|
|
3994
|
+
const directPlan = planDocumentReplacement({
|
|
3995
|
+
doc: tr.doc,
|
|
3996
|
+
item,
|
|
3997
|
+
replacement,
|
|
3998
|
+
noteReferences,
|
|
3999
|
+
planChanges: planTextChanges
|
|
4000
|
+
});
|
|
4001
|
+
const allocatedMarks = directPlan === null ? null : directReplacementMarks({
|
|
4002
|
+
doc: tr.doc,
|
|
4003
|
+
plan: directPlan,
|
|
4004
|
+
replacementBackground
|
|
4005
|
+
});
|
|
4006
|
+
const unifiesFormatting = directPlan === null ? false : replacementUnifiesFormatting(tr.doc, directPlan);
|
|
2840
4007
|
let nextTr = tr;
|
|
2841
4008
|
let backgroundRevisionIds = [];
|
|
2842
4009
|
let nextRevisionId = revisionIdBackgroundSeed;
|
|
@@ -2886,10 +4053,39 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2886
4053
|
...revisionAttrs
|
|
2887
4054
|
});
|
|
2888
4055
|
const insertedMarks = (marks) => marks.filter((mark) => !REVISION_MARK_NAMES.has(mark.type.name) && mark.type.name !== "runPropertyChange" && !(clearsBackground && BACKGROUND_MARK_NAMES.has(mark.type.name)));
|
|
2889
|
-
|
|
4056
|
+
const replacementStarts = [];
|
|
4057
|
+
let replacementShift = 0;
|
|
4058
|
+
for (const { change } of plan.planned) {
|
|
4059
|
+
replacementStarts.push(change.start + replacementShift);
|
|
4060
|
+
replacementShift += change.text.length - (change.end - change.start);
|
|
4061
|
+
}
|
|
4062
|
+
const insertedNodes = (text, replacementStart, fallback) => {
|
|
4063
|
+
if (allocatedMarks?.length !== replacement.length) return cleanTextInlineNodes({
|
|
4064
|
+
schema,
|
|
4065
|
+
text,
|
|
4066
|
+
marks: insertedMarks(fallback)
|
|
4067
|
+
});
|
|
4068
|
+
const marksAt = (offset) => allocatedMarks[replacementStart + offset] ?? fallback;
|
|
4069
|
+
const nodes = [];
|
|
4070
|
+
let groupStart = 0;
|
|
4071
|
+
for (let offset = 1; offset <= text.length; offset++) {
|
|
4072
|
+
if (offset < text.length && Mark.sameSet(marksAt(offset), marksAt(groupStart))) continue;
|
|
4073
|
+
nodes.push(...cleanTextInlineNodes({
|
|
4074
|
+
schema,
|
|
4075
|
+
text: text.slice(groupStart, offset),
|
|
4076
|
+
marks: insertedMarks(marksAt(groupStart))
|
|
4077
|
+
}));
|
|
4078
|
+
groupStart = offset;
|
|
4079
|
+
}
|
|
4080
|
+
return nodes;
|
|
4081
|
+
};
|
|
4082
|
+
for (let plannedIndex = plan.planned.length - 1; plannedIndex >= 0; plannedIndex--) {
|
|
4083
|
+
const change = plan.planned[plannedIndex];
|
|
4084
|
+
if (change === void 0) panic("A planned replacement change went missing", { plannedIndex });
|
|
2890
4085
|
const doc = nextTr.doc;
|
|
2891
4086
|
let at;
|
|
2892
4087
|
let marks;
|
|
4088
|
+
let removedFrom = null;
|
|
2893
4089
|
if (change.insertion !== void 0) {
|
|
2894
4090
|
at = change.insertion.at;
|
|
2895
4091
|
marks = change.insertion.marks(doc);
|
|
@@ -2899,6 +4095,7 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2899
4095
|
const styleSource = plan.replacementStyleSource(change.change);
|
|
2900
4096
|
if (first === void 0 || last === void 0 || styleSource === null) panic("A planned replacement lost the characters it removes");
|
|
2901
4097
|
at = last.to;
|
|
4098
|
+
removedFrom = first.from;
|
|
2902
4099
|
marks = addCarriedMarks(inheritedReplacementMarks(doc, styleSource.from, styleSource.to), surveyReplacedAnnotations(doc, first.from, last.to).carried);
|
|
2903
4100
|
for (const piece of change.pieces) {
|
|
2904
4101
|
nextTr = nextTr.addMark(piece.from, piece.to, deletion);
|
|
@@ -2906,21 +4103,36 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2906
4103
|
}
|
|
2907
4104
|
}
|
|
2908
4105
|
if (change.text.length === 0) continue;
|
|
2909
|
-
const
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
4106
|
+
const pieces = insertedNodes(change.text, replacementStarts[plannedIndex] ?? 0, marks);
|
|
4107
|
+
const positions = removedFrom === null ? pieces.map(() => at) : placeTrackedInsertion(nextTr.doc, removedFrom, at, pieces.map((node) => node.marks));
|
|
4108
|
+
const runs = [];
|
|
4109
|
+
for (const [index, piece] of pieces.entries()) {
|
|
4110
|
+
const position = positions[index] ?? at;
|
|
4111
|
+
const run = runs.at(-1);
|
|
4112
|
+
if (run?.at === position) run.pieces.push(piece);
|
|
4113
|
+
else runs.push({
|
|
4114
|
+
at: position,
|
|
4115
|
+
pieces: [piece]
|
|
4116
|
+
});
|
|
4117
|
+
}
|
|
4118
|
+
for (const run of runs.toReversed()) {
|
|
4119
|
+
let nodes = run.pieces;
|
|
4120
|
+
if (removedFrom === null) {
|
|
4121
|
+
const pieceMarks = commentsForTrackedInsertion(nextTr.doc, run.at, run.pieces.map((node) => node.marks));
|
|
4122
|
+
nodes = run.pieces.map((node, index) => node.mark(pieceMarks[index] ?? node.marks));
|
|
4123
|
+
}
|
|
4124
|
+
nextTr = nextTr.insert(run.at, nodes);
|
|
4125
|
+
const end = run.at + Fragment.fromArray(nodes).size;
|
|
4126
|
+
nextTr = nextTr.addMark(run.at, end, insertion);
|
|
4127
|
+
if (commentMark) nextTr = nextTr.addMark(run.at, end, commentMark);
|
|
4128
|
+
}
|
|
2918
4129
|
}
|
|
2919
4130
|
return {
|
|
2920
4131
|
type: "applied",
|
|
2921
4132
|
transaction: nextTr,
|
|
2922
4133
|
backgroundRevisionIds,
|
|
2923
|
-
nextRevisionId
|
|
4134
|
+
nextRevisionId,
|
|
4135
|
+
unifiesFormatting
|
|
2924
4136
|
};
|
|
2925
4137
|
};
|
|
2926
4138
|
/**
|
|
@@ -2980,7 +4192,7 @@ const clearTouchedBackground = ({ tr, ranges, styleResolver }) => {
|
|
|
2980
4192
|
});
|
|
2981
4193
|
return nextTr;
|
|
2982
4194
|
};
|
|
2983
|
-
const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId }) => {
|
|
4195
|
+
const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId, noteReferences }) => {
|
|
2984
4196
|
const anchor = snapshot.anchors[blockId];
|
|
2985
4197
|
if (!anchor) return {
|
|
2986
4198
|
type: "skip",
|
|
@@ -3006,7 +4218,10 @@ const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId
|
|
|
3006
4218
|
type: "skip",
|
|
3007
4219
|
reason: "changedBlock"
|
|
3008
4220
|
};
|
|
3009
|
-
const cleanBlock = buildCleanBlockText(live.node, live.from
|
|
4221
|
+
const cleanBlock = buildCleanBlockText(live.node, live.from, {
|
|
4222
|
+
fieldResults: "text",
|
|
4223
|
+
noteReferences
|
|
4224
|
+
});
|
|
3010
4225
|
const currentText = cleanBlock.text;
|
|
3011
4226
|
const currentTextHash = hashFolioAIBlockText(normalizeFolioAIBlockText(currentText));
|
|
3012
4227
|
const structuralBoundaryHash = hashFolioAIBlockStructuralBoundaries(cleanBlock);
|
|
@@ -3024,13 +4239,52 @@ const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId
|
|
|
3024
4239
|
currentTextHash
|
|
3025
4240
|
};
|
|
3026
4241
|
};
|
|
3027
|
-
const resolveOperation = (
|
|
4242
|
+
const resolveOperation = (args) => {
|
|
4243
|
+
const resolution = resolveOperationTarget(args);
|
|
4244
|
+
if (resolution.type === "skip") return resolution;
|
|
4245
|
+
return (noteReferenceEditsAllowed() ? null : refuseNoteReferenceEdit(resolution.operation, args.noteReferences)) ?? resolution;
|
|
4246
|
+
};
|
|
4247
|
+
/**
|
|
4248
|
+
* A text change may rewrite the prose around a footnote or endnote reference
|
|
4249
|
+
* but never the reference: its marker is structure the clean text shows, not
|
|
4250
|
+
* characters it holds. The replacement has to keep every marker its match
|
|
4251
|
+
* covers, in order, and may not write a marker-shaped string of its own; the
|
|
4252
|
+
* replacement's inline emphasis would rebuild the match whole, reference
|
|
4253
|
+
* included, so it cannot carry one either. Checked before anything is applied,
|
|
4254
|
+
* so direct and tracked mode refuse the same operations.
|
|
4255
|
+
*/
|
|
4256
|
+
const refuseNoteReferenceEdit = (item, noteReferences) => {
|
|
4257
|
+
const { operation } = item;
|
|
4258
|
+
let replacement;
|
|
4259
|
+
if (operation.type === "replaceInBlock" || operation.type === "replaceRange") replacement = operation.replace;
|
|
4260
|
+
else if (operation.type === "replaceBlock") replacement = operation.text;
|
|
4261
|
+
else return null;
|
|
4262
|
+
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
4263
|
+
fieldResults: "text",
|
|
4264
|
+
noteReferences
|
|
4265
|
+
});
|
|
4266
|
+
const span = replacedCleanSpan({ operation }, cleanBlock);
|
|
4267
|
+
if (span === null) return null;
|
|
4268
|
+
const spans = noteReferenceSpansWithin(cleanBlock, span.start, span.start + span.text.length);
|
|
4269
|
+
const rebuildsBlock = operation.type === "replaceBlock" && operation.preserveFormatting === false;
|
|
4270
|
+
if (spans.length > 0 && (hasInlineEmphasis(replacement) || rebuildsBlock)) return {
|
|
4271
|
+
type: "skip",
|
|
4272
|
+
reason: "protectedReference"
|
|
4273
|
+
};
|
|
4274
|
+
if (operation.type === "replaceBlock" && replacement === span.text) return null;
|
|
4275
|
+
return segmentsAroundNoteReferences(span.text, stripInlineEmphasisMarkers(replacement), spans) === null ? {
|
|
4276
|
+
type: "skip",
|
|
4277
|
+
reason: "protectedReference"
|
|
4278
|
+
} : null;
|
|
4279
|
+
};
|
|
4280
|
+
const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByParaId, noteReferences, doc }) => {
|
|
3028
4281
|
const blockId = operation.type === "replaceRange" || operation.type === "commentOnRange" || operation.type === "formatRange" ? operation.range.blockId : operation.blockId;
|
|
3029
4282
|
const primaryBlock = resolveStableBlock({
|
|
3030
4283
|
snapshot,
|
|
3031
4284
|
blockId,
|
|
3032
4285
|
liveBlocks,
|
|
3033
|
-
liveBlocksByParaId
|
|
4286
|
+
liveBlocksByParaId,
|
|
4287
|
+
noteReferences
|
|
3034
4288
|
});
|
|
3035
4289
|
if (primaryBlock.type === "skip") return primaryBlock;
|
|
3036
4290
|
const { blockNode, blockFrom, blockTo, cleanBlock, currentText, currentTextHash } = primaryBlock;
|
|
@@ -3049,6 +4303,16 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3049
4303
|
type: "skip",
|
|
3050
4304
|
reason: "staleRange"
|
|
3051
4305
|
};
|
|
4306
|
+
const characterSplit = describeCharacterSplit(currentText, [startOffset, endOffset], operation.type === "replaceRange" ? "grapheme" : "codePoint");
|
|
4307
|
+
if (characterSplit !== null) return {
|
|
4308
|
+
type: "skip",
|
|
4309
|
+
reason: "splitsCharacter",
|
|
4310
|
+
message: characterSplit
|
|
4311
|
+
};
|
|
4312
|
+
if (cutsIntoNoteReference(cleanBlock, startOffset, endOffset)) return {
|
|
4313
|
+
type: "skip",
|
|
4314
|
+
reason: "protectedReference"
|
|
4315
|
+
};
|
|
3052
4316
|
const range = resolveCleanTextRange({
|
|
3053
4317
|
cleanBlock,
|
|
3054
4318
|
startOffset,
|
|
@@ -3079,7 +4343,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3079
4343
|
const isInsertAfter = operation.type === "insertAfterBlock";
|
|
3080
4344
|
let insertFrom;
|
|
3081
4345
|
if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
|
|
3082
|
-
else insertFrom = isInsertAfter ?
|
|
4346
|
+
else insertFrom = isInsertAfter ? endOfParagraphWithItsTextBoxes(doc, blockFrom) : blockFrom;
|
|
3083
4347
|
const insertTexts = operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text) ? splitInsertParagraphTexts(operation.text) : [operation.text];
|
|
3084
4348
|
return {
|
|
3085
4349
|
type: "resolved",
|
|
@@ -3103,7 +4367,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3103
4367
|
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
3104
4368
|
let insertFrom;
|
|
3105
4369
|
if (tableBoundary) insertFrom = position === "after" ? tableBoundary.after : tableBoundary.before;
|
|
3106
|
-
else insertFrom = position === "after" ?
|
|
4370
|
+
else insertFrom = position === "after" ? endOfParagraphWithItsTextBoxes(doc, blockFrom) : blockFrom;
|
|
3107
4371
|
return {
|
|
3108
4372
|
type: "resolved",
|
|
3109
4373
|
operation: {
|
|
@@ -3122,10 +4386,20 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3122
4386
|
blockFrom,
|
|
3123
4387
|
position: operation.position ?? "after"
|
|
3124
4388
|
});
|
|
3125
|
-
if (!insertion
|
|
4389
|
+
if (!insertion) return {
|
|
3126
4390
|
type: "skip",
|
|
3127
4391
|
reason: "unsupportedBlock"
|
|
3128
4392
|
};
|
|
4393
|
+
const overflow = describeUnplacedCellTexts({
|
|
4394
|
+
cellTexts: operation.cellTexts,
|
|
4395
|
+
capacity: insertion.cells.length,
|
|
4396
|
+
target: "row"
|
|
4397
|
+
});
|
|
4398
|
+
if (overflow !== null) return {
|
|
4399
|
+
type: "skip",
|
|
4400
|
+
reason: "payloadDoesNotFit",
|
|
4401
|
+
message: overflow
|
|
4402
|
+
};
|
|
3129
4403
|
return {
|
|
3130
4404
|
type: "resolved",
|
|
3131
4405
|
operation: {
|
|
@@ -3248,7 +4522,8 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3248
4522
|
snapshot,
|
|
3249
4523
|
blockId: operation.endBlockId,
|
|
3250
4524
|
liveBlocks,
|
|
3251
|
-
liveBlocksByParaId
|
|
4525
|
+
liveBlocksByParaId,
|
|
4526
|
+
noteReferences
|
|
3252
4527
|
});
|
|
3253
4528
|
if (endTarget.type === "skip") return endTarget;
|
|
3254
4529
|
const endCell = findEnclosingTableCell(doc, endTarget.blockFrom);
|
|
@@ -3320,7 +4595,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3320
4595
|
if (operation.type === "insertTable") {
|
|
3321
4596
|
const boundary = findOutermostTableBoundary(doc, blockFrom) ?? {
|
|
3322
4597
|
before: blockFrom,
|
|
3323
|
-
after:
|
|
4598
|
+
after: endOfParagraphWithItsTextBoxes(doc, blockFrom)
|
|
3324
4599
|
};
|
|
3325
4600
|
const insertFrom = (operation.position ?? "after") === "after" ? boundary.after : boundary.before;
|
|
3326
4601
|
return {
|
|
@@ -3382,6 +4657,12 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3382
4657
|
type: "skip",
|
|
3383
4658
|
reason: "staleRange"
|
|
3384
4659
|
};
|
|
4660
|
+
const characterSplit = describeCharacterSplit(currentText, [startOffset, endOffset], "grapheme");
|
|
4661
|
+
if (characterSplit !== null) return {
|
|
4662
|
+
type: "skip",
|
|
4663
|
+
reason: "splitsCharacter",
|
|
4664
|
+
message: characterSplit
|
|
4665
|
+
};
|
|
3385
4666
|
const range = resolveCleanTextRange({
|
|
3386
4667
|
cleanBlock,
|
|
3387
4668
|
startOffset,
|
|
@@ -3420,10 +4701,14 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3420
4701
|
}
|
|
3421
4702
|
};
|
|
3422
4703
|
}
|
|
4704
|
+
if (operation.type === "replaceBlock" && isPendingDeletion(doc, blockFrom, blockNode, currentText)) return {
|
|
4705
|
+
type: "skip",
|
|
4706
|
+
reason: "pendingDeletion"
|
|
4707
|
+
};
|
|
3423
4708
|
if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
|
|
3424
4709
|
const replaceChangesText = operation.type === "replaceBlock" && operation.text !== currentText;
|
|
3425
4710
|
const replaceChangesStyle = operation.type === "replaceBlock" && operation.styleId !== void 0 && operation.styleId !== (expectParagraphAttrs(blockNode).styleId ?? null);
|
|
3426
|
-
if (replaceChangesText && cleanBlock.structuralBoundaries.
|
|
4711
|
+
if (replaceChangesText && cleanBlock.structuralBoundaries.some((boundary) => boundary.type !== "field" && boundary.type !== "noteReference")) return {
|
|
3427
4712
|
type: "skip",
|
|
3428
4713
|
reason: "unsupportedBlock"
|
|
3429
4714
|
};
|
|
@@ -3503,7 +4788,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3503
4788
|
reason: "noopOperation"
|
|
3504
4789
|
};
|
|
3505
4790
|
const quote = getOperationQuote(operation);
|
|
3506
|
-
const range = resolveTextInCleanBlock(cleanBlock, quote || currentText);
|
|
4791
|
+
const range = resolveTextInCleanBlock(cleanBlock, quote || currentText, operation.type === "replaceInBlock" ? "grapheme" : "codePoint");
|
|
3507
4792
|
if (range.type !== "resolved") return range;
|
|
3508
4793
|
return {
|
|
3509
4794
|
type: "resolved",
|
|
@@ -3517,7 +4802,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3517
4802
|
}
|
|
3518
4803
|
};
|
|
3519
4804
|
};
|
|
3520
|
-
const resolveTextInCleanBlock = (cleanBlock, find) => {
|
|
4805
|
+
const resolveTextInCleanBlock = (cleanBlock, find, strictness) => {
|
|
3521
4806
|
if (find.length === 0) return {
|
|
3522
4807
|
type: "skip",
|
|
3523
4808
|
reason: "emptyOperation"
|
|
@@ -3532,6 +4817,16 @@ const resolveTextInCleanBlock = (cleanBlock, find) => {
|
|
|
3532
4817
|
type: "skip",
|
|
3533
4818
|
reason: "ambiguousFind"
|
|
3534
4819
|
};
|
|
4820
|
+
const characterSplit = describeCharacterSplit(text, [firstIndex, firstIndex + find.length], strictness);
|
|
4821
|
+
if (characterSplit !== null) return {
|
|
4822
|
+
type: "skip",
|
|
4823
|
+
reason: "splitsCharacter",
|
|
4824
|
+
message: `the match's ${characterSplit}`
|
|
4825
|
+
};
|
|
4826
|
+
if (cutsIntoNoteReference(cleanBlock, firstIndex, firstIndex + find.length)) return {
|
|
4827
|
+
type: "skip",
|
|
4828
|
+
reason: "protectedReference"
|
|
4829
|
+
};
|
|
3535
4830
|
const range = resolveCleanTextRange({
|
|
3536
4831
|
cleanBlock,
|
|
3537
4832
|
startOffset: firstIndex,
|
|
@@ -3557,9 +4852,128 @@ const getTextRangeFromCleanBlock = (cleanBlock) => {
|
|
|
3557
4852
|
};
|
|
3558
4853
|
};
|
|
3559
4854
|
const getOperationCommentText = (operation) => "comment" in operation ? operation.comment?.text : void 0;
|
|
4855
|
+
/**
|
|
4856
|
+
* Every paragraph style an operation names, with the field that names it.
|
|
4857
|
+
* `null` clears the style and names none; an omitted one keeps the style the
|
|
4858
|
+
* paragraph or its anchor already carries, which the document holds by
|
|
4859
|
+
* construction.
|
|
4860
|
+
*/
|
|
4861
|
+
const operationParagraphStyleReferences = (operation) => {
|
|
4862
|
+
switch (operation.type) {
|
|
4863
|
+
case "insertAfterBlock":
|
|
4864
|
+
case "insertBeforeBlock":
|
|
4865
|
+
case "replaceBlock": return [{
|
|
4866
|
+
path: "styleId",
|
|
4867
|
+
styleId: operation.styleId
|
|
4868
|
+
}];
|
|
4869
|
+
case "setBlockParagraphProperties": return [{
|
|
4870
|
+
path: "properties.styleId",
|
|
4871
|
+
styleId: operation.properties.styleId
|
|
4872
|
+
}];
|
|
4873
|
+
case "splitBlock": return [{
|
|
4874
|
+
path: "firstParagraphProperties.styleId",
|
|
4875
|
+
styleId: operation.firstParagraphProperties?.styleId
|
|
4876
|
+
}, {
|
|
4877
|
+
path: "secondParagraphProperties.styleId",
|
|
4878
|
+
styleId: operation.secondParagraphProperties?.styleId
|
|
4879
|
+
}];
|
|
4880
|
+
case "mergeBlockWithNext": return [{
|
|
4881
|
+
path: "mergedParagraphProperties.styleId",
|
|
4882
|
+
styleId: operation.mergedParagraphProperties?.styleId
|
|
4883
|
+
}];
|
|
4884
|
+
default: return [];
|
|
4885
|
+
}
|
|
4886
|
+
};
|
|
4887
|
+
/**
|
|
4888
|
+
* The first paragraph style the operation names that the document does not
|
|
4889
|
+
* define as a paragraph style. A `w:pStyle` naming no definition, or a table
|
|
4890
|
+
* or character style, confers no formatting: the paragraph keeps its body
|
|
4891
|
+
* look while the operation reports a restyle. Refused here, before anything
|
|
4892
|
+
* is applied, the way an undefined block id is. `null` definitions (a state
|
|
4893
|
+
* without the styles plugin) cannot say what is defined, and check nothing.
|
|
4894
|
+
*/
|
|
4895
|
+
const findUndefinedParagraphStyleReference = (operation, definitions) => {
|
|
4896
|
+
if (definitions === null) return;
|
|
4897
|
+
for (const { path, styleId } of operationParagraphStyleReferences(operation)) {
|
|
4898
|
+
if (styleId === null || styleId === void 0) continue;
|
|
4899
|
+
const definition = definitions.get(styleId);
|
|
4900
|
+
if (definition?.type !== "paragraph") return {
|
|
4901
|
+
path,
|
|
4902
|
+
styleId,
|
|
4903
|
+
definedAs: definition?.type
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
};
|
|
4907
|
+
const MAX_LISTED_PARAGRAPH_STYLES = 12;
|
|
4908
|
+
const describeUndefinedParagraphStyle = ({ path, styleId, definedAs }, definitions) => {
|
|
4909
|
+
const defined = (definitions?.paragraphStyles() ?? []).map((style) => style.styleId);
|
|
4910
|
+
let available = "it defines no visible paragraph styles";
|
|
4911
|
+
if (defined.length > 0) available = `its paragraph styles include ${defined.slice(0, MAX_LISTED_PARAGRAPH_STYLES).join(", ")}${defined.length > MAX_LISTED_PARAGRAPH_STYLES ? ", …" : ""}`;
|
|
4912
|
+
return `${path} "${styleId}" ${definedAs === void 0 ? "names no style in this document" : `names a ${definedAs} style, not a paragraph style`} (${available}).`;
|
|
4913
|
+
};
|
|
4914
|
+
/** The `w:num` id a stated numbering names, when it names one. */
|
|
4915
|
+
const statedNumberingInstance = (numbering) => {
|
|
4916
|
+
if (numbering === null || numbering === void 0) return;
|
|
4917
|
+
const numId = Reflect.get(numbering, "numId");
|
|
4918
|
+
return typeof numId === "number" ? numId : void 0;
|
|
4919
|
+
};
|
|
4920
|
+
/**
|
|
4921
|
+
* Every numbering instance an operation names, with the field that names it.
|
|
4922
|
+
* `listLevel` is not among them: it keeps the instance the paragraph or its
|
|
4923
|
+
* anchor already references, which the document holds by construction.
|
|
4924
|
+
*/
|
|
4925
|
+
const operationNumberingReferences = (operation) => {
|
|
4926
|
+
switch (operation.type) {
|
|
4927
|
+
case "insertAfterBlock":
|
|
4928
|
+
case "insertBeforeBlock": return [{
|
|
4929
|
+
path: "numbering",
|
|
4930
|
+
numbering: operation.numbering
|
|
4931
|
+
}];
|
|
4932
|
+
case "setBlockParagraphProperties": return [{
|
|
4933
|
+
path: "properties.numbering",
|
|
4934
|
+
numbering: operation.properties.numbering
|
|
4935
|
+
}];
|
|
4936
|
+
case "splitBlock": return [{
|
|
4937
|
+
path: "firstParagraphProperties.numbering",
|
|
4938
|
+
numbering: operation.firstParagraphProperties?.numbering
|
|
4939
|
+
}, {
|
|
4940
|
+
path: "secondParagraphProperties.numbering",
|
|
4941
|
+
numbering: operation.secondParagraphProperties?.numbering
|
|
4942
|
+
}];
|
|
4943
|
+
case "mergeBlockWithNext": return [{
|
|
4944
|
+
path: "mergedParagraphProperties.numbering",
|
|
4945
|
+
numbering: operation.mergedParagraphProperties?.numbering
|
|
4946
|
+
}];
|
|
4947
|
+
default: return [];
|
|
4948
|
+
}
|
|
4949
|
+
};
|
|
4950
|
+
/**
|
|
4951
|
+
* The first numbering instance the operation names that the document does not
|
|
4952
|
+
* define. A paragraph referencing a missing `w:num` is a model no save can
|
|
4953
|
+
* write, so it is refused here, before anything is applied, the way a block id
|
|
4954
|
+
* naming no block is. `null` instance ids (a state without the numbering
|
|
4955
|
+
* plugin) cannot say what is defined, and check nothing.
|
|
4956
|
+
*/
|
|
4957
|
+
const findUndefinedNumberingReference = (operation, instanceIds) => {
|
|
4958
|
+
if (instanceIds === null) return;
|
|
4959
|
+
for (const { path, numbering } of operationNumberingReferences(operation)) {
|
|
4960
|
+
const numId = statedNumberingInstance(numbering);
|
|
4961
|
+
if (numId !== void 0 && !instanceIds.has(numId)) return {
|
|
4962
|
+
path,
|
|
4963
|
+
numId
|
|
4964
|
+
};
|
|
4965
|
+
}
|
|
4966
|
+
};
|
|
4967
|
+
const MAX_LISTED_NUMBERING_INSTANCES = 12;
|
|
4968
|
+
const describeUndefinedNumbering = ({ path, numId }, instanceIds) => {
|
|
4969
|
+
const defined = [...instanceIds ?? []].sort((left, right) => left - right);
|
|
4970
|
+
let available = "it defines none";
|
|
4971
|
+
if (defined.length > 0) available = `it defines ${defined.slice(0, MAX_LISTED_NUMBERING_INSTANCES).join(", ")}${defined.length > MAX_LISTED_NUMBERING_INSTANCES ? ", …" : ""}`;
|
|
4972
|
+
return `${path}.numId ${String(numId)} names no numbering instance in this document (${available}).`;
|
|
4973
|
+
};
|
|
3560
4974
|
const getOperationQuote = (operation) => {
|
|
3561
4975
|
if (operation.type === "replaceInBlock") return operation.find;
|
|
3562
4976
|
if (operation.type === "commentOnBlock") return operation.quote;
|
|
3563
4977
|
};
|
|
3564
4978
|
//#endregion
|
|
3565
|
-
export { applyFolioAIEditOperations, previewFolioAIEditOperations };
|
|
4979
|
+
export { applyFolioAIEditOperations, previewFolioAIEditOperations, previewFolioAIEditOperationsWithResult };
|