@stll/folio-core 0.53.0 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/ai-edits/apply.js +415 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +4 -2
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/docx/verticalMergeProjection.d.ts +17 -0
  41. package/dist/docx/verticalMergeProjection.js +120 -0
  42. package/dist/fonts/embeddedFonts.js +1 -1
  43. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  44. package/dist/i18n/messages/catalogs.gen.js +34 -34
  45. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  46. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  47. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  48. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  49. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  50. package/dist/markdown/escape.d.ts +2 -2
  51. package/dist/markdown/escape.js +4 -5
  52. package/dist/markdown/renderBlock.js +91 -25
  53. package/dist/markdown/renderRuns.d.ts +10 -1
  54. package/dist/markdown/renderRuns.js +320 -59
  55. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  56. package/dist/prosemirror/clearRunColor.js +46 -0
  57. package/dist/prosemirror/commands/comments.js +62 -22
  58. package/dist/prosemirror/commands/formatPainter.js +1 -1
  59. package/dist/prosemirror/commands/hyperlink.js +2 -1
  60. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  61. package/dist/prosemirror/commands/propertyChangeScope.js +28 -4
  62. package/dist/prosemirror/commands/resolveAllTableChanges.js +40 -41
  63. package/dist/prosemirror/commands/resolveParagraphProperties.js +33 -8
  64. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +7 -1
  65. package/dist/prosemirror/commands/tableCellMergeResolution.js +39 -20
  66. package/dist/prosemirror/commands/tableMergeFoldDecisions.d.ts +61 -0
  67. package/dist/prosemirror/commands/tableMergeFoldDecisions.js +79 -0
  68. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  69. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  70. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  71. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  72. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -13
  73. package/dist/prosemirror/conversion/toProseDoc.js +28 -262
  74. package/dist/prosemirror/documentSchema.d.ts +8 -0
  75. package/dist/prosemirror/documentSchema.js +18 -0
  76. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  77. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  78. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  79. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  80. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  81. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  82. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  83. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  84. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  85. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  86. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  87. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  88. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  89. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  90. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  91. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  92. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  93. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  94. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  95. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  96. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  97. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  98. package/dist/prosemirror/extensions/types.d.ts +7 -0
  99. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  100. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  101. package/dist/prosemirror/index.d.ts +2 -1
  102. package/dist/prosemirror/index.js +2 -1
  103. package/dist/prosemirror/listNumbering.d.ts +3 -21
  104. package/dist/prosemirror/listNumbering.js +24 -39
  105. package/dist/prosemirror/listRendering.d.ts +29 -0
  106. package/dist/prosemirror/listRendering.js +26 -0
  107. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  108. package/dist/prosemirror/markupViewNotes.js +49 -0
  109. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  110. package/dist/prosemirror/markupViewProjection.js +7 -2
  111. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  112. package/dist/prosemirror/noteReferenceReview.js +332 -0
  113. package/dist/prosemirror/paragraphIndentation.js +5 -2
  114. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  115. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  116. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  117. package/dist/prosemirror/paragraphPropertyCarry.js +155 -0
  118. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  119. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  120. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  121. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  122. package/dist/prosemirror/plugins/index.d.ts +2 -1
  123. package/dist/prosemirror/plugins/index.js +2 -1
  124. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  125. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  126. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  127. package/dist/prosemirror/plugins/suggestionMode.js +387 -28
  128. package/dist/prosemirror/plugins/templateDirectives.d.ts +6 -0
  129. package/dist/prosemirror/plugins/templateDirectives.js +21 -7
  130. package/dist/prosemirror/plugins/templatePreviewValues.js +14 -21
  131. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  132. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  133. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  134. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  135. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  136. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  137. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  138. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  139. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  140. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  141. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  142. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  143. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  144. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  145. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  146. package/dist/prosemirror/tableCellPaste.js +525 -0
  147. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  148. package/dist/prosemirror/tableGridMutation.js +166 -18
  149. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  150. package/dist/prosemirror/tableRunIn.js +165 -0
  151. package/dist/prosemirror/textInput.js +11 -2
  152. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  153. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  154. package/dist/server.d.ts +2 -2
  155. package/package.json +6 -6
@@ -2,10 +2,12 @@ import { paragraphNumberingReference, paragraphNumberingReferenceId } from "../d
2
2
  import { formattingEquals } from "../docx/runConsolidator.js";
3
3
  import { isStyleSourcedParagraphNumbering } from "../internal/paragraphFormattingSerialization.js";
4
4
  import { deleteTextBoxesAnchoredAt, droppedTextBoxAnchorIds, endOfParagraphWithItsTextBoxes, gatherTextBoxesAfterParagraph, textBoxAnchorIdOf, textBoxAnchorIdsBetween } from "../prosemirror/anchoredTextBoxes.js";
5
- import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
5
+ import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTableRowAttrs } from "../prosemirror/attrs/index.js";
6
6
  import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
7
+ import { resolveParagraphChangeAttrs } from "../prosemirror/commands/resolveParagraphProperties.js";
7
8
  import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
8
9
  import { encloseWholeControls } from "../prosemirror/contentControlRevisions.js";
10
+ import { JOINED_RUNS_RESTYLED_META } from "../prosemirror/extensions/features/JoinedRunStyleExtension.js";
9
11
  import { requestDeterministicParaIds } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
10
12
  import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
11
13
  import "../prosemirror/extensions/nodes/TextBoxAnchorExtension.js";
@@ -15,9 +17,11 @@ import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
15
17
  import { styleResolvedParagraphFormatting } from "../prosemirror/paragraphFormattingProvenance.js";
16
18
  import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
17
19
  import { joinAtParagraphMark } from "../prosemirror/paragraphMarkJoin.js";
20
+ import { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties } from "../prosemirror/paragraphPropertyCarry.js";
18
21
  import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
19
22
  import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosemirror/plugins/documentNumbering.js";
