@stll/folio-core 0.35.1 → 0.37.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.
Files changed (236) hide show
  1. package/README.md +59 -1
  2. package/dist/ai-edits/apply.js +878 -377
  3. package/dist/ai-edits/block-identity.d.ts +4 -1
  4. package/dist/ai-edits/block-identity.js +4 -1
  5. package/dist/ai-edits/blockRange.js +14 -14
  6. package/dist/ai-edits/clean-text.d.ts +40 -1
  7. package/dist/ai-edits/clean-text.js +52 -2
  8. package/dist/ai-edits/headless.d.ts +3 -1
  9. package/dist/ai-edits/headless.js +141 -73
  10. package/dist/ai-edits/index.d.ts +3 -3
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.js +38 -20
  13. package/dist/ai-edits/snapshot.d.ts +14 -6
  14. package/dist/ai-edits/snapshot.js +105 -20
  15. package/dist/ai-edits/table-cell-mutations.js +1 -1
  16. package/dist/ai-edits/table-row-column-mutations.js +7 -0
  17. package/dist/ai-edits/table-template.d.ts +11 -1
  18. package/dist/ai-edits/table-template.js +49 -17
  19. package/dist/ai-edits/types.d.ts +62 -61
  20. package/dist/ai-edits/word-diff.d.ts +18 -3
  21. package/dist/ai-edits/word-diff.js +556 -67
  22. package/dist/ai-suggestions/conflict.js +1 -4
  23. package/dist/ai-suggestions/text-positions.d.ts +8 -0
  24. package/dist/ai-suggestions/text-positions.js +27 -5
  25. package/dist/compare/__fixtures__/body-sequence.js +3 -4
  26. package/dist/compare/column-alignment.d.ts +13 -13
  27. package/dist/compare/column-alignment.js +78 -39
  28. package/dist/compare/compare.d.ts +4 -3
  29. package/dist/compare/compare.js +48 -21
  30. package/dist/compare/content-alignment.d.ts +95 -0
  31. package/dist/compare/content-alignment.js +1450 -0
  32. package/dist/compare/content-types.d.ts +100 -0
  33. package/dist/compare/content-types.js +0 -0
  34. package/dist/compare/content.d.ts +209 -0
  35. package/dist/compare/content.js +885 -0
  36. package/dist/compare/formatting.d.ts +5 -5
  37. package/dist/compare/formatting.js +6 -4
  38. package/dist/compare/plan.d.ts +6 -1
  39. package/dist/compare/plan.js +171 -566
  40. package/dist/compare/scenario.d.ts +2 -2
  41. package/dist/compare/types.d.ts +3 -3
  42. package/dist/compare/verification.d.ts +8 -9
  43. package/dist/compare/verification.js +72 -50
  44. package/dist/compat/eigenpal.d.ts +5 -3
  45. package/dist/compat/eigenpal.js +3 -2
  46. package/dist/controller/headerFooterEditorManager.js +18 -10
  47. package/dist/controller/hiddenEditorApi.js +1 -1
  48. package/dist/controller/hiddenEditorManager.d.ts +7 -2
  49. package/dist/controller/hiddenEditorManager.js +15 -6
  50. package/dist/controller/layoutPipeline.js +5 -3
  51. package/dist/controller/noteEditorManager.js +12 -7
  52. package/dist/display-list/build/headerFooterPrimitives.js +1 -0
  53. package/dist/display-list/build/storyPrimitives.d.ts +5 -2
  54. package/dist/display-list/build/storyPrimitives.js +60 -3
  55. package/dist/display-list/build/tablePrimitives.js +30 -0
  56. package/dist/display-list/primitives.d.ts +1 -1
  57. package/dist/document-operations.d.ts +8 -4
  58. package/dist/document-operations.js +104 -12
  59. package/dist/docx/blockContentParser.js +6 -27
  60. package/dist/docx/commentParser.js +2 -4
  61. package/dist/docx/commentRangeIntegrity.js +2 -4
  62. package/dist/docx/documentClone.d.ts +2 -0
  63. package/dist/docx/documentClone.js +2 -0
  64. package/dist/docx/ensureParaIds.js +9 -4
  65. package/dist/docx/headerFooterParser.js +2 -4
  66. package/dist/docx/normalizeBaseDirection.js +5 -7
  67. package/dist/docx/numberingParser.d.ts +3 -1
  68. package/dist/docx/numberingParser.js +4 -1
  69. package/dist/docx/paragraphParser.js +34 -35
  70. package/dist/docx/paragraphPropertySource.d.ts +49 -0
  71. package/dist/docx/paragraphPropertySource.js +113 -0
  72. package/dist/docx/paragraphTraversal.js +30 -34
  73. package/dist/docx/rezip.d.ts +0 -8
  74. package/dist/docx/rezip.js +41 -16
  75. package/dist/docx/runConsolidator.js +2 -4
  76. package/dist/docx/runParser.js +6 -17
  77. package/dist/docx/sectionParser.js +1 -15
  78. package/dist/docx/serializer/paragraphSerializer.js +289 -30
  79. package/dist/docx/serializer/runSerializer.js +6 -12
  80. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
  81. package/dist/docx/serializer/tableSerializer.js +4 -23
  82. package/dist/docx/serializer/trackedChangeAttributes.d.ts +11 -0
  83. package/dist/docx/serializer/trackedChangeAttributes.js +31 -0
  84. package/dist/docx/server/build.d.ts +1 -1
  85. package/dist/docx/server/createBilingualDocument.js +8 -10
  86. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
  87. package/dist/docx/server/validateDocxConformance.js +17 -13
  88. package/dist/docx/tableParser.js +1 -37
  89. package/dist/docx/trackedChangeInfo.d.ts +11 -0
  90. package/dist/docx/trackedChangeInfo.js +38 -0
  91. package/dist/docx/verbatimCapture.d.ts +20 -9
  92. package/dist/docx/verbatimCapture.js +208 -8
  93. package/dist/docx/xmlParser.d.ts +3 -1
  94. package/dist/docx/xmlParser.js +6 -1
  95. package/dist/docx/xmlSafety.d.ts +1 -1
  96. package/dist/docx/xmlSafety.js +157 -0
  97. package/dist/headless-layout.js +5 -4
  98. package/dist/index.d.ts +5 -3
  99. package/dist/index.js +3 -2
  100. package/dist/internal/headlessRevisionResolution.d.ts +23 -0
  101. package/dist/internal/headlessRevisionResolution.js +155 -0
  102. package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
  103. package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
  104. package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
  105. package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
  106. package/dist/internal/pageBreakRunPartition.d.ts +26 -0
  107. package/dist/internal/pageBreakRunPartition.js +35 -0
  108. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
  109. package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
  110. package/dist/internal/sectionEndpointResolution.d.ts +32 -0
  111. package/dist/internal/sectionEndpointResolution.js +38 -0
  112. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  113. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
  114. package/dist/layout-bridge/convert/headerFooterLayout.js +52 -9
  115. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
  116. package/dist/layout-bridge/convert/toFlowBlocks.js +391 -82
  117. package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
  118. package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
  119. package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
  120. package/dist/layout-bridge/dom/textStreamDom.js +75 -0
  121. package/dist/layout-bridge/headerFooterLayout.js +16 -30
  122. package/dist/layout-engine/index.js +12 -3
  123. package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
  124. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
  125. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  126. package/dist/layout-engine/types.d.ts +7 -0
  127. package/dist/layout-painter/imageLayout.d.ts +1 -1
  128. package/dist/layout-painter/renderTable.js +20 -0
  129. package/dist/markdown/renderBlock.js +7 -10
  130. package/dist/model.d.ts +1 -1
  131. package/dist/paged-layout/rangeProjection.js +6 -9
  132. package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
  133. package/dist/paged-layout/transactionDirtyRange.js +23 -1
  134. package/dist/prosemirror/attrs/index.d.ts +7 -3
  135. package/dist/prosemirror/attrs/index.js +128 -7
  136. package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
  137. package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
  138. package/dist/prosemirror/commands/comments.d.ts +10 -1
  139. package/dist/prosemirror/commands/comments.js +551 -87
  140. package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
  141. package/dist/prosemirror/commands/formatPainter.js +319 -63
  142. package/dist/prosemirror/commands/index.d.ts +2 -2
  143. package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
  144. package/dist/prosemirror/commands/pageBreak.js +18 -36
  145. package/dist/prosemirror/commands/propertyChangeScope.d.ts +27 -3
  146. package/dist/prosemirror/commands/propertyChangeScope.js +85 -14
  147. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -5
  148. package/dist/prosemirror/conversion/fromProseDoc.js +935 -223
  149. package/dist/prosemirror/conversion/runShadingMark.js +2 -2
  150. package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
  151. package/dist/prosemirror/conversion/toProseDoc.js +439 -291
  152. package/dist/prosemirror/extensions/StarterKit.js +4 -0
  153. package/dist/prosemirror/extensions/core/ParagraphExtension.js +54 -6
  154. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
  155. package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
  156. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +19 -6
  157. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
  158. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
  159. package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
  160. package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
  161. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
  162. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
  163. package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
  164. package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
  165. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
  166. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
  167. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
  168. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
  169. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  170. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
  171. package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
  172. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
  173. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  174. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
  175. package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
  176. package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
  177. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
  178. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
  179. package/dist/prosemirror/findReplaceSelection.js +8 -3
  180. package/dist/prosemirror/index.d.ts +3 -3
  181. package/dist/prosemirror/listMarker.d.ts +26 -1
  182. package/dist/prosemirror/listMarker.js +108 -14
  183. package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
  184. package/dist/prosemirror/pageBreakRunProjection.js +47 -0
  185. package/dist/prosemirror/paraText.js +6 -0
  186. package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
  187. package/dist/prosemirror/paragraphAlignment.js +13 -0
  188. package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
  189. package/dist/prosemirror/paragraphSpacing.js +125 -0
  190. package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
  191. package/dist/prosemirror/plugins/documentNumbering.js +18 -0
  192. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
  193. package/dist/prosemirror/plugins/pmTextScan.js +14 -4
  194. package/dist/prosemirror/plugins/suggestionMode.js +9 -11
  195. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
  196. package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
  197. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
  198. package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
  199. package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
  200. package/dist/prosemirror/runFormattingProvenance.js +48 -0
  201. package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
  202. package/dist/prosemirror/runFormattingReconciliation.js +54 -0
  203. package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
  204. package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
  205. package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
  206. package/dist/prosemirror/runStyleFormatting.js +105 -0
  207. package/dist/prosemirror/schema/index.d.ts +3 -3
  208. package/dist/prosemirror/schema/marks.d.ts +84 -9
  209. package/dist/prosemirror/schema/marks.js +73 -1
  210. package/dist/prosemirror/schema/nodes.d.ts +30 -6
  211. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
  212. package/dist/prosemirror/styles/resolvedStyleAttrs.js +45 -23
  213. package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
  214. package/dist/prosemirror/styles/styleResolver.js +3 -3
  215. package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
  216. package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
  217. package/dist/prosemirror/tableGridMutation.d.ts +20 -0
  218. package/dist/prosemirror/tableGridMutation.js +30 -0
  219. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
  220. package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
  221. package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
  222. package/dist/prosemirror/validation.js +23 -4
  223. package/dist/redline.js +4 -1
  224. package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
  225. package/dist/render-dom/RenderedDomContext.js +17 -34
  226. package/dist/server.d.ts +4 -4
  227. package/dist/server.js +2 -2
  228. package/dist/style-engine/styleEngine.d.ts +1 -1
  229. package/dist/utils/clipboard.js +1 -1
  230. package/dist/utils/findReplace.js +38 -21
  231. package/dist/utils/mergeDocumentContent.js +3 -3
  232. package/dist/utils/replaceText.js +2 -1
  233. package/dist/version-comparison.d.ts +38 -44
  234. package/dist/version-comparison.js +270 -399
  235. package/dist/watermark/index.js +2 -4
  236. package/package.json +8 -2
