@stll/folio-core 0.52.0 → 0.54.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 +1779 -141
- package/dist/ai-edits/batch-claims.d.ts +145 -0
- package/dist/ai-edits/batch-claims.js +275 -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 +91 -7
- package/dist/ai-edits/headless.js +520 -51
- 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 +6 -4
- package/dist/ai-edits/read.js +113 -50
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/revisionStretches.d.ts +17 -0
- package/dist/ai-edits/revisionStretches.js +80 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +180 -25
- package/dist/ai-edits/table-cell-mutations.d.ts +4 -2
- package/dist/ai-edits/table-cell-mutations.js +6 -2
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +44 -17
- package/dist/ai-edits/table-row-column-mutations.js +86 -34
- 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.d.ts +5 -1
- package/dist/compare/content-alignment.js +52 -34
- 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 +1 -1
- package/dist/compare/plan.js +10 -5
- package/dist/compare/scenario.js +14 -3
- package/dist/compare/section-boundary-properties.js +1 -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 +65 -11
- 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/archiveInflation.d.ts +127 -0
- package/dist/docx/archiveInflation.js +181 -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/metadataPrivacy.js +24 -1
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/noteReferenceMark.d.ts +15 -0
- package/dist/docx/noteReferenceMark.js +48 -0
- 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 +96 -27
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +10 -2
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +175 -22
- package/dist/docx/rezip.js +31 -7
- 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.d.ts +3 -1
- package/dist/docx/selectiveXmlPatch.js +186 -39
- 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/boundedArchive.d.ts +8 -13
- package/dist/docx/server/boundedArchive.js +70 -59
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/server/extractDocxText.d.ts +8 -2
- package/dist/docx/server/extractDocxText.js +2 -2
- package/dist/docx/server/validateDocxConformance.d.ts +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -0
- 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 +20 -2
- package/dist/docx/unzip.js +136 -25
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +170 -34
- package/dist/i18n/messages/catalogs.gen.js +187 -51
- package/dist/i18n/messages/messages.gen.d.ts +10 -2
- 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 +600 -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/measureParagraph.js +2 -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/measure/paragraphMeasureShared.d.ts +12 -1
- package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
- 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/escape.d.ts +2 -2
- package/dist/markdown/escape.js +4 -5
- 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 +121 -25
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.d.ts +10 -1
- package/dist/markdown/renderRuns.js +323 -69
- 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/clearRunColor.d.ts +6 -0
- package/dist/prosemirror/clearRunColor.js +46 -0
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +281 -212
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.js +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/hyperlink.js +2 -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/propertyChangeScope.d.ts +3 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +620 -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 +73 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +15 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
- package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
- package/dist/prosemirror/containerFinalParagraph.js +7 -6
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +177 -605
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
- package/dist/prosemirror/conversion/toProseDoc.js +70 -154
- package/dist/prosemirror/documentSchema.d.ts +8 -0
- package/dist/prosemirror/documentSchema.js +18 -0
- 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 +5 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +116 -63
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +4 -1
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +167 -128
- 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/pastedHeadingStyles.js +1 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +124 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +33 -11
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +15 -8
- package/dist/prosemirror/extensions/marks/markUtils.js +25 -10
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +12 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +46 -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/CommentReferenceExtension.js +2 -2
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
- 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 +121 -226
- package/dist/prosemirror/extensions/types.d.ts +11 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
- package/dist/prosemirror/hyperlinkRemoval.js +129 -0
- package/dist/prosemirror/index.d.ts +5 -3
- package/dist/prosemirror/index.js +5 -3
- 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 +92 -0
- package/dist/prosemirror/listNumbering.js +330 -0
- package/dist/prosemirror/listRendering.d.ts +27 -0
- package/dist/prosemirror/listRendering.js +27 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewNotes.d.ts +8 -0
- package/dist/prosemirror/markupViewNotes.js +49 -0
- package/dist/prosemirror/markupViewProjection.d.ts +97 -0
- package/dist/prosemirror/markupViewProjection.js +88 -0
- package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
- package/dist/prosemirror/noteReferenceReview.js +332 -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 +32 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +21 -0
- package/dist/prosemirror/paragraphMarkJoin.js +26 -0
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
- package/dist/prosemirror/paragraphPropertyCarry.js +158 -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/documentStyleState.d.ts +26 -0
- package/dist/prosemirror/plugins/documentStyleState.js +32 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +3 -13
- package/dist/prosemirror/plugins/documentStyles.js +67 -17
- package/dist/prosemirror/plugins/index.d.ts +2 -1
- package/dist/prosemirror/plugins/index.js +2 -1
- package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
- package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
- package/dist/prosemirror/plugins/suggestionMode.d.ts +27 -3
- package/dist/prosemirror/plugins/suggestionMode.js +490 -63
- 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 +23 -3
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
- package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
- package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
- 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/runFormattingFromMarks.d.ts +18 -0
- package/dist/prosemirror/runFormattingFromMarks.js +567 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/runFormattingReconciliation.js +10 -9
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +44 -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/styles/paragraphStyleCascade.d.ts +59 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
- package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
- package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
- package/dist/prosemirror/tableCellPaste.d.ts +74 -0
- package/dist/prosemirror/tableCellPaste.js +525 -0
- package/dist/prosemirror/tableGridMutation.d.ts +87 -2
- package/dist/prosemirror/tableGridMutation.js +255 -2
- package/dist/prosemirror/tableRunIn.d.ts +51 -0
- package/dist/prosemirror/tableRunIn.js +165 -0
- package/dist/prosemirror/textInput.js +11 -2
- package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
- package/dist/prosemirror/trackedRevisionPath.js +32 -0
- 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/server.d.ts +2 -2
- 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 +6 -5
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
package/dist/ai-edits/apply.js
CHANGED
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
import { paragraphNumberingReference, paragraphNumberingReferenceId } from "../docx/numberingReference.js";
|
|
2
2
|
import { formattingEquals } from "../docx/runConsolidator.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isStyleSourcedParagraphNumbering } from "../internal/paragraphFormattingSerialization.js";
|
|
4
|
+
import { deleteTextBoxesAnchoredAt, droppedTextBoxAnchorIds, endOfParagraphWithItsTextBoxes, gatherTextBoxesAfterParagraph, textBoxAnchorIdOf, textBoxAnchorIdsBetween } from "../prosemirror/anchoredTextBoxes.js";
|
|
5
|
+
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTableRowAttrs } from "../prosemirror/attrs/index.js";
|
|
4
6
|
import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
|
|
7
|
+
import { resolveParagraphChangeAttrs } from "../prosemirror/commands/resolveParagraphProperties.js";
|
|
5
8
|
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
|
|
6
9
|
import { encloseWholeControls } from "../prosemirror/contentControlRevisions.js";
|
|
10
|
+
import { JOINED_RUNS_RESTYLED_META } from "../prosemirror/extensions/features/JoinedRunStyleExtension.js";
|
|
7
11
|
import { requestDeterministicParaIds } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
|
|
8
12
|
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
13
|
+
import "../prosemirror/extensions/nodes/TextBoxAnchorExtension.js";
|
|
9
14
|
import { CLEARED_LIST_RENDERING_ATTRS } from "../prosemirror/listMarker.js";
|
|
10
|
-
import { paragraphNumberingAttr, readParagraphNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
15
|
+
import { paragraphNumberingAttr, readParagraphNumberingAttr, removedNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
11
16
|
import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
|
|
17
|
+
import { styleResolvedParagraphFormatting } from "../prosemirror/paragraphFormattingProvenance.js";
|
|
12
18
|
import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
|
|
19
|
+
import { joinAtParagraphMark } from "../prosemirror/paragraphMarkJoin.js";
|
|
20
|
+
import { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
|
|
13
21
|
import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
|
|
14
|
-
import { getDocumentNumbering } from "../prosemirror/plugins/documentNumbering.js";
|
|
15
|
-
import { getDocumentStyleResolver } from "../prosemirror/plugins/
|
|
16
|
-
import {
|
|
22
|
+
import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosemirror/plugins/documentNumbering.js";
|
|
23
|
+
import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
|
|
24
|
+
import { rebaseParagraphRuns } from "../prosemirror/rebaseParagraphRuns.js";
|
|
25
|
+
import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, placeTrackedInsertion, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
|
|
17
26
|
import { applyMarksToRunFormattingRepresentation, runFormattingInlineControlNodeName, selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
27
|
+
import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
|
|
18
28
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
19
|
-
import { paragraphRunStyleContext, paragraphRunStyleContextAt } from "../prosemirror/runStyleFormatting.js";
|
|
20
|
-
import {
|
|
29
|
+
import { paragraphRunStyleContext, paragraphRunStyleContextAt, resolveParagraphBodyRunFormatting } from "../prosemirror/runStyleFormatting.js";
|
|
30
|
+
import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
|
|
31
|
+
import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
|
|
32
|
+
import { removeRowsWithoutCellsAfterBatch } from "../prosemirror/tableGridMutation.js";
|
|
21
33
|
import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
|
|
22
34
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
35
|
+
import { BatchClaims } from "./batch-claims.js";
|
|
23
36
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
37
|
+
import { describeCharacterSplit } from "./character-boundaries.js";
|
|
24
38
|
import { buildCleanBlockText, resolveCleanTextRange } from "./clean-text.js";
|
|
25
39
|
import { hasInlineEmphasis, parseInlineEmphasisRuns, stripInlineEmphasisMarkers } from "./inline-emphasis.js";
|
|
26
|
-
import { applyTextChanges, changesFromSegments, planTextChanges, widenChangesToAtomicSpans } from "./minimal-replacement.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
40
|
+
import { applyTextChanges, changesFromSegments, keepAtomicSpans, planTextChanges, widenChangesToAtomicSpans } from "./minimal-replacement.js";
|
|
41
|
+
import { concreteListReference, resolveNewListOperations } from "./newListNumbering.js";
|
|
42
|
+
import { cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, segmentsAroundNoteReferences } from "./note-references.js";
|
|
43
|
+
import { separateRevisionStretches } from "./revisionStretches.js";
|
|
44
|
+
import { collectNoteReferenceLabels, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isHiddenTableRow, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
45
|
+
import { isEmptyTableCell, mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
|
|
29
46
|
import { planTableMutations } from "./table-mutation-plan.js";
|
|
30
|
-
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
|
|
47
|
+
import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, describeUnplacedCellTexts, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
|
|
31
48
|
import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBoundary, tableRectangleCutsMergedCell } from "./table-targets.js";
|
|
32
49
|
import { tableFromTemplate } from "./table-template.js";
|
|
33
50
|
import { wordDiffSessionFromOptions } from "./word-diff.js";
|
|
34
51
|
import { panic } from "better-result";
|
|
35
|
-
import { Fragment } from "prosemirror-model";
|
|
52
|
+
import { Fragment, Mark } from "prosemirror-model";
|
|
36
53
|
import { TableMap } from "prosemirror-tables";
|
|
37
54
|
import { canJoin, canSplit } from "prosemirror-transform";
|
|
38
55
|
//#region src/ai-edits/apply.ts
|
|
@@ -109,6 +126,12 @@ const SUGGESTED_SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
109
126
|
"insertTableColumn",
|
|
110
127
|
"deleteTableColumn"
|
|
111
128
|
]);
|
|
129
|
+
/** Operations that only add marks: they move no position of the document. */
|
|
130
|
+
const ANNOTATION_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
131
|
+
"formatRange",
|
|
132
|
+
"commentOnRange",
|
|
133
|
+
"commentOnBlock"
|
|
134
|
+
]);
|
|
112
135
|
const REPLACE_BLOCK_IMPACT = {
|
|
113
136
|
text: {
|
|
114
137
|
changesStyle: false,
|
|
@@ -167,6 +190,7 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
167
190
|
const styleChanged = nextStyleId !== void 0 && (attrs.styleId ?? null) !== nextStyleId;
|
|
168
191
|
if (styleChanged) {
|
|
169
192
|
patch["styleId"] = nextStyleId;
|
|
193
|
+
patch["_resolvedFormatting"] = styleResolvedParagraphFormatting(resolvedFormattingFromStyle);
|
|
170
194
|
patch["alignmentFromStyle"] = resolvedFormattingFromStyle?.alignment;
|
|
171
195
|
if (properties.alignment === void 0) patch["alignment"] = currentDirectAlignment ?? resolvedFormattingFromStyle?.alignment ?? null;
|
|
172
196
|
if (properties.spacing === void 0) Object.assign(patch, paragraphSpacingAttrPatch({
|
|
@@ -182,25 +206,42 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
182
206
|
else originalFormatting.styleId = nextStyleId;
|
|
183
207
|
if (currentDirectAlignment !== void 0) originalFormatting.alignment = currentDirectAlignment;
|
|
184
208
|
originalFormattingChanged = true;
|
|
209
|
+
patch["outlineLevel"] = attrs._originalFormatting?.outlineLevel ?? resolvedFormattingFromStyle?.outlineLevel ?? null;
|
|
210
|
+
Object.assign(patch, restyledListAttrs({
|
|
211
|
+
attrs: { ...attrs },
|
|
212
|
+
styleNumbering: resolvedFormattingFromStyle?.numPr,
|
|
213
|
+
removesNumbering: false,
|
|
214
|
+
numbering
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
if (properties.outlineLevel !== void 0) {
|
|
218
|
+
const directOutlineLevel = properties.outlineLevel ?? void 0;
|
|
219
|
+
originalFormatting ??= {};
|
|
220
|
+
if (directOutlineLevel === void 0) Reflect.deleteProperty(originalFormatting, "outlineLevel");
|
|
221
|
+
else originalFormatting.outlineLevel = directOutlineLevel;
|
|
222
|
+
patch["outlineLevel"] = directOutlineLevel ?? resolvedFormattingFromStyle?.outlineLevel ?? null;
|
|
223
|
+
originalFormattingChanged = true;
|
|
185
224
|
}
|
|
225
|
+
const numPrFromStyle = styleChanged ? readParagraphNumberingAttr(patch["numPrFromStyle"]) : attrs.numPrFromStyle;
|
|
186
226
|
if (properties.numbering !== void 0) if (properties.numbering === null) {
|
|
187
|
-
patch["numPr"] =
|
|
227
|
+
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
188
228
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
189
229
|
} else {
|
|
230
|
+
const listReference = concreteListReference(properties.numbering);
|
|
190
231
|
Object.assign(patch, listLevelAttrPatch(attrs, {
|
|
191
|
-
numId:
|
|
192
|
-
ilvl:
|
|
232
|
+
numId: listReference.numId,
|
|
233
|
+
ilvl: listReference.level
|
|
193
234
|
}, numbering));
|
|
194
235
|
if (properties.listLevel === null) {
|
|
195
|
-
patch["numPr"] = paragraphNumberingAttr(paragraphNumberingReference({ numId:
|
|
236
|
+
patch["numPr"] = paragraphNumberingAttr(paragraphNumberingReference({ numId: listReference.numId }));
|
|
196
237
|
patch["numPrFromStyle"] = null;
|
|
197
238
|
}
|
|
198
239
|
}
|
|
199
240
|
else if (properties.listLevel !== void 0) if (properties.listLevel === null) {
|
|
200
|
-
patch["numPr"] =
|
|
241
|
+
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
201
242
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
202
243
|
} else {
|
|
203
|
-
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(node.attrs["numPr"]) ?? void 0);
|
|
244
|
+
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(styleChanged ? patch["numPr"] : node.attrs["numPr"]) ?? void 0);
|
|
204
245
|
if (numId === void 0) {
|
|
205
246
|
patch["numPr"] = paragraphNumberingAttr({
|
|
206
247
|
kind: "levelOnly",
|
|
@@ -242,6 +283,24 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
242
283
|
return Object.keys(patch).length > 0 ? patch : null;
|
|
243
284
|
};
|
|
244
285
|
const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
286
|
+
const hasOrdinaryTextRevision = (block) => {
|
|
287
|
+
let found = false;
|
|
288
|
+
block.descendants((node) => {
|
|
289
|
+
if (node.marks.some((mark) => REVISION_MARK_NAMES.has(mark.type.name) && mark.attrs["provenance"] !== "suggested")) {
|
|
290
|
+
found = true;
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
});
|
|
295
|
+
return found;
|
|
296
|
+
};
|
|
297
|
+
const hasOrdinaryParagraphMarkRevision = (block) => {
|
|
298
|
+
const mark = block.attrs["pPrMark"];
|
|
299
|
+
if (typeof mark !== "object" || mark === null || !("info" in mark)) return false;
|
|
300
|
+
const info = mark.info;
|
|
301
|
+
if (typeof info !== "object" || info === null) return false;
|
|
302
|
+
return !("provenance" in info) || info.provenance !== "suggested";
|
|
303
|
+
};
|
|
245
304
|
/**
|
|
246
305
|
* Strip insertion and deletion marks from the zero-width anchors the batch
|
|
247
306
|
* touched.
|
|
@@ -251,10 +310,12 @@ const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
|
251
310
|
* must keep its existing revision ownership. Marking is by range everywhere;
|
|
252
311
|
* the guard lives here once rather than at every call site.
|
|
253
312
|
*/
|
|
254
|
-
const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds) => {
|
|
313
|
+
const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds, deletedTextBoxAnchorIds) => {
|
|
255
314
|
const anchors = [];
|
|
256
315
|
tr.doc.descendants((node, pos) => {
|
|
257
316
|
if (!isZeroWidthAnchor(node)) return true;
|
|
317
|
+
const textBoxAnchorId = textBoxAnchorIdOf(node);
|
|
318
|
+
if (textBoxAnchorId !== void 0 && deletedTextBoxAnchorIds.has(textBoxAnchorId)) return false;
|
|
258
319
|
const marks = node.marks.filter(({ attrs, type }) => REVISION_MARK_NAMES.has(type.name) && batchRevisionIds.has(attrs["revisionId"]));
|
|
259
320
|
if (marks.length > 0) anchors.push({
|
|
260
321
|
from: pos,
|
|
@@ -272,7 +333,9 @@ const withoutRevisionsOnZeroWidthAnchors = (tr, batchRevisionIds) => {
|
|
|
272
333
|
*
|
|
273
334
|
* Skips what is already deleted, so an earlier revision's run is not marked
|
|
274
335
|
* twice, and skips zero-width anchors because the operation did not create or
|
|
275
|
-
* delete them; their existing revision ownership must remain unchanged.
|
|
336
|
+
* delete them; their existing revision ownership must remain unchanged. A
|
|
337
|
+
* text box's anchor is the exception: it stands for the box's drawing run,
|
|
338
|
+
* which is the paragraph's content, and the box follows it.
|
|
276
339
|
*/
|
|
277
340
|
const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
278
341
|
const position = tr.mapping.map(blockFrom);
|
|
@@ -280,7 +343,7 @@ const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
|
280
343
|
if (!block?.isTextblock) return [];
|
|
281
344
|
const ranges = [];
|
|
282
345
|
block.forEach((child, offset) => {
|
|
283
|
-
if (child.isText || isZeroWidthAnchor(child) || child.marks.some(({ type }) => type.name === "deletion")) return;
|
|
346
|
+
if (child.isText || isZeroWidthAnchor(child) && child.type.name !== "textBoxAnchor" || child.marks.some(({ type }) => type.name === "deletion")) return;
|
|
284
347
|
const from = position + 1 + offset;
|
|
285
348
|
ranges.push({
|
|
286
349
|
from,
|
|
@@ -289,13 +352,23 @@ const undeletedContentAtomRanges = (tr, blockFrom) => {
|
|
|
289
352
|
});
|
|
290
353
|
return ranges;
|
|
291
354
|
};
|
|
292
|
-
/**
|
|
293
|
-
|
|
355
|
+
/**
|
|
356
|
+
* The carrier pPr before any pending change to it, for final-mark rotation.
|
|
357
|
+
*
|
|
358
|
+
* A paragraph added after the carrier copies its pending property change, as
|
|
359
|
+
* a break typed at a paragraph's end does: rejecting the addition leaves the
|
|
360
|
+
* added paragraph (its mark is the one that stays), so that change is what
|
|
361
|
+
* gives the carrier's words back the properties they had.
|
|
362
|
+
*/
|
|
363
|
+
const paragraphPropertiesBeforePendingChanges = (node) => {
|
|
294
364
|
const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
365
|
+
const earliest = Array.isArray(propertyChanges) ? propertyChanges[0] : void 0;
|
|
366
|
+
return earliest === void 0 ? paragraphPropertiesSnapshot(node) : earliest.previousFormatting ?? {};
|
|
367
|
+
};
|
|
368
|
+
/** The pPr rejecting every pending change of a paragraph's properties leaves. */
|
|
369
|
+
const paragraphPropertiesOnReject = (node) => {
|
|
370
|
+
const [earliest] = expectParagraphAttrs(node)._propertyChanges ?? [];
|
|
371
|
+
return earliest ? earliest.previousFormatting ?? {} : paragraphPropertiesSnapshot(node);
|
|
299
372
|
};
|
|
300
373
|
const resolveFormattingFromStyle = ({ attrs, styleId, styleResolver }) => {
|
|
301
374
|
const resolved = styleResolver?.resolveParagraphStyle(styleId).paragraphFormatting;
|
|
@@ -368,6 +441,7 @@ const preserveRunFormattingAcrossTrackedStyleChange = ({ allocateRevisionInfo, n
|
|
|
368
441
|
* the change left alone from one it explicitly cleared when rejecting it.
|
|
369
442
|
*/
|
|
370
443
|
const applyBlockParagraphProperties = ({ tr, position, node, properties, styleResolver, numbering = null, revisionInfo }) => {
|
|
444
|
+
const storedMarks = tr.storedMarks ?? tr.selection.$from.marks();
|
|
371
445
|
const attrs = expectParagraphAttrs(node);
|
|
372
446
|
const resolvedFormattingFromStyle = properties.styleId === void 0 ? resolveFormattingFromStyle({
|
|
373
447
|
attrs,
|
|
@@ -390,6 +464,12 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
|
|
|
390
464
|
revisionId: null,
|
|
391
465
|
revisionIds: []
|
|
392
466
|
};
|
|
467
|
+
const nextStyleId = properties.styleId === void 0 ? attrs.styleId : properties.styleId;
|
|
468
|
+
if ((attrs.styleId ?? null) !== (nextStyleId ?? null)) patch["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
469
|
+
styleId: properties.styleId ?? void 0,
|
|
470
|
+
tableRunFormatting: attrs._tableRunFormatting,
|
|
471
|
+
styleResolver
|
|
472
|
+
}).defaultFormatting ?? null;
|
|
393
473
|
const changeInfo = revisionInfo?.();
|
|
394
474
|
const change = changeInfo ? {
|
|
395
475
|
type: "paragraphPropertyChange",
|
|
@@ -414,12 +494,25 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
|
|
|
414
494
|
revisionIds: []
|
|
415
495
|
};
|
|
416
496
|
return {
|
|
417
|
-
tr:
|
|
497
|
+
tr: rebaseParagraphRuns({
|
|
498
|
+
tr: bridgeResult.tr.setNodeMarkup(position, void 0, nextAttrs),
|
|
499
|
+
position,
|
|
500
|
+
previous: node,
|
|
501
|
+
styleResolver,
|
|
502
|
+
storedMarks
|
|
503
|
+
}),
|
|
418
504
|
changed: true,
|
|
419
505
|
revisionId: change?.info.id ?? null,
|
|
420
506
|
revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
|
|
421
507
|
};
|
|
422
508
|
};
|
|
509
|
+
/** No inherited formatting: every run-formatting mark reads as authored. */
|
|
510
|
+
const OPERATION_TEXT_CONTEXT = {
|
|
511
|
+
baseParagraphFormatting: void 0,
|
|
512
|
+
paragraphFormatting: void 0,
|
|
513
|
+
paragraphMarkFormatting: void 0,
|
|
514
|
+
paragraphMarkPrecedesStyle: false
|
|
515
|
+
};
|
|
423
516
|
const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) => {
|
|
424
517
|
if (item.operation.type !== "replaceBlock" || item.operation.styleId === void 0) return {
|
|
425
518
|
tr,
|
|
@@ -433,6 +526,7 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
|
|
|
433
526
|
revisionId: null,
|
|
434
527
|
revisionIds: []
|
|
435
528
|
};
|
|
529
|
+
const storedMarks = tr.storedMarks ?? tr.selection.$from.marks();
|
|
436
530
|
const attrs = expectParagraphAttrs(block);
|
|
437
531
|
const resolvedFormattingFromStyle = resolveFormattingFromStyle({
|
|
438
532
|
attrs,
|
|
@@ -449,6 +543,11 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
|
|
|
449
543
|
revisionId: null,
|
|
450
544
|
revisionIds: []
|
|
451
545
|
};
|
|
546
|
+
if ((attrs.styleId ?? null) !== item.operation.styleId) patch["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
547
|
+
styleId: item.operation.styleId ?? void 0,
|
|
548
|
+
tableRunFormatting: attrs._tableRunFormatting,
|
|
549
|
+
styleResolver
|
|
550
|
+
}).defaultFormatting ?? null;
|
|
452
551
|
const existing = attrs._propertyChanges;
|
|
453
552
|
const changeInfo = revisionInfo?.();
|
|
454
553
|
const change = changeInfo ? {
|
|
@@ -473,7 +572,13 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
|
|
|
473
572
|
revisionIds: []
|
|
474
573
|
};
|
|
475
574
|
return {
|
|
476
|
-
tr:
|
|
575
|
+
tr: rebaseParagraphRuns({
|
|
576
|
+
tr: bridgeResult.tr.setNodeMarkup(blockPosition, void 0, nextAttrs),
|
|
577
|
+
position: blockPosition,
|
|
578
|
+
previous: block,
|
|
579
|
+
styleResolver,
|
|
580
|
+
storedMarks
|
|
581
|
+
}),
|
|
477
582
|
revisionId: change?.info.id ?? null,
|
|
478
583
|
revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
|
|
479
584
|
};
|
|
@@ -747,10 +852,14 @@ let revisionIdCursor = Date.now() * 1e3;
|
|
|
747
852
|
*/
|
|
748
853
|
const collectLiveBlocksByHash = (doc) => {
|
|
749
854
|
const byHash = /* @__PURE__ */ new Map();
|
|
855
|
+
const noteReferences = collectNoteReferenceLabels(doc);
|
|
750
856
|
doc.descendants((node, pos) => {
|
|
751
857
|
if (isHiddenTableRow(node)) return false;
|
|
752
858
|
if (!node.isTextblock) return true;
|
|
753
|
-
const cleanText = buildCleanBlockText(node, pos
|
|
859
|
+
const cleanText = buildCleanBlockText(node, pos, {
|
|
860
|
+
fieldResults: "text",
|
|
861
|
+
noteReferences
|
|
862
|
+
}).text;
|
|
754
863
|
const hash = hashFolioAIBlockText(normalizeFolioAIBlockText(cleanText));
|
|
755
864
|
const bucket = byHash.get(hash) ?? [];
|
|
756
865
|
bucket.push({
|
|
@@ -804,6 +913,211 @@ const getTableMutationPlanTarget = (item) => {
|
|
|
804
913
|
return { type: "none" };
|
|
805
914
|
};
|
|
806
915
|
/**
|
|
916
|
+
* `insertion`, resolved against the document as it was read, placed through
|
|
917
|
+
* what `tr` has already changed. The batch runs from the end backwards, so
|
|
918
|
+
* nothing it has applied yet sits before the new row; mapped all the same, so
|
|
919
|
+
* that stays true of the positions the row is written at and not only of the
|
|
920
|
+
* order they are visited in. A row another operation of the batch inserted
|
|
921
|
+
* at the same boundary is the later one in input order, so the new row goes
|
|
922
|
+
* before it.
|
|
923
|
+
*/
|
|
924
|
+
const mapTableRowInsertion = (tr, insertion) => {
|
|
925
|
+
const tableStart = tr.mapping.map(insertion.tableStart, -1);
|
|
926
|
+
return {
|
|
927
|
+
...insertion,
|
|
928
|
+
tableStart,
|
|
929
|
+
rowPosition: tr.mapping.map(insertion.rowPosition, -1),
|
|
930
|
+
rowspanUpdates: insertion.rowspanUpdates.map((update) => tr.mapping.map(insertion.tableStart + update, 1) - tableStart)
|
|
931
|
+
};
|
|
932
|
+
};
|
|
933
|
+
/** The cells a column deletion removes: every cell standing in that column alone. */
|
|
934
|
+
const deletedColumnCellRanges = (doc, { tablePosition, columnIndex }) => {
|
|
935
|
+
const table = doc.nodeAt(tablePosition);
|
|
936
|
+
if (!table || table.type.spec["tableRole"] !== "table") return [];
|
|
937
|
+
const map = TableMap.get(table);
|
|
938
|
+
const tableStart = tablePosition + 1;
|
|
939
|
+
const ranges = [];
|
|
940
|
+
const seen = /* @__PURE__ */ new Set();
|
|
941
|
+
for (let row = 0; row < map.height; row++) {
|
|
942
|
+
const relative = map.map[row * map.width + columnIndex];
|
|
943
|
+
if (relative === void 0 || seen.has(relative)) continue;
|
|
944
|
+
seen.add(relative);
|
|
945
|
+
const rectangle = map.findCell(relative);
|
|
946
|
+
const cell = table.nodeAt(relative);
|
|
947
|
+
if (!cell || rectangle.left !== columnIndex || rectangle.right !== columnIndex + 1) continue;
|
|
948
|
+
ranges.push({
|
|
949
|
+
from: tableStart + relative,
|
|
950
|
+
to: tableStart + relative + cell.nodeSize
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return ranges;
|
|
954
|
+
};
|
|
955
|
+
/** A row deletion also changes cells whose vertical merge began above it. */
|
|
956
|
+
const deletedRowAffectedRanges = ({ doc, deletion, row }) => {
|
|
957
|
+
const ranges = [{
|
|
958
|
+
from: deletion.rowPosition,
|
|
959
|
+
to: deletion.rowPosition + row.nodeSize
|
|
960
|
+
}];
|
|
961
|
+
const table = doc.nodeAt(deletion.tablePosition);
|
|
962
|
+
if (table?.type.spec["tableRole"] !== "table") return panic("A resolved row deletion lost its table", { position: deletion.tablePosition });
|
|
963
|
+
const map = TableMap.get(table);
|
|
964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
965
|
+
for (let column = 0; column < map.width; column++) {
|
|
966
|
+
const relative = map.map[deletion.rowIndex * map.width + column];
|
|
967
|
+
if (relative === void 0 || seen.has(relative)) continue;
|
|
968
|
+
seen.add(relative);
|
|
969
|
+
const position = deletion.tableStart + relative;
|
|
970
|
+
if (position >= deletion.rowPosition) continue;
|
|
971
|
+
const cell = table.nodeAt(relative);
|
|
972
|
+
if (cell) ranges.push({
|
|
973
|
+
from: position,
|
|
974
|
+
to: position + cell.nodeSize
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
return ranges;
|
|
978
|
+
};
|
|
979
|
+
/**
|
|
980
|
+
* The blank cells a merge folds into the first cell of its rectangle. Only a
|
|
981
|
+
* blank cell can fold away tracked, so an edit that fills one decides whether
|
|
982
|
+
* the merge applies in one mode and not the other; see `batch-claims.ts`. A
|
|
983
|
+
* cell with content folds directly and is refused tracked whatever the batch
|
|
984
|
+
* does inside it.
|
|
985
|
+
*/
|
|
986
|
+
const foldedBlankCellRanges = (doc, { tablePosition, rectangle }) => {
|
|
987
|
+
const table = doc.nodeAt(tablePosition);
|
|
988
|
+
if (table?.type.spec["tableRole"] !== "table") return panic("A resolved cell merge lost its table", { position: tablePosition });
|
|
989
|
+
const map = TableMap.get(table);
|
|
990
|
+
const tableStart = tablePosition + 1;
|
|
991
|
+
const kept = map.map[rectangle.top * map.width + rectangle.left];
|
|
992
|
+
return map.cellsInRect(rectangle).flatMap((relative) => {
|
|
993
|
+
const cell = relative === kept ? null : table.nodeAt(relative);
|
|
994
|
+
return cell && isEmptyTableCell(cell) ? [{
|
|
995
|
+
from: tableStart + relative,
|
|
996
|
+
to: tableStart + relative + cell.nodeSize
|
|
997
|
+
}] : [];
|
|
998
|
+
});
|
|
999
|
+
};
|
|
1000
|
+
/** What `item` claims of the document its batch resolved against; see `batch-claims.ts`. */
|
|
1001
|
+
const batchClaimOf = ({ item, doc, producesTrackedChanges }) => {
|
|
1002
|
+
const block = item.blockFrom;
|
|
1003
|
+
switch (item.operation.type) {
|
|
1004
|
+
case "replaceInBlock":
|
|
1005
|
+
case "replaceRange": return {
|
|
1006
|
+
type: "text",
|
|
1007
|
+
block,
|
|
1008
|
+
from: item.from,
|
|
1009
|
+
to: item.to
|
|
1010
|
+
};
|
|
1011
|
+
case "formatRange":
|
|
1012
|
+
case "commentOnRange":
|
|
1013
|
+
case "commentOnBlock": return {
|
|
1014
|
+
type: "annotation",
|
|
1015
|
+
block,
|
|
1016
|
+
from: item.from,
|
|
1017
|
+
to: item.to
|
|
1018
|
+
};
|
|
1019
|
+
case "splitBlock": return {
|
|
1020
|
+
type: "split",
|
|
1021
|
+
block,
|
|
1022
|
+
from: item.from,
|
|
1023
|
+
to: item.to
|
|
1024
|
+
};
|
|
1025
|
+
case "replaceBlock": return item.replaceBlockImpact === "style" ? {
|
|
1026
|
+
type: "paragraphProperties",
|
|
1027
|
+
block
|
|
1028
|
+
} : {
|
|
1029
|
+
type: "rewriteBlock",
|
|
1030
|
+
block
|
|
1031
|
+
};
|
|
1032
|
+
case "setBlockParagraphProperties": return {
|
|
1033
|
+
type: "paragraphProperties",
|
|
1034
|
+
block
|
|
1035
|
+
};
|
|
1036
|
+
case "mergeBlockWithNext": return {
|
|
1037
|
+
type: "merge",
|
|
1038
|
+
block,
|
|
1039
|
+
next: item.blockTo,
|
|
1040
|
+
joinsNow: !producesTrackedChanges
|
|
1041
|
+
};
|
|
1042
|
+
case "deleteBlock": {
|
|
1043
|
+
const at = doc.resolve(block);
|
|
1044
|
+
const endsItsContainer = paragraphEndsItsContainer(at, item.blockNode.type.name);
|
|
1045
|
+
const keepsParagraph = producesTrackedChanges ? endsItsContainer : endsItsContainer && at.nodeBefore?.type.name !== item.blockNode.type.name;
|
|
1046
|
+
return {
|
|
1047
|
+
type: "deleteBlock",
|
|
1048
|
+
block,
|
|
1049
|
+
end: item.blockTo,
|
|
1050
|
+
keepsParagraph
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
case "insertAfterBlock":
|
|
1054
|
+
case "insertBeforeBlock":
|
|
1055
|
+
case "insertSignatureTable":
|
|
1056
|
+
case "insertTable": return {
|
|
1057
|
+
type: "insertion",
|
|
1058
|
+
at: item.from
|
|
1059
|
+
};
|
|
1060
|
+
case "deleteTable": {
|
|
1061
|
+
const deleted = item.deletedTable;
|
|
1062
|
+
return deleted ? {
|
|
1063
|
+
type: "tableRemoval",
|
|
1064
|
+
table: deleted.position,
|
|
1065
|
+
wholeTable: true,
|
|
1066
|
+
axis: "table",
|
|
1067
|
+
ranges: [{
|
|
1068
|
+
from: deleted.position,
|
|
1069
|
+
to: deleted.position + deleted.node.nodeSize
|
|
1070
|
+
}]
|
|
1071
|
+
} : { type: "unclaimed" };
|
|
1072
|
+
}
|
|
1073
|
+
case "deleteTableRow": {
|
|
1074
|
+
const deletion = item.tableRowDeletion;
|
|
1075
|
+
const row = deletion ? doc.nodeAt(deletion.rowPosition) : null;
|
|
1076
|
+
const table = deletion ? doc.nodeAt(deletion.tablePosition) : null;
|
|
1077
|
+
return deletion && row && table?.type.spec["tableRole"] === "table" ? {
|
|
1078
|
+
type: "tableRemoval",
|
|
1079
|
+
table: deletion.tablePosition,
|
|
1080
|
+
wholeTable: table.childCount === 1,
|
|
1081
|
+
axis: "row",
|
|
1082
|
+
ranges: table.childCount === 1 ? [{
|
|
1083
|
+
from: deletion.tablePosition,
|
|
1084
|
+
to: deletion.tablePosition + table.nodeSize
|
|
1085
|
+
}] : deletedRowAffectedRanges({
|
|
1086
|
+
doc,
|
|
1087
|
+
deletion,
|
|
1088
|
+
row
|
|
1089
|
+
})
|
|
1090
|
+
} : { type: "unclaimed" };
|
|
1091
|
+
}
|
|
1092
|
+
case "deleteTableColumn": {
|
|
1093
|
+
const deletion = item.tableColumnDeletion;
|
|
1094
|
+
return deletion ? {
|
|
1095
|
+
type: "tableRemoval",
|
|
1096
|
+
table: deletion.tablePosition,
|
|
1097
|
+
wholeTable: false,
|
|
1098
|
+
axis: "column",
|
|
1099
|
+
ranges: deletedColumnCellRanges(doc, deletion)
|
|
1100
|
+
} : { type: "unclaimed" };
|
|
1101
|
+
}
|
|
1102
|
+
case "insertTableRow":
|
|
1103
|
+
case "insertTableColumn":
|
|
1104
|
+
case "mergeTableCells":
|
|
1105
|
+
case "splitTableCell": {
|
|
1106
|
+
const target = getTableMutationPlanTarget(item);
|
|
1107
|
+
let axis = "cell";
|
|
1108
|
+
if (item.operation.type === "insertTableRow") axis = "row";
|
|
1109
|
+
if (item.operation.type === "insertTableColumn") axis = "column";
|
|
1110
|
+
if (target.type === "none") return { type: "unclaimed" };
|
|
1111
|
+
return {
|
|
1112
|
+
type: "tableStructure",
|
|
1113
|
+
table: target.tablePosition,
|
|
1114
|
+
axis,
|
|
1115
|
+
folded: target.type === "mergeCells" ? foldedBlankCellRanges(doc, target) : []
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
/**
|
|
807
1121
|
* The snapshot recorded an `hashOccurrenceCount` per anchor but
|
|
808
1122
|
* not which ordinal within that bucket the block was — recompute
|
|
809
1123
|
* on demand from the snapshot's anchor map. Stable iteration
|
|
@@ -956,6 +1270,94 @@ const isBatchableParagraphInsertion = (item, mode) => {
|
|
|
956
1270
|
const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
|
|
957
1271
|
return !(mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert);
|
|
958
1272
|
};
|
|
1273
|
+
/** The comments running across both edges of the table at `tablePosition`: over all of it. */
|
|
1274
|
+
const commentsAcrossTable = (doc, tablePosition) => {
|
|
1275
|
+
const table = doc.nodeAt(tablePosition);
|
|
1276
|
+
if (!table) return [];
|
|
1277
|
+
const atEnd = commentsAcrossBlockBoundary(doc, tablePosition + table.nodeSize);
|
|
1278
|
+
return commentsAcrossBlockBoundary(doc, tablePosition).filter((comment) => comment.isInSet(atEnd));
|
|
1279
|
+
};
|
|
1280
|
+
/** `comments` over the inline content of the node at `position`, which an insertion just placed. */
|
|
1281
|
+
const addCommentsOver = (tr, position, comments) => {
|
|
1282
|
+
const node = tr.doc.nodeAt(position);
|
|
1283
|
+
if (!node || !comments) return tr;
|
|
1284
|
+
let next = tr;
|
|
1285
|
+
for (const comment of comments) next = next.addMark(position + 1, position + node.nodeSize - 1, comment);
|
|
1286
|
+
return next;
|
|
1287
|
+
};
|
|
1288
|
+
const insertBlocksInsideComments = ({ tr, at, blocks, carriedComments }) => {
|
|
1289
|
+
const spanning = carriedComments ?? commentsAcrossBlockBoundary(tr.doc, at);
|
|
1290
|
+
const fragment = Fragment.from(blocks);
|
|
1291
|
+
let next = tr.insert(at, fragment);
|
|
1292
|
+
for (const comment of spanning) next = next.addMark(at, at + fragment.size, comment);
|
|
1293
|
+
return next;
|
|
1294
|
+
};
|
|
1295
|
+
/** A paragraph mark that is a pending insertion (not a relocation's end). */
|
|
1296
|
+
const isInsertedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "ins";
|
|
1297
|
+
const isSuggestedParagraphInsert = (paragraph) => typeof paragraph.attrs["_suggestedInsert"] === "object" && paragraph.attrs["_suggestedInsert"] !== null;
|
|
1298
|
+
/** A paragraph mark that is a pending deletion, or a relocation's source. */
|
|
1299
|
+
const isDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && (value.kind === "del" || value.kind === "moveFrom");
|
|
1300
|
+
const hasRevisionMarker = (value) => typeof value === "object" && value !== null && "revisionId" in value;
|
|
1301
|
+
const isDeletedCell = (cell) => {
|
|
1302
|
+
const marker = cell.attrs["cellMarker"];
|
|
1303
|
+
return typeof marker === "object" && marker !== null && "kind" in marker && marker.kind === "del";
|
|
1304
|
+
};
|
|
1305
|
+
/**
|
|
1306
|
+
* Whether accepting every change removes the paragraph at `blockFrom` whole:
|
|
1307
|
+
* its paragraph mark and all of its text pending deletion (`cleanText`, the
|
|
1308
|
+
* accepted reading, is empty), or the table row or cell holding it pending
|
|
1309
|
+
* deletion. A reader lists such a paragraph as a blank block.
|
|
1310
|
+
*
|
|
1311
|
+
* Word keeps a paragraph whose mark is deleted joined to the next one: text
|
|
1312
|
+
* typed into it stays inserted there, and accepting runs it into the next
|
|
1313
|
+
* paragraph (or, in a deleted row, takes it away with the row). An operation
|
|
1314
|
+
* that asks for the paragraph to read something cannot get that without
|
|
1315
|
+
* rejecting the deletion, which is the reviewer's call, not a side effect.
|
|
1316
|
+
*/
|
|
1317
|
+
const isPendingDeletion = (doc, blockFrom, paragraph, cleanText) => {
|
|
1318
|
+
if (cleanText.length === 0 && isDeletedPPrMark(paragraph.attrs["pPrMark"])) return true;
|
|
1319
|
+
const $block = doc.resolve(blockFrom);
|
|
1320
|
+
for (let depth = $block.depth; depth > 0; depth -= 1) {
|
|
1321
|
+
const ancestor = $block.node(depth);
|
|
1322
|
+
const name = ancestor.type.name;
|
|
1323
|
+
if (name === "tableRow" && hasRevisionMarker(ancestor.attrs["trDel"])) return true;
|
|
1324
|
+
if ((name === "tableCell" || name === "tableHeader") && isDeletedCell(ancestor)) return true;
|
|
1325
|
+
}
|
|
1326
|
+
return false;
|
|
1327
|
+
};
|
|
1328
|
+
/** Whether every piece of the paragraph's content is pending inserted text. */
|
|
1329
|
+
const holdsOnlyInsertedContent = (paragraph) => {
|
|
1330
|
+
let whollyInserted = true;
|
|
1331
|
+
paragraph.descendants((node) => {
|
|
1332
|
+
if (!whollyInserted) return false;
|
|
1333
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
1334
|
+
whollyInserted = node.marks.some((mark) => mark.type.name === "insertion" && mark.attrs["moveKind"] !== "moveTo");
|
|
1335
|
+
return false;
|
|
1336
|
+
});
|
|
1337
|
+
return whollyInserted;
|
|
1338
|
+
};
|
|
1339
|
+
/**
|
|
1340
|
+
* How to take back the paragraph at `position` when it is wholly a pending
|
|
1341
|
+
* insertion (the break that introduced it inserted, and everything in it
|
|
1342
|
+
* inserted text), or `null` when it is not. Such a paragraph goes outright:
|
|
1343
|
+
* accepting and rejecting both leave it out.
|
|
1344
|
+
*
|
|
1345
|
+
* The inserted break is the paragraph's own (an `ins` mark, or a suggested
|
|
1346
|
+
* whole-paragraph insert), except on the paragraph that ends its container:
|
|
1347
|
+
* that one keeps the container's original mark and the inserted break was
|
|
1348
|
+
* rotated onto the paragraph before it, whose `ins` mark then goes too.
|
|
1349
|
+
*/
|
|
1350
|
+
const pendingParagraphRetraction = (doc, position) => {
|
|
1351
|
+
const paragraph = doc.nodeAt(position);
|
|
1352
|
+
if (paragraph?.type.name !== "paragraph" || !holdsOnlyInsertedContent(paragraph)) return null;
|
|
1353
|
+
const at = doc.resolve(position);
|
|
1354
|
+
const before = at.nodeBefore;
|
|
1355
|
+
const endsItsContainer = paragraphEndsItsContainer(at, paragraph.type.name);
|
|
1356
|
+
if (at.parent.childCount < 2 || endsItsContainer && before?.type.name !== paragraph.type.name) return null;
|
|
1357
|
+
if (isInsertedPPrMark(paragraph.attrs["pPrMark"]) || isSuggestedParagraphInsert(paragraph)) return { clearBreakAt: null };
|
|
1358
|
+
if (endsItsContainer && before && paragraph.attrs["pPrMark"] == null) return isInsertedPPrMark(before.attrs["pPrMark"]) ? { clearBreakAt: position - before.nodeSize } : null;
|
|
1359
|
+
return null;
|
|
1360
|
+
};
|
|
959
1361
|
const addedBreakRevisionId = (value) => {
|
|
960
1362
|
if (typeof value !== "object" || value === null || !("kind" in value) || !("info" in value)) return null;
|
|
961
1363
|
if (value.kind !== "ins" && value.kind !== "moveTo") return null;
|
|
@@ -963,6 +1365,277 @@ const addedBreakRevisionId = (value) => {
|
|
|
963
1365
|
if (typeof info !== "object" || info === null || !("id" in info)) return null;
|
|
964
1366
|
return typeof info.id === "number" ? info.id : null;
|
|
965
1367
|
};
|
|
1368
|
+
/** The revision id of a paragraph mark that says its break was added or deleted. */
|
|
1369
|
+
const addedOrDeletedBreakRevisionId = (value) => {
|
|
1370
|
+
if (typeof value !== "object" || value === null || !("kind" in value) || !("info" in value)) return null;
|
|
1371
|
+
if (value.kind !== "del") return addedBreakRevisionId(value);
|
|
1372
|
+
const info = value.info;
|
|
1373
|
+
if (typeof info !== "object" || info === null || !("id" in info)) return null;
|
|
1374
|
+
return typeof info.id === "number" ? info.id : null;
|
|
1375
|
+
};
|
|
1376
|
+
/** Whether every piece of the paragraph's content is pending deleted text (or it has none). */
|
|
1377
|
+
const holdsOnlyDeletedContent = (paragraph) => {
|
|
1378
|
+
let whollyDeleted = true;
|
|
1379
|
+
paragraph.descendants((node) => {
|
|
1380
|
+
if (!whollyDeleted) return false;
|
|
1381
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
1382
|
+
whollyDeleted = node.marks.some((mark) => mark.type.name === "deletion");
|
|
1383
|
+
return false;
|
|
1384
|
+
});
|
|
1385
|
+
return whollyDeleted;
|
|
1386
|
+
};
|
|
1387
|
+
/**
|
|
1388
|
+
* Whether everything after `position` in its container is paragraphs pending
|
|
1389
|
+
* deletion: a join there, once accepted, has no words after it.
|
|
1390
|
+
*/
|
|
1391
|
+
const followedOnlyByDeletedParagraphs = (doc, position) => {
|
|
1392
|
+
const at = doc.resolve(position);
|
|
1393
|
+
let deletes = false;
|
|
1394
|
+
for (let index = at.index(); index < at.parent.childCount; index++) {
|
|
1395
|
+
const node = at.parent.child(index);
|
|
1396
|
+
if (node.type.name !== "paragraph" || !holdsOnlyDeletedContent(node)) return false;
|
|
1397
|
+
deletes ||= node.content.size > 0 || isPlainDeletedPPrMark(node.attrs["pPrMark"]);
|
|
1398
|
+
}
|
|
1399
|
+
return deletes;
|
|
1400
|
+
};
|
|
1401
|
+
/** A paragraph mark that is a plain pending deletion (not a relocation's source). */
|
|
1402
|
+
const isPlainDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "del";
|
|
1403
|
+
/** An emptied paragraph still carries deleted content, rather than being an original blank. */
|
|
1404
|
+
const hasDeletedContent = (paragraph) => {
|
|
1405
|
+
let found = false;
|
|
1406
|
+
paragraph.descendants((node) => {
|
|
1407
|
+
if (node.isInline && node.marks.some((mark) => mark.type.name === "deletion")) {
|
|
1408
|
+
found = true;
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
return !found;
|
|
1412
|
+
});
|
|
1413
|
+
return found;
|
|
1414
|
+
};
|
|
1415
|
+
/** The final paragraph reached by a chain of wholly deleted paragraphs. */
|
|
1416
|
+
const deletedFinalParagraphAfter = (at, paragraphTypeName) => {
|
|
1417
|
+
let position = at.pos;
|
|
1418
|
+
for (let index = at.index(); index < at.parent.childCount; index++) {
|
|
1419
|
+
const paragraph = at.parent.child(index);
|
|
1420
|
+
if (paragraph.type.name !== paragraphTypeName || !holdsOnlyDeletedContent(paragraph)) return null;
|
|
1421
|
+
if (index === at.parent.childCount - 1) return paragraph.attrs["pPrMark"] == null && hasDeletedContent(paragraph) ? position : null;
|
|
1422
|
+
if (!isPlainDeletedPPrMark(paragraph.attrs["pPrMark"])) return null;
|
|
1423
|
+
position += paragraph.nodeSize;
|
|
1424
|
+
}
|
|
1425
|
+
return null;
|
|
1426
|
+
};
|
|
1427
|
+
/**
|
|
1428
|
+
* Where the added break of the final paragraph at `at` rotates to: the free
|
|
1429
|
+
* mark `addedBreakCarrierBefore` finds, or else a paragraph the batch deletes
|
|
1430
|
+
* whole, whose deleted break the added one cancels (`cancelsDeletedBreak`).
|
|
1431
|
+
* A table whose rows all share one insertion revision from this batch is not
|
|
1432
|
+
* a barrier: rejecting that table removes it before the paragraphs join.
|
|
1433
|
+
* Existing and partially inserted tables remain barriers.
|
|
1434
|
+
* The cancelling paragraph must be followed directly by the paragraph its
|
|
1435
|
+
* words run into.
|
|
1436
|
+
*/
|
|
1437
|
+
const finalBreakCarrierBefore = (at, paragraphTypeName, batchRevisionIds) => {
|
|
1438
|
+
const free = addedBreakCarrierBefore({
|
|
1439
|
+
at,
|
|
1440
|
+
paragraphTypeName,
|
|
1441
|
+
canCrossTable: (table) => {
|
|
1442
|
+
if (table.childCount === 0) return false;
|
|
1443
|
+
let revisionId;
|
|
1444
|
+
for (let index = 0; index < table.childCount; index++) {
|
|
1445
|
+
const row = table.child(index);
|
|
1446
|
+
if (row.type.spec["tableRole"] !== "row") return false;
|
|
1447
|
+
const { trIns, trDel } = expectTableRowAttrs(row);
|
|
1448
|
+
if (!trIns || trDel || !batchRevisionIds.has(trIns.revisionId)) return false;
|
|
1449
|
+
revisionId ??= trIns.revisionId;
|
|
1450
|
+
if (trIns.revisionId !== revisionId) return false;
|
|
1451
|
+
}
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
if (free) return {
|
|
1456
|
+
...free,
|
|
1457
|
+
cancelsDeletedBreak: false
|
|
1458
|
+
};
|
|
1459
|
+
let position = at.pos;
|
|
1460
|
+
for (let index = at.index() - 1; index >= 0; index--) {
|
|
1461
|
+
const sibling = at.parent.child(index);
|
|
1462
|
+
position -= sibling.nodeSize;
|
|
1463
|
+
if (sibling.type.name !== paragraphTypeName) {
|
|
1464
|
+
if (sibling.type.spec["tableRole"] === "table") return null;
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
const mark = sibling.attrs["pPrMark"];
|
|
1468
|
+
const revisionId = addedBreakRevisionId(mark);
|
|
1469
|
+
if (revisionId !== null && batchRevisionIds.has(revisionId)) continue;
|
|
1470
|
+
const deletedBreakId = isPlainDeletedPPrMark(mark) ? addedOrDeletedBreakRevisionId(mark) : null;
|
|
1471
|
+
return deletedBreakId !== null && batchRevisionIds.has(deletedBreakId) && holdsOnlyDeletedContent(sibling) && at.parent.maybeChild(index + 1)?.type.name === paragraphTypeName ? {
|
|
1472
|
+
position,
|
|
1473
|
+
node: sibling,
|
|
1474
|
+
cancelsDeletedBreak: true
|
|
1475
|
+
} : null;
|
|
1476
|
+
}
|
|
1477
|
+
return null;
|
|
1478
|
+
};
|
|
1479
|
+
/** The preceding paragraph whose break a final deletion can retire. */
|
|
1480
|
+
const finalParagraphPredecessor = (doc, at) => {
|
|
1481
|
+
const emptied = doc.nodeAt(at);
|
|
1482
|
+
if (emptied?.type.name !== "paragraph") return null;
|
|
1483
|
+
const $emptied = doc.resolve(at);
|
|
1484
|
+
if (!paragraphEndsItsContainer($emptied, emptied.type.name)) return null;
|
|
1485
|
+
let position = at;
|
|
1486
|
+
for (let index = $emptied.index() - 1; index >= 0; index--) {
|
|
1487
|
+
const node = $emptied.parent.child(index);
|
|
1488
|
+
position -= node.nodeSize;
|
|
1489
|
+
if (node.type !== emptied.type) return null;
|
|
1490
|
+
if (isPlainDeletedPPrMark(node.attrs["pPrMark"]) && holdsOnlyDeletedContent(node)) continue;
|
|
1491
|
+
return {
|
|
1492
|
+
node,
|
|
1493
|
+
position
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
return null;
|
|
1497
|
+
};
|
|
1498
|
+
/** Pending retirements, read after the merge has mapped any inserted separator. */
|
|
1499
|
+
const pendingRemovedFinalBreakPositions = (tr, deletions) => {
|
|
1500
|
+
const positions = /* @__PURE__ */ new Set();
|
|
1501
|
+
for (const deletion of deletions) {
|
|
1502
|
+
const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
|
|
1503
|
+
const predecessor = finalParagraphPredecessor(tr.doc, at);
|
|
1504
|
+
if (!predecessor || predecessor.node.attrs["pPrMark"] != null) continue;
|
|
1505
|
+
positions.add(predecessor.position);
|
|
1506
|
+
}
|
|
1507
|
+
return positions;
|
|
1508
|
+
};
|
|
1509
|
+
/**
|
|
1510
|
+
* Finish the tracked deletion of every paragraph a container ends with.
|
|
1511
|
+
*
|
|
1512
|
+
* Its own mark cannot be deleted (see `paragraphEndsItsContainer`), so the
|
|
1513
|
+
* deletion emptied it and left the mark. Applied directly, the paragraph goes
|
|
1514
|
+
* whenever a paragraph precedes it, which becomes the container's last; only
|
|
1515
|
+
* a container that would be left empty or ending in a table keeps an empty
|
|
1516
|
+
* paragraph. Tracked resolves to the same: the preceding paragraph's mark is
|
|
1517
|
+
* deleted, and accepting runs that paragraph into the emptied one, keeping
|
|
1518
|
+
* its own properties since it holds the words.
|
|
1519
|
+
*
|
|
1520
|
+
* A paragraph with a break pending deletion already runs into this one. If
|
|
1521
|
+
* its content is also wholly deleted, look past it for the surviving carrier;
|
|
1522
|
+
* otherwise another final-break revision would depend on batch boundaries.
|
|
1523
|
+
* A preceding break that is itself a pending insertion is
|
|
1524
|
+
* retracted instead of deleted: the paragraph joins the emptied one now, and
|
|
1525
|
+
* records the emptied one's properties as `w:pPrChange` where they differ,
|
|
1526
|
+
* which is what rejecting reads to put them back.
|
|
1527
|
+
*
|
|
1528
|
+
* This runs once over the finished batch: which paragraph precedes the
|
|
1529
|
+
* emptied one is settled only then, since the batch runs backwards and an
|
|
1530
|
+
* insertion or deletion before it lands after it.
|
|
1531
|
+
*/
|
|
1532
|
+
const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appliedIds, batchRevisionIds, revisionSeed, author, date, styleResolver, numbering }) => {
|
|
1533
|
+
let nextRevisionId = revisionSeed;
|
|
1534
|
+
const addedRevisions = [];
|
|
1535
|
+
const retractedRevisionIds = [];
|
|
1536
|
+
const resolvedOperationIds = /* @__PURE__ */ new Set();
|
|
1537
|
+
const located = deletions.filter(({ operationId }) => appliedIds.has(operationId)).map((deletion) => ({
|
|
1538
|
+
deletion,
|
|
1539
|
+
at: tr.mapping.slice(deletion.mappedFrom).map(deletion.position)
|
|
1540
|
+
})).toSorted((left, right) => right.at - left.at);
|
|
1541
|
+
for (const { deletion: { operationId, revisionExtras }, at } of located) {
|
|
1542
|
+
const emptied = tr.doc.nodeAt(at);
|
|
1543
|
+
if (emptied?.type.name !== "paragraph") continue;
|
|
1544
|
+
const predecessor = finalParagraphPredecessor(tr.doc, at);
|
|
1545
|
+
if (!predecessor) continue;
|
|
1546
|
+
const { node: previous, position } = predecessor;
|
|
1547
|
+
const mark = previous.attrs["pPrMark"];
|
|
1548
|
+
if (mark == null) {
|
|
1549
|
+
const revisionId = nextRevisionId++;
|
|
1550
|
+
tr.setNodeAttribute(position, "pPrMark", {
|
|
1551
|
+
kind: "del",
|
|
1552
|
+
info: {
|
|
1553
|
+
id: revisionId,
|
|
1554
|
+
author,
|
|
1555
|
+
date,
|
|
1556
|
+
...revisionExtras
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
const beforeBatch = resolveParagraphChangeAttrs({
|
|
1560
|
+
node: previous,
|
|
1561
|
+
mode: "reject",
|
|
1562
|
+
boundaryCovered: true,
|
|
1563
|
+
revisionSet: batchRevisionIds,
|
|
1564
|
+
styleResolver,
|
|
1565
|
+
numbering
|
|
1566
|
+
});
|
|
1567
|
+
if (!mergedPropertyTargets.some(({ position: target, mappedFrom }) => {
|
|
1568
|
+
const mapped = tr.mapping.slice(mappedFrom).mapResult(target);
|
|
1569
|
+
return !mapped.deleted && mapped.pos === at;
|
|
1570
|
+
})) carryParagraphProperties({
|
|
1571
|
+
tr,
|
|
1572
|
+
position: at,
|
|
1573
|
+
source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
|
|
1574
|
+
styleResolver,
|
|
1575
|
+
numbering,
|
|
1576
|
+
revision: {
|
|
1577
|
+
id: revisionId,
|
|
1578
|
+
author,
|
|
1579
|
+
date,
|
|
1580
|
+
...revisionExtras
|
|
1581
|
+
},
|
|
1582
|
+
keep: propertiesSetInBatch(emptied, batchRevisionIds)
|
|
1583
|
+
});
|
|
1584
|
+
resolvedOperationIds.add(operationId);
|
|
1585
|
+
addedRevisions.push({
|
|
1586
|
+
operationId,
|
|
1587
|
+
revisionId
|
|
1588
|
+
});
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (isInsertedPPrMark(mark)) {
|
|
1592
|
+
const following = tr.doc.nodeAt(position + previous.nodeSize);
|
|
1593
|
+
if (following?.type.name !== emptied.type.name) continue;
|
|
1594
|
+
const previousFormatting = paragraphPropertiesBeforePendingChanges(following);
|
|
1595
|
+
const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
|
|
1596
|
+
const existing = expectParagraphAttrs(previous)._propertyChanges;
|
|
1597
|
+
if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) continue;
|
|
1598
|
+
joinAtParagraphMark({
|
|
1599
|
+
tr,
|
|
1600
|
+
paragraphPos: position,
|
|
1601
|
+
paragraph: previous,
|
|
1602
|
+
next: following,
|
|
1603
|
+
styleResolver
|
|
1604
|
+
});
|
|
1605
|
+
resolvedOperationIds.add(operationId);
|
|
1606
|
+
const retracted = addedBreakRevisionId(mark);
|
|
1607
|
+
if (retracted !== null) retractedRevisionIds.push(retracted);
|
|
1608
|
+
const revisionId = nextRevisionId;
|
|
1609
|
+
if (carryParagraphProperties({
|
|
1610
|
+
tr,
|
|
1611
|
+
position,
|
|
1612
|
+
source: previous,
|
|
1613
|
+
styleResolver,
|
|
1614
|
+
numbering,
|
|
1615
|
+
revision: {
|
|
1616
|
+
id: revisionId,
|
|
1617
|
+
author,
|
|
1618
|
+
date,
|
|
1619
|
+
...revisionExtras
|
|
1620
|
+
},
|
|
1621
|
+
keep: propertiesSetInBatch(following, batchRevisionIds)
|
|
1622
|
+
}).tracked) {
|
|
1623
|
+
nextRevisionId++;
|
|
1624
|
+
addedRevisions.push({
|
|
1625
|
+
operationId,
|
|
1626
|
+
revisionId
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
nextRevisionId,
|
|
1634
|
+
resolvedOperationIds,
|
|
1635
|
+
addedRevisions,
|
|
1636
|
+
retractedRevisionIds
|
|
1637
|
+
};
|
|
1638
|
+
};
|
|
966
1639
|
/**
|
|
967
1640
|
* Move an ADDED paragraph break off every paragraph its container ends with.
|
|
968
1641
|
*
|
|
@@ -985,8 +1658,52 @@ const addedBreakRevisionId = (value) => {
|
|
|
985
1658
|
* which paragraph ends a container is only settled when the batch is, and an
|
|
986
1659
|
* insertion that looked final was undone by the next operation writing a table
|
|
987
1660
|
* after it.
|
|
1661
|
+
* A wholly inserted table from this batch can be crossed to reach the carrier;
|
|
1662
|
+
* its removal on reject exposes that same paragraph boundary again.
|
|
1663
|
+
*
|
|
1664
|
+
* The paragraph the run was appended after may be one the same batch deletes:
|
|
1665
|
+
* the insertion landed first, so the deletion took that paragraph's mark as
|
|
1666
|
+
* well. The break rotating onto it then cancels the deleted one, as deleting a
|
|
1667
|
+
* paragraph whose break is inserted does one operation at a time: the deleted
|
|
1668
|
+
* paragraph's words run into the one after it, which ends with the free mark,
|
|
1669
|
+
* and neither break is left to resolve.
|
|
988
1670
|
*/
|
|
989
|
-
const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials }) => {
|
|
1671
|
+
const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials, styleResolver }) => {
|
|
1672
|
+
const supersededDeletedBreaks = [];
|
|
1673
|
+
const collectSupersededDeletedBreaks = (parent, contentStart) => {
|
|
1674
|
+
let previous = null;
|
|
1675
|
+
parent.forEach((node, offset) => {
|
|
1676
|
+
const position = contentStart + offset;
|
|
1677
|
+
if (node.type.name !== "paragraph") {
|
|
1678
|
+
previous = null;
|
|
1679
|
+
if (!node.isTextblock && node.childCount > 0) collectSupersededDeletedBreaks(node, position + 1);
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (previous) {
|
|
1683
|
+
const deletedMark = previous.node.attrs["pPrMark"];
|
|
1684
|
+
const insertedMark = node.attrs["pPrMark"];
|
|
1685
|
+
const insertedRevisionId = addedBreakRevisionId(insertedMark);
|
|
1686
|
+
const trackedInsertion = insertedRevisionId !== null && batchRevisionIds.has(insertedRevisionId);
|
|
1687
|
+
const suggestedInsertion = isSuggestedParagraphInsert(node);
|
|
1688
|
+
const deletedBreakRevisionId = addedOrDeletedBreakRevisionId(deletedMark);
|
|
1689
|
+
if (isPlainDeletedPPrMark(deletedMark) && deletedBreakRevisionId !== null && !batchRevisionIds.has(deletedBreakRevisionId) && !holdsOnlyDeletedContent(previous.node) && (trackedInsertion || suggestedInsertion) && holdsOnlyInsertedContent(node)) supersededDeletedBreaks.push({
|
|
1690
|
+
preceding: previous.position,
|
|
1691
|
+
inserted: position,
|
|
1692
|
+
deletedMark,
|
|
1693
|
+
precedingMark: trackedInsertion ? insertedMark : null
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
previous = {
|
|
1697
|
+
node,
|
|
1698
|
+
position
|
|
1699
|
+
};
|
|
1700
|
+
});
|
|
1701
|
+
};
|
|
1702
|
+
collectSupersededDeletedBreaks(tr.doc, 0);
|
|
1703
|
+
for (const { preceding, inserted, deletedMark, precedingMark } of supersededDeletedBreaks) {
|
|
1704
|
+
tr.setNodeAttribute(preceding, "pPrMark", precedingMark);
|
|
1705
|
+
tr.setNodeAttribute(inserted, "pPrMark", deletedMark);
|
|
1706
|
+
}
|
|
990
1707
|
const paragraphTypeName = tr.doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
|
|
991
1708
|
const rotations = finalParagraphsOf(tr.doc, paragraphTypeName).flatMap(({ node, position }) => {
|
|
992
1709
|
const mark = node.attrs["pPrMark"];
|
|
@@ -997,13 +1714,14 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
997
1714
|
ownerRevisionId: revisionId,
|
|
998
1715
|
position
|
|
999
1716
|
}] : [];
|
|
1000
|
-
});
|
|
1717
|
+
}).toSorted((left, right) => right.position - left.position);
|
|
1001
1718
|
let next = tr;
|
|
1002
1719
|
let nextRevisionId = revisionSeed;
|
|
1003
1720
|
const synthesizedRevisions = [];
|
|
1721
|
+
const retractedRevisionIds = [];
|
|
1004
1722
|
for (const { position: finalPosition, node: final, mark: finalMark } of rotations) {
|
|
1005
1723
|
const resolved = next.doc.resolve(finalPosition);
|
|
1006
|
-
const carrier =
|
|
1724
|
+
const carrier = finalBreakCarrierBefore(resolved, final.type.name, batchRevisionIds);
|
|
1007
1725
|
if (!carrier) {
|
|
1008
1726
|
next = next.setNodeAttribute(finalPosition, "pPrMark", null);
|
|
1009
1727
|
continue;
|
|
@@ -1031,7 +1749,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1031
1749
|
const revisionId = addedBreakRevisionId(node.attrs["pPrMark"]);
|
|
1032
1750
|
return revisionId === null || !batchRevisionIds.has(revisionId);
|
|
1033
1751
|
})) continue;
|
|
1034
|
-
const previousFormatting =
|
|
1752
|
+
const previousFormatting = paragraphPropertiesBeforePendingChanges(carrier.node);
|
|
1035
1753
|
next = next.setNodeAttribute(finalPosition, "pPrMark", null);
|
|
1036
1754
|
let carriedMark = finalMark;
|
|
1037
1755
|
for (let index = 1; index < path.length; index++) {
|
|
@@ -1041,7 +1759,8 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1041
1759
|
const ownerRevisionId = addedBreakRevisionId(carriedMark);
|
|
1042
1760
|
if (ownerRevisionId === null || !batchRevisionIds.has(ownerRevisionId)) panic("A final-mark rotation crossed a break outside its operation batch", { ownerRevisionId });
|
|
1043
1761
|
const displacedMark = previous.node.attrs["pPrMark"];
|
|
1044
|
-
|
|
1762
|
+
const cancels = carrier.cancelsDeletedBreak && previous.position === carrier.position;
|
|
1763
|
+
if (!cancels) next = next.setNodeAttribute(previous.position, "pPrMark", carriedMark);
|
|
1045
1764
|
const currentFormatting = paragraphPropertiesSnapshot(current.node);
|
|
1046
1765
|
if (JSON.stringify(previousFormatting) !== JSON.stringify(currentFormatting)) {
|
|
1047
1766
|
const liveCurrent = next.doc.nodeAt(current.position);
|
|
@@ -1067,6 +1786,22 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1067
1786
|
revisionId
|
|
1068
1787
|
});
|
|
1069
1788
|
}
|
|
1789
|
+
if (cancels) {
|
|
1790
|
+
const deleted = next.doc.nodeAt(previous.position);
|
|
1791
|
+
const following = next.doc.nodeAt(current.position);
|
|
1792
|
+
if (!deleted || !following) panic("A cancelled final-mark rotation lost its paragraphs", { position: previous.position });
|
|
1793
|
+
joinAtParagraphMark({
|
|
1794
|
+
tr: next,
|
|
1795
|
+
paragraphPos: previous.position,
|
|
1796
|
+
paragraph: deleted,
|
|
1797
|
+
next: following,
|
|
1798
|
+
styleResolver
|
|
1799
|
+
});
|
|
1800
|
+
retractedRevisionIds.push(ownerRevisionId);
|
|
1801
|
+
const deletedBreakId = addedOrDeletedBreakRevisionId(displacedMark);
|
|
1802
|
+
if (deletedBreakId !== null) retractedRevisionIds.push(deletedBreakId);
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1070
1805
|
if (displacedMark == null) break;
|
|
1071
1806
|
carriedMark = displacedMark;
|
|
1072
1807
|
}
|
|
@@ -1074,10 +1809,26 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
1074
1809
|
return {
|
|
1075
1810
|
transaction: next,
|
|
1076
1811
|
nextRevisionId,
|
|
1077
|
-
synthesizedRevisions
|
|
1812
|
+
synthesizedRevisions,
|
|
1813
|
+
retractedRevisionIds
|
|
1078
1814
|
};
|
|
1079
1815
|
};
|
|
1080
|
-
const
|
|
1816
|
+
const restyledListAttrs = ({ attrs, styleNumbering, removesNumbering, numbering }) => {
|
|
1817
|
+
const stated = readParagraphNumberingAttr(attrs["numPr"]);
|
|
1818
|
+
const statedByOldStyle = isStyleSourcedParagraphNumbering(stated, readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1819
|
+
const fromStyle = styleNumbering === void 0 ? null : paragraphNumberingAttr(styleNumbering);
|
|
1820
|
+
const numPr = removesNumbering ? removedNumberingAttr(fromStyle) : stated !== null && !statedByOldStyle ? stated : fromStyle;
|
|
1821
|
+
return {
|
|
1822
|
+
...CLEARED_LIST_RENDERING_ATTRS,
|
|
1823
|
+
...numPr?.kind === "reference" && listAttrsFromNumbering({
|
|
1824
|
+
numId: numPr.numId,
|
|
1825
|
+
ilvl: numPr.ilvl ?? 0
|
|
1826
|
+
}, numbering),
|
|
1827
|
+
numPr,
|
|
1828
|
+
numPrFromStyle: fromStyle
|
|
1829
|
+
};
|
|
1830
|
+
};
|
|
1831
|
+
const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, author, date, initials, commentMark, suggestionId, revisionSeed, isPairedMove, numbering, styleResolver }) => {
|
|
1081
1832
|
const operation = item.operation;
|
|
1082
1833
|
if (operation.type !== "insertAfterBlock" && operation.type !== "insertBeforeBlock") panic("Only paragraph insertions can build inserted paragraphs", { type: operation.type });
|
|
1083
1834
|
const insertionType = schema.marks["insertion"];
|
|
@@ -1131,11 +1882,11 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1131
1882
|
}) : buildEmphasisInlineContent(schema, text, marks);
|
|
1132
1883
|
const attrs = formatsParagraph ? { ...baseAttrs } : {};
|
|
1133
1884
|
if (isFirstParagraph && operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
|
|
1134
|
-
const explicitNumbering = operation.numbering;
|
|
1885
|
+
const explicitNumbering = operation.numbering === void 0 || operation.numbering === null ? operation.numbering : concreteListReference(operation.numbering);
|
|
1135
1886
|
const listLevel = operation.listLevel;
|
|
1136
1887
|
if (formatsParagraph) {
|
|
1137
1888
|
if (explicitNumbering === null) {
|
|
1138
|
-
attrs["numPr"] =
|
|
1889
|
+
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1139
1890
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
1140
1891
|
} else if (explicitNumbering !== void 0) {
|
|
1141
1892
|
Object.assign(attrs, listLevelAttrPatch(operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode), {
|
|
@@ -1147,7 +1898,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1147
1898
|
attrs["numPrFromStyle"] = null;
|
|
1148
1899
|
}
|
|
1149
1900
|
} else if (listLevel === null) {
|
|
1150
|
-
attrs["numPr"] =
|
|
1901
|
+
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
1151
1902
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
1152
1903
|
} else if (typeof listLevel === "number") {
|
|
1153
1904
|
const numId = paragraphNumberingReferenceId(readParagraphNumberingAttr(Reflect.get(baseAttrs, "numPr")) ?? void 0);
|
|
@@ -1165,9 +1916,24 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1165
1916
|
}
|
|
1166
1917
|
if (formatsParagraph && operation.styleId !== void 0) {
|
|
1167
1918
|
attrs["styleId"] = operation.styleId;
|
|
1168
|
-
|
|
1919
|
+
attrs["_resolvedFormatting"] = styleResolvedParagraphFormatting(formattingFromStyle);
|
|
1920
|
+
attrs["defaultTextFormatting"] = resolveParagraphBodyRunFormatting({
|
|
1921
|
+
styleId: operation.styleId ?? void 0,
|
|
1922
|
+
tableRunFormatting: expectParagraphAttrs(item.blockNode)._tableRunFormatting,
|
|
1923
|
+
styleResolver
|
|
1924
|
+
}).defaultFormatting ?? null;
|
|
1925
|
+
if (operation.inheritFormatting !== false && operation.styleId !== null) {
|
|
1926
|
+
Object.assign(attrs, restyledListAttrs({
|
|
1927
|
+
attrs,
|
|
1928
|
+
styleNumbering: formattingFromStyle?.numPr,
|
|
1929
|
+
removesNumbering: explicitNumbering === null || explicitNumbering === void 0 && listLevel === null,
|
|
1930
|
+
numbering
|
|
1931
|
+
}));
|
|
1932
|
+
attrs["outlineLevel"] = attrs["_originalFormatting"]?.outlineLevel ?? formattingFromStyle?.outlineLevel ?? null;
|
|
1933
|
+
}
|
|
1169
1934
|
}
|
|
1170
|
-
if (formatsParagraph &&
|
|
1935
|
+
if (formatsParagraph && operation.outlineLevel !== void 0) attrs["outlineLevel"] = operation.outlineLevel ?? formattingFromStyle?.outlineLevel ?? null;
|
|
1936
|
+
if (formatsParagraph && (operation.styleId !== void 0 || operation.outlineLevel !== void 0 || operation.alignment !== void 0 || operation.spacing !== void 0 || operation.indentation !== void 0)) {
|
|
1171
1937
|
const inheritedDirectAlignment = operation.inheritFormatting === false ? void 0 : directParagraphAlignment(expectParagraphAttrs(item.blockNode));
|
|
1172
1938
|
const inheritedDirectSpacing = operation.inheritFormatting === false ? void 0 : directParagraphSpacing(expectParagraphAttrs(item.blockNode));
|
|
1173
1939
|
const inheritedDirectIndentation = operation.inheritFormatting === false ? void 0 : directParagraphIndentation(expectParagraphAttrs(item.blockNode));
|
|
@@ -1183,6 +1949,11 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1183
1949
|
if (operation.styleId === null) Reflect.deleteProperty(originalFormatting, "styleId");
|
|
1184
1950
|
else originalFormatting.styleId = operation.styleId;
|
|
1185
1951
|
}
|
|
1952
|
+
if (operation.outlineLevel !== void 0) {
|
|
1953
|
+
originalFormatting ??= {};
|
|
1954
|
+
if (operation.outlineLevel === null) Reflect.deleteProperty(originalFormatting, "outlineLevel");
|
|
1955
|
+
else originalFormatting.outlineLevel = operation.outlineLevel;
|
|
1956
|
+
}
|
|
1186
1957
|
if (operation.alignment !== void 0 || inheritedDirectAlignment !== void 0) {
|
|
1187
1958
|
originalFormatting ??= {};
|
|
1188
1959
|
if (directAlignment === void 0) Reflect.deleteProperty(originalFormatting, "alignment");
|
|
@@ -1200,6 +1971,17 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1200
1971
|
}));
|
|
1201
1972
|
attrs["_originalFormatting"] = originalFormatting && Object.keys(originalFormatting).length > 0 ? originalFormatting : null;
|
|
1202
1973
|
}
|
|
1974
|
+
if (formatsParagraph && operation.styleId !== void 0 && formattingFromStyle?.numPr?.kind === "reference" && operation.numbering === void 0 && operation.listLevel === void 0) {
|
|
1975
|
+
const { numId, ilvl = 0 } = formattingFromStyle.numPr;
|
|
1976
|
+
const levelIndentation = numbering?.getLevel(numId, ilvl)?.pPr;
|
|
1977
|
+
if (levelIndentation && operation.indentation === void 0) {
|
|
1978
|
+
const direct = attrs["_originalFormatting"];
|
|
1979
|
+
const directIndentLeft = typeof direct === "object" && direct !== null && "indentLeft" in direct ? direct.indentLeft : void 0;
|
|
1980
|
+
const directIndentFirstLine = typeof direct === "object" && direct !== null && "indentFirstLine" in direct ? direct.indentFirstLine : void 0;
|
|
1981
|
+
if (directIndentLeft === void 0 && formattingFromStyle.indentLeft === void 0) attrs["indentLeft"] = levelIndentation.indentLeft ?? attrs["indentLeft"];
|
|
1982
|
+
if (directIndentFirstLine === void 0 && formattingFromStyle.indentFirstLine === void 0) attrs["indentFirstLine"] = levelIndentation.indentFirstLine ?? attrs["indentFirstLine"];
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1203
1985
|
if (isSuggested && suggestionId !== null && paragraphRevisionId !== null) attrs["_suggestedInsert"] = {
|
|
1204
1986
|
suggestionId,
|
|
1205
1987
|
revisionId: paragraphRevisionId,
|
|
@@ -1207,7 +1989,28 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1207
1989
|
date,
|
|
1208
1990
|
...initials ? { initials } : {}
|
|
1209
1991
|
};
|
|
1210
|
-
|
|
1992
|
+
const paragraph = item.blockNode.type.create(attrs, content);
|
|
1993
|
+
const sourceContext = OPERATION_TEXT_CONTEXT;
|
|
1994
|
+
const targetContext = paragraphRunStyleContext(paragraph, styleResolver);
|
|
1995
|
+
const reconciled = [];
|
|
1996
|
+
paragraph.forEach((child) => {
|
|
1997
|
+
if (!child.isInline) {
|
|
1998
|
+
reconciled.push(child);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
const reconciledMarks = reconcileRunFormattingMarks({
|
|
2002
|
+
authoredFormatting: readAuthoredRunFormatting({
|
|
2003
|
+
context: sourceContext,
|
|
2004
|
+
marks: child.marks,
|
|
2005
|
+
styleResolver
|
|
2006
|
+
}),
|
|
2007
|
+
context: targetContext,
|
|
2008
|
+
node: child,
|
|
2009
|
+
styleResolver
|
|
2010
|
+
});
|
|
2011
|
+
reconciled.push(child.mark(reconciledMarks));
|
|
2012
|
+
});
|
|
2013
|
+
nodes.push(paragraph.copy(Fragment.fromArray(reconciled)));
|
|
1211
2014
|
}
|
|
1212
2015
|
if (producesTrackedChanges && !isSuggested) for (const [index, node] of nodes.entries()) {
|
|
1213
2016
|
const revisionId = nextRevisionId++;
|
|
@@ -1231,7 +2034,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1231
2034
|
nextRevisionId
|
|
1232
2035
|
};
|
|
1233
2036
|
};
|
|
1234
|
-
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates, replacementBackground = "clear" }) => {
|
|
2037
|
+
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: requestedOperations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates, replacementBackground = "clear", undefinedStyles = "refuse" }) => {
|
|
1235
2038
|
const applied = [];
|
|
1236
2039
|
const skipped = [];
|
|
1237
2040
|
const normalizations = [];
|
|
@@ -1263,7 +2066,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1263
2066
|
return id;
|
|
1264
2067
|
};
|
|
1265
2068
|
const styleResolver = getDocumentStyleResolver(view.state);
|
|
1266
|
-
const
|
|
2069
|
+
const styleDefinitions = undefinedStyles === "keep" ? null : getDocumentStyleDefinitions(view.state);
|
|
2070
|
+
const { operations, numbering } = resolveNewListOperations(requestedOperations, getDocumentNumbering(view.state));
|
|
1267
2071
|
const formattingFromStyleForInsertion = (item) => {
|
|
1268
2072
|
if (item.operation.type !== "insertAfterBlock" && item.operation.type !== "insertBeforeBlock") return;
|
|
1269
2073
|
const anchorAttrs = expectParagraphAttrs(item.blockNode);
|
|
@@ -1277,6 +2081,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1277
2081
|
};
|
|
1278
2082
|
const claimedTableRows = /* @__PURE__ */ new Set();
|
|
1279
2083
|
const claimedTableColumns = /* @__PURE__ */ new Set();
|
|
2084
|
+
const batchClaims = new BatchClaims();
|
|
1280
2085
|
const diffText = wordDiffMode === "coarse" ? coarseWordDiff : wordDiffSessionFromOptions(wordDiff).diff;
|
|
1281
2086
|
const producesTrackedChanges = mode !== "direct";
|
|
1282
2087
|
const isSuggested = mode === "suggested";
|
|
@@ -1290,6 +2095,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1290
2095
|
};
|
|
1291
2096
|
const liveBlocks = collectLiveBlocksByHash(view.state.doc);
|
|
1292
2097
|
const liveBlocksByParaId = collectLiveBlocksByParaId(view.state.doc);
|
|
2098
|
+
const noteReferences = collectNoteReferenceLabels(view.state.doc);
|
|
2099
|
+
const numberingInstanceIds = getDocumentNumberingInstanceIds(view.state) === null ? null : new Set(numbering?.definitions.nums.map(({ numId }) => numId) ?? []);
|
|
1293
2100
|
for (const [index, operation] of operations.entries()) {
|
|
1294
2101
|
const commentText = getOperationCommentText(operation);
|
|
1295
2102
|
if (commentText !== void 0 && (!commentType || createCommentId === void 0)) {
|
|
@@ -1304,12 +2111,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1304
2111
|
operation,
|
|
1305
2112
|
liveBlocks,
|
|
1306
2113
|
liveBlocksByParaId,
|
|
2114
|
+
noteReferences,
|
|
1307
2115
|
doc: view.state.doc
|
|
1308
2116
|
});
|
|
1309
2117
|
if (resolution.type === "skip") {
|
|
1310
2118
|
skipped.push({
|
|
1311
2119
|
id: operation.id,
|
|
1312
|
-
reason: resolution.reason
|
|
2120
|
+
reason: resolution.reason,
|
|
2121
|
+
...resolution.message !== void 0 && { message: resolution.message }
|
|
1313
2122
|
});
|
|
1314
2123
|
continue;
|
|
1315
2124
|
}
|
|
@@ -1320,6 +2129,24 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1320
2129
|
});
|
|
1321
2130
|
continue;
|
|
1322
2131
|
}
|
|
2132
|
+
const undefinedStyle = findUndefinedParagraphStyleReference(operation, styleDefinitions);
|
|
2133
|
+
if (undefinedStyle !== void 0) {
|
|
2134
|
+
skipped.push({
|
|
2135
|
+
id: operation.id,
|
|
2136
|
+
reason: "missingStyle",
|
|
2137
|
+
message: describeUndefinedParagraphStyle(undefinedStyle, styleDefinitions)
|
|
2138
|
+
});
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
const undefinedNumbering = findUndefinedNumberingReference(operation, numberingInstanceIds);
|
|
2142
|
+
if (undefinedNumbering !== void 0) {
|
|
2143
|
+
skipped.push({
|
|
2144
|
+
id: operation.id,
|
|
2145
|
+
reason: "missingNumbering",
|
|
2146
|
+
message: describeUndefinedNumbering(undefinedNumbering, numberingInstanceIds)
|
|
2147
|
+
});
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
1323
2150
|
const deletion = resolution.operation.tableRowDeletion;
|
|
1324
2151
|
if (deletion) {
|
|
1325
2152
|
const rowKey = `${deletion.tablePosition}:${deletion.rowIndex}`;
|
|
@@ -1330,7 +2157,6 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1330
2157
|
});
|
|
1331
2158
|
continue;
|
|
1332
2159
|
}
|
|
1333
|
-
claimedTableRows.add(rowKey);
|
|
1334
2160
|
}
|
|
1335
2161
|
const columnDeletion = resolution.operation.tableColumnDeletion;
|
|
1336
2162
|
if (columnDeletion) {
|
|
@@ -1342,8 +2168,24 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1342
2168
|
});
|
|
1343
2169
|
continue;
|
|
1344
2170
|
}
|
|
1345
|
-
claimedTableColumns.add(columnKey);
|
|
1346
2171
|
}
|
|
2172
|
+
const claim = batchClaimOf({
|
|
2173
|
+
item: resolution.operation,
|
|
2174
|
+
doc: view.state.doc,
|
|
2175
|
+
producesTrackedChanges
|
|
2176
|
+
});
|
|
2177
|
+
const claimedBy = batchClaims.conflictOf(claim);
|
|
2178
|
+
if (claimedBy !== null) {
|
|
2179
|
+
skipped.push({
|
|
2180
|
+
id: operation.id,
|
|
2181
|
+
reason: "overlappingOperation",
|
|
2182
|
+
message: `operation ${JSON.stringify(claimedBy)}, earlier in this batch, already claims its target.`
|
|
2183
|
+
});
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
batchClaims.add(operation.id, claim);
|
|
2187
|
+
if (deletion) claimedTableRows.add(`${deletion.tablePosition}:${deletion.rowIndex}`);
|
|
2188
|
+
if (columnDeletion) claimedTableColumns.add(getTableColumnCoordinateKey(columnDeletion));
|
|
1347
2189
|
if ((operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") && operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text)) normalizations.push({
|
|
1348
2190
|
id: operation.id,
|
|
1349
2191
|
code: "splitMultilineText",
|
|
@@ -1394,6 +2236,12 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1394
2236
|
});
|
|
1395
2237
|
}
|
|
1396
2238
|
let tr = view.state.tr;
|
|
2239
|
+
/** Text boxes a tracked block deletion deletes, by anchor. */
|
|
2240
|
+
const deletedTextBoxAnchorIds = /* @__PURE__ */ new Set();
|
|
2241
|
+
/** Container-ending paragraphs a tracked `deleteBlock` emptied: see `withRetiredFinalParagraphs`. */
|
|
2242
|
+
const deletedFinalParagraphs = [];
|
|
2243
|
+
const mergedPropertyTargets = [];
|
|
2244
|
+
const deferredNoopFinalDeletions = /* @__PURE__ */ new Set();
|
|
1397
2245
|
const ownsSharedRevisionIdCursor = revisionIdSeed === void 0 && revisionStamp === void 0;
|
|
1398
2246
|
let revisionSeed = revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor;
|
|
1399
2247
|
/**
|
|
@@ -1409,7 +2257,31 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1409
2257
|
};
|
|
1410
2258
|
const date = revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1411
2259
|
const insertedColumnCounts = /* @__PURE__ */ new Map();
|
|
2260
|
+
const emptiedRows = { pending: false };
|
|
2261
|
+
const tableAfterDeletedSplit = /* @__PURE__ */ new Map();
|
|
2262
|
+
if (mode === "tracked-changes") {
|
|
2263
|
+
const afterTables = /* @__PURE__ */ new Map();
|
|
2264
|
+
for (const item of executableResolved) {
|
|
2265
|
+
if (item.operation.type !== "insertTable" || item.operation.position === "before") continue;
|
|
2266
|
+
const boundaries = afterTables.get(item.blockFrom) ?? /* @__PURE__ */ new Set();
|
|
2267
|
+
boundaries.add(item.from);
|
|
2268
|
+
afterTables.set(item.blockFrom, boundaries);
|
|
2269
|
+
}
|
|
2270
|
+
for (const item of executableResolved) {
|
|
2271
|
+
if (item.operation.type !== "deleteBlock") continue;
|
|
2272
|
+
if (!isInsertedPPrMark(item.blockNode.attrs["pPrMark"])) continue;
|
|
2273
|
+
const at = view.state.doc.resolve(item.blockFrom);
|
|
2274
|
+
if (at.parent.maybeChild(at.index() + 1)?.type !== item.blockNode.type) continue;
|
|
2275
|
+
if (afterTables.get(item.blockFrom)?.has(item.blockTo)) tableAfterDeletedSplit.set(item.blockFrom, commentsAcrossBlockBoundary(view.state.doc, item.blockTo));
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
1412
2278
|
const executionOrder = executableResolved.toSorted((left, right) => {
|
|
2279
|
+
if (left.blockFrom === right.blockFrom && tableAfterDeletedSplit.has(left.blockFrom)) {
|
|
2280
|
+
if (left.operation.type === "deleteBlock" && right.operation.type === "insertTable" && right.from === left.blockTo) return -1;
|
|
2281
|
+
if (left.operation.type === "insertTable" && right.operation.type === "deleteBlock" && left.from === right.blockTo) return 1;
|
|
2282
|
+
}
|
|
2283
|
+
const leftAnnotates = ANNOTATION_OPERATION_TYPES.has(left.operation.type);
|
|
2284
|
+
if (leftAnnotates !== ANNOTATION_OPERATION_TYPES.has(right.operation.type)) return leftAnnotates ? -1 : 1;
|
|
1413
2285
|
const leftCellShape = left.tableCellMerge ?? left.tableCellSplit;
|
|
1414
2286
|
const rightCellShape = right.tableCellMerge ?? right.tableCellSplit;
|
|
1415
2287
|
if (!leftCellShape && rightCellShape) return -1;
|
|
@@ -1473,7 +2345,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1473
2345
|
suggestionId: insertionSuggestionId,
|
|
1474
2346
|
revisionSeed,
|
|
1475
2347
|
isPairedMove,
|
|
1476
|
-
numbering
|
|
2348
|
+
numbering,
|
|
2349
|
+
styleResolver
|
|
1477
2350
|
});
|
|
1478
2351
|
revisionSeed = built.nextRevisionId;
|
|
1479
2352
|
claimSharedRevisionIds(revisionSeed);
|
|
@@ -1499,6 +2372,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1499
2372
|
const commentMark = provisionalCommentId !== void 0 && commentType ? commentType.create({ commentId: provisionalCommentId }) : null;
|
|
1500
2373
|
const stepsBefore = tr.steps.length;
|
|
1501
2374
|
let appliedRevisionIds = [];
|
|
2375
|
+
let unifiedFormatting = false;
|
|
1502
2376
|
if (isSuggested && !SUGGESTED_SUPPORTED_OPERATION_TYPES.has(item.operation.type)) {
|
|
1503
2377
|
skipped.push({
|
|
1504
2378
|
id: item.operation.id,
|
|
@@ -1528,13 +2402,15 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1528
2402
|
const minimal = applyMinimalDirectReplacement({
|
|
1529
2403
|
tr,
|
|
1530
2404
|
item,
|
|
2405
|
+
noteReferences,
|
|
1531
2406
|
replacement: item.operation.replace,
|
|
1532
2407
|
commentMark,
|
|
1533
2408
|
replacementBackground,
|
|
1534
2409
|
...styleResolver !== void 0 ? { styleResolver } : {}
|
|
1535
2410
|
});
|
|
1536
2411
|
if (minimal !== null) {
|
|
1537
|
-
tr = minimal;
|
|
2412
|
+
tr = minimal.transaction;
|
|
2413
|
+
unifiedFormatting = minimal.unifiesFormatting;
|
|
1538
2414
|
break;
|
|
1539
2415
|
}
|
|
1540
2416
|
}
|
|
@@ -1545,6 +2421,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1545
2421
|
const tracked = applyMinimalTrackedReplacement({
|
|
1546
2422
|
tr,
|
|
1547
2423
|
item,
|
|
2424
|
+
noteReferences,
|
|
1548
2425
|
replacement: stripInlineEmphasisMarkers(item.operation.replace),
|
|
1549
2426
|
commentMark,
|
|
1550
2427
|
replacementBackground,
|
|
@@ -1568,6 +2445,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1568
2445
|
if (tracked) {
|
|
1569
2446
|
tr = tracked.transaction;
|
|
1570
2447
|
operationRevisionSeed = tracked.nextRevisionId;
|
|
2448
|
+
unifiedFormatting = tracked.unifiesFormatting;
|
|
1571
2449
|
appliedRevisionIds = [
|
|
1572
2450
|
revisionIdDelete,
|
|
1573
2451
|
revisionIdInsert,
|
|
@@ -1576,6 +2454,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1576
2454
|
break;
|
|
1577
2455
|
}
|
|
1578
2456
|
}
|
|
2457
|
+
if (rangeHoldsNoteReference(item)) {
|
|
2458
|
+
skipped.push({
|
|
2459
|
+
id: item.operation.id,
|
|
2460
|
+
reason: "protectedReference"
|
|
2461
|
+
});
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
1579
2464
|
const stepsBeforeBackgroundClear = tr.steps.length;
|
|
1580
2465
|
const backgroundResult = clearReplacementBackground({
|
|
1581
2466
|
tr,
|
|
@@ -1705,6 +2590,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1705
2590
|
const minimal = changesText && mode === "direct" ? applyMinimalDirectReplacement({
|
|
1706
2591
|
tr,
|
|
1707
2592
|
item,
|
|
2593
|
+
noteReferences,
|
|
1708
2594
|
replacement: item.operation.text,
|
|
1709
2595
|
commentMark,
|
|
1710
2596
|
replacementBackground,
|
|
@@ -1713,6 +2599,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1713
2599
|
const tracked = changesText && producesTrackedChanges ? applyMinimalTrackedReplacement({
|
|
1714
2600
|
tr,
|
|
1715
2601
|
item,
|
|
2602
|
+
noteReferences,
|
|
1716
2603
|
replacement: stripInlineEmphasisMarkers(item.operation.text),
|
|
1717
2604
|
commentMark,
|
|
1718
2605
|
replacementBackground,
|
|
@@ -1733,13 +2620,30 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1733
2620
|
});
|
|
1734
2621
|
continue;
|
|
1735
2622
|
}
|
|
1736
|
-
if (minimal !== null)
|
|
1737
|
-
|
|
2623
|
+
if (minimal !== null) {
|
|
2624
|
+
tr = minimal.transaction;
|
|
2625
|
+
unifiedFormatting = minimal.unifiesFormatting;
|
|
2626
|
+
} else if (tracked !== null) {
|
|
1738
2627
|
tr = tracked.transaction;
|
|
1739
2628
|
operationRevisionSeed = tracked.nextRevisionId;
|
|
2629
|
+
unifiedFormatting = tracked.unifiesFormatting;
|
|
1740
2630
|
clearedBackground = tracked.backgroundRevisionIds.length > 0;
|
|
1741
2631
|
backgroundRevisionIds = tracked.backgroundRevisionIds;
|
|
1742
2632
|
} else if (changesText) {
|
|
2633
|
+
if (rangeHoldsNoteReference(item)) {
|
|
2634
|
+
skipped.push({
|
|
2635
|
+
id: item.operation.id,
|
|
2636
|
+
reason: "protectedReference"
|
|
2637
|
+
});
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (buildCleanBlockText(item.blockNode, item.blockFrom).structuralBoundaries.length > 0) {
|
|
2641
|
+
skipped.push({
|
|
2642
|
+
id: item.operation.id,
|
|
2643
|
+
reason: "unsupportedBlock"
|
|
2644
|
+
});
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
1743
2647
|
const stepsBeforeBackgroundClear = tr.steps.length;
|
|
1744
2648
|
const backgroundResult = clearReplacementBackground({
|
|
1745
2649
|
tr,
|
|
@@ -1802,6 +2706,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1802
2706
|
}
|
|
1803
2707
|
case "insertAfterBlock":
|
|
1804
2708
|
case "insertBeforeBlock": {
|
|
2709
|
+
if (isSuggested && isPendingDeletion(tr.doc, item.blockFrom, item.blockNode, buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
2710
|
+
fieldResults: "text",
|
|
2711
|
+
noteReferences
|
|
2712
|
+
}).text)) {
|
|
2713
|
+
skipped.push({
|
|
2714
|
+
id: item.operation.id,
|
|
2715
|
+
reason: "pendingDeletion"
|
|
2716
|
+
});
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
1805
2719
|
const built = buildInsertedParagraphs({
|
|
1806
2720
|
item,
|
|
1807
2721
|
schema: view.state.schema,
|
|
@@ -1814,11 +2728,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1814
2728
|
suggestionId,
|
|
1815
2729
|
revisionSeed: operationRevisionSeed,
|
|
1816
2730
|
isPairedMove,
|
|
1817
|
-
numbering
|
|
2731
|
+
numbering,
|
|
2732
|
+
styleResolver
|
|
1818
2733
|
});
|
|
1819
2734
|
operationRevisionSeed = built.nextRevisionId;
|
|
1820
2735
|
if (built.revisionIds.length > 0) appliedRevisionIds = built.revisionIds;
|
|
1821
|
-
tr =
|
|
2736
|
+
tr = insertBlocksInsideComments({
|
|
2737
|
+
tr,
|
|
2738
|
+
at: item.from,
|
|
2739
|
+
blocks: built.nodes
|
|
2740
|
+
});
|
|
1822
2741
|
break;
|
|
1823
2742
|
}
|
|
1824
2743
|
case "insertSignatureTable": {
|
|
@@ -1869,9 +2788,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1869
2788
|
}
|
|
1870
2789
|
const revision = structuralRevision;
|
|
1871
2790
|
const template = tableTemplates?.get(item.operation.id);
|
|
2791
|
+
const mappedInsertion = mapTableRowInsertion(tr, insertion);
|
|
1872
2792
|
const result = applyTableRowInsertion({
|
|
1873
2793
|
tr,
|
|
1874
|
-
insertion,
|
|
2794
|
+
insertion: mappedInsertion,
|
|
1875
2795
|
cellTexts: item.operation.cellTexts,
|
|
1876
2796
|
revision,
|
|
1877
2797
|
...template !== void 0 && { template }
|
|
@@ -1883,7 +2803,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1883
2803
|
});
|
|
1884
2804
|
continue;
|
|
1885
2805
|
}
|
|
1886
|
-
tr = result.transaction;
|
|
2806
|
+
tr = addCommentsOver(result.transaction, mappedInsertion.rowPosition, item.tableRowComments);
|
|
1887
2807
|
if (result.revisionId !== null) {
|
|
1888
2808
|
operationRevisionSeed++;
|
|
1889
2809
|
appliedRevisionIds = [result.revisionId];
|
|
@@ -1906,6 +2826,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1906
2826
|
cellTexts: item.operation.cellTexts,
|
|
1907
2827
|
revision
|
|
1908
2828
|
});
|
|
2829
|
+
if (result.type === "payloadDoesNotFit") {
|
|
2830
|
+
skipped.push({
|
|
2831
|
+
id: item.operation.id,
|
|
2832
|
+
reason: "payloadDoesNotFit",
|
|
2833
|
+
message: result.message
|
|
2834
|
+
});
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
1909
2837
|
if (result.type === "unsupported") {
|
|
1910
2838
|
skipped.push({
|
|
1911
2839
|
id: item.operation.id,
|
|
@@ -1913,11 +2841,9 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1913
2841
|
});
|
|
1914
2842
|
continue;
|
|
1915
2843
|
}
|
|
1916
|
-
tr = result.transaction;
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
appliedRevisionIds = [result.revisionId];
|
|
1920
|
-
}
|
|
2844
|
+
tr = addCommentsOver(result.transaction, tr.mapping.map(insertion.tablePosition, 1), item.tableColumnComments);
|
|
2845
|
+
operationRevisionSeed += result.revisionIds.length;
|
|
2846
|
+
appliedRevisionIds = [...result.revisionIds];
|
|
1921
2847
|
const columnKey = getTableColumnCoordinateKey(insertion);
|
|
1922
2848
|
insertedColumnCounts.set(columnKey, (insertedColumnCounts.get(columnKey) ?? 0) + 1);
|
|
1923
2849
|
break;
|
|
@@ -1937,7 +2863,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1937
2863
|
tr,
|
|
1938
2864
|
deletion,
|
|
1939
2865
|
insertedColumnCount: insertedColumnCounts.get(columnKey) ?? 0,
|
|
1940
|
-
revision
|
|
2866
|
+
revision,
|
|
2867
|
+
emptiedRows
|
|
1941
2868
|
});
|
|
1942
2869
|
if (result.type === "unsupported") {
|
|
1943
2870
|
skipped.push({
|
|
@@ -1947,10 +2874,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1947
2874
|
continue;
|
|
1948
2875
|
}
|
|
1949
2876
|
tr = result.transaction;
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
appliedRevisionIds = [result.revisionId];
|
|
1953
|
-
}
|
|
2877
|
+
operationRevisionSeed += result.revisionIds.length;
|
|
2878
|
+
appliedRevisionIds = [...result.revisionIds];
|
|
1954
2879
|
break;
|
|
1955
2880
|
}
|
|
1956
2881
|
case "mergeTableCells": {
|
|
@@ -2061,12 +2986,50 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2061
2986
|
}
|
|
2062
2987
|
break;
|
|
2063
2988
|
}
|
|
2064
|
-
case "deleteBlock":
|
|
2989
|
+
case "deleteBlock": {
|
|
2990
|
+
if (isSuggested && (hasOrdinaryTextRevision(item.blockNode) || hasOrdinaryParagraphMarkRevision(item.blockNode))) {
|
|
2991
|
+
skipped.push({
|
|
2992
|
+
id: item.operation.id,
|
|
2993
|
+
reason: "unsupportedMode"
|
|
2994
|
+
});
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2065
2997
|
if (mode === "direct") {
|
|
2066
2998
|
const at = tr.doc.resolve(item.blockFrom);
|
|
2067
2999
|
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
3000
|
break;
|
|
2069
3001
|
}
|
|
3002
|
+
const retraction = isPairedMove(item.operation.moveId) ? null : pendingParagraphRetraction(tr.doc, item.blockFrom);
|
|
3003
|
+
if (retraction) {
|
|
3004
|
+
if (isSuggested && !isSuggestedParagraphInsert(item.blockNode)) {
|
|
3005
|
+
skipped.push({
|
|
3006
|
+
id: item.operation.id,
|
|
3007
|
+
reason: "unsupportedMode"
|
|
3008
|
+
});
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
const retracted = tr.doc.nodeAt(item.blockFrom);
|
|
3012
|
+
tr = tr.delete(item.blockFrom, item.blockTo);
|
|
3013
|
+
if (retraction.clearBreakAt !== null) {
|
|
3014
|
+
tr = tr.setNodeAttribute(retraction.clearBreakAt, "pPrMark", null);
|
|
3015
|
+
const revisionIdProperties = operationRevisionSeed;
|
|
3016
|
+
if (retracted && recordReplacedParagraphProperties({
|
|
3017
|
+
tr,
|
|
3018
|
+
position: retraction.clearBreakAt,
|
|
3019
|
+
replaced: retracted,
|
|
3020
|
+
revision: {
|
|
3021
|
+
id: revisionIdProperties,
|
|
3022
|
+
author,
|
|
3023
|
+
date,
|
|
3024
|
+
...trackedRevisionExtras
|
|
3025
|
+
}
|
|
3026
|
+
})) {
|
|
3027
|
+
operationRevisionSeed++;
|
|
3028
|
+
appliedRevisionIds = [revisionIdProperties];
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
break;
|
|
3032
|
+
}
|
|
2070
3033
|
if (deletionType) {
|
|
2071
3034
|
const revisionId = operationRevisionSeed++;
|
|
2072
3035
|
const deletionMark = deletionType.create({
|
|
@@ -2079,6 +3042,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2079
3042
|
tr = tr.addMark(item.from, item.to, deletionMark);
|
|
2080
3043
|
const atomRanges = undeletedContentAtomRanges(tr, item.blockFrom);
|
|
2081
3044
|
for (const { from, to } of atomRanges) tr = tr.addMark(from, to, deletionMark);
|
|
3045
|
+
for (const anchorId of textBoxAnchorIdsBetween(tr.doc, tr.mapping.map(item.blockFrom), tr.mapping.map(item.blockTo))) deletedTextBoxAnchorIds.add(anchorId);
|
|
2082
3046
|
encloseWholeControls({
|
|
2083
3047
|
tr,
|
|
2084
3048
|
from: item.blockFrom,
|
|
@@ -2088,7 +3052,39 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2088
3052
|
const inlineRevisionApplied = item.from < item.to || atomRanges.length > 0;
|
|
2089
3053
|
appliedRevisionIds = inlineRevisionApplied ? [revisionId] : [];
|
|
2090
3054
|
const markPosition = tr.mapping.map(item.blockFrom);
|
|
2091
|
-
|
|
3055
|
+
const endsItsContainer = paragraphEndsItsContainer(tr.doc.resolve(markPosition), item.blockNode.type.name);
|
|
3056
|
+
const deleted = tr.doc.nodeAt(markPosition);
|
|
3057
|
+
const following = deleted && tr.doc.nodeAt(markPosition + deleted.nodeSize);
|
|
3058
|
+
if (deleted && following?.type === deleted.type && !isPairedMove(item.operation.moveId) && isInsertedPPrMark(deleted.attrs["pPrMark"])) {
|
|
3059
|
+
const restored = paragraphPropertiesOnReject(deleted);
|
|
3060
|
+
const followingChanges = expectParagraphAttrs(following)._propertyChanges;
|
|
3061
|
+
const recordsRestored = isInsertedPPrMark(following.attrs["pPrMark"]) && !hasSerializableParagraphPropertyChange(followingChanges) && JSON.stringify(restored) !== JSON.stringify(paragraphPropertiesSnapshot(following));
|
|
3062
|
+
joinAtParagraphMark({
|
|
3063
|
+
tr,
|
|
3064
|
+
paragraphPos: markPosition,
|
|
3065
|
+
paragraph: deleted,
|
|
3066
|
+
next: following,
|
|
3067
|
+
styleResolver: styleResolver ?? null
|
|
3068
|
+
});
|
|
3069
|
+
const joined = tr.doc.nodeAt(markPosition);
|
|
3070
|
+
if (recordsRestored && joined) {
|
|
3071
|
+
const propertyRevisionId = operationRevisionSeed++;
|
|
3072
|
+
tr = tr.setNodeMarkup(markPosition, void 0, {
|
|
3073
|
+
...joined.attrs,
|
|
3074
|
+
_propertyChanges: [...Array.isArray(followingChanges) ? followingChanges : [], {
|
|
3075
|
+
type: "paragraphPropertyChange",
|
|
3076
|
+
info: {
|
|
3077
|
+
id: propertyRevisionId,
|
|
3078
|
+
author,
|
|
3079
|
+
date,
|
|
3080
|
+
...trackedRevisionExtras
|
|
3081
|
+
},
|
|
3082
|
+
previousFormatting: restored
|
|
3083
|
+
}]
|
|
3084
|
+
});
|
|
3085
|
+
appliedRevisionIds = [...appliedRevisionIds, propertyRevisionId];
|
|
3086
|
+
}
|
|
3087
|
+
} else if (!endsItsContainer && deleted?.attrs["pPrMark"] == null) {
|
|
2092
3088
|
const markRevisionId = operationRevisionSeed++;
|
|
2093
3089
|
tr = tr.setNodeAttribute(markPosition, "pPrMark", {
|
|
2094
3090
|
kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
|
|
@@ -2100,10 +3096,25 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2100
3096
|
}
|
|
2101
3097
|
});
|
|
2102
3098
|
appliedRevisionIds = inlineRevisionApplied ? [revisionId, markRevisionId] : [markRevisionId];
|
|
3099
|
+
} else if (endsItsContainer && !isPairedMove(item.operation.moveId)) deletedFinalParagraphs.push({
|
|
3100
|
+
operationId: item.operation.id,
|
|
3101
|
+
position: markPosition,
|
|
3102
|
+
mappedFrom: tr.mapping.maps.length,
|
|
3103
|
+
revisionExtras: trackedRevisionExtras
|
|
3104
|
+
});
|
|
3105
|
+
else if (deleted && !isPairedMove(item.operation.moveId) && isPlainDeletedPPrMark(deleted.attrs["pPrMark"]) && holdsOnlyDeletedContent(deleted)) {
|
|
3106
|
+
const finalPosition = deletedFinalParagraphAfter(tr.doc.resolve(markPosition), deleted.type.name);
|
|
3107
|
+
if (finalPosition !== null) deletedFinalParagraphs.push({
|
|
3108
|
+
operationId: item.operation.id,
|
|
3109
|
+
position: finalPosition,
|
|
3110
|
+
mappedFrom: tr.mapping.maps.length,
|
|
3111
|
+
revisionExtras: trackedRevisionExtras
|
|
3112
|
+
});
|
|
2103
3113
|
}
|
|
2104
3114
|
}
|
|
2105
3115
|
if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
|
|
2106
3116
|
break;
|
|
3117
|
+
}
|
|
2107
3118
|
case "insertTable": {
|
|
2108
3119
|
const revision = {
|
|
2109
3120
|
revisionId: operationRevisionSeed,
|
|
@@ -2129,7 +3140,55 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2129
3140
|
continue;
|
|
2130
3141
|
}
|
|
2131
3142
|
if (producesTrackedChanges) appliedRevisionIds = [operationRevisionSeed++];
|
|
2132
|
-
|
|
3143
|
+
const carriedComments = tableAfterDeletedSplit.get(item.blockFrom);
|
|
3144
|
+
const usesRetractedSplit = carriedComments !== void 0 && item.from === item.blockTo;
|
|
3145
|
+
let at = usesRetractedSplit ? tr.mapping.map(item.blockFrom, -1) : item.from;
|
|
3146
|
+
let insertionComments = carriedComments;
|
|
3147
|
+
if (!usesRetractedSplit) {
|
|
3148
|
+
const boundary = tr.doc.resolve(at);
|
|
3149
|
+
const before = boundary.nodeBefore;
|
|
3150
|
+
const finalDeleted = deletedFinalParagraphAfter(boundary, "paragraph");
|
|
3151
|
+
const mapping = tr.mapping;
|
|
3152
|
+
const pendingIndex = finalDeleted === null ? -1 : deletedFinalParagraphs.findIndex(({ position, mappedFrom }) => mapping.slice(mappedFrom).map(position) === finalDeleted);
|
|
3153
|
+
const pending = pendingIndex < 0 ? void 0 : deletedFinalParagraphs.at(pendingIndex);
|
|
3154
|
+
if (before?.type.name === "paragraph" && finalDeleted !== null && (pending !== void 0 || isPlainDeletedPPrMark(before.attrs["pPrMark"]) && !holdsOnlyDeletedContent(before))) {
|
|
3155
|
+
insertionComments = commentsAcrossBlockBoundary(tr.doc, at);
|
|
3156
|
+
const final = tr.doc.nodeAt(finalDeleted);
|
|
3157
|
+
if (!final) panic("A deleted final paragraph vanished before table insertion", { finalDeleted });
|
|
3158
|
+
if (pending) {
|
|
3159
|
+
const receiptIndex = applied.findIndex(({ id }) => id === pending.operationId);
|
|
3160
|
+
const receipt = receiptIndex < 0 ? void 0 : applied.at(receiptIndex);
|
|
3161
|
+
if (receipt === void 0) panic("A deleted final paragraph lost its operation receipt", { operationId: pending.operationId });
|
|
3162
|
+
const revisionId = operationRevisionSeed++;
|
|
3163
|
+
tr.setNodeAttribute(finalDeleted, "pPrMark", {
|
|
3164
|
+
kind: "del",
|
|
3165
|
+
info: {
|
|
3166
|
+
id: revisionId,
|
|
3167
|
+
author,
|
|
3168
|
+
date,
|
|
3169
|
+
...pending.revisionExtras
|
|
3170
|
+
}
|
|
3171
|
+
});
|
|
3172
|
+
applied[receiptIndex] = {
|
|
3173
|
+
...receipt,
|
|
3174
|
+
revisionId: receipt.revisionId ?? revisionId,
|
|
3175
|
+
revisionIds: [...receipt.revisionIds ?? [], revisionId]
|
|
3176
|
+
};
|
|
3177
|
+
deletedFinalParagraphs.splice(pendingIndex, 1);
|
|
3178
|
+
deferredNoopFinalDeletions.delete(pending.operationId);
|
|
3179
|
+
} else {
|
|
3180
|
+
tr.setNodeAttribute(at - before.nodeSize, "pPrMark", null);
|
|
3181
|
+
tr.setNodeAttribute(finalDeleted, "pPrMark", before.attrs["pPrMark"]);
|
|
3182
|
+
}
|
|
3183
|
+
at = finalDeleted + final.nodeSize;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
tr = insertBlocksInsideComments({
|
|
3187
|
+
tr,
|
|
3188
|
+
at,
|
|
3189
|
+
blocks: table,
|
|
3190
|
+
...insertionComments !== void 0 && { carriedComments: insertionComments }
|
|
3191
|
+
});
|
|
2133
3192
|
markStructuralChange(tr);
|
|
2134
3193
|
break;
|
|
2135
3194
|
}
|
|
@@ -2173,10 +3232,21 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2173
3232
|
};
|
|
2174
3233
|
const rowPositions = [];
|
|
2175
3234
|
let rowOffset = livePosition + 1;
|
|
3235
|
+
let holdsPendingInsertedRow = false;
|
|
2176
3236
|
liveTable.forEach((row) => {
|
|
2177
|
-
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null)
|
|
3237
|
+
if (row.type.spec["tableRole"] === "row" && row.attrs["trDel"] == null) {
|
|
3238
|
+
rowPositions.push(rowOffset);
|
|
3239
|
+
holdsPendingInsertedRow ||= row.attrs["trIns"] != null;
|
|
3240
|
+
}
|
|
2178
3241
|
rowOffset += row.nodeSize;
|
|
2179
3242
|
});
|
|
3243
|
+
if (holdsPendingInsertedRow) {
|
|
3244
|
+
skipped.push({
|
|
3245
|
+
id: item.operation.id,
|
|
3246
|
+
reason: "unsupportedBlock"
|
|
3247
|
+
});
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
2180
3250
|
if (rowPositions.length === 0) {
|
|
2181
3251
|
skipped.push({
|
|
2182
3252
|
id: item.operation.id,
|
|
@@ -2198,8 +3268,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2198
3268
|
break;
|
|
2199
3269
|
}
|
|
2200
3270
|
case "setBlockParagraphProperties": {
|
|
2201
|
-
const
|
|
2202
|
-
const liveBlock = tr.doc.nodeAt(
|
|
3271
|
+
const mapped = tr.mapping.mapResult(item.blockFrom);
|
|
3272
|
+
const liveBlock = mapped.deletedAcross ? null : tr.doc.nodeAt(mapped.pos);
|
|
3273
|
+
const paraId = item.blockNode.attrs["paraId"];
|
|
3274
|
+
if (!liveBlock || liveBlock.type !== item.blockNode.type || typeof paraId === "string" && liveBlock.attrs["paraId"] !== paraId) {
|
|
3275
|
+
skipped.push({
|
|
3276
|
+
id: item.operation.id,
|
|
3277
|
+
reason: "missingBlock"
|
|
3278
|
+
});
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
const blockPosition = mapped.pos;
|
|
2203
3282
|
const appliedProperties = applyBlockParagraphProperties({
|
|
2204
3283
|
tr,
|
|
2205
3284
|
position: blockPosition,
|
|
@@ -2226,9 +3305,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2226
3305
|
break;
|
|
2227
3306
|
}
|
|
2228
3307
|
case "splitBlock": {
|
|
3308
|
+
const splitsFromItsTextBoxes = textBoxAnchorIdsBetween(tr.doc, item.blockFrom, item.from).size > 0 && endOfParagraphWithItsTextBoxes(tr.doc, item.blockFrom) > item.blockTo;
|
|
2229
3309
|
if (mode === "direct") {
|
|
2230
3310
|
if (item.to > item.from) tr = tr.delete(item.from, item.to);
|
|
2231
3311
|
tr = tr.split(item.from);
|
|
3312
|
+
if (splitsFromItsTextBoxes) gatherTextBoxesAfterParagraph(tr, item.blockFrom);
|
|
3313
|
+
} else if (splitsFromItsTextBoxes) {
|
|
3314
|
+
skipped.push({
|
|
3315
|
+
id: item.operation.id,
|
|
3316
|
+
reason: "unsupportedBlock"
|
|
3317
|
+
});
|
|
3318
|
+
continue;
|
|
2232
3319
|
} else {
|
|
2233
3320
|
const revisionIdMark = operationRevisionSeed++;
|
|
2234
3321
|
const info = {
|
|
@@ -2286,17 +3373,61 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2286
3373
|
break;
|
|
2287
3374
|
}
|
|
2288
3375
|
case "mergeBlockWithNext": {
|
|
2289
|
-
|
|
3376
|
+
if (mode === "direct" && !canJoin(tr.doc, item.blockTo)) {
|
|
3377
|
+
skipped.push({
|
|
3378
|
+
id: item.operation.id,
|
|
3379
|
+
reason: "unsupportedBlock"
|
|
3380
|
+
});
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
const separator = followedOnlyByDeletedParagraphs(tr.doc, item.blockTo) ? "" : item.operation.separator ?? "";
|
|
2290
3384
|
const insertAt = item.blockTo - 1;
|
|
3385
|
+
let mergedPropertiesAt = item.blockFrom;
|
|
3386
|
+
let mergedPropertiesRecorded = false;
|
|
3387
|
+
const second = tr.doc.nodeAt(item.blockTo);
|
|
3388
|
+
const spanningComments = commentsAcrossBlockBoundary(tr.doc, item.blockTo);
|
|
3389
|
+
const withSeparator = (transaction) => {
|
|
3390
|
+
const paragraph = transaction.doc.nodeAt(item.blockFrom);
|
|
3391
|
+
const sourceMarks = paragraph?.lastChild?.marks.filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name)) ?? [];
|
|
3392
|
+
const sourceNode = view.state.schema.text(separator, sourceMarks);
|
|
3393
|
+
const context = paragraph && paragraphRunStyleContext(paragraph, styleResolver);
|
|
3394
|
+
const marks = context ? reconcileRunFormattingMarks({
|
|
3395
|
+
authoredFormatting: readAuthoredRunFormatting({
|
|
3396
|
+
context,
|
|
3397
|
+
marks: sourceMarks,
|
|
3398
|
+
styleResolver
|
|
3399
|
+
}),
|
|
3400
|
+
context,
|
|
3401
|
+
node: sourceNode,
|
|
3402
|
+
styleResolver
|
|
3403
|
+
}) : sourceMarks;
|
|
3404
|
+
let next = transaction.replaceWith(insertAt, insertAt, view.state.schema.text(separator, marks));
|
|
3405
|
+
for (const comment of spanningComments) next = next.addMark(insertAt, insertAt + separator.length, comment);
|
|
3406
|
+
return next;
|
|
3407
|
+
};
|
|
2291
3408
|
if (mode === "direct") {
|
|
2292
|
-
if (separator.length > 0) tr = tr
|
|
3409
|
+
if (separator.length > 0) tr = withSeparator(tr);
|
|
3410
|
+
const nextPosition = item.blockTo + separator.length;
|
|
3411
|
+
const next = tr.doc.nodeAt(nextPosition);
|
|
3412
|
+
const paragraph = tr.doc.nodeAt(item.blockFrom);
|
|
3413
|
+
if (next && paragraph) tr = rebaseParagraphRuns({
|
|
3414
|
+
tr,
|
|
3415
|
+
position: nextPosition,
|
|
3416
|
+
previous: next,
|
|
3417
|
+
target: paragraph,
|
|
3418
|
+
styleResolver
|
|
3419
|
+
});
|
|
3420
|
+
tr.setMeta(JOINED_RUNS_RESTYLED_META, true);
|
|
2293
3421
|
tr = tr.join(item.blockTo + separator.length);
|
|
3422
|
+
const secondSection = second ? sectionPropertiesOf(second) : null;
|
|
3423
|
+
const joined = tr.doc.nodeAt(item.blockFrom);
|
|
3424
|
+
if (joined && sectionPropertiesOf(joined) !== secondSection) tr = tr.setNodeAttribute(item.blockFrom, "_sectionProperties", secondSection);
|
|
2294
3425
|
} else {
|
|
2295
3426
|
const revisionIdMark = operationRevisionSeed++;
|
|
2296
3427
|
appliedRevisionIds = [revisionIdMark];
|
|
2297
3428
|
if (separator.length > 0 && insertionType) {
|
|
2298
3429
|
const revisionIdSeparator = operationRevisionSeed++;
|
|
2299
|
-
tr = tr
|
|
3430
|
+
tr = withSeparator(tr);
|
|
2300
3431
|
tr = tr.addMark(insertAt, insertAt + separator.length, insertionType.create({
|
|
2301
3432
|
revisionId: revisionIdSeparator,
|
|
2302
3433
|
author,
|
|
@@ -2305,27 +3436,86 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2305
3436
|
}));
|
|
2306
3437
|
appliedRevisionIds.push(revisionIdSeparator);
|
|
2307
3438
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
3439
|
+
const paragraph = tr.doc.nodeAt(item.blockFrom);
|
|
3440
|
+
const joinPos = item.blockTo + separator.length;
|
|
3441
|
+
const next = tr.doc.nodeAt(joinPos);
|
|
3442
|
+
if (!paragraph || !next) panic("A resolved merge lost its paragraphs");
|
|
3443
|
+
if (isInsertedPPrMark(paragraph.attrs["pPrMark"])) {
|
|
3444
|
+
joinAtParagraphMark({
|
|
3445
|
+
tr,
|
|
3446
|
+
paragraphPos: item.blockFrom,
|
|
3447
|
+
paragraph,
|
|
3448
|
+
next,
|
|
3449
|
+
styleResolver
|
|
3450
|
+
});
|
|
3451
|
+
const joinedMark = tr.doc.nodeAt(item.blockFrom)?.attrs["pPrMark"];
|
|
3452
|
+
const removedBreakPositions = pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs);
|
|
3453
|
+
const carryPos = isDeletedPPrMark(joinedMark) || removedBreakPositions.has(item.blockFrom) ? paragraphLeftAfter({
|
|
3454
|
+
doc: tr.doc,
|
|
3455
|
+
paragraphPos: item.blockFrom,
|
|
3456
|
+
removedBreakPositions
|
|
3457
|
+
}) ?? item.blockFrom : item.blockFrom;
|
|
3458
|
+
const carried = carryParagraphProperties({
|
|
3459
|
+
tr,
|
|
3460
|
+
position: carryPos,
|
|
3461
|
+
source: paragraph,
|
|
3462
|
+
styleResolver,
|
|
3463
|
+
numbering,
|
|
3464
|
+
revision: {
|
|
3465
|
+
id: revisionIdMark,
|
|
3466
|
+
author,
|
|
3467
|
+
date,
|
|
3468
|
+
...trackedRevisionExtras
|
|
3469
|
+
}
|
|
3470
|
+
});
|
|
3471
|
+
mergedPropertiesAt = carryPos;
|
|
3472
|
+
tr = carried.tr;
|
|
3473
|
+
mergedPropertiesRecorded = carried.tracked;
|
|
3474
|
+
if (!carried.tracked) appliedRevisionIds = appliedRevisionIds.filter((id) => id !== revisionIdMark);
|
|
3475
|
+
} else {
|
|
3476
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
|
|
3477
|
+
kind: "del",
|
|
3478
|
+
info: {
|
|
3479
|
+
id: revisionIdMark,
|
|
3480
|
+
author,
|
|
3481
|
+
date,
|
|
3482
|
+
...trackedRevisionExtras
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
const survivorPos = paragraphLeftAfter({
|
|
3486
|
+
doc: tr.doc,
|
|
3487
|
+
paragraphPos: item.blockFrom,
|
|
3488
|
+
removedBreakPositions: pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs)
|
|
3489
|
+
}) ?? joinPos;
|
|
3490
|
+
const carried = carryParagraphProperties({
|
|
3491
|
+
tr,
|
|
3492
|
+
position: survivorPos,
|
|
3493
|
+
source: paragraph,
|
|
3494
|
+
styleResolver,
|
|
3495
|
+
numbering,
|
|
3496
|
+
revision: {
|
|
3497
|
+
id: revisionIdMark,
|
|
3498
|
+
author,
|
|
3499
|
+
date,
|
|
3500
|
+
...trackedRevisionExtras
|
|
3501
|
+
}
|
|
3502
|
+
});
|
|
3503
|
+
tr = carried.tr;
|
|
3504
|
+
mergedPropertiesRecorded = carried.tracked;
|
|
3505
|
+
mergedPropertiesAt = survivorPos;
|
|
3506
|
+
}
|
|
2317
3507
|
}
|
|
2318
3508
|
if (item.operation.mergedParagraphProperties) {
|
|
2319
|
-
const mergedParagraph = tr.doc.nodeAt(
|
|
3509
|
+
const mergedParagraph = tr.doc.nodeAt(mergedPropertiesAt);
|
|
2320
3510
|
if (!mergedParagraph) panic("A resolved merge did not produce a paragraph");
|
|
2321
3511
|
const appliedProperties = applyBlockParagraphProperties({
|
|
2322
3512
|
tr,
|
|
2323
|
-
position:
|
|
3513
|
+
position: mergedPropertiesAt,
|
|
2324
3514
|
node: mergedParagraph,
|
|
2325
3515
|
properties: item.operation.mergedParagraphProperties,
|
|
2326
3516
|
styleResolver,
|
|
2327
3517
|
numbering,
|
|
2328
|
-
...mode === "direct" ? {} : { revisionInfo: () => ({
|
|
3518
|
+
...mode === "direct" || mergedPropertiesRecorded ? {} : { revisionInfo: () => ({
|
|
2329
3519
|
id: operationRevisionSeed++,
|
|
2330
3520
|
author,
|
|
2331
3521
|
date,
|
|
@@ -2335,6 +3525,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2335
3525
|
tr = appliedProperties.tr;
|
|
2336
3526
|
if (appliedProperties.revisionIds.length > 0) appliedRevisionIds.push(...appliedProperties.revisionIds);
|
|
2337
3527
|
}
|
|
3528
|
+
if (mode !== "direct") mergedPropertyTargets.push({
|
|
3529
|
+
position: mergedPropertiesAt,
|
|
3530
|
+
mappedFrom: tr.mapping.maps.length
|
|
3531
|
+
});
|
|
2338
3532
|
break;
|
|
2339
3533
|
}
|
|
2340
3534
|
case "commentOnBlock":
|
|
@@ -2343,11 +3537,14 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2343
3537
|
default: break;
|
|
2344
3538
|
}
|
|
2345
3539
|
if (tr.steps.length === stepsBefore) {
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
3540
|
+
if (!deletedFinalParagraphs.some(({ operationId }) => operationId === item.operation.id)) {
|
|
3541
|
+
skipped.push({
|
|
3542
|
+
id: item.operation.id,
|
|
3543
|
+
reason: "noopOperation"
|
|
3544
|
+
});
|
|
3545
|
+
continue;
|
|
3546
|
+
}
|
|
3547
|
+
deferredNoopFinalDeletions.add(item.operation.id);
|
|
2351
3548
|
}
|
|
2352
3549
|
let committedCommentId;
|
|
2353
3550
|
if (item.commentText !== void 0) {
|
|
@@ -2366,6 +3563,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2366
3563
|
}
|
|
2367
3564
|
revisionSeed = operationRevisionSeed;
|
|
2368
3565
|
claimSharedRevisionIds(revisionSeed);
|
|
3566
|
+
if (unifiedFormatting) normalizations.push({
|
|
3567
|
+
id: item.operation.id,
|
|
3568
|
+
code: "uniformReplacementFormatting"
|
|
3569
|
+
});
|
|
2369
3570
|
applied.push({
|
|
2370
3571
|
id: item.operation.id,
|
|
2371
3572
|
...committedCommentId !== void 0 && { commentId: committedCommentId },
|
|
@@ -2376,9 +3577,11 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2376
3577
|
...suggestionId !== null && { suggestionId }
|
|
2377
3578
|
});
|
|
2378
3579
|
}
|
|
2379
|
-
|
|
3580
|
+
removeRowsWithoutCellsAfterBatch(tr, emptiedRows);
|
|
3581
|
+
if (tr.docChanged || deferredNoopFinalDeletions.size > 0) {
|
|
2380
3582
|
const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
|
|
2381
|
-
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds);
|
|
3583
|
+
tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds, deletedTextBoxAnchorIds);
|
|
3584
|
+
if (deleteTextBoxesAnchoredAt(tr, droppedTextBoxAnchorIds(view.state.doc, tr.doc))) markStructuralChange(tr);
|
|
2382
3585
|
continueSharedRevisionIds();
|
|
2383
3586
|
const rotated = withRotatedAddedFinalBreaks({
|
|
2384
3587
|
tr,
|
|
@@ -2386,10 +3589,33 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2386
3589
|
revisionSeed,
|
|
2387
3590
|
author,
|
|
2388
3591
|
date,
|
|
2389
|
-
initials
|
|
3592
|
+
initials,
|
|
3593
|
+
styleResolver: styleResolver ?? null
|
|
2390
3594
|
});
|
|
2391
3595
|
tr = rotated.transaction;
|
|
2392
3596
|
revisionSeed = rotated.nextRevisionId;
|
|
3597
|
+
const retired = withRetiredFinalParagraphs({
|
|
3598
|
+
tr,
|
|
3599
|
+
deletions: deletedFinalParagraphs,
|
|
3600
|
+
mergedPropertyTargets,
|
|
3601
|
+
appliedIds: new Set(applied.map(({ id }) => id)),
|
|
3602
|
+
batchRevisionIds,
|
|
3603
|
+
revisionSeed,
|
|
3604
|
+
author,
|
|
3605
|
+
date,
|
|
3606
|
+
styleResolver,
|
|
3607
|
+
numbering
|
|
3608
|
+
});
|
|
3609
|
+
revisionSeed = retired.nextRevisionId;
|
|
3610
|
+
for (const operationId of deferredNoopFinalDeletions) {
|
|
3611
|
+
if (retired.resolvedOperationIds.has(operationId)) continue;
|
|
3612
|
+
const receiptIndex = applied.findIndex(({ id }) => id === operationId);
|
|
3613
|
+
if (receiptIndex >= 0) applied.splice(receiptIndex, 1);
|
|
3614
|
+
skipped.push({
|
|
3615
|
+
id: operationId,
|
|
3616
|
+
reason: "noopOperation"
|
|
3617
|
+
});
|
|
3618
|
+
}
|
|
2393
3619
|
const receiptIndexByRevisionId = /* @__PURE__ */ new Map();
|
|
2394
3620
|
for (const [receiptIndex, receipt] of applied.entries()) for (const revisionId of receipt.revisionIds ?? []) receiptIndexByRevisionId.set(revisionId, receiptIndex);
|
|
2395
3621
|
for (const { ownerRevisionId, revisionId } of rotated.synthesizedRevisions) {
|
|
@@ -2404,9 +3630,48 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2404
3630
|
revisionIds: [...receipt.revisionIds, revisionId]
|
|
2405
3631
|
};
|
|
2406
3632
|
}
|
|
3633
|
+
for (const { operationId, revisionId } of retired.addedRevisions) {
|
|
3634
|
+
const receiptIndex = applied.findIndex(({ id }) => id === operationId);
|
|
3635
|
+
const receipt = applied.at(receiptIndex);
|
|
3636
|
+
if (receiptIndex < 0 || !receipt) panic("A retired final-mark revision lost its operation receipt", {
|
|
3637
|
+
operationId,
|
|
3638
|
+
revisionId
|
|
3639
|
+
});
|
|
3640
|
+
const revisionIds = [...receipt.revisionIds ?? [], revisionId];
|
|
3641
|
+
applied[receiptIndex] = {
|
|
3642
|
+
...receipt,
|
|
3643
|
+
revisionId: receipt.revisionId ?? revisionId,
|
|
3644
|
+
revisionIds
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
const retractedRevisionIds = /* @__PURE__ */ new Set([...rotated.retractedRevisionIds, ...retired.retractedRevisionIds]);
|
|
3648
|
+
if (retractedRevisionIds.size > 0) for (const [receiptIndex, receipt] of applied.entries()) {
|
|
3649
|
+
const revisionIds = receipt.revisionIds?.filter((id) => !retractedRevisionIds.has(id));
|
|
3650
|
+
if (revisionIds === void 0) continue;
|
|
3651
|
+
const { revisionId: _oldRevisionId, revisionIds: _oldRevisionIds, ...withoutRevisions } = receipt;
|
|
3652
|
+
const firstRevisionId = revisionIds.at(0);
|
|
3653
|
+
applied[receiptIndex] = firstRevisionId === void 0 ? withoutRevisions : {
|
|
3654
|
+
...withoutRevisions,
|
|
3655
|
+
revisionId: firstRevisionId,
|
|
3656
|
+
revisionIds
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
const separated = separateRevisionStretches({
|
|
3660
|
+
tr,
|
|
3661
|
+
revisionSeed
|
|
3662
|
+
});
|
|
3663
|
+
revisionSeed = separated.nextRevisionId;
|
|
3664
|
+
for (const { revisionId, from } of separated.minted) {
|
|
3665
|
+
const receiptIndex = applied.findIndex(({ revisionIds }) => revisionIds?.includes(from));
|
|
3666
|
+
const receipt = applied.at(receiptIndex);
|
|
3667
|
+
if (receiptIndex >= 0 && receipt?.revisionIds) applied[receiptIndex] = {
|
|
3668
|
+
...receipt,
|
|
3669
|
+
revisionIds: [...receipt.revisionIds, revisionId]
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
2407
3672
|
claimSharedRevisionIds(revisionSeed);
|
|
2408
3673
|
if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
|
|
2409
|
-
view.dispatch(tr);
|
|
3674
|
+
if (tr.docChanged) view.dispatch(tr);
|
|
2410
3675
|
}
|
|
2411
3676
|
return {
|
|
2412
3677
|
applied,
|
|
@@ -2416,9 +3681,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
2416
3681
|
};
|
|
2417
3682
|
};
|
|
2418
3683
|
const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
|
|
2419
|
-
const previewFolioAIEditOperations = (options) =>
|
|
3684
|
+
const previewFolioAIEditOperations = (options) => previewFolioAIEditOperationsWithResult(options).outcome;
|
|
3685
|
+
/**
|
|
3686
|
+
* A preview together with the document it would leave and the provisional
|
|
3687
|
+
* comment ids it placed, for a caller that checks the result before reporting
|
|
3688
|
+
* it. Internal: not re-exported from the package entry points.
|
|
3689
|
+
*/
|
|
3690
|
+
const previewFolioAIEditOperationsWithResult = (options) => {
|
|
2420
3691
|
const { view, createCommentId, ...applyOptions } = options;
|
|
2421
3692
|
let previewCommentId = -1;
|
|
3693
|
+
const commentIds = [];
|
|
2422
3694
|
const previewView = {
|
|
2423
3695
|
state: view.state,
|
|
2424
3696
|
dispatch: (transaction) => {
|
|
@@ -2428,15 +3700,23 @@ const previewFolioAIEditOperations = (options) => {
|
|
|
2428
3700
|
const result = applyFolioAIEditOperationsInternal({
|
|
2429
3701
|
...applyOptions,
|
|
2430
3702
|
view: previewView,
|
|
2431
|
-
...createCommentId !== void 0 && { createCommentId: () =>
|
|
3703
|
+
...createCommentId !== void 0 && { createCommentId: () => {
|
|
3704
|
+
const id = previewCommentId--;
|
|
3705
|
+
commentIds.push(id);
|
|
3706
|
+
return id;
|
|
3707
|
+
} },
|
|
2432
3708
|
revisionIdSeed: -1e9,
|
|
2433
3709
|
wordDiffMode: "coarse"
|
|
2434
3710
|
});
|
|
2435
3711
|
return {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
3712
|
+
outcome: {
|
|
3713
|
+
applied: result.applied.map(({ id }) => ({ id })),
|
|
3714
|
+
skipped: result.skipped,
|
|
3715
|
+
...result.normalizations !== void 0 && { normalizations: result.normalizations },
|
|
3716
|
+
nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
|
|
3717
|
+
},
|
|
3718
|
+
doc: previewView.state.doc,
|
|
3719
|
+
commentIds
|
|
2440
3720
|
};
|
|
2441
3721
|
};
|
|
2442
3722
|
const CLEAN_TEXT_CONTROL_PATTERN = /[\t\n]/gu;
|
|
@@ -2460,9 +3740,10 @@ const cleanTextInlineNodes = ({ schema, text, marks = [] }) => {
|
|
|
2460
3740
|
if (trailing.length > 0) nodes.push(schema.text(trailing, marks));
|
|
2461
3741
|
return nodes;
|
|
2462
3742
|
};
|
|
2463
|
-
const insertCleanText = ({ tr, from, to, text, standsInFor }) => {
|
|
3743
|
+
const insertCleanText = ({ tr, from, to, text, standsInFor, keepsReplacedText = false }) => {
|
|
2464
3744
|
const annotations = surveyReplacedAnnotations(tr.doc, from, to);
|
|
2465
|
-
const
|
|
3745
|
+
const carried = standsInFor ? surveyReplacedAnnotations(tr.doc, standsInFor.from, standsInFor.to).carried : [];
|
|
3746
|
+
const replaced = keepsReplacedText ? commentsForTrackedInsertion(tr.doc, from, [carried])[0] ?? [] : carried;
|
|
2466
3747
|
if (!hasReplacedAnnotations(annotations) && replaced.length === 0 && !hasCleanTextControls(text)) return {
|
|
2467
3748
|
transaction: tr.insertText(text, from, to),
|
|
2468
3749
|
start: from,
|
|
@@ -2539,7 +3820,8 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
2539
3820
|
standsInFor: {
|
|
2540
3821
|
from: item.from,
|
|
2541
3822
|
to: item.to
|
|
2542
|
-
}
|
|
3823
|
+
},
|
|
3824
|
+
keepsReplacedText: true
|
|
2543
3825
|
});
|
|
2544
3826
|
nextTr = inserted.transaction;
|
|
2545
3827
|
nextTr = nextTr.addMark(inserted.start, inserted.end, insertionType.create(insAttrs));
|
|
@@ -2551,6 +3833,17 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
|
|
|
2551
3833
|
}
|
|
2552
3834
|
return nextTr;
|
|
2553
3835
|
};
|
|
3836
|
+
/** Whether the resolved range covers a footnote or endnote reference. */
|
|
3837
|
+
const rangeHoldsNoteReference = ({ blockNode, blockFrom, from, to }) => {
|
|
3838
|
+
let holds = false;
|
|
3839
|
+
blockNode.descendants((node, pos) => {
|
|
3840
|
+
if (holds) return false;
|
|
3841
|
+
const start = blockFrom + 1 + pos;
|
|
3842
|
+
if (noteReferenceOf(node) !== null && start < to && start + node.nodeSize > from) holds = true;
|
|
3843
|
+
return !holds;
|
|
3844
|
+
});
|
|
3845
|
+
return holds;
|
|
3846
|
+
};
|
|
2554
3847
|
/**
|
|
2555
3848
|
* The clean-text span a text replacement matched: where it starts in the
|
|
2556
3849
|
* block's clean text and what it says. `null` when the operation does not
|
|
@@ -2592,17 +3885,27 @@ const isCarriedMark = (mark) => NON_INCLUSIVE_MARK_DISPOSITION[mark.type.name] =
|
|
|
2592
3885
|
* document character by character, or the changes do not produce the
|
|
2593
3886
|
* replacement.
|
|
2594
3887
|
*/
|
|
2595
|
-
const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
2596
|
-
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom
|
|
3888
|
+
const planDocumentReplacement = ({ doc, item, replacement, noteReferences, planChanges, keepFieldsOf }) => {
|
|
3889
|
+
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
3890
|
+
fieldResults: "text",
|
|
3891
|
+
noteReferences
|
|
3892
|
+
});
|
|
2597
3893
|
const span = replacedCleanSpan(item, cleanBlock);
|
|
2598
3894
|
if (span === null) return null;
|
|
2599
3895
|
const spanStart = span.start;
|
|
2600
3896
|
const spanEnd = span.start + span.text.length;
|
|
2601
|
-
const
|
|
3897
|
+
const noteReferenceSpans = noteReferenceSpansWithin(cleanBlock, spanStart, spanEnd);
|
|
3898
|
+
const editsReferences = !(segmentsAroundNoteReferences(span.text, replacement, noteReferenceSpans) !== null) && noteReferenceEditsAllowed();
|
|
3899
|
+
const atomicSpans = cleanBlock.structuralBoundaries.flatMap((boundary) => boundary.type === "field" || editsReferences && boundary.type === "noteReference" ? [{
|
|
2602
3900
|
offset: boundary.offset - spanStart,
|
|
2603
3901
|
length: boundary.length
|
|
2604
3902
|
}] : []);
|
|
2605
|
-
const
|
|
3903
|
+
const planPieces = (plan) => editsReferences ? [...plan(span.text, replacement)] : planChangesAroundNoteReferences(span.text, replacement, noteReferenceSpans, plan);
|
|
3904
|
+
const pieceChanges = planPieces(planChanges);
|
|
3905
|
+
if (pieceChanges === null) return null;
|
|
3906
|
+
const widened = widenChangesToAtomicSpans(span.text, pieceChanges, atomicSpans);
|
|
3907
|
+
const keptPieces = keepFieldsOf === void 0 ? null : planPieces(keepFieldsOf);
|
|
3908
|
+
const changes = keptPieces === null ? widened : keepAtomicSpans(span.text, replacement, widened, widenChangesToAtomicSpans(span.text, keptPieces, atomicSpans), atomicSpans);
|
|
2606
3909
|
if (applyTextChanges(span.text, changes) !== replacement) return null;
|
|
2607
3910
|
const unitAt = (index) => {
|
|
2608
3911
|
const from = cleanBlock.offsets[index];
|
|
@@ -2610,7 +3913,7 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2610
3913
|
if (from === void 0 || !node) return null;
|
|
2611
3914
|
return {
|
|
2612
3915
|
from,
|
|
2613
|
-
to: node.isText ? from +
|
|
3916
|
+
to: !node.isText || noteReferenceOf(node) !== null ? from + node.nodeSize : from + 1
|
|
2614
3917
|
};
|
|
2615
3918
|
};
|
|
2616
3919
|
const matchedFrom = item.from;
|
|
@@ -2730,6 +4033,72 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2730
4033
|
replacementStyleSource
|
|
2731
4034
|
};
|
|
2732
4035
|
};
|
|
4036
|
+
/** The marks that present a character: its formatting, and the links and comments over it. */
|
|
4037
|
+
const presentationMarks = (marks) => marks.filter((mark) => isFormattingMark(mark) || isCarriedMark(mark));
|
|
4038
|
+
/**
|
|
4039
|
+
* Whether a change of `plan` replaces characters that do not all present
|
|
4040
|
+
* alike. Its new text takes one formatting, the first replaced character's,
|
|
4041
|
+
* and every link and comment over the stretch, so the others' formatting ends
|
|
4042
|
+
* where the replaced text did. That is the rule for every mode, and the
|
|
4043
|
+
* receipt says it was applied (`uniformReplacementFormatting`).
|
|
4044
|
+
*/
|
|
4045
|
+
const replacementUnifiesFormatting = (doc, plan) => plan.planned.some(({ change, pieces }) => {
|
|
4046
|
+
if (pieces.length === 0 || change.text.length === 0) return false;
|
|
4047
|
+
let first = null;
|
|
4048
|
+
for (let index = change.start; index < change.end; index++) {
|
|
4049
|
+
const unit = plan.unitAt(plan.spanStart + index);
|
|
4050
|
+
const marks = presentationMarks((unit ? doc.nodeAt(unit.from)?.marks : null) ?? Mark.none);
|
|
4051
|
+
if (first === null) first = marks;
|
|
4052
|
+
else if (!Mark.sameSet(first, marks)) return true;
|
|
4053
|
+
}
|
|
4054
|
+
return false;
|
|
4055
|
+
});
|
|
4056
|
+
/**
|
|
4057
|
+
* The marks each UTF-16 unit of `replacement` carries once a direct
|
|
4058
|
+
* replacement has written it (see {@link applyMinimalDirectReplacement}): a
|
|
4059
|
+
* character the direct plan keeps keeps its own, and one it writes takes the
|
|
4060
|
+
* marks that plan gives its change. Background cleared from a touched stretch
|
|
4061
|
+
* is left off either way.
|
|
4062
|
+
*
|
|
4063
|
+
* A tracked replacement writes its insertions with these marks, so accepting
|
|
4064
|
+
* it leaves exactly the text and formatting the direct edit writes, although
|
|
4065
|
+
* its redline cuts the text into coarser, readable changes than the direct
|
|
4066
|
+
* edit does.
|
|
4067
|
+
*/
|
|
4068
|
+
const directReplacementMarks = ({ doc, plan, replacementBackground }) => {
|
|
4069
|
+
const clearsBackground = replacementBackground === "clear";
|
|
4070
|
+
const touched = clearsBackground ? touchedBackgroundIndices({
|
|
4071
|
+
doc,
|
|
4072
|
+
plan
|
|
4073
|
+
}) : /* @__PURE__ */ new Set();
|
|
4074
|
+
const withoutBackground = (marks) => marks.filter((mark) => !BACKGROUND_MARK_NAMES.has(mark.type.name));
|
|
4075
|
+
const allocated = [];
|
|
4076
|
+
let cursor = 0;
|
|
4077
|
+
const keepUntil = (end) => {
|
|
4078
|
+
for (; cursor < end; cursor++) {
|
|
4079
|
+
const index = plan.spanStart + cursor;
|
|
4080
|
+
const unit = plan.unitAt(index);
|
|
4081
|
+
const marks = (unit ? doc.nodeAt(unit.from)?.marks : null) ?? Mark.none;
|
|
4082
|
+
allocated.push(touched.has(index) ? withoutBackground(marks) : marks);
|
|
4083
|
+
}
|
|
4084
|
+
};
|
|
4085
|
+
for (const { change, pieces, insertion } of plan.planned) {
|
|
4086
|
+
keepUntil(change.start);
|
|
4087
|
+
cursor = change.end;
|
|
4088
|
+
let marks;
|
|
4089
|
+
if (insertion !== void 0) marks = insertion.marks(doc);
|
|
4090
|
+
else {
|
|
4091
|
+
const first = pieces.at(0);
|
|
4092
|
+
const last = pieces.at(-1);
|
|
4093
|
+
if (first === void 0 || last === void 0) panic("A planned replacement lost the characters it removes");
|
|
4094
|
+
marks = addCarriedMarks(inheritedReplacementMarks(doc, first.from, first.to), surveyReplacedAnnotations(doc, first.from, last.to).carried);
|
|
4095
|
+
}
|
|
4096
|
+
const written = clearsBackground ? withoutBackground(marks) : marks;
|
|
4097
|
+
for (let offset = 0; offset < change.text.length; offset++) allocated.push(written);
|
|
4098
|
+
}
|
|
4099
|
+
keepUntil(plan.spanEnd - plan.spanStart);
|
|
4100
|
+
return allocated;
|
|
4101
|
+
};
|
|
2733
4102
|
/**
|
|
2734
4103
|
* Apply a direct-mode text replacement as the difference between the matched
|
|
2735
4104
|
* text and its replacement; `minimal-replacement.ts` states the contract.
|
|
@@ -2738,15 +4107,17 @@ const planDocumentReplacement = ({ doc, item, replacement, planChanges }) => {
|
|
|
2738
4107
|
* cannot be mapped onto the document character by character. The caller then
|
|
2739
4108
|
* replaces the matched span whole, as it always did.
|
|
2740
4109
|
*/
|
|
2741
|
-
const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, replacementBackground, styleResolver }) => {
|
|
4110
|
+
const applyMinimalDirectReplacement = ({ tr, item, noteReferences, replacement, commentMark, replacementBackground, styleResolver }) => {
|
|
2742
4111
|
const plan = planDocumentReplacement({
|
|
2743
4112
|
doc: tr.doc,
|
|
2744
4113
|
item,
|
|
2745
4114
|
replacement,
|
|
2746
|
-
|
|
4115
|
+
noteReferences,
|
|
4116
|
+
planChanges: planTextChanges
|
|
2747
4117
|
});
|
|
2748
4118
|
if (plan === null || plan.changes.length === 0) return null;
|
|
2749
4119
|
const { planned, matchedFrom, matchedTo } = plan;
|
|
4120
|
+
const unifiesFormatting = replacementUnifiesFormatting(tr.doc, plan);
|
|
2750
4121
|
let nextTr = tr;
|
|
2751
4122
|
if (replacementBackground === "clear") nextTr = clearTouchedBackground({
|
|
2752
4123
|
tr: nextTr,
|
|
@@ -2810,7 +4181,10 @@ const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, rep
|
|
|
2810
4181
|
}
|
|
2811
4182
|
if (to > from) nextTr = nextTr.addMark(from, to, commentMark);
|
|
2812
4183
|
}
|
|
2813
|
-
return
|
|
4184
|
+
return {
|
|
4185
|
+
transaction: nextTr,
|
|
4186
|
+
unifiesFormatting
|
|
4187
|
+
};
|
|
2814
4188
|
};
|
|
2815
4189
|
/**
|
|
2816
4190
|
* Apply a tracked-changes or suggested text replacement as its redline: each
|
|
@@ -2825,7 +4199,7 @@ const applyMinimalDirectReplacement = ({ tr, item, replacement, commentMark, rep
|
|
|
2825
4199
|
* cannot be mapped onto the document character by character; the caller then
|
|
2826
4200
|
* marks the whole match as replaced, as it always did.
|
|
2827
4201
|
*/
|
|
2828
|
-
const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, replacementBackground, styleResolver, diffText, revisionIdDelete, revisionIdInsert, revisionIdBackgroundSeed, author, date, initials, suggestionId }) => {
|
|
4202
|
+
const applyMinimalTrackedReplacement = ({ tr, item, noteReferences, replacement, commentMark, replacementBackground, styleResolver, diffText, revisionIdDelete, revisionIdInsert, revisionIdBackgroundSeed, author, date, initials, suggestionId }) => {
|
|
2829
4203
|
const schema = tr.doc.type.schema;
|
|
2830
4204
|
const insertionType = schema.marks["insertion"];
|
|
2831
4205
|
const deletionType = schema.marks["deletion"];
|
|
@@ -2834,9 +4208,24 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2834
4208
|
doc: tr.doc,
|
|
2835
4209
|
item,
|
|
2836
4210
|
replacement,
|
|
2837
|
-
|
|
4211
|
+
noteReferences,
|
|
4212
|
+
planChanges: (source, target) => changesFromSegments(diffText(source, target)),
|
|
4213
|
+
keepFieldsOf: planTextChanges
|
|
2838
4214
|
});
|
|
2839
4215
|
if (plan === null) return null;
|
|
4216
|
+
const directPlan = planDocumentReplacement({
|
|
4217
|
+
doc: tr.doc,
|
|
4218
|
+
item,
|
|
4219
|
+
replacement,
|
|
4220
|
+
noteReferences,
|
|
4221
|
+
planChanges: planTextChanges
|
|
4222
|
+
});
|
|
4223
|
+
const allocatedMarks = directPlan === null ? null : directReplacementMarks({
|
|
4224
|
+
doc: tr.doc,
|
|
4225
|
+
plan: directPlan,
|
|
4226
|
+
replacementBackground
|
|
4227
|
+
});
|
|
4228
|
+
const unifiesFormatting = directPlan === null ? false : replacementUnifiesFormatting(tr.doc, directPlan);
|
|
2840
4229
|
let nextTr = tr;
|
|
2841
4230
|
let backgroundRevisionIds = [];
|
|
2842
4231
|
let nextRevisionId = revisionIdBackgroundSeed;
|
|
@@ -2886,10 +4275,39 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2886
4275
|
...revisionAttrs
|
|
2887
4276
|
});
|
|
2888
4277
|
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
|
-
|
|
4278
|
+
const replacementStarts = [];
|
|
4279
|
+
let replacementShift = 0;
|
|
4280
|
+
for (const { change } of plan.planned) {
|
|
4281
|
+
replacementStarts.push(change.start + replacementShift);
|
|
4282
|
+
replacementShift += change.text.length - (change.end - change.start);
|
|
4283
|
+
}
|
|
4284
|
+
const insertedNodes = (text, replacementStart, fallback) => {
|
|
4285
|
+
if (allocatedMarks?.length !== replacement.length) return cleanTextInlineNodes({
|
|
4286
|
+
schema,
|
|
4287
|
+
text,
|
|
4288
|
+
marks: insertedMarks(fallback)
|
|
4289
|
+
});
|
|
4290
|
+
const marksAt = (offset) => allocatedMarks[replacementStart + offset] ?? fallback;
|
|
4291
|
+
const nodes = [];
|
|
4292
|
+
let groupStart = 0;
|
|
4293
|
+
for (let offset = 1; offset <= text.length; offset++) {
|
|
4294
|
+
if (offset < text.length && Mark.sameSet(marksAt(offset), marksAt(groupStart))) continue;
|
|
4295
|
+
nodes.push(...cleanTextInlineNodes({
|
|
4296
|
+
schema,
|
|
4297
|
+
text: text.slice(groupStart, offset),
|
|
4298
|
+
marks: insertedMarks(marksAt(groupStart))
|
|
4299
|
+
}));
|
|
4300
|
+
groupStart = offset;
|
|
4301
|
+
}
|
|
4302
|
+
return nodes;
|
|
4303
|
+
};
|
|
4304
|
+
for (let plannedIndex = plan.planned.length - 1; plannedIndex >= 0; plannedIndex--) {
|
|
4305
|
+
const change = plan.planned[plannedIndex];
|
|
4306
|
+
if (change === void 0) panic("A planned replacement change went missing", { plannedIndex });
|
|
2890
4307
|
const doc = nextTr.doc;
|
|
2891
4308
|
let at;
|
|
2892
4309
|
let marks;
|
|
4310
|
+
let removedFrom = null;
|
|
2893
4311
|
if (change.insertion !== void 0) {
|
|
2894
4312
|
at = change.insertion.at;
|
|
2895
4313
|
marks = change.insertion.marks(doc);
|
|
@@ -2899,6 +4317,7 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2899
4317
|
const styleSource = plan.replacementStyleSource(change.change);
|
|
2900
4318
|
if (first === void 0 || last === void 0 || styleSource === null) panic("A planned replacement lost the characters it removes");
|
|
2901
4319
|
at = last.to;
|
|
4320
|
+
removedFrom = first.from;
|
|
2902
4321
|
marks = addCarriedMarks(inheritedReplacementMarks(doc, styleSource.from, styleSource.to), surveyReplacedAnnotations(doc, first.from, last.to).carried);
|
|
2903
4322
|
for (const piece of change.pieces) {
|
|
2904
4323
|
nextTr = nextTr.addMark(piece.from, piece.to, deletion);
|
|
@@ -2906,21 +4325,36 @@ const applyMinimalTrackedReplacement = ({ tr, item, replacement, commentMark, re
|
|
|
2906
4325
|
}
|
|
2907
4326
|
}
|
|
2908
4327
|
if (change.text.length === 0) continue;
|
|
2909
|
-
const
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
4328
|
+
const pieces = insertedNodes(change.text, replacementStarts[plannedIndex] ?? 0, marks);
|
|
4329
|
+
const positions = removedFrom === null ? pieces.map(() => at) : placeTrackedInsertion(nextTr.doc, removedFrom, at, pieces.map((node) => node.marks));
|
|
4330
|
+
const runs = [];
|
|
4331
|
+
for (const [index, piece] of pieces.entries()) {
|
|
4332
|
+
const position = positions[index] ?? at;
|
|
4333
|
+
const run = runs.at(-1);
|
|
4334
|
+
if (run?.at === position) run.pieces.push(piece);
|
|
4335
|
+
else runs.push({
|
|
4336
|
+
at: position,
|
|
4337
|
+
pieces: [piece]
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
for (const run of runs.toReversed()) {
|
|
4341
|
+
let nodes = run.pieces;
|
|
4342
|
+
if (removedFrom === null) {
|
|
4343
|
+
const pieceMarks = commentsForTrackedInsertion(nextTr.doc, run.at, run.pieces.map((node) => node.marks));
|
|
4344
|
+
nodes = run.pieces.map((node, index) => node.mark(pieceMarks[index] ?? node.marks));
|
|
4345
|
+
}
|
|
4346
|
+
nextTr = nextTr.insert(run.at, nodes);
|
|
4347
|
+
const end = run.at + Fragment.fromArray(nodes).size;
|
|
4348
|
+
nextTr = nextTr.addMark(run.at, end, insertion);
|
|
4349
|
+
if (commentMark) nextTr = nextTr.addMark(run.at, end, commentMark);
|
|
4350
|
+
}
|
|
2918
4351
|
}
|
|
2919
4352
|
return {
|
|
2920
4353
|
type: "applied",
|
|
2921
4354
|
transaction: nextTr,
|
|
2922
4355
|
backgroundRevisionIds,
|
|
2923
|
-
nextRevisionId
|
|
4356
|
+
nextRevisionId,
|
|
4357
|
+
unifiesFormatting
|
|
2924
4358
|
};
|
|
2925
4359
|
};
|
|
2926
4360
|
/**
|
|
@@ -2980,7 +4414,7 @@ const clearTouchedBackground = ({ tr, ranges, styleResolver }) => {
|
|
|
2980
4414
|
});
|
|
2981
4415
|
return nextTr;
|
|
2982
4416
|
};
|
|
2983
|
-
const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId }) => {
|
|
4417
|
+
const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId, noteReferences }) => {
|
|
2984
4418
|
const anchor = snapshot.anchors[blockId];
|
|
2985
4419
|
if (!anchor) return {
|
|
2986
4420
|
type: "skip",
|
|
@@ -3006,7 +4440,10 @@ const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId
|
|
|
3006
4440
|
type: "skip",
|
|
3007
4441
|
reason: "changedBlock"
|
|
3008
4442
|
};
|
|
3009
|
-
const cleanBlock = buildCleanBlockText(live.node, live.from
|
|
4443
|
+
const cleanBlock = buildCleanBlockText(live.node, live.from, {
|
|
4444
|
+
fieldResults: "text",
|
|
4445
|
+
noteReferences
|
|
4446
|
+
});
|
|
3010
4447
|
const currentText = cleanBlock.text;
|
|
3011
4448
|
const currentTextHash = hashFolioAIBlockText(normalizeFolioAIBlockText(currentText));
|
|
3012
4449
|
const structuralBoundaryHash = hashFolioAIBlockStructuralBoundaries(cleanBlock);
|
|
@@ -3024,13 +4461,52 @@ const resolveStableBlock = ({ snapshot, blockId, liveBlocks, liveBlocksByParaId
|
|
|
3024
4461
|
currentTextHash
|
|
3025
4462
|
};
|
|
3026
4463
|
};
|
|
3027
|
-
const resolveOperation = (
|
|
4464
|
+
const resolveOperation = (args) => {
|
|
4465
|
+
const resolution = resolveOperationTarget(args);
|
|
4466
|
+
if (resolution.type === "skip") return resolution;
|
|
4467
|
+
return (noteReferenceEditsAllowed() ? null : refuseNoteReferenceEdit(resolution.operation, args.noteReferences)) ?? resolution;
|
|
4468
|
+
};
|
|
4469
|
+
/**
|
|
4470
|
+
* A text change may rewrite the prose around a footnote or endnote reference
|
|
4471
|
+
* but never the reference: its marker is structure the clean text shows, not
|
|
4472
|
+
* characters it holds. The replacement has to keep every marker its match
|
|
4473
|
+
* covers, in order, and may not write a marker-shaped string of its own; the
|
|
4474
|
+
* replacement's inline emphasis would rebuild the match whole, reference
|
|
4475
|
+
* included, so it cannot carry one either. Checked before anything is applied,
|
|
4476
|
+
* so direct and tracked mode refuse the same operations.
|
|
4477
|
+
*/
|
|
4478
|
+
const refuseNoteReferenceEdit = (item, noteReferences) => {
|
|
4479
|
+
const { operation } = item;
|
|
4480
|
+
let replacement;
|
|
4481
|
+
if (operation.type === "replaceInBlock" || operation.type === "replaceRange") replacement = operation.replace;
|
|
4482
|
+
else if (operation.type === "replaceBlock") replacement = operation.text;
|
|
4483
|
+
else return null;
|
|
4484
|
+
const cleanBlock = buildCleanBlockText(item.blockNode, item.blockFrom, {
|
|
4485
|
+
fieldResults: "text",
|
|
4486
|
+
noteReferences
|
|
4487
|
+
});
|
|
4488
|
+
const span = replacedCleanSpan({ operation }, cleanBlock);
|
|
4489
|
+
if (span === null) return null;
|
|
4490
|
+
const spans = noteReferenceSpansWithin(cleanBlock, span.start, span.start + span.text.length);
|
|
4491
|
+
const rebuildsBlock = operation.type === "replaceBlock" && operation.preserveFormatting === false;
|
|
4492
|
+
if (spans.length > 0 && (hasInlineEmphasis(replacement) || rebuildsBlock)) return {
|
|
4493
|
+
type: "skip",
|
|
4494
|
+
reason: "protectedReference"
|
|
4495
|
+
};
|
|
4496
|
+
if (operation.type === "replaceBlock" && replacement === span.text) return null;
|
|
4497
|
+
return segmentsAroundNoteReferences(span.text, stripInlineEmphasisMarkers(replacement), spans) === null ? {
|
|
4498
|
+
type: "skip",
|
|
4499
|
+
reason: "protectedReference"
|
|
4500
|
+
} : null;
|
|
4501
|
+
};
|
|
4502
|
+
const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByParaId, noteReferences, doc }) => {
|
|
3028
4503
|
const blockId = operation.type === "replaceRange" || operation.type === "commentOnRange" || operation.type === "formatRange" ? operation.range.blockId : operation.blockId;
|
|
3029
4504
|
const primaryBlock = resolveStableBlock({
|
|
3030
4505
|
snapshot,
|
|
3031
4506
|
blockId,
|
|
3032
4507
|
liveBlocks,
|
|
3033
|
-
liveBlocksByParaId
|
|
4508
|
+
liveBlocksByParaId,
|
|
4509
|
+
noteReferences
|
|
3034
4510
|
});
|
|
3035
4511
|
if (primaryBlock.type === "skip") return primaryBlock;
|
|
3036
4512
|
const { blockNode, blockFrom, blockTo, cleanBlock, currentText, currentTextHash } = primaryBlock;
|
|
@@ -3049,6 +4525,16 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3049
4525
|
type: "skip",
|
|
3050
4526
|
reason: "staleRange"
|
|
3051
4527
|
};
|
|
4528
|
+
const characterSplit = describeCharacterSplit(currentText, [startOffset, endOffset], operation.type === "replaceRange" ? "grapheme" : "codePoint");
|
|
4529
|
+
if (characterSplit !== null) return {
|
|
4530
|
+
type: "skip",
|
|
4531
|
+
reason: "splitsCharacter",
|
|
4532
|
+
message: characterSplit
|
|
4533
|
+
};
|
|
4534
|
+
if (cutsIntoNoteReference(cleanBlock, startOffset, endOffset)) return {
|
|
4535
|
+
type: "skip",
|
|
4536
|
+
reason: "protectedReference"
|
|
4537
|
+
};
|
|
3052
4538
|
const range = resolveCleanTextRange({
|
|
3053
4539
|
cleanBlock,
|
|
3054
4540
|
startOffset,
|
|
@@ -3079,7 +4565,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3079
4565
|
const isInsertAfter = operation.type === "insertAfterBlock";
|
|
3080
4566
|
let insertFrom;
|
|
3081
4567
|
if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
|
|
3082
|
-
else insertFrom = isInsertAfter ?
|
|
4568
|
+
else insertFrom = isInsertAfter ? endOfParagraphWithItsTextBoxes(doc, blockFrom) : blockFrom;
|
|
3083
4569
|
const insertTexts = operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text) ? splitInsertParagraphTexts(operation.text) : [operation.text];
|
|
3084
4570
|
return {
|
|
3085
4571
|
type: "resolved",
|
|
@@ -3103,7 +4589,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3103
4589
|
const tableBoundary = findOutermostTableBoundary(doc, blockFrom);
|
|
3104
4590
|
let insertFrom;
|
|
3105
4591
|
if (tableBoundary) insertFrom = position === "after" ? tableBoundary.after : tableBoundary.before;
|
|
3106
|
-
else insertFrom = position === "after" ?
|
|
4592
|
+
else insertFrom = position === "after" ? endOfParagraphWithItsTextBoxes(doc, blockFrom) : blockFrom;
|
|
3107
4593
|
return {
|
|
3108
4594
|
type: "resolved",
|
|
3109
4595
|
operation: {
|
|
@@ -3122,10 +4608,20 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3122
4608
|
blockFrom,
|
|
3123
4609
|
position: operation.position ?? "after"
|
|
3124
4610
|
});
|
|
3125
|
-
if (!insertion
|
|
4611
|
+
if (!insertion) return {
|
|
3126
4612
|
type: "skip",
|
|
3127
4613
|
reason: "unsupportedBlock"
|
|
3128
4614
|
};
|
|
4615
|
+
const overflow = describeUnplacedCellTexts({
|
|
4616
|
+
cellTexts: operation.cellTexts,
|
|
4617
|
+
capacity: insertion.cells.length,
|
|
4618
|
+
target: "row"
|
|
4619
|
+
});
|
|
4620
|
+
if (overflow !== null) return {
|
|
4621
|
+
type: "skip",
|
|
4622
|
+
reason: "payloadDoesNotFit",
|
|
4623
|
+
message: overflow
|
|
4624
|
+
};
|
|
3129
4625
|
return {
|
|
3130
4626
|
type: "resolved",
|
|
3131
4627
|
operation: {
|
|
@@ -3135,7 +4631,8 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3135
4631
|
blockFrom,
|
|
3136
4632
|
blockTo,
|
|
3137
4633
|
blockNode,
|
|
3138
|
-
tableRowInsertion: insertion
|
|
4634
|
+
tableRowInsertion: insertion,
|
|
4635
|
+
tableRowComments: commentsAcrossBlockBoundary(doc, insertion.rowPosition)
|
|
3139
4636
|
}
|
|
3140
4637
|
};
|
|
3141
4638
|
}
|
|
@@ -3183,7 +4680,8 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3183
4680
|
blockFrom,
|
|
3184
4681
|
blockTo,
|
|
3185
4682
|
blockNode,
|
|
3186
|
-
tableColumnInsertion
|
|
4683
|
+
tableColumnInsertion,
|
|
4684
|
+
tableColumnComments: commentsAcrossTable(doc, tableColumnInsertion.tablePosition)
|
|
3187
4685
|
}
|
|
3188
4686
|
};
|
|
3189
4687
|
}
|
|
@@ -3248,7 +4746,8 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3248
4746
|
snapshot,
|
|
3249
4747
|
blockId: operation.endBlockId,
|
|
3250
4748
|
liveBlocks,
|
|
3251
|
-
liveBlocksByParaId
|
|
4749
|
+
liveBlocksByParaId,
|
|
4750
|
+
noteReferences
|
|
3252
4751
|
});
|
|
3253
4752
|
if (endTarget.type === "skip") return endTarget;
|
|
3254
4753
|
const endCell = findEnclosingTableCell(doc, endTarget.blockFrom);
|
|
@@ -3320,7 +4819,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3320
4819
|
if (operation.type === "insertTable") {
|
|
3321
4820
|
const boundary = findOutermostTableBoundary(doc, blockFrom) ?? {
|
|
3322
4821
|
before: blockFrom,
|
|
3323
|
-
after:
|
|
4822
|
+
after: endOfParagraphWithItsTextBoxes(doc, blockFrom)
|
|
3324
4823
|
};
|
|
3325
4824
|
const insertFrom = (operation.position ?? "after") === "after" ? boundary.after : boundary.before;
|
|
3326
4825
|
return {
|
|
@@ -3382,6 +4881,12 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3382
4881
|
type: "skip",
|
|
3383
4882
|
reason: "staleRange"
|
|
3384
4883
|
};
|
|
4884
|
+
const characterSplit = describeCharacterSplit(currentText, [startOffset, endOffset], "grapheme");
|
|
4885
|
+
if (characterSplit !== null) return {
|
|
4886
|
+
type: "skip",
|
|
4887
|
+
reason: "splitsCharacter",
|
|
4888
|
+
message: characterSplit
|
|
4889
|
+
};
|
|
3385
4890
|
const range = resolveCleanTextRange({
|
|
3386
4891
|
cleanBlock,
|
|
3387
4892
|
startOffset,
|
|
@@ -3420,10 +4925,14 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3420
4925
|
}
|
|
3421
4926
|
};
|
|
3422
4927
|
}
|
|
4928
|
+
if (operation.type === "replaceBlock" && isPendingDeletion(doc, blockFrom, blockNode, currentText)) return {
|
|
4929
|
+
type: "skip",
|
|
4930
|
+
reason: "pendingDeletion"
|
|
4931
|
+
};
|
|
3423
4932
|
if (operation.type === "deleteBlock" || operation.type === "replaceBlock") {
|
|
3424
4933
|
const replaceChangesText = operation.type === "replaceBlock" && operation.text !== currentText;
|
|
3425
4934
|
const replaceChangesStyle = operation.type === "replaceBlock" && operation.styleId !== void 0 && operation.styleId !== (expectParagraphAttrs(blockNode).styleId ?? null);
|
|
3426
|
-
if (replaceChangesText && cleanBlock.structuralBoundaries.
|
|
4935
|
+
if (replaceChangesText && cleanBlock.structuralBoundaries.some((boundary) => boundary.type !== "field" && boundary.type !== "noteReference")) return {
|
|
3427
4936
|
type: "skip",
|
|
3428
4937
|
reason: "unsupportedBlock"
|
|
3429
4938
|
};
|
|
@@ -3503,7 +5012,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3503
5012
|
reason: "noopOperation"
|
|
3504
5013
|
};
|
|
3505
5014
|
const quote = getOperationQuote(operation);
|
|
3506
|
-
const range = resolveTextInCleanBlock(cleanBlock, quote || currentText);
|
|
5015
|
+
const range = resolveTextInCleanBlock(cleanBlock, quote || currentText, operation.type === "replaceInBlock" ? "grapheme" : "codePoint");
|
|
3507
5016
|
if (range.type !== "resolved") return range;
|
|
3508
5017
|
return {
|
|
3509
5018
|
type: "resolved",
|
|
@@ -3517,7 +5026,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
|
|
|
3517
5026
|
}
|
|
3518
5027
|
};
|
|
3519
5028
|
};
|
|
3520
|
-
const resolveTextInCleanBlock = (cleanBlock, find) => {
|
|
5029
|
+
const resolveTextInCleanBlock = (cleanBlock, find, strictness) => {
|
|
3521
5030
|
if (find.length === 0) return {
|
|
3522
5031
|
type: "skip",
|
|
3523
5032
|
reason: "emptyOperation"
|
|
@@ -3532,6 +5041,16 @@ const resolveTextInCleanBlock = (cleanBlock, find) => {
|
|
|
3532
5041
|
type: "skip",
|
|
3533
5042
|
reason: "ambiguousFind"
|
|
3534
5043
|
};
|
|
5044
|
+
const characterSplit = describeCharacterSplit(text, [firstIndex, firstIndex + find.length], strictness);
|
|
5045
|
+
if (characterSplit !== null) return {
|
|
5046
|
+
type: "skip",
|
|
5047
|
+
reason: "splitsCharacter",
|
|
5048
|
+
message: `the match's ${characterSplit}`
|
|
5049
|
+
};
|
|
5050
|
+
if (cutsIntoNoteReference(cleanBlock, firstIndex, firstIndex + find.length)) return {
|
|
5051
|
+
type: "skip",
|
|
5052
|
+
reason: "protectedReference"
|
|
5053
|
+
};
|
|
3535
5054
|
const range = resolveCleanTextRange({
|
|
3536
5055
|
cleanBlock,
|
|
3537
5056
|
startOffset: firstIndex,
|
|
@@ -3557,9 +5076,128 @@ const getTextRangeFromCleanBlock = (cleanBlock) => {
|
|
|
3557
5076
|
};
|
|
3558
5077
|
};
|
|
3559
5078
|
const getOperationCommentText = (operation) => "comment" in operation ? operation.comment?.text : void 0;
|
|
5079
|
+
/**
|
|
5080
|
+
* Every paragraph style an operation names, with the field that names it.
|
|
5081
|
+
* `null` clears the style and names none; an omitted one keeps the style the
|
|
5082
|
+
* paragraph or its anchor already carries, which the document holds by
|
|
5083
|
+
* construction.
|
|
5084
|
+
*/
|
|
5085
|
+
const operationParagraphStyleReferences = (operation) => {
|
|
5086
|
+
switch (operation.type) {
|
|
5087
|
+
case "insertAfterBlock":
|
|
5088
|
+
case "insertBeforeBlock":
|
|
5089
|
+
case "replaceBlock": return [{
|
|
5090
|
+
path: "styleId",
|
|
5091
|
+
styleId: operation.styleId
|
|
5092
|
+
}];
|
|
5093
|
+
case "setBlockParagraphProperties": return [{
|
|
5094
|
+
path: "properties.styleId",
|
|
5095
|
+
styleId: operation.properties.styleId
|
|
5096
|
+
}];
|
|
5097
|
+
case "splitBlock": return [{
|
|
5098
|
+
path: "firstParagraphProperties.styleId",
|
|
5099
|
+
styleId: operation.firstParagraphProperties?.styleId
|
|
5100
|
+
}, {
|
|
5101
|
+
path: "secondParagraphProperties.styleId",
|
|
5102
|
+
styleId: operation.secondParagraphProperties?.styleId
|
|
5103
|
+
}];
|
|
5104
|
+
case "mergeBlockWithNext": return [{
|
|
5105
|
+
path: "mergedParagraphProperties.styleId",
|
|
5106
|
+
styleId: operation.mergedParagraphProperties?.styleId
|
|
5107
|
+
}];
|
|
5108
|
+
default: return [];
|
|
5109
|
+
}
|
|
5110
|
+
};
|
|
5111
|
+
/**
|
|
5112
|
+
* The first paragraph style the operation names that the document does not
|
|
5113
|
+
* define as a paragraph style. A `w:pStyle` naming no definition, or a table
|
|
5114
|
+
* or character style, confers no formatting: the paragraph keeps its body
|
|
5115
|
+
* look while the operation reports a restyle. Refused here, before anything
|
|
5116
|
+
* is applied, the way an undefined block id is. `null` definitions (a state
|
|
5117
|
+
* without the styles plugin) cannot say what is defined, and check nothing.
|
|
5118
|
+
*/
|
|
5119
|
+
const findUndefinedParagraphStyleReference = (operation, definitions) => {
|
|
5120
|
+
if (definitions === null) return;
|
|
5121
|
+
for (const { path, styleId } of operationParagraphStyleReferences(operation)) {
|
|
5122
|
+
if (styleId === null || styleId === void 0) continue;
|
|
5123
|
+
const definition = definitions.get(styleId);
|
|
5124
|
+
if (definition?.type !== "paragraph") return {
|
|
5125
|
+
path,
|
|
5126
|
+
styleId,
|
|
5127
|
+
definedAs: definition?.type
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
5130
|
+
};
|
|
5131
|
+
const MAX_LISTED_PARAGRAPH_STYLES = 12;
|
|
5132
|
+
const describeUndefinedParagraphStyle = ({ path, styleId, definedAs }, definitions) => {
|
|
5133
|
+
const defined = (definitions?.paragraphStyles() ?? []).map((style) => style.styleId);
|
|
5134
|
+
let available = "it defines no visible paragraph styles";
|
|
5135
|
+
if (defined.length > 0) available = `its paragraph styles include ${defined.slice(0, MAX_LISTED_PARAGRAPH_STYLES).join(", ")}${defined.length > MAX_LISTED_PARAGRAPH_STYLES ? ", …" : ""}`;
|
|
5136
|
+
return `${path} "${styleId}" ${definedAs === void 0 ? "names no style in this document" : `names a ${definedAs} style, not a paragraph style`} (${available}).`;
|
|
5137
|
+
};
|
|
5138
|
+
/** The `w:num` id a stated numbering names, when it names one. */
|
|
5139
|
+
const statedNumberingInstance = (numbering) => {
|
|
5140
|
+
if (numbering === null || numbering === void 0) return;
|
|
5141
|
+
const numId = Reflect.get(numbering, "numId");
|
|
5142
|
+
return typeof numId === "number" ? numId : void 0;
|
|
5143
|
+
};
|
|
5144
|
+
/**
|
|
5145
|
+
* Every numbering instance an operation names, with the field that names it.
|
|
5146
|
+
* `listLevel` is not among them: it keeps the instance the paragraph or its
|
|
5147
|
+
* anchor already references, which the document holds by construction.
|
|
5148
|
+
*/
|
|
5149
|
+
const operationNumberingReferences = (operation) => {
|
|
5150
|
+
switch (operation.type) {
|
|
5151
|
+
case "insertAfterBlock":
|
|
5152
|
+
case "insertBeforeBlock": return [{
|
|
5153
|
+
path: "numbering",
|
|
5154
|
+
numbering: operation.numbering
|
|
5155
|
+
}];
|
|
5156
|
+
case "setBlockParagraphProperties": return [{
|
|
5157
|
+
path: "properties.numbering",
|
|
5158
|
+
numbering: operation.properties.numbering
|
|
5159
|
+
}];
|
|
5160
|
+
case "splitBlock": return [{
|
|
5161
|
+
path: "firstParagraphProperties.numbering",
|
|
5162
|
+
numbering: operation.firstParagraphProperties?.numbering
|
|
5163
|
+
}, {
|
|
5164
|
+
path: "secondParagraphProperties.numbering",
|
|
5165
|
+
numbering: operation.secondParagraphProperties?.numbering
|
|
5166
|
+
}];
|
|
5167
|
+
case "mergeBlockWithNext": return [{
|
|
5168
|
+
path: "mergedParagraphProperties.numbering",
|
|
5169
|
+
numbering: operation.mergedParagraphProperties?.numbering
|
|
5170
|
+
}];
|
|
5171
|
+
default: return [];
|
|
5172
|
+
}
|
|
5173
|
+
};
|
|
5174
|
+
/**
|
|
5175
|
+
* The first numbering instance the operation names that the document does not
|
|
5176
|
+
* define. A paragraph referencing a missing `w:num` is a model no save can
|
|
5177
|
+
* write, so it is refused here, before anything is applied, the way a block id
|
|
5178
|
+
* naming no block is. `null` instance ids (a state without the numbering
|
|
5179
|
+
* plugin) cannot say what is defined, and check nothing.
|
|
5180
|
+
*/
|
|
5181
|
+
const findUndefinedNumberingReference = (operation, instanceIds) => {
|
|
5182
|
+
if (instanceIds === null) return;
|
|
5183
|
+
for (const { path, numbering } of operationNumberingReferences(operation)) {
|
|
5184
|
+
const numId = statedNumberingInstance(numbering);
|
|
5185
|
+
if (numId !== void 0 && !instanceIds.has(numId)) return {
|
|
5186
|
+
path,
|
|
5187
|
+
numId
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5190
|
+
};
|
|
5191
|
+
const MAX_LISTED_NUMBERING_INSTANCES = 12;
|
|
5192
|
+
const describeUndefinedNumbering = ({ path, numId }, instanceIds) => {
|
|
5193
|
+
const defined = [...instanceIds ?? []].sort((left, right) => left - right);
|
|
5194
|
+
let available = "it defines none";
|
|
5195
|
+
if (defined.length > 0) available = `it defines ${defined.slice(0, MAX_LISTED_NUMBERING_INSTANCES).join(", ")}${defined.length > MAX_LISTED_NUMBERING_INSTANCES ? ", …" : ""}`;
|
|
5196
|
+
return `${path}.numId ${String(numId)} names no numbering instance in this document (${available}).`;
|
|
5197
|
+
};
|
|
3560
5198
|
const getOperationQuote = (operation) => {
|
|
3561
5199
|
if (operation.type === "replaceInBlock") return operation.find;
|
|
3562
5200
|
if (operation.type === "commentOnBlock") return operation.quote;
|
|
3563
5201
|
};
|
|
3564
5202
|
//#endregion
|
|
3565
|
-
export { applyFolioAIEditOperations, previewFolioAIEditOperations };
|
|
5203
|
+
export { applyFolioAIEditOperations, previewFolioAIEditOperations, previewFolioAIEditOperationsWithResult };
|