20
- import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
23
+ import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
24
+ import { rebaseParagraphRuns } from "../prosemirror/rebaseParagraphRuns.js";
21
25
  import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, placeTrackedInsertion, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
22
26
  import { applyMarksToRunFormattingRepresentation, runFormattingInlineControlNodeName, selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
23
27
  import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
@@ -25,6 +29,7 @@ import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prose
25
29
  import { paragraphRunStyleContext, paragraphRunStyleContextAt, resolveParagraphBodyRunFormatting } from "../prosemirror/runStyleFormatting.js";
26
30
  import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
27
31
  import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
32
+ import { removeRowsWithoutCellsAfterBatch } from "../prosemirror/tableGridMutation.js";
28
33
  import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
29
34
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
30
35
  import { BatchClaims } from "./batch-claims.js";
@@ -35,8 +40,9 @@ import { hasInlineEmphasis, parseInlineEmphasisRuns, stripInlineEmphasisMarkers
35
40
  import { applyTextChanges, changesFromSegments, keepAtomicSpans, planTextChanges, widenChangesToAtomicSpans } from "./minimal-replacement.js";
36
41
  import { concreteListReference, resolveNewListOperations } from "./newListNumbering.js";
37
42
  import { cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, segmentsAroundNoteReferences } from "./note-references.js";
43
+ import { separateRevisionStretches } from "./revisionStretches.js";
38
44
  import { collectNoteReferenceLabels, hashFolioAIBlockStructuralBoundaries, hashFolioAIBlockText, isHiddenTableRow, normalizeFolioAIBlockText } from "./snapshot.js";
39
- import { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
45
+ import { isEmptyTableCell, mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
40
46
  import { planTableMutations } from "./table-mutation-plan.js";
41
47
  import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, describeUnplacedCellTexts, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
42
48
  import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBoundary, tableRectangleCutsMergedCell } from "./table-targets.js";
@@ -346,13 +352,23 @@ const undeletedContentAtomRanges = (tr, blockFrom) => {
346
352
  });
347
353
  return ranges;
348
354
  };
349
- /** The carrier pPr before this batch changed it, for final-mark rotation. */
350
- const paragraphPropertiesBeforeBatch = (node, batchRevisionIds) => {
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) => {
351
364
  const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
352
- if (!Array.isArray(propertyChanges)) return paragraphPropertiesSnapshot(node);
353
- const earliestBatchChange = propertyChanges.find(({ info }) => batchRevisionIds.has(info.id));
354
- if (!earliestBatchChange) return paragraphPropertiesSnapshot(node);
355
- return earliestBatchChange.previousFormatting ?? {};
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);
356
372
  };