@@ -1,12 +1,19 @@
1
+ import { appendHeadlessInlineResolution } from "../../internal/headlessRevisionResolution.js";
2
+ import { stateAllowsHeadlessRevisionResolution } from "../../internal/headlessRevisionResolutionGuard.js";
1
3
  import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
2
- import { paragraphEndsItsContainer } from "../containerFinalParagraph.js";
3
- import { textFormattingToMarks } from "../conversion/toProseDoc.js";
4
- import { markStructuralChange } from "../extensions/features/ParagraphChangeTrackerExtension.js";
4
+ import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../containerFinalParagraph.js";
5
+ import { resolveParagraphDefaultTextFormatting } from "../conversion/toProseDoc.js";
6
+ import { markChangedParagraphRanges, markStructuralChange, markTrackedSectionEndpointRemoval } from "../extensions/features/ParagraphChangeTrackerExtension.js";
7
+ import { getDocumentStyleResolver } from "../plugins/documentStyles.js";
8
+ import { setParagraphAttrsWithRebasedRunFormatting } from "../rebaseParagraphRunFormatting.js";
5
9
  import { getFolioNodeRevisionCarriers } from "../revisionCarriers.js";
6
10
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
11
+ import { reconstructRejectedRunFormattingMarks } from "../runPropertyChangeResolution.js";
12
+ import { paragraphRunStyleContextAt } from "../runStyleFormatting.js";
7
13
  import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
14
+ import { reconcileTableGridAfterColumnRemoval } from "../tableGridMutation.js";
8
15
  import { holdsNoContent } from "../zeroWidthAnchors.js";
9
- import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
16
+ import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, removeParagraphPropertyChanges, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
10
17
  import { hasMatchingCollapsedTableCellMerge, resolveCollapsedTableCellMerge, resolveVisibleTableCellMerge } from "./tableCellMergeResolution.js";
11
18
  import { TableMap, removeRow } from "prosemirror-tables";
12
19
  import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
@@ -43,6 +50,7 @@ function removeCommentMark(commentId) {
43
50
  return true;
44
51
  };
45
52
  }
53
+ const BULK_INLINE_RESOLUTION_THRESHOLD = 256;
46
54
  /**
47
55
  * Resolve a tracked change: accept or reject.
48
56
  * - Accept: keep insertions (remove mark), delete deletions (remove text)
@@ -55,14 +63,16 @@ function removeCommentMark(commentId) {
55
63
  * the operation matches every revision mark or property-change entry in the
56
64
  * range (the bulk accept-all/reject-all path).
57
65
  */
