@stll/folio-core 0.11.0 → 0.13.0

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