357
373
  const resolveFormattingFromStyle = ({ attrs, styleId, styleResolver }) => {
358
374
  const resolved = styleResolver?.resolveParagraphStyle(styleId).paragraphFormatting;
@@ -425,6 +441,7 @@ const preserveRunFormattingAcrossTrackedStyleChange = ({ allocateRevisionInfo, n
425
441
  * the change left alone from one it explicitly cleared when rejecting it.
426
442
  */
427
443
  const applyBlockParagraphProperties = ({ tr, position, node, properties, styleResolver, numbering = null, revisionInfo }) => {
444
+ const storedMarks = tr.storedMarks ?? tr.selection.$from.marks();
428
445
  const attrs = expectParagraphAttrs(node);
429
446
  const resolvedFormattingFromStyle = properties.styleId === void 0 ? resolveFormattingFromStyle({
430
447
  attrs,
@@ -476,45 +493,25 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
476
493
  tr,
477
494
  revisionIds: []
478
495
  };
479
- const styled = bridgeResult.tr.setNodeMarkup(position, void 0, nextAttrs);
480
496
  return {
481
- tr: (attrs.styleId ?? null) === (nextStyleId ?? null) ? styled : rebaseParagraphRuns({
482
- tr: styled,
497
+ tr: rebaseParagraphRuns({
498
+ tr: bridgeResult.tr.setNodeMarkup(position, void 0, nextAttrs),
483
499
  position,
484
500
  previous: node,
485
- styleResolver
501
+ styleResolver,
502
+ storedMarks
486
503
  }),
487
504
  changed: true,
488
505
  revisionId: change?.info.id ?? null,
489
506
  revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
490
507
  };
491
508
  };
492
- /** Rebuild rendered marks after the paragraph's style cascade changes. */
493
- const rebaseParagraphRuns = ({ tr, position, previous, target, styleResolver }) => {
494
- const paragraph = tr.doc.nodeAt(position);
495
- if (!paragraph) return tr;
496
- const sourceContext = paragraphRunStyleContext(previous, styleResolver);
497
- const targetContext = paragraphRunStyleContext(target ?? paragraph, styleResolver);
498
- const representations = selectRunFormattingCarrierRepresentations({
499
- doc: tr.doc,
500
- from: position + 1,
501
- to: position + paragraph.nodeSize - 1
502
- });
503
- for (const representation of representations) applyMarksToRunFormattingRepresentation({
504
- tr,
505
- representation,
506
- marks: reconcileRunFormattingMarks({
507
- authoredFormatting: readAuthoredRunFormatting({
508
- context: sourceContext,
509
- marks: representation.node.marks,
510
- styleResolver
511
- }),
512
- context: targetContext,
513
- node: representation.node,
514
- styleResolver
515
- })
516
- });
517
- return tr;
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
518
515
  };
519
516
  const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) => {
520
517
  if (item.operation.type !== "replaceBlock" || item.operation.styleId === void 0) return {
@@ -529,6 +526,7 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
529
526
  revisionId: null,
530
527
  revisionIds: []
531
528
  };
529
+ const storedMarks = tr.storedMarks ?? tr.selection.$from.marks();
532
530
  const attrs = expectParagraphAttrs(block);
533
531
  const resolvedFormattingFromStyle = resolveFormattingFromStyle({
534
532
  attrs,
@@ -573,13 +571,13 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
573
571
  tr,
574
572
  revisionIds: []
575
573
  };
576
- const styled = bridgeResult.tr.setNodeMarkup(blockPosition, void 0, nextAttrs);
577
574
  return {
578
- tr: (attrs.styleId ?? null) === item.operation.styleId ? styled : rebaseParagraphRuns({
579
- tr: styled,
575
+ tr: rebaseParagraphRuns({
576
+ tr: bridgeResult.tr.setNodeMarkup(blockPosition, void 0, nextAttrs),
580
577
  position: blockPosition,
581
578
  previous: block,
582
- styleResolver
579
+ styleResolver,
580
+ storedMarks
583
581
  }),
584
582
  revisionId: change?.info.id ?? null,
585
583
  revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
@@ -978,6 +976,27 @@ const deletedRowAffectedRanges = ({ doc, deletion, row }) => {
978
976
  }
979
977
  return ranges;
980
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
+ };
981
1000
  /** What `item` claims of the document its batch resolved against; see `batch-claims.ts`. */
982
1001
  const batchClaimOf = ({ item, doc, producesTrackedChanges }) => {
983
1002
  const block = item.blockFrom;
@@ -1028,9 +1047,7 @@ const batchClaimOf = ({ item, doc, producesTrackedChanges }) => {
1028
1047
  type: "deleteBlock",
1029
1048
  block,
1030
1049
  end: item.blockTo,
1031
- keepsParagraph,
1032
- removesNode: !producesTrackedChanges && !keepsParagraph,
1033
- direct: !producesTrackedChanges
1050
+ keepsParagraph
1034
1051
  };
1035
1052
  }
1036
1053
  case "insertAfterBlock":
@@ -1090,10 +1107,12 @@ const batchClaimOf = ({ item, doc, producesTrackedChanges }) => {
1090
1107
  let axis = "cell";
1091
1108
  if (item.operation.type === "insertTableRow") axis = "row";
1092
1109
  if (item.operation.type === "insertTableColumn") axis = "column";
1093
- return target.type === "none" ? { type: "unclaimed" } : {
1110
+ if (target.type === "none") return { type: "unclaimed" };
1111
+ return {
1094
1112
  type: "tableStructure",
1095
1113
  table: target.tablePosition,
1096
- axis
1114
+ axis,
1115
+ folded: target.type === "mergeCells" ? foldedBlankCellRanges(doc, target) : []
1097
1116
  };
1098
1117
  }
1099
1118
  }
@@ -1251,6 +1270,21 @@ const isBatchableParagraphInsertion = (item, mode) => {
1251
1270
  const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
1252
1271
  return !(mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert);
1253
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
+ };
1254
1288
  const insertBlocksInsideComments = ({ tr, at, blocks, carriedComments }) => {
1255
1289
  const spanning = carriedComments ?? commentsAcrossBlockBoundary(tr.doc, at);
1256
1290
  const fragment = Fragment.from(blocks);
@@ -1350,6 +1384,20 @@ const holdsOnlyDeletedContent = (paragraph) => {
1350
1384
  });
1351
1385
  return whollyDeleted;
1352
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
+ };
1353
1401
  /** A paragraph mark that is a plain pending deletion (not a relocation's source). */
1354
1402
  const isPlainDeletedPPrMark = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "del";
1355
1403
  /** An emptied paragraph still carries deleted content, rather than being an original blank. */
@@ -1380,11 +1428,30 @@ const deletedFinalParagraphAfter = (at, paragraphTypeName) => {
1380
1428
  * Where the added break of the final paragraph at `at` rotates to: the free
1381
1429
  * mark `addedBreakCarrierBefore` finds, or else a paragraph the batch deletes
1382
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.
1383
1434
  * The cancelling paragraph must be followed directly by the paragraph its
1384
1435
  * words run into.
1385
1436
  */
1386
1437
  const finalBreakCarrierBefore = (at, paragraphTypeName, batchRevisionIds) => {
1387
- const free = addedBreakCarrierBefore(at, paragraphTypeName);
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
+ });
1388
1455
  if (free) return {
1389
1456
  ...free,
1390
1457
  cancelsDeletedBreak: false
@@ -1409,6 +1476,43 @@ const finalBreakCarrierBefore = (at, paragraphTypeName, batchRevisionIds) => {
1409
1476
  }
1410
1477
  return null;
1411
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.spec["tableRole"] === "table" && node.childCount > 0) {
1490
+ let allRowsDeleted = true;
1491
+ node.forEach((row) => {
1492
+ allRowsDeleted &&= expectTableRowAttrs(row).trDel != null;
1493
+ });
1494
+ if (allRowsDeleted) continue;
1495
+ }
1496
+ if (node.type !== emptied.type) return null;
1497
+ if (isPlainDeletedPPrMark(node.attrs["pPrMark"]) && holdsOnlyDeletedContent(node)) continue;
1498
+ return {
1499
+ node,
1500
+ position
1501
+ };
1502
+ }
1503
+ return null;
1504
+ };
1505
+ /** Pending retirements, read after the merge has mapped any inserted separator. */
1506
+ const pendingRemovedFinalBreakPositions = (tr, deletions) => {
1507
+ const positions = /* @__PURE__ */ new Set();
1508
+ for (const deletion of deletions) {
1509
+ const at = tr.mapping.slice(deletion.mappedFrom).map(deletion.position);
1510
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1511
+ if (!predecessor || predecessor.node.attrs["pPrMark"] != null) continue;
1512
+ positions.add(predecessor.position);
1513
+ }
1514
+ return positions;
1515
+ };
1412
1516
  /**
1413
1517
  * Finish the tracked deletion of every paragraph a container ends with.
1414
1518
  *
@@ -1432,7 +1536,7 @@ const finalBreakCarrierBefore = (at, paragraphTypeName, batchRevisionIds) => {
1432
1536
  * emptied one is settled only then, since the batch runs backwards and an
1433
1537
  * insertion or deletion before it lands after it.
1434
1538
  */
1435
- const withRetiredFinalParagraphs = ({ tr, deletions, appliedIds, batchRevisionIds, revisionSeed, author, date }) => {
1539
+ const withRetiredFinalParagraphs = ({ tr, deletions, mergedPropertyTargets, appliedIds, batchRevisionIds, revisionSeed, author, date, styleResolver, numbering }) => {
1436
1540
  let nextRevisionId = revisionSeed;
1437
1541
  const addedRevisions = [];
1438
1542
  const retractedRevisionIds = [];
@@ -1444,76 +1548,92 @@ const withRetiredFinalParagraphs = ({ tr, deletions, appliedIds, batchRevisionId
1444
1548
  for (const { deletion: { operationId, revisionExtras }, at } of located) {
1445
1549
  const emptied = tr.doc.nodeAt(at);
1446
1550
  if (emptied?.type.name !== "paragraph") continue;
1447
- const $emptied = tr.doc.resolve(at);
1448
- if (!paragraphEndsItsContainer($emptied, emptied.type.name)) continue;
1449
- let position = at;
1450
- for (let index = $emptied.index() - 1; index >= 0; index--) {
1451
- const previous = $emptied.parent.child(index);
1452
- position -= previous.nodeSize;
1453
- if (previous.type.name !== emptied.type.name) break;
1454
- const mark = previous.attrs["pPrMark"];
1455
- if (mark == null) {
1456
- const revisionId = nextRevisionId++;
1457
- tr.setNodeAttribute(position, "pPrMark", {
1458
- kind: "del",
1459
- info: {
1460
- id: revisionId,
1461
- author,
1462
- date,
1463
- ...revisionExtras
1464
- }
1465
- });
1466
- resolvedOperationIds.add(operationId);
1551
+ const predecessor = finalParagraphPredecessor(tr.doc, at);
1552
+ if (!predecessor) continue;
1553
+ const { node: previous, position } = predecessor;
1554
+ const mark = previous.attrs["pPrMark"];
1555
+ if (mark == null) {
1556
+ const revisionId = nextRevisionId++;
1557
+ tr.setNodeAttribute(position, "pPrMark", {
1558
+ kind: "del",
1559
+ info: {
1560
+ id: revisionId,
1561
+ author,
1562
+ date,
1563
+ ...revisionExtras
1564
+ }
1565
+ });
1566
+ const beforeBatch = resolveParagraphChangeAttrs({
1567
+ node: previous,
1568
+ mode: "reject",
1569
+ boundaryCovered: true,
1570
+ revisionSet: batchRevisionIds,
1571
+ styleResolver,
1572
+ numbering
1573
+ });
1574
+ if (!mergedPropertyTargets.some(({ position: target, mappedFrom }) => {
1575
+ const mapped = tr.mapping.slice(mappedFrom).mapResult(target);
1576
+ return !mapped.deleted && mapped.pos === at;
1577
+ })) carryParagraphProperties({
1578
+ tr,
1579
+ position: at,
1580
+ source: beforeBatch ? previous.type.create(beforeBatch, previous.content, previous.marks) : previous,
1581
+ styleResolver,
1582
+ numbering,
1583
+ revision: {
1584
+ id: revisionId,
1585
+ author,
1586
+ date,
1587
+ ...revisionExtras
1588
+ },
1589
+ keep: propertiesSetInBatch(emptied, batchRevisionIds)
1590
+ });
1591
+ resolvedOperationIds.add(operationId);
1592
+ addedRevisions.push({
1593
+ operationId,
1594
+ revisionId
1595
+ });
1596
+ continue;
1597
+ }
1598
+ if (isInsertedPPrMark(mark)) {
1599
+ const following = tr.doc.nodeAt(position + previous.nodeSize);
1600
+ if (following?.type.name !== emptied.type.name) continue;
1601
+ const previousFormatting = paragraphPropertiesBeforePendingChanges(following);
1602
+ const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
1603
+ const existing = expectParagraphAttrs(previous)._propertyChanges;
1604
+ if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) continue;
1605
+ joinAtParagraphMark({
1606
+ tr,
1607
+ paragraphPos: position,
1608
+ paragraph: previous,
1609
+ next: following,
1610
+ styleResolver
1611
+ });
1612
+ resolvedOperationIds.add(operationId);
1613
+ const retracted = addedBreakRevisionId(mark);
1614
+ if (retracted !== null) retractedRevisionIds.push(retracted);
1615
+ const revisionId = nextRevisionId;
1616
+ if (carryParagraphProperties({
1617
+ tr,
1618
+ position,
1619
+ source: previous,
1620
+ styleResolver,
1621
+ numbering,
1622
+ revision: {
1623
+ id: revisionId,
1624
+ author,
1625
+ date,
1626
+ ...revisionExtras
1627
+ },
1628
+ keep: propertiesSetInBatch(following, batchRevisionIds)
1629
+ }).tracked) {
1630
+ nextRevisionId++;
1467
1631
  addedRevisions.push({
1468
1632
  operationId,
1469
1633
  revisionId
1470
1634
  });
1471
- break;
1472
1635
  }
1473
- if (isPlainDeletedPPrMark(mark)) {
1474
- if (holdsOnlyDeletedContent(previous)) continue;
1475
- break;
1476
- }
1477
- if (isInsertedPPrMark(mark)) {
1478
- const following = tr.doc.nodeAt(position + previous.nodeSize);
1479
- if (following?.type.name !== emptied.type.name) break;
1480
- const previousFormatting = paragraphPropertiesBeforeBatch(following, batchRevisionIds);
1481
- const formattingChanges = JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(previous));
1482
- const existing = expectParagraphAttrs(previous)._propertyChanges;
1483
- if (formattingChanges && hasSerializableParagraphPropertyChange(existing)) break;
1484
- joinAtParagraphMark({
1485
- tr,
1486
- paragraphPos: position,
1487
- paragraph: previous,
1488
- next: following
1489
- });
1490
- resolvedOperationIds.add(operationId);
1491
- const retracted = addedBreakRevisionId(mark);
1492
- if (retracted !== null) retractedRevisionIds.push(retracted);
1493
- const joined = tr.doc.nodeAt(position);
1494
- if (formattingChanges && joined) {
1495
- const revisionId = nextRevisionId++;
1496
- tr.setNodeMarkup(position, void 0, {
1497
- ...joined.attrs,
1498
- _propertyChanges: [...Array.isArray(existing) ? existing : [], {
1499
- type: "paragraphPropertyChange",
1500
- info: {
1501
- id: revisionId,
1502
- author,
1503
- date,
1504
- ...revisionExtras
1505
- },
1506
- previousFormatting
1507
- }]
1508
- });
1509
- addedRevisions.push({
1510
- operationId,
1511
- revisionId
1512
- });
1513
- }
1514
- break;
1515
- }
1516
- break;
1636
+ continue;
1517
1637
  }
1518
1638
  }
1519
1639
  return {
@@ -1545,6 +1665,8 @@ const withRetiredFinalParagraphs = ({ tr, deletions, appliedIds, batchRevisionId
1545
1665
  * which paragraph ends a container is only settled when the batch is, and an
1546
1666
  * insertion that looked final was undone by the next operation writing a table
1547
1667
  * after it.
1668
+ * A wholly inserted table from this batch can be crossed to reach the carrier;
1669
+ * its removal on reject exposes that same paragraph boundary again.
1548
1670
  *
1549
1671
  * The paragraph the run was appended after may be one the same batch deletes:
1550
1672
  * the insertion landed first, so the deletion took that paragraph's mark as
@@ -1553,7 +1675,7 @@ const withRetiredFinalParagraphs = ({ tr, deletions, appliedIds, batchRevisionId
1553
1675
  * paragraph's words run into the one after it, which ends with the free mark,
1554
1676
  * and neither break is left to resolve.
1555
1677
  */
1556
- const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials }) => {
1678
+ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials, styleResolver }) => {
1557
1679
  const supersededDeletedBreaks = [];
1558
1680
  const collectSupersededDeletedBreaks = (parent, contentStart) => {
1559
1681
  let previous = null;
@@ -1586,6 +1708,24 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
1586
1708
  };
1587
1709
  collectSupersededDeletedBreaks(tr.doc, 0);
1588
1710
  for (const { preceding, inserted, deletedMark, precedingMark } of supersededDeletedBreaks) {
1711
+ const insertionRevisionId = addedBreakRevisionId(precedingMark);
1712
+ if (insertionRevisionId !== null) {
1713
+ const previous = tr.doc.nodeAt(preceding);
1714
+ const current = tr.doc.nodeAt(inserted);
1715
+ if (!previous || !current) panic("A shifted paragraph break lost its carrier");
1716
+ const previousFormatting = paragraphPropertiesOnReject(previous);
1717
+ const existing = expectParagraphAttrs(current)._propertyChanges;
1718
+ if (!hasSerializableParagraphPropertyChange(existing) && JSON.stringify(previousFormatting) !== JSON.stringify(paragraphPropertiesSnapshot(current))) tr.setNodeAttribute(inserted, "_propertyChanges", [...Array.isArray(existing) ? existing : [], {
1719
+ type: "paragraphPropertyChange",
1720
+ info: {
1721
+ id: insertionRevisionId,
1722
+ author,
1723
+ date,
1724
+ ...initials ? { initials } : {}
1725
+ },
1726
+ previousFormatting
1727
+ }]);
1728
+ }
1589
1729
  tr.setNodeAttribute(preceding, "pPrMark", precedingMark);
1590
1730
  tr.setNodeAttribute(inserted, "pPrMark", deletedMark);
1591
1731
  }
@@ -1634,7 +1774,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
1634
1774
  const revisionId = addedBreakRevisionId(node.attrs["pPrMark"]);
1635
1775
  return revisionId === null || !batchRevisionIds.has(revisionId);
1636
1776
  })) continue;
1637
- const previousFormatting = paragraphPropertiesBeforeBatch(carrier.node, batchRevisionIds);
1777
+ const previousFormatting = paragraphPropertiesBeforePendingChanges(carrier.node);
1638
1778
  next = next.setNodeAttribute(finalPosition, "pPrMark", null);
1639
1779
  let carriedMark = finalMark;
1640
1780
  for (let index = 1; index < path.length; index++) {
@@ -1680,7 +1820,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
1680
1820
  paragraphPos: previous.position,
1681
1821
  paragraph: deleted,
1682
1822
  next: following,
1683
- firstIsGoing: true
1823
+ styleResolver
1684
1824
  });
1685
1825
  retractedRevisionIds.push(ownerRevisionId);
1686
1826
  const deletedBreakId = addedOrDeletedBreakRevisionId(displacedMark);
@@ -1875,7 +2015,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
1875
2015
  ...initials ? { initials } : {}
1876
2016
  };
1877
2017
  const paragraph = item.blockNode.type.create(attrs, content);
1878
- const sourceContext = paragraphRunStyleContext(operation.inheritFormatting === false ? item.blockNode.type.create({}) : item.blockNode, styleResolver);
2018
+ const sourceContext = OPERATION_TEXT_CONTEXT;
1879
2019
  const targetContext = paragraphRunStyleContext(paragraph, styleResolver);
1880
2020
  const reconciled = [];
1881
2021
  paragraph.forEach((child) => {
@@ -2125,6 +2265,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2125
2265
  const deletedTextBoxAnchorIds = /* @__PURE__ */ new Set();
2126
2266
  /** Container-ending paragraphs a tracked `deleteBlock` emptied: see `withRetiredFinalParagraphs`. */
2127
2267
  const deletedFinalParagraphs = [];
2268
+ const mergedPropertyTargets = [];
2128
2269
  const deferredNoopFinalDeletions = /* @__PURE__ */ new Set();
2129
2270
  const ownsSharedRevisionIdCursor = revisionIdSeed === void 0 && revisionStamp === void 0;
2130
2271
  let revisionSeed = revisionIdSeed ?? revisionStamp?.idSeed ?? revisionIdCursor;
@@ -2141,6 +2282,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2141
2282
  };
2142
2283
  const date = revisionStamp?.date ?? (/* @__PURE__ */ new Date()).toISOString();
2143
2284
  const insertedColumnCounts = /* @__PURE__ */ new Map();
2285
+ const emptiedRows = { pending: false };
2144
2286
  const tableAfterDeletedSplit = /* @__PURE__ */ new Map();
2145
2287
  if (mode === "tracked-changes") {
2146
2288
  const afterTables = /* @__PURE__ */ new Map();
@@ -2671,9 +2813,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2671
2813
  }
2672
2814
  const revision = structuralRevision;
2673
2815
  const template = tableTemplates?.get(item.operation.id);
2816
+ const mappedInsertion = mapTableRowInsertion(tr, insertion);
2674
2817
  const result = applyTableRowInsertion({
2675
2818
  tr,
2676
- insertion: mapTableRowInsertion(tr, insertion),
2819
+ insertion: mappedInsertion,
2677
2820
  cellTexts: item.operation.cellTexts,
2678
2821
  revision,
2679
2822
  ...template !== void 0 && { template }
@@ -2685,7 +2828,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2685
2828
  });
2686
2829
  continue;
2687
2830
  }
2688
- tr = result.transaction;
2831
+ tr = addCommentsOver(result.transaction, mappedInsertion.rowPosition, item.tableRowComments);
2689
2832
  if (result.revisionId !== null) {
2690
2833
  operationRevisionSeed++;
2691
2834
  appliedRevisionIds = [result.revisionId];
@@ -2723,11 +2866,9 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2723
2866
  });
2724
2867
  continue;
2725
2868
  }
2726
- tr = result.transaction;
2727
- if (result.revisionId !== null) {
2728
- operationRevisionSeed++;
2729
- appliedRevisionIds = [result.revisionId];
2730
- }
2869
+ tr = addCommentsOver(result.transaction, tr.mapping.map(insertion.tablePosition, 1), item.tableColumnComments);
2870
+ operationRevisionSeed += result.revisionIds.length;
2871
+ appliedRevisionIds = [...result.revisionIds];
2731
2872
  const columnKey = getTableColumnCoordinateKey(insertion);
2732
2873
  insertedColumnCounts.set(columnKey, (insertedColumnCounts.get(columnKey) ?? 0) + 1);
2733
2874
  break;
@@ -2747,7 +2888,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2747
2888
  tr,
2748
2889
  deletion,
2749
2890
  insertedColumnCount: insertedColumnCounts.get(columnKey) ?? 0,
2750
- revision
2891
+ revision,
2892
+ emptiedRows
2751
2893
  });
2752
2894
  if (result.type === "unsupported") {
2753
2895
  skipped.push({
@@ -2757,10 +2899,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2757
2899
  continue;
2758
2900
  }
2759
2901
  tr = result.transaction;
2760
- if (result.revisionId !== null) {
2761
- operationRevisionSeed++;
2762
- appliedRevisionIds = [result.revisionId];
2763
- }
2902
+ operationRevisionSeed += result.revisionIds.length;
2903
+ appliedRevisionIds = [...result.revisionIds];
2764
2904
  break;
2765
2905
  }
2766
2906
  case "mergeTableCells": {
@@ -2893,8 +3033,26 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2893
3033
  });
2894
3034
  continue;
2895
3035
  }
3036
+ const retracted = tr.doc.nodeAt(item.blockFrom);
2896
3037
  tr = tr.delete(item.blockFrom, item.blockTo);
2897
- if (retraction.clearBreakAt !== null) tr = tr.setNodeAttribute(retraction.clearBreakAt, "pPrMark", null);
3038
+ if (retraction.clearBreakAt !== null) {
3039
+ tr = tr.setNodeAttribute(retraction.clearBreakAt, "pPrMark", null);
3040
+ const revisionIdProperties = operationRevisionSeed;
3041
+ if (retracted && recordReplacedParagraphProperties({
3042
+ tr,
3043
+ position: retraction.clearBreakAt,
3044
+ replaced: retracted,
3045
+ revision: {
3046
+ id: revisionIdProperties,
3047
+ author,
3048
+ date,
3049
+ ...trackedRevisionExtras
3050
+ }
3051
+ })) {
3052
+ operationRevisionSeed++;
3053
+ appliedRevisionIds = [revisionIdProperties];
3054
+ }
3055
+ }
2898
3056
  break;
2899
3057
  }
2900
3058
  if (deletionType) {
@@ -2922,14 +3080,36 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
2922
3080
  const endsItsContainer = paragraphEndsItsContainer(tr.doc.resolve(markPosition), item.blockNode.type.name);
2923
3081
  const deleted = tr.doc.nodeAt(markPosition);
2924
3082
  const following = deleted && tr.doc.nodeAt(markPosition + deleted.nodeSize);
2925
- if (deleted && following?.type === deleted.type && !isPairedMove(item.operation.moveId) && isInsertedPPrMark(deleted.attrs["pPrMark"])) joinAtParagraphMark({
2926
- tr,
2927
- paragraphPos: markPosition,
2928
- paragraph: deleted,
2929
- next: following,
2930
- firstIsGoing: true
2931
- });
2932
- else if (!endsItsContainer && deleted?.attrs["pPrMark"] == null) {
3083
+ if (deleted && following?.type === deleted.type && !isPairedMove(item.operation.moveId) && isInsertedPPrMark(deleted.attrs["pPrMark"])) {
3084
+ const restored = paragraphPropertiesOnReject(deleted);
3085
+ const followingChanges = expectParagraphAttrs(following)._propertyChanges;
3086
+ const recordsRestored = isInsertedPPrMark(following.attrs["pPrMark"]) && !hasSerializableParagraphPropertyChange(followingChanges) && JSON.stringify(restored) !== JSON.stringify(paragraphPropertiesSnapshot(following));
3087
+ joinAtParagraphMark({
3088
+ tr,
3089
+ paragraphPos: markPosition,
3090
+ paragraph: deleted,
3091
+ next: following,
3092
+ styleResolver: styleResolver ?? null
3093
+ });
3094
+ const joined = tr.doc.nodeAt(markPosition);
3095
+ if (recordsRestored && joined) {
3096
+ const propertyRevisionId = operationRevisionSeed++;
3097
+ tr = tr.setNodeMarkup(markPosition, void 0, {
3098
+ ...joined.attrs,
3099
+ _propertyChanges: [...Array.isArray(followingChanges) ? followingChanges : [], {
3100
+ type: "paragraphPropertyChange",
3101
+ info: {
3102
+ id: propertyRevisionId,
3103
+ author,
3104
+ date,
3105
+ ...trackedRevisionExtras
3106
+ },
3107
+ previousFormatting: restored
3108
+ }]
3109
+ });
3110
+ appliedRevisionIds = [...appliedRevisionIds, propertyRevisionId];
3111
+ }
3112
+ } else if (!endsItsContainer && deleted?.attrs["pPrMark"] == null) {
2933
3113
  const markRevisionId = operationRevisionSeed++;
2934
3114
  tr = tr.setNodeAttribute(markPosition, "pPrMark", {
2935
3115
  kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
@@ -3218,8 +3398,17 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3218
3398
  break;
3219
3399
  }
3220
3400
  case "mergeBlockWithNext": {
3221
- const separator = item.operation.separator ?? "";
3401
+ if (mode === "direct" && !canJoin(tr.doc, item.blockTo)) {
3402
+ skipped.push({
3403
+ id: item.operation.id,
3404
+ reason: "unsupportedBlock"
3405
+ });
3406
+ continue;
3407
+ }
3408
+ const separator = followedOnlyByDeletedParagraphs(tr.doc, item.blockTo) ? "" : item.operation.separator ?? "";
3222
3409
  const insertAt = item.blockTo - 1;
3410
+ let mergedPropertiesAt = item.blockFrom;
3411
+ let mergedPropertiesRecorded = false;
3223
3412
  const second = tr.doc.nodeAt(item.blockTo);
3224
3413
  const spanningComments = commentsAcrossBlockBoundary(tr.doc, item.blockTo);
3225
3414
  const withSeparator = (transaction) => {
@@ -3253,6 +3442,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3253
3442
  target: paragraph,
3254
3443
  styleResolver
3255
3444
  });
3445
+ tr.setMeta(JOINED_RUNS_RESTYLED_META, true);
3256
3446
  tr = tr.join(item.blockTo + separator.length);
3257
3447
  const secondSection = second ? sectionPropertiesOf(second) : null;
3258
3448
  const joined = tr.doc.nodeAt(item.blockFrom);
@@ -3280,42 +3470,77 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3280
3470
  tr,
3281
3471
  paragraphPos: item.blockFrom,
3282
3472
  paragraph,
3283
- next
3473
+ next,
3474
+ styleResolver
3284
3475
  });
3285
- const previousFormatting = paragraphPropertiesSnapshot(next);
3286
- const existing = expectParagraphAttrs(paragraph)._propertyChanges;
3287
- if (holdsOnlyInsertedContent(paragraph) && !hasSerializableParagraphPropertyChange(existing) && JSON.stringify(paragraphPropertiesSnapshot(paragraph)) !== JSON.stringify(previousFormatting)) tr = tr.setNodeAttribute(item.blockFrom, "_propertyChanges", [...Array.isArray(existing) ? existing : [], {
3288
- type: "paragraphPropertyChange",
3476
+ const joinedMark = tr.doc.nodeAt(item.blockFrom)?.attrs["pPrMark"];
3477
+ const removedBreakPositions = pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs);
3478
+ const carryPos = isDeletedPPrMark(joinedMark) || removedBreakPositions.has(item.blockFrom) ? paragraphLeftAfter({
3479
+ doc: tr.doc,
3480
+ paragraphPos: item.blockFrom,
3481
+ removedBreakPositions
3482
+ }) ?? item.blockFrom : item.blockFrom;
3483
+ const carried = carryParagraphProperties({
3484
+ tr,
3485
+ position: carryPos,
3486
+ source: paragraph,
3487
+ styleResolver,
3488
+ numbering,
3489
+ revision: {
3490
+ id: revisionIdMark,
3491
+ author,
3492
+ date,
3493
+ ...trackedRevisionExtras
3494
+ }
3495
+ });
3496
+ mergedPropertiesAt = carryPos;
3497
+ tr = carried.tr;
3498
+ mergedPropertiesRecorded = carried.tracked;
3499
+ if (!carried.tracked) appliedRevisionIds = appliedRevisionIds.filter((id) => id !== revisionIdMark);
3500
+ } else {
3501
+ tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
3502
+ kind: "del",
3289
3503
  info: {
3290
3504
  id: revisionIdMark,
3291
3505
  author,
3292
3506
  date,
3293
3507
  ...trackedRevisionExtras
3294
- },
3295
- previousFormatting
3296
- }]);
3297
- else appliedRevisionIds = appliedRevisionIds.filter((id) => id !== revisionIdMark);
3298
- } else tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", {
3299
- kind: "del",
3300
- info: {
3301
- id: revisionIdMark,
3302
- author,
3303
- date,
3304
- ...trackedRevisionExtras
3305
- }
3306
- });
3508
+ }
3509
+ });
3510
+ const survivorPos = paragraphLeftAfter({
3511
+ doc: tr.doc,
3512
+ paragraphPos: item.blockFrom,
3513
+ removedBreakPositions: pendingRemovedFinalBreakPositions(tr, deletedFinalParagraphs)
3514
+ }) ?? joinPos;
3515
+ const carried = carryParagraphProperties({
3516
+ tr,
3517
+ position: survivorPos,
3518
+ source: paragraph,
3519
+ styleResolver,
3520
+ numbering,
3521
+ revision: {
3522
+ id: revisionIdMark,
3523
+ author,
3524
+ date,
3525
+ ...trackedRevisionExtras
3526
+ }
3527
+ });
3528
+ tr = carried.tr;
3529
+ mergedPropertiesRecorded = carried.tracked;
3530
+ mergedPropertiesAt = survivorPos;
3531
+ }
3307
3532
  }