58
- function resolveChange(from, to, mode, revisionIds) {
66
+ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
59
67
  return (state, dispatch) => {
60
68
  const insertionType = state.schema.marks["insertion"];
61
69
  const deletionType = state.schema.marks["deletion"];
70
+ const styleResolver = getDocumentStyleResolver(state);
62
71
  const keepType = mode === "accept" ? insertionType : deletionType;
63
72
  const removeType = mode === "accept" ? deletionType : insertionType;
64
73
  const revisionSet = revisionIds === void 0 ? null : new Set(revisionIds);
65
74
  const removeKeptMarksInBulk = revisionSet === null && keepType !== void 0;
75
+ const canBulkInlineResolution = execution === "headless-bulk-inline" && revisionSet === null && from === 0 && to === state.doc.content.size && stateAllowsHeadlessRevisionResolution(state);
66
76
  const matchesRevision = (mark) => revisionSet === null || typeof mark.attrs["revisionId"] === "number" && revisionSet.has(mark.attrs["revisionId"]);
67
77
  if (dispatch) {
68
78
  const tr = state.tr;
@@ -70,6 +80,10 @@ function resolveChange(from, to, mode, revisionIds) {
70
80
  const pPrMarkOps = [];
71
81
  const tableRowStructuralOps = [];
72
82
  const tableCellStructuralOps = [];
83
+ const deferredRunPropertyChanges = [];
84
+ let bulkInlineCarrierCount = 0;
85
+ let removedSectionEndpointCount = 0;
86
+ const removedSectionReferences = [];
73
87
  state.doc.nodesBetween(from, to, (node, pos) => {
74
88
  if (node.type.name === "paragraph") {
75
89
  const op = collectPPrMarkOp(node, pos, from, to, mode, revisionSet);
@@ -78,16 +92,26 @@ function resolveChange(from, to, mode, revisionIds) {
78
92
  let nextAttrs = null;
79
93
  const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
80
94
  if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && boundaryCovered) {
81
- const matches = propertyChanges.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
82
- if (matches.length > 0) {
83
- const remaining = propertyChanges.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
95
+ const matchesPropertyChange = (change) => revisionSet === null || revisionSet.has(change.info.id);
96
+ if (propertyChanges.some(matchesPropertyChange)) {
97
+ const rejection = mode === "reject" ? removeParagraphPropertyChanges(propertyChanges, matchesPropertyChange) : null;
98
+ let remaining;
99
+ if (rejection === null) remaining = propertyChanges.filter((change) => !matchesPropertyChange(change));
100
+ else if (rejection.type === "unchanged") remaining = propertyChanges;
101
+ else remaining = rejection.remaining;
84
102
  nextAttrs = {
85
103
  ...node.attrs,
86
104
  _propertyChanges: remaining.length > 0 ? remaining : null
87
105
  };
88
- if (mode === "reject") for (const change of matches.toReversed()) {
89
- Object.assign(nextAttrs, paragraphRejectAttrPatch(change.previousFormatting));
90
- nextAttrs["_originalFormatting"] = paragraphRejectOriginalFormatting(change.previousFormatting, node.attrs["_originalFormatting"]);
106
+ if (rejection?.type === "restore-previous") {
107
+ const inheritedAlignment = expectParagraphAttrs(node).alignmentFromStyle;
108
+ let previousFormattingFromStyle;
109
+ if (styleResolver) previousFormattingFromStyle = styleResolver.resolveParagraphStyle(rejection.previousFormatting?.styleId).paragraphFormatting;
110
+ else if (inheritedAlignment !== void 0) previousFormattingFromStyle = { alignment: inheritedAlignment };
111
+ Object.assign(nextAttrs, paragraphRejectAttrPatch(rejection.previousFormatting, previousFormattingFromStyle));
112
+ const restoredFormatting = paragraphRejectOriginalFormatting(rejection.previousFormatting, node.attrs["_originalFormatting"]);
113
+ nextAttrs["_originalFormatting"] = restoredFormatting;
114
+ if (styleResolver) nextAttrs["defaultTextFormatting"] = resolveParagraphDefaultTextFormatting(rejection.previousFormatting?.styleId, restoredFormatting ?? void 0, styleResolver) ?? null;
91
115
  }
92
116
  }
93
117
  }
@@ -106,7 +130,13 @@ function resolveChange(from, to, mode, revisionIds) {
106
130
  nextAttrs["sectionBreakType"] = sectionBreakTypeFromSectionStart(restored.sectionStart);
107
131
  }
108
132
  }
109
- if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
133
+ if (nextAttrs) if (nextAttrs["styleId"] !== node.attrs["styleId"] && styleResolver) setParagraphAttrsWithRebasedRunFormatting({
134
+ nextAttrs,
135
+ paragraphPosition: pos,
136
+ styleResolver,
137
+ tr
138
+ });
139
+ else tr.setNodeMarkup(pos, void 0, nextAttrs);
110
140
  return true;
111
141
  }
112
142
  if (node.type.name === "tableRow" && rangeCoversNode(from, to, pos, node)) {
@@ -118,7 +148,10 @@ function resolveChange(from, to, mode, revisionIds) {
118
148
  if (tableChangeAttrName !== void 0) {
119
149
  if (rangeCoversNode(from, to, pos, node)) {
120
150
  const nextAttrs = resolveTablePropertyChangeAttrs(node, tableChangeAttrName, mode, revisionSet);
121
- if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
151
+ if (nextAttrs) {
152
+ tr.setNodeMarkup(pos, void 0, nextAttrs);
153
+ markStructuralChange(tr);
154
+ }
122
155
  }
123
156
  return true;
124
157
  }
@@ -127,6 +160,27 @@ function resolveChange(from, to, mode, revisionIds) {
127
160
  const rangeFrom = Math.max(from, pos);
128
161
  const rangeTo = Math.min(to, nodeEnd);
129
162
  const runPropertyChangeMark = node.marks.find((mark) => mark.type.name === "runPropertyChange");
163
+ const resolvesRunPropertyChange = runPropertyChangeMark !== void 0 && expectRunPropertyChangeMarkAttrs(runPropertyChangeMark).changes.length > 0;
164
+ const removesNode = removeType !== void 0 && node.marks.some((mark) => mark.type === removeType && matchesRevision(mark));
165
+ const removesKeptMark = keepType !== void 0 && node.marks.some((mark) => mark.type === keepType && matchesRevision(mark));
166
+ if (canBulkInlineResolution) {
167
+ if (resolvesRunPropertyChange) deferredRunPropertyChanges.push({
168
+ tr,
169
+ node,
170
+ from: rangeFrom,
171
+ to: rangeTo,
172
+ mark: runPropertyChangeMark,
173
+ mode,
174
+ revisionSet,
175
+ styleResolver
176
+ });
177
+ if (removesNode) deleteRanges.push({
178
+ from: rangeFrom,
179
+ to: rangeTo
180
+ });
181
+ if (resolvesRunPropertyChange || removesNode || removesKeptMark) bulkInlineCarrierCount++;
182
+ return true;
183
+ }
130
184
  if (runPropertyChangeMark) resolveRunPropertyChange({
131
185
  tr,
132
186
  node,
@@ -134,9 +188,10 @@ function resolveChange(from, to, mode, revisionIds) {
134
188
  to: rangeTo,
135
189
  mark: runPropertyChangeMark,
136
190
  mode,
137
- revisionSet
191
+ revisionSet,
192
+ styleResolver
138
193
  });
139
- if (removeType && node.marks.some((m) => m.type === removeType && matchesRevision(m))) deleteRanges.push({
194
+ if (removesNode) deleteRanges.push({
140
195
  from: rangeFrom,
141
196
  to: rangeTo
142
197
  });
@@ -145,24 +200,35 @@ function resolveChange(from, to, mode, revisionIds) {
145
200
  }
146
201
  return true;
147
202
  });
148
- if (removeKeptMarksInBulk) tr.removeMark(from, to, keepType);
149
- let rangesToDelete = deleteRanges;
150
- if (revisionSet === null) {
151
- const coalescedDeleteRanges = [];
152
- for (const range of deleteRanges) {
153
- const previous = coalescedDeleteRanges.at(-1);
154
- if (previous && range.from <= previous.to) {
155
- previous.to = Math.max(previous.to, range.to);
156
- continue;
203
+ let bulkInlineChangeTracking = null;
204
+ if (canBulkInlineResolution && bulkInlineCarrierCount >= BULK_INLINE_RESOLUTION_THRESHOLD) bulkInlineChangeTracking = appendHeadlessInlineResolution({
205
+ tr,
206
+ mode,
207
+ keepType,
208
+ removeType,
209
+ styleResolver
210
+ });
211
+ else {
212
+ for (const deferred of deferredRunPropertyChanges) resolveRunPropertyChange(deferred);
213
+ if (removeKeptMarksInBulk) tr.removeMark(from, to, keepType);
214
+ let rangesToDelete = deleteRanges;
215
+ if (revisionSet === null) {
216
+ const coalescedDeleteRanges = [];
217
+ for (const range of deleteRanges) {
218
+ const previous = coalescedDeleteRanges.at(-1);
219
+ if (previous && range.from <= previous.to) {
220
+ previous.to = Math.max(previous.to, range.to);
221
+ continue;
222
+ }
223
+ coalescedDeleteRanges.push({
224
+ from: range.from,
225
+ to: range.to
226
+ });
157
227
  }
158
- coalescedDeleteRanges.push({
159
- from: range.from,
160
- to: range.to
161
- });
228
+ rangesToDelete = coalescedDeleteRanges;
162
229
  }
163
- rangesToDelete = coalescedDeleteRanges;
230
+ for (const range of rangesToDelete.toReversed()) tr.delete(range.from, range.to);
164
231
  }
165
- for (const range of rangesToDelete.toReversed()) tr.delete(range.from, range.to);
166
232
  pPrMarkOps.sort((a, b) => b.paragraphPos - a.paragraphPos);
167
233
  for (const op of pPrMarkOps) {
168
234
  const mappedPos = tr.mapping.map(op.paragraphPos);
@@ -174,35 +240,34 @@ function resolveChange(from, to, mode, revisionIds) {
174
240
  }
175
241
  const joinPos = mappedPos + paragraph.nodeSize;
176
242
  const nextNode = joinPos < tr.doc.content.size ? tr.doc.nodeAt(joinPos) : null;
177
- if (!(nextNode?.type.name === paragraph.type.name && !(carriesSection(paragraph) && carriesSection(nextNode)))) {
243
+ if (!(nextNode?.type.name === paragraph.type.name)) {
178
244
  const resolved = tr.doc.resolve(mappedPos);
179
- const previous = resolved.nodeBefore;
180
- const sectionCanMoveBack = !carriesSection(paragraph) || previous?.type.name === paragraph.type.name && !carriesSection(previous);
181
245
  const endsItsContainer = paragraphEndsItsContainer(resolved, paragraph.type.name);
182
246
  const canGo = op.markWasAdded || !endsItsContainer;
183
- if (sectionCanMoveBack && holdsNoContent(paragraph) && canGo && resolved.parent.childCount > 1) {
184
- if (carriesSection(paragraph) && previous) tr.setNodeMarkup(mappedPos - previous.nodeSize, void 0, {
185
- ...previous.attrs,
186
- sectionBreakType: paragraph.attrs["sectionBreakType"],
187
- _sectionProperties: paragraph.attrs["_sectionProperties"]
188
- });
247
+ if (holdsNoContent(paragraph) && canGo && resolved.parent.childCount > 1) {
248
+ if (ownsSectionEndpoint(paragraph)) {
249
+ removedSectionEndpointCount++;
250
+ removedSectionReferences.push(...sectionReferencesOf(paragraph));
251
+ }
189
252
  tr.delete(mappedPos, mappedPos + paragraph.nodeSize);
190
253
  continue;
191
254
  }
192
255
  tr.setNodeAttribute(mappedPos, "pPrMark", null);
193
256
  continue;
194
257
  }
195
- const formattingOwner = holdsNoContent(paragraph) ? nextNode : paragraph;
196
- const sectionOwner = carriesSection(paragraph) ? paragraph : nextNode;
197
258
  const joinedAttrs = {
198
- ...formattingOwner.attrs,
259
+ ...(holdsNoContent(paragraph) ? nextNode : paragraph).attrs,
199
260
  pPrMark: nextNode.attrs["pPrMark"],
200
- sectionBreakType: sectionOwner.attrs["sectionBreakType"],
201
- _sectionProperties: sectionOwner.attrs["_sectionProperties"]
261
+ sectionBreakType: nextNode.attrs["sectionBreakType"],
262
+ _sectionProperties: nextNode.attrs["_sectionProperties"]
202
263
  };
203
264
  try {
204
265
  tr.join(joinPos);
205
266
  tr.setNodeMarkup(mappedPos, void 0, joinedAttrs);
267
+ if (ownsSectionEndpoint(paragraph)) {
268
+ removedSectionEndpointCount++;
269
+ removedSectionReferences.push(...sectionReferencesOf(paragraph));
270
+ }
206
271
  } catch {}
207
272
  }
208
273
  tableRowStructuralOps.sort((left, right) => right.rowPos - left.rowPos);
@@ -252,12 +317,18 @@ function resolveChange(from, to, mode, revisionIds) {
252
317
  }
253
318
  if (failedTableCellMergeResolution) return false;
254
319
  if (resolvedTableCellStructure) markStructuralChange(tr);
320
+ if (bulkInlineChangeTracking) markChangedParagraphRanges(tr, bulkInlineChangeTracking);
321
+ if (removedSectionEndpointCount > 0) markTrackedSectionEndpointRemoval(tr, {
322
+ sourceDoc: state.doc,
323
+ removedEndpointCount: removedSectionEndpointCount,
324
+ removedReferences: removedSectionReferences
325
+ });
255
326
  if (tr.steps.length > 0) dispatch(tr);
256
327
  }
257
328
  return true;
258
329
  };
259
330
  }
260
- const resolveRunPropertyChange = ({ tr, node, from, to, mark, mode, revisionSet }) => {
331
+ const resolveRunPropertyChange = ({ tr, node, from, to, mark, mode, revisionSet, styleResolver }) => {
261
332
  const { changes } = expectRunPropertyChangeMarkAttrs(mark);
262
333
  const matches = changes.filter((change) => revisionSet === null || revisionSet.has(change.info.id));
263
334
  if (matches.length === 0) return;
@@ -266,15 +337,18 @@ const resolveRunPropertyChange = ({ tr, node, from, to, mark, mode, revisionSet
266
337
  if (remaining.length > 0) tr.addMark(from, to, mark.type.create({ changes: remaining }));
267
338
  if (mode === "accept") return;
268
339
  const previousFormatting = matches.at(0)?.previousFormatting;
340
+ const styleContext = paragraphRunStyleContextAt({
341
+ doc: tr.doc,
342
+ pos: from,
343
+ styleResolver
344
+ });
269
345
  for (const currentMark of node.marks) if (RUN_FORMATTING_MARK_NAMES.has(currentMark.type.name)) tr.removeMark(from, to, currentMark.type);
270
- for (const previousMark of textFormattingToMarks(previousFormatting)) tr.addMark(from, to, previousMark);
271
- if (previousFormatting?.styleId) {
272
- const characterStyle = node.type.schema.marks["characterStyle"];
273
- if (characterStyle) tr.addMark(from, to, characterStyle.create({
274
- styleId: previousFormatting.styleId,
275
- _styleRPr: null
276
- }));
277
- }
346
+ for (const previousMark of reconstructRejectedRunFormattingMarks({
347
+ node,
348
+ paragraphContext: styleContext,
349
+ previousFormatting,
350
+ styleResolver
351
+ })) tr.addMark(from, to, previousMark);
278
352
  };
279
353
  function collectTableRowStructuralOp(node, rowPos, mode, revisionSet) {
280
354
  for (const attrName of ["trIns", "trDel"]) {
@@ -369,11 +443,33 @@ function deleteTableCellAt(tr, cellPos) {
369
443
  const resolved = tr.doc.resolve(cellPos);
370
444
  const row = resolved.parent;
371
445
  if (row.type.spec["tableRole"] !== "row") return;
446
+ const tableDepth = resolved.depth - 1;
447
+ const table = resolved.node(tableDepth);
448
+ if (table.type.spec["tableRole"] !== "table") return;
449
+ const tablePosition = resolved.before(tableDepth);
450
+ const cellOffset = cellPos - resolved.start(tableDepth);
451
+ const removedColumn = TableMap.get(table).findCell(cellOffset).left;
372
452
  if (row.childCount > 1) {
373
453
  tr.delete(cellPos, cellPos + cell.nodeSize);
454
+ reconcileTableGridAfterColumnRemoval({
455
+ tr,
456
+ tablePosition,
457
+ previousTable: table,
458
+ removedColumn
459
+ });
460
+ return;
461
+ }
462
+ if (table.childCount === 1) {
463
+ deleteTableRowAt(tr, resolved.start() - 1);
374
464
  return;
375
465
  }
376
466
  deleteTableRowAt(tr, resolved.start() - 1);
467
+ reconcileTableGridAfterColumnRemoval({
468
+ tr,
469
+ tablePosition,
470
+ previousTable: table,
471
+ removedColumn
472
+ });
377
473
  }
378
474
  function deleteTableRowAt(tr, rowPos) {
379
475
  const resolved = tr.doc.resolve(rowPos);
@@ -480,15 +576,24 @@ function isPPrMarkAttr(value) {
480
576
  * the two ends belong together, not because they resolve differently.
481
577
  */
482
578
  const paragraphMarkWasAdded = (kind) => kind === "ins" || kind === "moveTo";
483
- /**
484
- * Whether the paragraph's mark carries a section's properties.
485
- *
486
- * A section break lives on a paragraph mark, so the paragraph is where the
487
- * section ends. Removing it removes the section: a document that had two ends
488
- * up with one, and every setting the dropped section carried — page size,
489
- * margins, its header and footer references — goes with it.
490
- */
491
- const carriesSection = (paragraph) => paragraph.attrs["sectionBreakType"] != null || paragraph.attrs["_sectionProperties"] != null;
579
+ const ownsSectionEndpoint = (paragraph) => {
580
+ const attrs = expectParagraphAttrs(paragraph);
581
+ return attrs._sectionProperties !== void 0 || attrs.sectionBreakType !== void 0;
582
+ };
583
+ const sectionReferencesOf = (paragraph) => {
584
+ const sectionProperties = expectParagraphAttrs(paragraph)._sectionProperties;
585
+ if (!sectionProperties) return [];
586
+ return [...(sectionProperties.headerReferences ?? []).map(({ type, rId }) => ({
587
+ part: "header",
588
+ type,
589
+ relationshipId: rId
590
+ })), ...(sectionProperties.footerReferences ?? []).map(({ type, rId }) => ({
591
+ part: "footer",
592
+ type,
593
+ relationshipId: rId
594
+ }))];
595
+ };
596
+ const isAddedPPrMarkAttr = (value) => isPPrMarkAttr(value) && (value.kind === "ins" || value.kind === "moveTo");
492
597
  function readRevisionInfo(info) {
493
598
  const revision = {};
494
599
  if (typeof info?.author === "string") revision.author = info.author;
@@ -566,6 +671,20 @@ function rejectAllChanges() {
566
671
  return (state, dispatch) => rejectChange(0, state.doc.content.size)(state, dispatch);
567
672
  }
568
673
  /**
674
+ * Resolve a complete state synchronously for a headless reader or writer.
675
+ *
676
+ * @internal The replacement transaction is consumed here and never exposed:
677
+ * it is deliberately not an editor command and must not be transported or
678
+ * mapped through concurrent edits.
679
+ */
680
+ function resolveAllChangesInHeadlessState(state, mode) {
681
+ let resolvedState = state;
682
+ resolveChange(0, state.doc.content.size, mode, void 0, "headless-bulk-inline")(state, (transaction) => {
683
+ resolvedState = state.apply(transaction);
684
+ });
685
+ return resolvedState;
686
+ }
687
+ /**
569
688
  * Find the document range covered by all revision carriers with any of the
570
689
  * given ids. Returns null when none are present (already accepted/rejected, or
571
690
  * never existed). A replace operation typically passes two ids; standalone
@@ -682,6 +801,13 @@ const readSuggestedMarker = (marker) => {
682
801
  revisionId
683
802
  };
684
803
  };
804
+ const readSuggestedParagraphPropertyChange = (change) => {
805
+ if (change.info.provenance !== "suggested" || typeof change.info.suggestionId !== "string") return null;
806
+ return {
807
+ suggestionId: change.info.suggestionId,
808
+ revisionId: change.info.id
809
+ };
810
+ };
685
811
  const readStructuralSuggestion = (node) => {
686
812
  const attrs = node.attrs;
687
813
  const name = node.type.name;
@@ -754,6 +880,20 @@ const collectSuggestions = (state) => {
754
880
  entry.kinds.add(structural.kind);
755
881
  entry.revisionIds.add(structural.revisionId);
756
882
  }
883
+ if (node.type.name === "paragraph") {
884
+ const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
885
+ if (Array.isArray(propertyChanges)) for (const change of propertyChanges) {
886
+ const suggested = readSuggestedParagraphPropertyChange(change);
887
+ if (!suggested) continue;
888
+ const entry = entryFor(suggested.suggestionId);
889
+ entry.segments.push({
890
+ from: pos,
891
+ to: pos + node.nodeSize
892
+ });
893
+ entry.kinds.add("formatting");
894
+ entry.revisionIds.add(suggested.revisionId);
895
+ }
896
+ }
757
897
  if (!node.isInline) return;
758
898
  for (const mark of node.marks) {
759
899
  if (!isSuggestionMark(mark)) continue;
@@ -848,6 +988,22 @@ const normalizeAcceptDate = (date) => {
848
988
  const parsed = new Date(date);
849
989
  return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : parsed.toISOString();
850
990
  };
991
+ const acceptedParagraphMark = (node, author, date) => {
992
+ if (node.type.name !== "paragraph") return null;
993
+ const marker = node.attrs["_suggestedInsert"];
994
+ if (typeof marker !== "object" || marker === null || !("revisionId" in marker)) return null;
995
+ if (typeof marker.revisionId !== "number") return null;
996
+ const initials = "initials" in marker ? marker.initials : void 0;
997
+ return {
998
+ kind: "ins",
999
+ info: {
1000
+ id: marker.revisionId,
1001
+ author,
1002
+ date,
1003
+ ...typeof initials === "string" ? { initials } : {}
1004
+ }
1005
+ };
1006
+ };
851
1007
  /**
852
1008
  * The node-attr patch that converts a suggested block/table revision into a
853
1009
  * normal (`"user"`) one authored by the accepting user. Returns `null` when the
@@ -868,23 +1024,15 @@ const convertStructuralSuggestionAttrs = (node, author, date) => {
868
1024
  const name = node.type.name;
869
1025
  const insertMarker = attrs["_suggestedInsert"];
870
1026
  if ((name === "paragraph" || name === "table") && typeof insertMarker === "object" && insertMarker !== null) {
871
- const marker = insertMarker;
872
1027
  if (name === "table") return {
873
1028
  ...attrs,
874
1029
  _suggestedInsert: null
875
1030
  };
876
- if (typeof marker.revisionId === "number") return {
1031
+ const paragraphMark = acceptedParagraphMark(node, author, date);
1032
+ if (paragraphMark) return {
877
1033
  ...attrs,
878
1034
  _suggestedInsert: null,
879
- pPrMark: {
880
- kind: "ins",
881
- info: {
882
- id: marker.revisionId,
883
- author,
884
- date,
885
- ...typeof marker.initials === "string" ? { initials: marker.initials } : {}
886
- }
887
- }
1035
+ pPrMark: attrs["pPrMark"] ?? paragraphMark
888
1036
  };
889
1037
  }
890
1038
  if (node.type.name === "tableRow") {
@@ -909,6 +1057,266 @@ const convertStructuralSuggestionAttrs = (node, author, date) => {
909
1057
  }
910
1058
  return null;
911
1059
  };
1060
+ const reauthorParagraphPropertySuggestions = ({ node, matchesSuggestion, author, date }) => {
1061
+ if (node.type.name !== "paragraph") return null;
1062
+ const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
1063
+ if (!Array.isArray(propertyChanges)) return null;
1064
+ let changed = false;
1065
+ const next = propertyChanges.map((change) => {
1066
+ const suggested = readSuggestedParagraphPropertyChange(change);
1067
+ if (!suggested || !matchesSuggestion(suggested.suggestionId)) return change;
1068
+ changed = true;
1069
+ return Object.assign({}, change, { info: {
1070
+ ...change.info,
1071
+ author,
1072
+ date,
1073
+ provenance: "user",
1074
+ suggestionId: null
1075
+ } });
1076
+ });
1077
+ return changed ? next : null;
1078
+ };
1079
+ const canReauthorParagraphPropertySuggestions = (doc, matchesSuggestion) => {
1080
+ let valid = true;
1081
+ doc.descendants((node) => {
1082
+ if (!valid || node.type.name !== "paragraph") return;
1083
+ const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
1084
+ if (!Array.isArray(propertyChanges)) return;
1085
+ let serializableCount = 0;
1086
+ for (const change of propertyChanges) {
1087
+ const suggested = readSuggestedParagraphPropertyChange(change);
1088
+ if (!suggested || matchesSuggestion(suggested.suggestionId)) serializableCount++;
1089
+ }
1090
+ if (serializableCount > 1) {
1091
+ valid = false;
1092
+ return false;
1093
+ }
1094
+ });
1095
+ return valid;
1096
+ };
1097
+ const appendRotatedParagraphPropertyChange = ({ tr, currentPosition, previous, mark, fallbackAuthor }) => {
1098
+ const current = tr.doc.nodeAt(currentPosition);
1099
+ if (!current || current.type.name !== "paragraph") return false;
1100
+ const author = typeof mark.info.author === "string" ? mark.info.author : fallbackAuthor;
1101
+ if (author === void 0) return false;
1102
+ const existing = expectParagraphAttrs(current)._propertyChanges;
1103
+ if (hasSerializableParagraphPropertyChange(existing)) return false;
1104
+ tr.setNodeMarkup(currentPosition, void 0, {
1105
+ ...current.attrs,
1106
+ _propertyChanges: [...Array.isArray(existing) ? existing : [], {
1107
+ type: "paragraphPropertyChange",
1108
+ info: {
1109
+ id: mark.info.id,
1110
+ author,
1111
+ ...typeof mark.info.date === "string" ? { date: mark.info.date } : {},
1112
+ ...typeof mark.info.initials === "string" ? { initials: mark.info.initials } : {}
1113
+ },
1114
+ previousFormatting: paragraphPropertiesSnapshot(previous)
1115
+ }]
1116
+ });
1117
+ return true;
1118
+ };
1119
+ /**
1120
+ * Shift a final run of inserted paragraph marks one paragraph to the left.
1121
+ * The container-final paragraph stays markless, while every inserted
1122
+ * paragraph keeps a same-revision property snapshot that makes targeted
1123
+ * rejection restore the paragraph whose mark now carries its break.
1124
+ */
1125
+ const rotateAddedFinalParagraphBreaks = ({ tr, shouldRotate, fallbackAuthor }) => {
1126
+ const paragraphTypeName = tr.doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
1127
+ const finals = finalParagraphsOf(tr.doc, paragraphTypeName).filter(({ node, position }) => shouldRotate(node, position));
1128
+ for (const { position: finalPosition } of finals) {
1129
+ const final = tr.doc.nodeAt(finalPosition);
1130
+ const finalMark = final?.attrs["pPrMark"];
1131
+ if (!final || !isAddedPPrMarkAttr(finalMark)) continue;
1132
+ tr.setNodeAttribute(finalPosition, "pPrMark", null);
1133
+ if (!shiftAddedParagraphBreakBefore({
1134
+ tr,
1135
+ paragraphPosition: finalPosition,
1136
+ mark: finalMark,
1137
+ ...fallbackAuthor !== void 0 && { fallbackAuthor }
1138
+ })) return false;
1139
+ }
1140
+ return true;
1141
+ };
1142
+ const rebaseRotatedParagraphPropertyChange = ({ tr, currentPosition, previous, revisionId }) => {
1143
+ const current = tr.doc.nodeAt(currentPosition);
1144
+ if (!current || current.type.name !== "paragraph") return false;
1145
+ const existing = expectParagraphAttrs(current)._propertyChanges;
1146
+ if (!Array.isArray(existing)) return false;
1147
+ if (existing.filter(({ info }) => info.id === revisionId).length !== 1) return false;
1148
+ tr.setNodeMarkup(currentPosition, void 0, {
1149
+ ...current.attrs,
1150
+ _propertyChanges: existing.map((change) => change.info.id === revisionId ? Object.assign({}, change, { previousFormatting: paragraphPropertiesSnapshot(previous) }) : change)
1151
+ });
1152
+ return true;
1153
+ };
1154
+ /**
1155
+ * Rebase every property change in one rotated paragraph-break chain to the
1156
+ * formatting of its original, leftmost carrier. A later targeted suggestion
1157
+ * may extend an already-rotated chain, so normalizing only the newly shifted
1158
+ * prefix would leave the serialized history dependent on acceptance order.
1159
+ */
1160
+ const rebaseAddedParagraphBreakChain = ({ tr, carrierPosition, previous }) => {
1161
+ const carrier = tr.doc.nodeAt(carrierPosition);
1162
+ if (!carrier || carrier.type.name !== "paragraph") return false;
1163
+ const resolved = tr.doc.resolve(carrierPosition);
1164
+ let current = carrier;
1165
+ let nextPosition = carrierPosition + carrier.nodeSize;
1166
+ for (let index = resolved.index() + 1; index < resolved.parent.childCount; index++) {
1167
+ const currentMark = current.attrs["pPrMark"];
1168
+ if (!isAddedPPrMarkAttr(currentMark)) return true;
1169
+ const sibling = resolved.parent.child(index);
1170
+ if (sibling.type.name !== carrier.type.name) {
1171
+ if (sibling.type.spec["tableRole"] === "table") return false;
1172
+ nextPosition += sibling.nodeSize;
1173
+ continue;
1174
+ }
1175
+ if (!rebaseRotatedParagraphPropertyChange({
1176
+ tr,
1177
+ currentPosition: nextPosition,
1178
+ previous,
1179
+ revisionId: currentMark.info.id
1180
+ })) return false;
1181
+ const next = tr.doc.nodeAt(nextPosition);
1182
+ if (!next || next.type.name !== carrier.type.name) return false;
1183
+ current = next;
1184
+ nextPosition += sibling.nodeSize;
1185
+ }
1186
+ return !isAddedPPrMarkAttr(current.attrs["pPrMark"]);
1187
+ };
1188
+ /**
1189
+ * Insert an added paragraph break immediately before `paragraphPosition`.
1190
+ * Existing added marks shift left as one chain until the first free carrier;
1191
+ * each shifted mark keeps a property snapshot on the paragraph to its right.
1192
+ */
1193
+ const shiftAddedParagraphBreakBefore = ({ tr, paragraphPosition, mark, fallbackAuthor, firstPropertyChange = { type: "append" } }) => {
1194
+ const paragraph = tr.doc.nodeAt(paragraphPosition);
1195
+ if (!paragraph || paragraph.type.name !== "paragraph") return false;
1196
+ const resolved = tr.doc.resolve(paragraphPosition);
1197
+ const carrier = addedBreakCarrierBefore(resolved, paragraph.type.name);
1198
+ if (!carrier) return false;
1199
+ const path = [{
1200
+ node: paragraph,
1201
+ position: paragraphPosition
1202
+ }];
1203
+ let position = paragraphPosition;
1204
+ for (let index = resolved.index() - 1; index >= 0; index--) {
1205
+ const sibling = resolved.parent.child(index);
1206
+ position -= sibling.nodeSize;
1207
+ if (sibling.type.name !== paragraph.type.name) {
1208
+ if (sibling.type.spec["tableRole"] === "table") break;
1209
+ continue;
1210
+ }
1211
+ path.push({
1212
+ node: sibling,
1213
+ position
1214
+ });
1215
+ if (position === carrier.position) break;
1216
+ }
1217
+ if (path.at(-1)?.position !== carrier.position) return false;
1218
+ const propertyChangeBaseline = carrier.node;
1219
+ let carriedMark = mark;
1220
+ for (let index = 1; index < path.length; index++) {
1221
+ const current = path[index - 1];
1222
+ const previous = path[index];
1223
+ if (!current || !previous) return false;
1224
+ const displacedMark = previous.node.attrs["pPrMark"];
1225
+ tr.setNodeAttribute(previous.position, "pPrMark", carriedMark);
1226
+ if (!(index === 1 && firstPropertyChange.type === "rebase" ? rebaseRotatedParagraphPropertyChange({
1227
+ tr,
1228
+ currentPosition: current.position,
1229
+ previous: propertyChangeBaseline,
1230
+ revisionId: firstPropertyChange.revisionId
1231
+ }) : appendRotatedParagraphPropertyChange({
1232
+ tr,
1233
+ currentPosition: current.position,
1234
+ previous: propertyChangeBaseline,
1235
+ mark: carriedMark,
1236
+ ...fallbackAuthor !== void 0 && { fallbackAuthor }
1237
+ }))) return false;
1238
+ if (displacedMark == null) return rebaseAddedParagraphBreakChain({
1239
+ tr,
1240
+ carrierPosition: carrier.position,
1241
+ previous: propertyChangeBaseline
1242
+ });
1243
+ if (!isAddedPPrMarkAttr(displacedMark)) return false;
1244
+ carriedMark = displacedMark;
1245
+ }
1246
+ return false;
1247
+ };
1248
+ /**
1249
+ * Place an accepted paragraph's break when a following accepted insertion has
1250
+ * already rotated its own break onto that paragraph.
1251
+ */
1252
+ const placeAcceptedParagraphBreakBefore = ({ tr, paragraphPosition, mark, fallbackAuthor }) => {
1253
+ const paragraph = tr.doc.nodeAt(paragraphPosition);
1254
+ const displaced = paragraph?.attrs["pPrMark"];
1255
+ if (!paragraph || paragraph.type.name !== "paragraph" || !isPPrMarkAttr(displaced) || !paragraphMarkWasAdded(displaced.kind)) return false;
1256
+ return shiftAddedParagraphBreakBefore({
1257
+ tr,
1258
+ paragraphPosition,
1259
+ mark,
1260
+ fallbackAuthor
1261
+ });
1262
+ };
1263
+ /**
1264
+ * Remember non-final added breaks before suggested nodes are removed. If one
1265
+ * becomes final because of those removals, it must rotate with its own
1266
+ * revision rather than remain as an unresolvable final mark.
1267
+ */
1268
+ const collectAddedBreakRotationCandidates = (doc) => {
1269
+ const paragraphTypeName = doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
1270
+ const finalPositions = new Set(finalParagraphsOf(doc, paragraphTypeName).map(({ position }) => position));
1271
+ const candidates = [];
1272
+ doc.descendants((node, position) => {
1273
+ if (node.type.name !== paragraphTypeName || finalPositions.has(position)) return;
1274
+ const mark = node.attrs["pPrMark"];
1275
+ if (isPPrMarkAttr(mark) && paragraphMarkWasAdded(mark.kind)) candidates.push({
1276
+ position,
1277
+ revisionId: mark.info.id
1278
+ });
1279
+ });
1280
+ return candidates;
1281
+ };
1282
+ const mappedRotationCandidatePredicate = ({ candidates, tr }) => {
1283
+ const revisionIdsByPosition = /* @__PURE__ */ new Map();
1284
+ for (const candidate of candidates) {
1285
+ const mapped = tr.mapping.mapResult(candidate.position, 1);
1286
+ if (mapped.deleted) continue;
1287
+ const mappedPosition = mapped.pos;
1288
+ const revisionIds = revisionIdsByPosition.get(mappedPosition) ?? /* @__PURE__ */ new Set();
1289
+ revisionIds.add(candidate.revisionId);
1290
+ revisionIdsByPosition.set(mappedPosition, revisionIds);
1291
+ }
1292
+ return (node, position) => {
1293
+ const mark = node.attrs["pPrMark"];
1294
+ return isPPrMarkAttr(mark) && paragraphMarkWasAdded(mark.kind) && revisionIdsByPosition.get(position)?.has(mark.info.id) === true;
1295
+ };
1296
+ };
1297
+ /**
1298
+ * Remove suggested nodes without deleting a following accepted insertion's
1299
+ * paragraph break that has rotated onto one of them.
1300
+ */
1301
+ const removeSuggestedInsertNodes = ({ tr, positions, resolvedRevisionIds }) => {
1302
+ for (const position of positions.toSorted((left, right) => right - left)) {
1303
+ const node = tr.doc.nodeAt(position);
1304
+ if (!node) continue;
1305
+ const mark = node.attrs["pPrMark"];
1306
+ const followingBreak = node.type.name === "paragraph" && isAddedPPrMarkAttr(mark) && !resolvedRevisionIds.has(mark.info.id) ? mark : null;
1307
+ tr.delete(position, position + node.nodeSize);
1308
+ if (followingBreak && !shiftAddedParagraphBreakBefore({
1309
+ tr,
1310
+ paragraphPosition: position,
1311
+ mark: followingBreak,
1312
+ firstPropertyChange: {
1313
+ type: "rebase",
1314
+ revisionId: followingBreak.info.id
1315
+ }
1316
+ })) return false;
1317
+ }
1318
+ return true;
1319
+ };
912
1320
  /**
913
1321
  * Convert suggested marks AND block/table node revisions to normal (`"user"`)
914
1322
  * tracked changes. `matchesSuggestion(id)` selects which suggestion to convert
@@ -916,21 +1324,43 @@ const convertStructuralSuggestionAttrs = (node, author, date) => {
916
1324
  */
917
1325
  const acceptSuggestions = (matchesSuggestion, options) => {
918
1326
  return (state, dispatch) => {
1327
+ if (!canReauthorParagraphPropertySuggestions(state.doc, matchesSuggestion)) return false;
919
1328
  const insertionType = state.schema.marks["insertion"];
920
1329
  const deletionType = state.schema.marks["deletion"];
921
1330
  const runPropertyChangeType = state.schema.marks["runPropertyChange"];
922
1331
  const date = normalizeAcceptDate(options.date);
923
1332
  const tr = state.tr;
1333
+ const acceptedParagraphBreaks = [];
924
1334
  let changed = false;
925
1335
  state.doc.descendants((node, pos) => {
926
1336
  const structural = readStructuralSuggestion(node);
1337
+ let nextAttrs = null;
927
1338
  if (structural && matchesSuggestion(structural.suggestionId)) {
928
- const nextAttrs = convertStructuralSuggestionAttrs(node, options.author, date);
929
- if (nextAttrs) {
930
- tr.setNodeMarkup(pos, void 0, nextAttrs);
931
- changed = true;
1339
+ nextAttrs = convertStructuralSuggestionAttrs(node, options.author, date);
1340
+ if (structural.kind === "insertBlock") {
1341
+ const mark = acceptedParagraphMark(node, options.author, date);
1342
+ if (!mark) return;
1343
+ acceptedParagraphBreaks.push({
1344
+ position: pos,
1345
+ mark,
1346
+ hadBreak: node.attrs["pPrMark"] != null
1347
+ });
932
1348
  }
933
1349
  }
1350
+ const propertyChanges = reauthorParagraphPropertySuggestions({
1351
+ node,
1352
+ matchesSuggestion,
1353
+ author: options.author,
1354
+ date
1355
+ });
1356
+ if (propertyChanges) nextAttrs = {
1357
+ ...nextAttrs ?? node.attrs,
1358
+ _propertyChanges: propertyChanges
1359
+ };
1360
+ if (nextAttrs) {
1361
+ tr.setNodeMarkup(pos, void 0, nextAttrs);
1362
+ changed = true;
1363
+ }
934
1364
  if (!node.isInline) return;
935
1365
  const from = pos;
936
1366
  const to = pos + node.nodeSize;
@@ -964,6 +1394,18 @@ const acceptSuggestions = (matchesSuggestion, options) => {
964
1394
  }));
965
1395
  }
966
1396
  });
1397
+ for (const accepted of acceptedParagraphBreaks) if (accepted.hadBreak && !placeAcceptedParagraphBreakBefore({
1398
+ tr,
1399
+ paragraphPosition: accepted.position,
1400
+ mark: accepted.mark,
1401
+ fallbackAuthor: options.author
1402
+ })) return false;
1403
+ const directlyMarkedParagraphPositions = new Set(acceptedParagraphBreaks.filter(({ hadBreak }) => !hadBreak).map(({ position }) => position));
1404
+ if (directlyMarkedParagraphPositions.size > 0 && !rotateAddedFinalParagraphBreaks({
1405
+ tr,
1406
+ shouldRotate: (_node, position) => directlyMarkedParagraphPositions.has(position),
1407
+ fallbackAuthor: options.author
1408
+ })) return false;
967
1409
  if (!changed) return false;
968
1410
  if (dispatch) dispatch(tr);
969
1411
  return true;
@@ -1001,6 +1443,7 @@ function rejectSuggestion(suggestionId) {
1001
1443
  return (state, dispatch) => {
1002
1444
  const entry = collectSuggestions(state).get(suggestionId);
1003
1445
  if (!entry) return false;
1446
+ const rotationCandidates = entry.kinds.has("insertBlock") || entry.kinds.has("insertTable") ? collectAddedBreakRotationCandidates(state.doc) : [];
1004
1447
  let tr = null;
1005
1448
  if (entry.revisionIds.size > 0) {
1006
1449
  const ranges = mergeSegments(entry.segments);
@@ -1015,10 +1458,18 @@ function rejectSuggestion(suggestionId) {
1015
1458
  const structural = readStructuralSuggestion(node);
1016
1459
  if (structural?.isNodeInsert && structural.suggestionId === suggestionId) positions.push(pos);
1017
1460
  });
1018
- for (const pos of positions.toSorted((a, b) => b - a)) {
1019
- const node = workingTr.doc.nodeAt(pos);
1020
- if (node) workingTr.delete(pos, pos + node.nodeSize);
1021
- }
1461
+ if (!removeSuggestedInsertNodes({
1462
+ tr: workingTr,
1463
+ positions,
1464
+ resolvedRevisionIds: entry.revisionIds
1465
+ })) return false;
1466
+ if (positions.length > 0 && rotationCandidates.length > 0 && !rotateAddedFinalParagraphBreaks({
1467
+ tr: workingTr,
1468
+ shouldRotate: mappedRotationCandidatePredicate({
1469
+ candidates: rotationCandidates,
1470
+ tr: workingTr
1471
+ })
1472
+ })) return false;
1022
1473
  if (workingTr.steps.length === 0) return false;
1023
1474
  if (dispatch) dispatch(workingTr);
1024
1475
  return true;
@@ -1030,7 +1481,12 @@ function rejectAllSuggestions() {
1030
1481
  const bySuggestion = collectSuggestions(state);
1031
1482
  if (bySuggestion.size === 0) return false;
1032
1483
  const revisionIds = /* @__PURE__ */ new Set();
1033
- for (const entry of bySuggestion.values()) for (const id of entry.revisionIds) revisionIds.add(id);
1484
+ let removesWholeNode = false;
1485
+ for (const entry of bySuggestion.values()) {
1486
+ removesWholeNode ||= entry.kinds.has("insertBlock") || entry.kinds.has("insertTable");
1487
+ for (const id of entry.revisionIds) revisionIds.add(id);
1488
+ }
1489
+ const rotationCandidates = removesWholeNode ? collectAddedBreakRotationCandidates(state.doc) : [];
1034
1490
  let tr = null;
1035
1491
  if (revisionIds.size > 0) resolveChange(0, state.doc.content.size, "reject", [...revisionIds])(state, (resolved) => {
1036
1492
  tr = resolved;
@@ -1040,10 +1496,18 @@ function rejectAllSuggestions() {
1040
1496
  workingTr.doc.descendants((node, pos) => {
1041
1497
  if (readStructuralSuggestion(node)?.isNodeInsert) positions.push(pos);
1042
1498
  });
1043
- for (const pos of positions.toSorted((a, b) => b - a)) {
1044
- const node = workingTr.doc.nodeAt(pos);
1045
- if (node) workingTr.delete(pos, pos + node.nodeSize);
1046
- }
1499
+ if (!removeSuggestedInsertNodes({
1500
+ tr: workingTr,
1501
+ positions,
1502
+ resolvedRevisionIds: revisionIds
1503
+ })) return false;
1504
+ if (positions.length > 0 && rotationCandidates.length > 0 && !rotateAddedFinalParagraphBreaks({
1505
+ tr: workingTr,
1506
+ shouldRotate: mappedRotationCandidatePredicate({
1507
+ candidates: rotationCandidates,
1508
+ tr: workingTr
1509
+ })
1510
+ })) return false;
1047
1511
  if (workingTr.steps.length === 0) return false;
1048
1512
  if (dispatch) dispatch(workingTr);
1049
1513
  return true;
@@ -1214,4 +1678,4 @@ function findPreviousChange(state, startPos) {
1214
1678
  return result.value;
1215
1679
  }
1216
1680
  //#endregion
1217
- export { acceptAIEditRevision, acceptAllChanges, acceptAllSuggestions, acceptChange, acceptSuggestion, addCommentMark, findAIEditRevisionRange, findChangeAtPosition, findNextChange, findParagraphBoundaryChangeAtPosition, findPreviousChange, findSuggestionRange, getSuggestions, rejectAIEditRevision, rejectAllChanges, rejectAllSuggestions, rejectChange, rejectSuggestion, removeCommentMark };
1681
+ export { acceptAIEditRevision, acceptAllChanges, acceptAllSuggestions, acceptChange, acceptSuggestion, addCommentMark, findAIEditRevisionRange, findChangeAtPosition, findNextChange, findParagraphBoundaryChangeAtPosition, findPreviousChange, findSuggestionRange, getSuggestions, rejectAIEditRevision, rejectAllChanges, rejectAllSuggestions, rejectChange, rejectSuggestion, removeCommentMark, resolveAllChangesInHeadlessState };