3308
3533
  if (item.operation.mergedParagraphProperties) {
3309
- const mergedParagraph = tr.doc.nodeAt(item.blockFrom);
3534
+ const mergedParagraph = tr.doc.nodeAt(mergedPropertiesAt);
3310
3535
  if (!mergedParagraph) panic("A resolved merge did not produce a paragraph");
3311
3536
  const appliedProperties = applyBlockParagraphProperties({
3312
3537
  tr,
3313
- position: item.blockFrom,
3538
+ position: mergedPropertiesAt,
3314
3539
  node: mergedParagraph,
3315
3540
  properties: item.operation.mergedParagraphProperties,
3316
3541
  styleResolver,
3317
3542
  numbering,
3318
- ...mode === "direct" ? {} : { revisionInfo: () => ({
3543
+ ...mode === "direct" || mergedPropertiesRecorded ? {} : { revisionInfo: () => ({
3319
3544
  id: operationRevisionSeed++,
3320
3545
  author,
3321
3546
  date,
@@ -3325,6 +3550,10 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3325
3550
  tr = appliedProperties.tr;
3326
3551
  if (appliedProperties.revisionIds.length > 0) appliedRevisionIds.push(...appliedProperties.revisionIds);
3327
3552
  }
3553
+ if (mode !== "direct") mergedPropertyTargets.push({
3554
+ position: mergedPropertiesAt,
3555
+ mappedFrom: tr.mapping.maps.length
3556
+ });
3328
3557
  break;
3329
3558
  }
3330
3559
  case "commentOnBlock":
@@ -3373,6 +3602,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3373
3602
  ...suggestionId !== null && { suggestionId }
3374
3603
  });
3375
3604
  }
3605
+ removeRowsWithoutCellsAfterBatch(tr, emptiedRows);
3376
3606
  if (tr.docChanged || deferredNoopFinalDeletions.size > 0) {
3377
3607
  const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
3378
3608
  tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds, deletedTextBoxAnchorIds);
@@ -3384,18 +3614,22 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3384
3614
  revisionSeed,
3385
3615
  author,
3386
3616
  date,
3387
- initials
3617
+ initials,
3618
+ styleResolver: styleResolver ?? null
3388
3619
  });
3389
3620
  tr = rotated.transaction;
3390
3621
  revisionSeed = rotated.nextRevisionId;
3391
3622
  const retired = withRetiredFinalParagraphs({
3392
3623
  tr,
3393
3624
  deletions: deletedFinalParagraphs,
3625
+ mergedPropertyTargets,
3394
3626
  appliedIds: new Set(applied.map(({ id }) => id)),
3395
3627
  batchRevisionIds,
3396
3628
  revisionSeed,
3397
3629
  author,
3398
- date
3630
+ date,
3631
+ styleResolver,
3632
+ numbering
3399
3633
  });
3400
3634
  revisionSeed = retired.nextRevisionId;
3401
3635
  for (const operationId of deferredNoopFinalDeletions) {
@@ -3447,6 +3681,19 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
3447
3681
  revisionIds
3448
3682
  };
3449
3683
  }
3684
+ const separated = separateRevisionStretches({
3685
+ tr,
3686
+ revisionSeed
3687
+ });
3688
+ revisionSeed = separated.nextRevisionId;
3689
+ for (const { revisionId, from } of separated.minted) {
3690
+ const receiptIndex = applied.findIndex(({ revisionIds }) => revisionIds?.includes(from));
3691
+ const receipt = applied.at(receiptIndex);
3692
+ if (receiptIndex >= 0 && receipt?.revisionIds) applied[receiptIndex] = {
3693
+ ...receipt,
3694
+ revisionIds: [...receipt.revisionIds, revisionId]
3695
+ };
3696
+ }
3450
3697
  claimSharedRevisionIds(revisionSeed);
3451
3698
  if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
3452
3699
  if (tr.docChanged) view.dispatch(tr);
@@ -4409,7 +4656,8 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4409
4656
  blockFrom,
4410
4657
  blockTo,
4411
4658
  blockNode,
4412
- tableRowInsertion: insertion
4659
+ tableRowInsertion: insertion,
4660
+ tableRowComments: commentsAcrossBlockBoundary(doc, insertion.rowPosition)
4413
4661
  }
4414
4662
  };
4415
4663
  }
@@ -4457,7 +4705,8 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
4457
4705
  blockFrom,
4458
4706
  blockTo,
4459
4707
  blockNode,
4460
- tableColumnInsertion
4708
+ tableColumnInsertion,
4709
+ tableColumnComments: commentsAcrossTable(doc, tableColumnInsertion.tablePosition)
4461
4710
  }
4462
4711
  };
4463
4712
  }