@stll/folio-core 0.33.1 → 0.34.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 (121) hide show
  1. package/dist/ai-edits/apply.d.ts +15 -0
  2. package/dist/ai-edits/apply.js +271 -48
  3. package/dist/ai-edits/headless.d.ts +47 -3
  4. package/dist/ai-edits/headless.js +53 -3
  5. package/dist/ai-edits/index.d.ts +1 -1
  6. package/dist/ai-edits/snapshot.d.ts +19 -1
  7. package/dist/ai-edits/snapshot.js +49 -5
  8. package/dist/ai-edits/table-geometry.d.ts +56 -0
  9. package/dist/ai-edits/table-geometry.js +217 -0
  10. package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
  11. package/dist/ai-edits/table-row-column-mutations.js +12 -4
  12. package/dist/ai-edits/table-template.d.ts +60 -0
  13. package/dist/ai-edits/table-template.js +176 -0
  14. package/dist/ai-edits/types.d.ts +13 -6
  15. package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
  16. package/dist/compare/__fixtures__/body-sequence.js +94 -6
  17. package/dist/compare/compare.d.ts +14 -7
  18. package/dist/compare/compare.js +136 -38
  19. package/dist/compare/formatting.d.ts +1 -1
  20. package/dist/compare/formatting.js +38 -9
  21. package/dist/compare/plan.d.ts +31 -1
  22. package/dist/compare/plan.js +233 -66
  23. package/dist/compare/types.d.ts +9 -7
  24. package/dist/compare/types.js +7 -5
  25. package/dist/compare/verification.d.ts +31 -12
  26. package/dist/compare/verification.js +106 -18
  27. package/dist/compat/eigenpal.d.ts +4 -4
  28. package/dist/compat/eigenpal.js +2 -2
  29. package/dist/controller/headerFooterEditorManager.js +11 -9
  30. package/dist/controller/layoutPipeline.js +24 -3
  31. package/dist/display-list/build/watermarkPrimitives.js +15 -3
  32. package/dist/display-list/primitives.d.ts +1 -1
  33. package/dist/document-operations.d.ts +10 -1
  34. package/dist/document-operations.js +33 -5
  35. package/dist/docx/appVersionNormalization.d.ts +49 -0
  36. package/dist/docx/appVersionNormalization.js +74 -0
  37. package/dist/docx/blockContentParser.js +3 -2
  38. package/dist/docx/drawingUtils.js +4 -3
  39. package/dist/docx/headerFooterParser.js +8 -14
  40. package/dist/docx/paragraphParser.js +48 -2
  41. package/dist/docx/rezip.d.ts +10 -4
  42. package/dist/docx/rezip.js +54 -18
  43. package/dist/docx/runParser.js +9 -8
  44. package/dist/docx/sdtProperties.js +4 -3
  45. package/dist/docx/selectiveSave.js +7 -6
  46. package/dist/docx/serializer/commentSerializer.d.ts +1 -1
  47. package/dist/docx/serializer/commentSerializer.js +43 -19
  48. package/dist/docx/serializer/documentSerializer.d.ts +3 -1
  49. package/dist/docx/serializer/documentSerializer.js +42 -78
  50. package/dist/docx/serializer/fontTableSerializer.js +8 -5
  51. package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
  52. package/dist/docx/serializer/headerFooterSerializer.js +55 -31
  53. package/dist/docx/serializer/noteSerializer.js +34 -30
  54. package/dist/docx/serializer/numberingSerializer.js +8 -4
  55. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
  56. package/dist/docx/serializer/paragraphSerializer.js +22 -12
  57. package/dist/docx/serializer/partNamespaces.d.ts +78 -0
  58. package/dist/docx/serializer/partNamespaces.js +324 -0
  59. package/dist/docx/serializer/runSerializer.js +1 -1
  60. package/dist/docx/serializer/settingsSerializer.js +8 -2
  61. package/dist/docx/serializer/stylesSerializer.js +8 -5
  62. package/dist/docx/serializer/tableSerializer.js +83 -18
  63. package/dist/docx/serializer/themeSerializer.js +10 -2
  64. package/dist/docx/server/build.d.ts +1 -1
  65. package/dist/docx/settingsParser.js +3 -0
  66. package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
  67. package/dist/docx/strictValueEncodings.gen.js +275 -0
  68. package/dist/docx/tableParser.d.ts +0 -6
  69. package/dist/docx/tableParser.js +52 -7
  70. package/dist/docx/transitionalSpelling.d.ts +23 -0
  71. package/dist/docx/transitionalSpelling.js +36 -0
  72. package/dist/docx/universalMeasure.d.ts +20 -0
  73. package/dist/docx/universalMeasure.js +32 -0
  74. package/dist/docx/verbatimCapture.d.ts +20 -0
  75. package/dist/docx/verbatimCapture.js +131 -0
  76. package/dist/docx/vmlImageParser.js +4 -3
  77. package/dist/docx/watermarkParser.d.ts +2 -4
  78. package/dist/docx/watermarkParser.js +8 -9
  79. package/dist/docx/xmlParser.d.ts +23 -2
  80. package/dist/docx/xmlParser.js +43 -11
  81. package/dist/headless-layout.js +14 -2
  82. package/dist/index.d.ts +4 -4
  83. package/dist/index.js +2 -2
  84. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
  85. package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
  86. package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
  87. package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
  88. package/dist/layout-engine/index.js +11 -4
  89. package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
  90. package/dist/layout-engine/justifiedLineFit.js +4 -4
  91. package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
  92. package/dist/layout-engine/measure/measureBlocks.js +1 -1
  93. package/dist/layout-engine/measure/measureParagraph.js +29 -29
  94. package/dist/layout-painter/renderPage.js +6 -1
  95. package/dist/layout-painter/renderParagraph.js +18 -8
  96. package/dist/layout-painter/renderWatermark.js +11 -4
  97. package/dist/model.d.ts +3 -3
  98. package/dist/model.js +2 -2
  99. package/dist/prosemirror/attrs/index.js +11 -0
  100. package/dist/prosemirror/commands/comments.js +24 -3
  101. package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
  102. package/dist/prosemirror/containerFinalParagraph.js +92 -0
  103. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
  104. package/dist/prosemirror/conversion/fromProseDoc.js +47 -9
  105. package/dist/prosemirror/conversion/toProseDoc.js +61 -14
  106. package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
  107. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  108. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
  109. package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
  110. package/dist/prosemirror/schema/marks.d.ts +3 -9
  111. package/dist/prosemirror/schema/nodes.d.ts +22 -0
  112. package/dist/server.d.ts +1 -1
  113. package/dist/types/block-id.d.ts +12 -1
  114. package/dist/types/block-id.js +17 -1
  115. package/dist/utils/canonicalJson.d.ts +14 -0
  116. package/dist/utils/canonicalJson.js +19 -0
  117. package/dist/utils/fontResolver.js +51 -0
  118. package/dist/utils/formatToStyle.d.ts +1 -1
  119. package/dist/utils/formatToStyle.js +41 -1
  120. package/dist/watermark/index.js +7 -0
  121. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { FolioTableTemplates } from "./table-template.js";
1
2
  import { FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditSnapshot } from "./types.js";
2
3
  import { WordDiffGranularity } from "./word-diff.js";
3
4
  import { EditorState, Transaction } from "prosemirror-state";
@@ -42,6 +43,20 @@ type ApplyFolioAIEditOperationsOptions = {
42
43
  revisionStamp?: FolioRevisionStamp;
43
44
  /** How a replacement's redline is cut. */
44
45
  wordDiff?: FolioWordDiffOptions;
46
+ /**
47
+ * Tables and rows an `insertTable` / `insertTableRow` operation should place
48
+ * verbatim, by operation id.
49
+ *
50
+ * The operations describe their content as cell texts, which is what a
51
+ * caller writing a table from nothing has. A caller copying one it already
52
+ * holds — a comparison placing the target document's table — has the whole
53
+ * thing: `w:tblPr`, the `w:tblGrid` widths, `w:trPr`, `w:tcPr` with its
54
+ * spans and merges, and cell paragraphs with their own properties. Rebuilt
55
+ * from text, none of that survives. Kept out of the operation payload
56
+ * because it is a document node rather than JSON: the serialized contract
57
+ * still describes a table by its cell texts.
58
+ */
59
+ tableTemplates?: FolioTableTemplates;
45
60
  };
46
61
  /**
47
62
  * How an apply batch cuts the redline for a replacement. Word-level by
@@ -1,5 +1,6 @@
1
- import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
1
+ import { expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
2
2
  import { PPR_CHANGE_SCOPED_ATTR_KEYS } from "../prosemirror/commands/propertyChangeScope.js";
3
+ import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
3
4
  import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
4
5
  import { requestDeterministicParaIds } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
5
6
  import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
@@ -11,8 +12,9 @@ import { hasInlineEmphasis, parseInlineEmphasisRuns, stripInlineEmphasisMarkers
11
12
  import { hashFolioAIBlockText, isHiddenTableRow, normalizeFolioAIBlockText } from "./snapshot.js";
12
13
  import { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell } from "./table-cell-mutations.js";
13
14
  import { planTableMutations } from "./table-mutation-plan.js";
14
- import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, splitCellParagraphTexts } from "./table-row-column-mutations.js";
15
+ import { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts } from "./table-row-column-mutations.js";
15
16
  import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBoundary, tableRectangleCutsMergedCell } from "./table-targets.js";
17
+ import { tableFromTemplate } from "./table-template.js";
16
18
  import { diffWordSegments } from "./word-diff.js";
17
19
  import { panic } from "better-result";
18
20
  import { TableMap } from "prosemirror-tables";
@@ -152,24 +154,132 @@ const REPLACEMENT_BACKGROUND_CLEAR_FORMATTING = {
152
154
  highlight: false,
153
155
  runShading: false
154
156
  };
155
- const applyInlineFormatting = ({ tr, schema, from, to, formatting }) => {
156
- for (const [name, enabled] of Object.entries(formatting)) {
157
- const markType = schema.marks[name];
157
+ const INLINE_FORMATTING_MARK_NAMES = {
158
+ bold: "bold",
159
+ italic: "italic",
160
+ underline: "underline",
161
+ strike: "strike",
162
+ fontFamily: "fontFamily",
163
+ fontSizePt: "fontSize",
164
+ color: "textColor",
165
+ highlight: "highlight",
166
+ runShading: "runShading"
167
+ };
168
+ const DIRECT_FONT_PROPERTIES = [
169
+ "fontFamily",
170
+ "fontSize",
171
+ "color"
172
+ ];
173
+ const formattingMarkName = (property) => {
174
+ if (!Object.hasOwn(INLINE_FORMATTING_MARK_NAMES, property)) return null;
175
+ const name = Reflect.get(INLINE_FORMATTING_MARK_NAMES, property);
176
+ return typeof name === "string" ? name : null;
177
+ };
178
+ const formattingMarkAttrs = (property, value) => {
179
+ switch (property) {
180
+ case "underline": return { style: "single" };
181
+ case "fontFamily": return typeof value === "string" ? {
182
+ ascii: value,
183
+ hAnsi: value
184
+ } : null;
185
+ case "fontSizePt": return typeof value === "number" ? { size: value * 2 } : null;
186
+ case "color": return typeof value === "string" ? { rgb: value.replace(/^#/u, "").toUpperCase() } : null;
187
+ default: return {};
188
+ }
189
+ };
190
+ const applyInlineFormatting = ({ tr, schema, from, to, formatting, includedProperties }) => {
191
+ for (const [property, value] of Object.entries(formatting)) {
192
+ if (includedProperties && !includedProperties.includes(property)) continue;
193
+ const markName = formattingMarkName(property);
194
+ const markType = markName ? schema.marks[markName] : void 0;
158
195
  if (!markType) continue;
159
- if (enabled) {
160
- tr.addMark(from, to, name === "underline" ? markType.create({ style: "single" }) : markType.create());
196
+ if (value !== false && value !== null) {
197
+ const attrs = formattingMarkAttrs(property, value);
198
+ if (attrs) tr.addMark(from, to, markType.create(attrs));
161
199
  continue;
162
200
  }
163
201
  tr.removeMark(from, to, markType);
164
202
  }
203
+ applyDirectFontProvenance({
204
+ tr,
205
+ schema,
206
+ from,
207
+ to,
208
+ formatting,
209
+ ...includedProperties ? { includedProperties } : {}
210
+ });
165
211
  return tr;
166
212
  };
167
- const formattingWouldChange = (marks, formatting) => Object.entries(formatting).some(([name, enabled]) => {
168
- const mark = marks.find((candidate) => candidate.type.name === name);
169
- if (!enabled) return mark !== void 0;
170
- if (name === "underline") return mark?.attrs["style"] !== "single";
213
+ const applyDirectFontProvenance = ({ tr, schema, from, to, formatting, includedProperties }) => {
214
+ if ("highlight" in formatting) return;
215
+ const changed = [
216
+ [
217
+ "fontFamily",
218
+ "fontFamily",
219
+ formatting.fontFamily
220
+ ],
221
+ [
222
+ "fontSize",
223
+ "fontSizePt",
224
+ formatting.fontSizePt
225
+ ],
226
+ [
227
+ "color",
228
+ "color",
229
+ formatting.color
230
+ ]
231
+ ].filter(([, inputProperty, value]) => value !== void 0 && (!includedProperties || includedProperties.includes(inputProperty)));
232
+ const markType = schema.marks["runFormattingOverride"];
233
+ if (!markType || changed.length === 0) return;
234
+ tr.doc.nodesBetween(from, to, (node, pos) => {
235
+ if (!node.isText) return;
236
+ const segmentFrom = Math.max(from, pos);
237
+ const segmentTo = Math.min(to, pos + node.nodeSize);
238
+ const existing = node.marks.find((mark) => mark.type === markType);
239
+ const attrs = existing ? expectRunFormattingOverrideMarkAttrs(existing) : {};
240
+ const directFontProperties = new Set(attrs.directFontProperties);
241
+ for (const [property, , value] of changed) if (value === null) directFontProperties.delete(property);
242
+ else directFontProperties.add(property);
243
+ tr.removeMark(segmentFrom, segmentTo, markType);
244
+ const nextAttrs = {
245
+ ...attrs,
246
+ directFontProperties: DIRECT_FONT_PROPERTIES.filter((property) => directFontProperties.has(property))
247
+ };
248
+ if (Object.entries(nextAttrs).some(([key, value]) => key === "directFontProperties" ? Array.isArray(value) && value.length > 0 : value !== null && value !== void 0)) tr.addMark(segmentFrom, segmentTo, markType.create(nextAttrs));
249
+ });
250
+ };
251
+ const directFontPropertyForFormattingProperty = (property) => {
252
+ switch (property) {
253
+ case "fontFamily": return "fontFamily";
254
+ case "fontSizePt": return "fontSize";
255
+ case "color": return "color";
256
+ default: return null;
257
+ }
258
+ };
259
+ const formattingPropertyWouldChange = (marks, property, value) => {
260
+ const markName = formattingMarkName(property);
261
+ const mark = marks.find((candidate) => candidate.type.name === markName);
262
+ const directFontProperty = directFontPropertyForFormattingProperty(property);
263
+ if (directFontProperty) {
264
+ const overrideMark = marks.find((candidate) => candidate.type.name === "runFormattingOverride");
265
+ const isDirect = overrideMark ? (expectRunFormattingOverrideMarkAttrs(overrideMark).directFontProperties ?? []).includes(directFontProperty) : false;
266
+ if (value === false || value === null) return isDirect;
267
+ if (!isDirect) return true;
268
+ } else if (value === false || value === null) return mark !== void 0;
269
+ if (property === "underline") return mark?.attrs["style"] !== "single";
270
+ if (property === "fontFamily") return mark?.attrs["ascii"] !== value || mark?.attrs["hAnsi"] !== value;
271
+ if (property === "fontSizePt") return Number(mark?.attrs["size"]) !== Number(value) * 2;
272
+ if (property === "color") {
273
+ const current = mark?.attrs["rgb"];
274
+ return (typeof current === "string" ? current.replace(/^#/u, "").toUpperCase() : null) !== String(value).replace(/^#/u, "").toUpperCase();
275
+ }
171
276
  return mark === void 0;
172
- });
277
+ };
278
+ const formattingChangesForMarks = (marks, formatting) => {
279
+ const changes = [];
280
+ for (const [property, value] of Object.entries(formatting)) if (formattingPropertyWouldChange(marks, property, value)) changes.push(property);
281
+ return changes;
282
+ };
173
283
  const clearReplacementBackground = ({ tr, schema, from, to, mode, revisionId, author, date, initials, suggestionId = null }) => {
174
284
  if (![schema.marks["highlight"], schema.marks["runShading"]].some((markType) => markType !== void 0 && tr.doc.rangeHasMark(from, to, markType))) return tr;
175
285
  if (mode === "direct") return applyInlineFormatting({
@@ -198,7 +308,9 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
198
308
  if (!propertyChangeType) return tr;
199
309
  const segments = [];
200
310
  doc.nodesBetween(from, to, (node, pos) => {
201
- if (!node.isText || !formattingWouldChange(node.marks, formatting)) return;
311
+ if (!node.isText) return;
312
+ const includedProperties = formattingChangesForMarks(node.marks, formatting);
313
+ if (includedProperties.length === 0) return;
202
314
  const segmentFrom = Math.max(from, pos);
203
315
  const segmentTo = Math.min(to, pos + node.nodeSize);
204
316
  const previousFormatting = marksToTextFormatting(node.marks);
@@ -217,6 +329,7 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
217
329
  segments.push({
218
330
  from: segmentFrom,
219
331
  to: segmentTo,
332
+ includedProperties,
220
333
  changes: [...existingChanges, change]
221
334
  });
222
335
  });
@@ -225,17 +338,20 @@ const applyTrackedInlineFormatting = ({ tr, schema, doc, from, to, formatting, r
225
338
  provenance: "suggested",
226
339
  suggestionId
227
340
  };
228
- applyInlineFormatting({
229
- tr,
230
- schema,
231
- from,
232
- to,
233
- formatting
234
- });
235
- for (const segment of segments) tr.addMark(segment.from, segment.to, propertyChangeType.create({
236
- changes: segment.changes,
237
- ...suggestionAttrs
238
- }));
341
+ for (const segment of segments) {
342
+ applyInlineFormatting({
343
+ tr,
344
+ schema,
345
+ from: segment.from,
346
+ to: segment.to,
347
+ formatting,
348
+ includedProperties: segment.includedProperties
349
+ });
350
+ tr.addMark(segment.from, segment.to, propertyChangeType.create({
351
+ changes: segment.changes,
352
+ ...suggestionAttrs
353
+ }));
354
+ }
239
355
  return tr;
240
356
  };
241
357
  /**
@@ -364,9 +480,11 @@ const ordinalAmongSameHash = (snapshot, blockId) => {
364
480
  const SIGNATURE_LINE = "_".repeat(28);
365
481
  /**
366
482
  * A plain table from a grid of cell texts, `null` when the schema has no
367
- * tables. In tracked mode every row carries `trIns`, which is how Word says
368
- * "this table is new": there is no whole-table insertion element, only rows
369
- * that were inserted.
483
+ * tables. In tracked mode every row carries `trIns` and every run inside it
484
+ * carries `w:ins`, which is how the format says "this table is new": there is
485
+ * no whole-table insertion element, only rows that were inserted, and a
486
+ * consumer that reads only run-level revisions keeps the text of a rejected
487
+ * insertion when the row marker stands alone.
370
488
  */
371
489
  const buildTableNode = ({ schema, rows, revision }) => {
372
490
  const paragraphType = schema.nodes["paragraph"];
@@ -374,7 +492,16 @@ const buildTableNode = ({ schema, rows, revision }) => {
374
492
  const rowType = schema.nodes["tableRow"];
375
493
  const tableType = schema.nodes["table"];
376
494
  if (!paragraphType || !cellType || !rowType || !tableType || rows.length === 0) return null;
377
- const rowNodes = rows.map((cells) => rowType.create(revision ? { trIns: revision } : null, cells.map((text) => cellType.create(null, splitCellParagraphTexts(text).map((line) => paragraphType.create(null, line.length > 0 ? schema.text(line) : null))))));
495
+ const insertionType = schema.marks["insertion"];
496
+ const marks = revision && insertionType ? [insertionType.create({
497
+ revisionId: revision.revisionId,
498
+ author: revision.author,
499
+ date: revision.date,
500
+ ...revision.initials != null && { initials: revision.initials },
501
+ ...revision.provenance != null && { provenance: revision.provenance },
502
+ ...revision.suggestionId != null && { suggestionId: revision.suggestionId }
503
+ })] : void 0;
504
+ const rowNodes = rows.map((cells) => rowType.create(revision ? { trIns: revision } : null, cells.map((text) => cellType.create(null, splitCellParagraphTexts(text).map((line) => paragraphType.create(null, line.length > 0 ? schema.text(line, marks) : null))))));
378
505
  return tableType.create(null, rowNodes);
379
506
  };
380
507
  /**
@@ -468,6 +595,75 @@ const isBatchableParagraphInsertion = (item, mode) => {
468
595
  const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
469
596
  return !(mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert);
470
597
  };
598
+ /**
599
+ * Move an ADDED paragraph break off every paragraph its container ends with.
600
+ *
601
+ * A container's final mark carries no revision in either direction: nothing
602
+ * follows it, so "join this paragraph with the one after it" and its mirror
603
+ * both state an edit that cannot be carried out, and a reader is left with a
604
+ * revision neither accepting nor rejecting everything can clear.
605
+ *
606
+ * Appending at a container's end therefore rotates exactly as removing from it
607
+ * does. The break that was added sits between the paragraph the run was
608
+ * appended after and the first appended one, so THAT paragraph's mark is the
609
+ * inserted one, each appended paragraph but the last keeps an inserted mark of
610
+ * its own, and the paragraph the container now ends with takes the free mark
611
+ * the run was appended after. Only which paragraph is left markless changes.
612
+ *
613
+ * A paragraph's properties live on its mark, so the paragraph that inherits
614
+ * the free one records the other's as `w:pPrChange` — what a rejection reads
615
+ * to put the container's ending back the way it was.
616
+ *
617
+ * This runs once over the finished document rather than at each insertion:
618
+ * which paragraph ends a container is only settled when the batch is, and an
619
+ * insertion that looked final was undone by the next operation writing a table
620
+ * after it.
621
+ */
622
+ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, author, date, initials }) => {
623
+ const paragraphTypeName = tr.doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
624
+ const rotations = finalParagraphsOf(tr.doc, paragraphTypeName).filter(({ node }) => {
625
+ const mark = node.attrs["pPrMark"];
626
+ if (typeof mark !== "object" || mark === null || !("kind" in mark)) return false;
627
+ if (mark.kind !== "ins" && mark.kind !== "moveTo") return false;
628
+ const info = "info" in mark ? mark.info : void 0;
629
+ const revisionId = typeof info === "object" && info !== null && "id" in info ? info.id : void 0;
630
+ return typeof revisionId === "number" && batchRevisionIds.has(revisionId);
631
+ });
632
+ let next = tr;
633
+ let nextRevisionId = revisionSeed;
634
+ for (const { position: finalPosition, node: final } of rotations) {
635
+ const carrier = addedBreakCarrierBefore(next.doc.resolve(finalPosition), final.type.name);
636
+ if (!carrier) {
637
+ next = next.setNodeAttribute(finalPosition, "pPrMark", null);
638
+ continue;
639
+ }
640
+ next = next.setNodeAttribute(carrier.position, "pPrMark", final.attrs["pPrMark"]);
641
+ const previousFormatting = paragraphPropertiesSnapshot(carrier.node);
642
+ if (JSON.stringify(previousFormatting) === JSON.stringify(paragraphPropertiesSnapshot(final))) {
643
+ next = next.setNodeAttribute(finalPosition, "pPrMark", null);
644
+ continue;
645
+ }
646
+ const existing = expectParagraphAttrs(final)._propertyChanges;
647
+ next = next.setNodeMarkup(finalPosition, void 0, {
648
+ ...final.attrs,
649
+ pPrMark: null,
650
+ _propertyChanges: [...Array.isArray(existing) ? existing : [], {
651
+ type: "paragraphPropertyChange",
652
+ info: {
653
+ id: nextRevisionId++,
654
+ author,
655
+ date,
656
+ ...initials ? { initials } : {}
657
+ },
658
+ previousFormatting
659
+ }]
660
+ });
661
+ }
662
+ return {
663
+ transaction: next,
664
+ nextRevisionId
665
+ };
666
+ };
471
667
  const buildInsertedParagraphs = ({ item, schema, mode, author, date, initials, commentMark, suggestionId, revisionSeed, isPairedMove }) => {
472
668
  const operation = item.operation;
473
669
  if (operation.type !== "insertAfterBlock" && operation.type !== "insertBeforeBlock") panic("Only paragraph insertions can build inserted paragraphs", { type: operation.type });
@@ -551,7 +747,7 @@ const buildInsertedParagraphs = ({ item, schema, mode, author, date, initials, c
551
747
  nextRevisionId
552
748
  };
553
749
  };
554
- const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff }) => {
750
+ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, tableTemplates }) => {
555
751
  const applied = [];
556
752
  const skipped = [];
557
753
  const normalizations = [];
@@ -978,11 +1174,13 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
978
1174
  continue;
979
1175
  }
980
1176
  const revision = structuralRevision;
1177
+ const template = tableTemplates?.get(item.operation.id);
981
1178
  const result = applyTableRowInsertion({
982
1179
  tr,
983
1180
  insertion,
984
1181
  cellTexts: item.operation.cellTexts,
985
- revision
1182
+ revision,
1183
+ ...template !== void 0 && { template }
986
1184
  });
987
1185
  if (result.type === "unsupported") {
988
1186
  skipped.push({
@@ -1172,7 +1370,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1172
1370
  case "deleteBlock":
1173
1371
  if (mode === "direct") {
1174
1372
  const at = tr.doc.resolve(item.blockFrom);
1175
- tr = !(at.index() === at.parent.childCount - 1) || at.nodeBefore?.type.name === item.blockNode.type.name ? tr.delete(item.blockFrom, item.blockTo) : tr.delete(item.blockFrom + 1, item.blockTo - 1);
1373
+ tr = !paragraphEndsItsContainer(at, item.blockNode.type.name) || at.nodeBefore?.type.name === item.blockNode.type.name ? tr.delete(item.blockFrom, item.blockTo) : tr.delete(item.blockFrom + 1, item.blockTo - 1);
1176
1374
  break;
1177
1375
  }
1178
1376
  if (deletionType) {
@@ -1190,8 +1388,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1190
1388
  const inlineRevisionApplied = item.from < item.to || atomRanges.length > 0;
1191
1389
  appliedRevisionIds = inlineRevisionApplied ? [revisionId] : [];
1192
1390
  const markPosition = tr.mapping.map(item.blockFrom);
1193
- const markPlace = tr.doc.resolve(markPosition);
1194
- if (!(markPlace.index() === markPlace.parent.childCount - 1) && tr.doc.nodeAt(markPosition)?.attrs["pPrMark"] == null) {
1391
+ if (!paragraphEndsItsContainer(tr.doc.resolve(markPosition), item.blockNode.type.name) && tr.doc.nodeAt(markPosition)?.attrs["pPrMark"] == null) {
1195
1392
  const markRevisionId = revisionSeed++;
1196
1393
  tr = tr.setNodeAttribute(markPosition, "pPrMark", {
1197
1394
  kind: isPairedMove(item.operation.moveId) ? "moveFrom" : "del",
@@ -1208,15 +1405,21 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1208
1405
  if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
1209
1406
  break;
1210
1407
  case "insertTable": {
1211
- const table = buildTableNode({
1408
+ const revision = {
1409
+ revisionId: revisionSeed,
1410
+ author,
1411
+ date,
1412
+ ...trackedRevisionExtras
1413
+ };
1414
+ const template = tableTemplates?.get(item.operation.id);
1415
+ const table = (template === void 0 ? null : tableFromTemplate({
1416
+ schema: view.state.schema,
1417
+ template,
1418
+ ...producesTrackedChanges && { revision }
1419
+ })) ?? buildTableNode({
1212
1420
  schema: view.state.schema,
1213
1421
  rows: item.operation.rows,
1214
- ...producesTrackedChanges && { revision: {
1215
- revisionId: revisionSeed,
1216
- author,
1217
- date,
1218
- ...trackedRevisionExtras
1219
- } }
1422
+ ...producesTrackedChanges && { revision }
1220
1423
  });
1221
1424
  if (!table) {
1222
1425
  skipped.push({
@@ -1281,13 +1484,23 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1281
1484
  });
1282
1485
  continue;
1283
1486
  }
1284
- for (const rowPosition of rowPositions.toReversed()) tr = tr.setNodeAttribute(rowPosition, "trDel", revision);
1487
+ for (const rowPosition of rowPositions.toReversed()) {
1488
+ tr = tr.setNodeAttribute(rowPosition, "trDel", revision);
1489
+ markTableRowContent({
1490
+ tr,
1491
+ rowPosition,
1492
+ kind: "deletion",
1493
+ revision
1494
+ });
1495
+ }
1285
1496
  appliedRevisionIds = [revision.revisionId];
1286
1497
  markStructuralChange(tr);
1287
1498
  break;
1288
1499
  }
1289
1500
  case "setBlockParagraphProperties": {
1290
- const patch = paragraphPropertiesPatch(item.blockNode, item.operation.properties);
1501
+ const blockPosition = tr.mapping.map(item.blockFrom);
1502
+ const liveBlock = tr.doc.nodeAt(blockPosition) ?? item.blockNode;
1503
+ const patch = paragraphPropertiesPatch(liveBlock, item.operation.properties);
1291
1504
  if (patch === null) {
1292
1505
  skipped.push({
1293
1506
  id: item.operation.id,
@@ -1296,8 +1509,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1296
1509
  continue;
1297
1510
  }
1298
1511
  if (mode === "direct") {
1299
- tr = tr.setNodeMarkup(item.blockFrom, void 0, {
1300
- ...item.blockNode.attrs,
1512
+ tr = tr.setNodeMarkup(blockPosition, void 0, {
1513
+ ...liveBlock.attrs,
1301
1514
  ...patch
1302
1515
  });
1303
1516
  break;
@@ -1311,11 +1524,11 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1311
1524
  date,
1312
1525
  ...initials ? { initials } : {}
1313
1526
  },
1314
- previousFormatting: paragraphPropertiesSnapshot(item.blockNode)
1527
+ previousFormatting: paragraphPropertiesSnapshot(liveBlock)
1315
1528
  };
1316
- const existing = expectParagraphAttrs(item.blockNode)._propertyChanges;
1317
- tr = tr.setNodeMarkup(item.blockFrom, void 0, {
1318
- ...item.blockNode.attrs,
1529
+ const existing = expectParagraphAttrs(liveBlock)._propertyChanges;
1530
+ tr = tr.setNodeMarkup(blockPosition, void 0, {
1531
+ ...liveBlock.attrs,
1319
1532
  ...patch,
1320
1533
  _propertyChanges: [...Array.isArray(existing) ? existing : [], change]
1321
1534
  });
@@ -1410,6 +1623,16 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1410
1623
  if (tr.docChanged) {
1411
1624
  const batchRevisionIds = new Set(applied.flatMap(({ revisionIds }) => revisionIds ?? []));
1412
1625
  tr = withoutRevisionsOnZeroWidthAnchors(tr, batchRevisionIds);
1626
+ const rotated = withRotatedAddedFinalBreaks({
1627
+ tr,
1628
+ batchRevisionIds,
1629
+ revisionSeed,
1630
+ author,
1631
+ date,
1632
+ initials
1633
+ });
1634
+ tr = rotated.transaction;
1635
+ revisionSeed = rotated.nextRevisionId;
1413
1636
  if (revisionStamp) requestDeterministicParaIds(tr, `${revisionStamp.date}:${String(revisionStamp.idSeed)}`);
1414
1637
  view.dispatch(tr);
1415
1638
  }
@@ -1,8 +1,12 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { FolioTableTemplates } from "./table-template.js";
1
3
  import { FolioAIBlock, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditSnapshot } from "./types.js";
2
4
  import { FolioRevisionStamp, FolioWordDiffOptions } from "./apply.js";
3
- import { document_d_exports } from "../types/document.js";
4
5
  import { FolioDocumentOperationBatch, FolioDocumentOperationResult, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult } from "../document-operations.js";
5
6
  import { FolioReviewChange, FolioReviewChangeKind } from "./read.js";
7
+ import { FolioStoryTable } from "./snapshot.js";
8
+ import { TableGeometryPairing } from "./table-geometry.js";
9
+ import { Node } from "prosemirror-model";
6
10
  //#region src/ai-edits/headless.d.ts
7
11
  /** Options for {@link FolioDocxReviewer.fromBuffer}. */
8
12
  type FolioDocxReviewerOptions = {
@@ -76,6 +80,15 @@ type FolioReadReviewedStoryOptions = {
76
80
  story?: FolioEditableDocumentStoryHandle;
77
81
  view?: FolioReviewedView;
78
82
  };
83
+ /** What {@link FolioDocxReviewer.matchStoryTableGeometry} needs to move a table's properties. */
84
+ type FolioMatchStoryTableGeometryOptions = {
85
+ story?: FolioEditableDocumentStoryHandle;
86
+ /** The other document's tables, by the index its snapshot numbers them with. */
87
+ targetTables: ReadonlyMap<number, Node>;
88
+ /** Base cells and the target cells they were aligned with. */
89
+ pairings: readonly TableGeometryPairing[];
90
+ revisionStamp: FolioRevisionStamp;
91
+ };
79
92
  type FolioResolveReviewedStoryOptions = {
80
93
  story?: FolioEditableDocumentStoryHandle;
81
94
  view: FolioResolvedReviewedView;
@@ -100,6 +113,14 @@ declare class FolioDocumentStoryNotFoundError extends FolioDocumentStoryNotFound
100
113
  type FolioApplyDocumentOperationsToStoryOptions = FolioApplyDocumentOperationsOptions & {
101
114
  story: FolioEditableDocumentStoryHandle;
102
115
  batch: FolioDocumentOperationBatch;
116
+ /**
117
+ * Tables and rows an `insertTable` / `insertTableRow` in the batch places
118
+ * verbatim, by operation id. A caller copying a table it already holds —
119
+ * `compareDocx` placing the target document's table — hands it over whole
120
+ * instead of letting the operation rebuild it from its cell texts and lose
121
+ * every table, row and cell property on the way.
122
+ */
123
+ tableTemplates?: FolioTableTemplates;
103
124
  };
104
125
  declare const isFolioReviewedView: (value: unknown) => value is FolioReviewedView;
105
126
  declare const isFolioResolvedReviewedView: (value: unknown) => value is FolioResolvedReviewedView;
@@ -201,6 +222,29 @@ declare class FolioDocxReviewer {
201
222
  getDocumentProperties(): Readonly<NonNullable<document_d_exports.Document["package"]["properties"]>> | null;
202
223
  /** Snapshot one editable story into stable, operation-ready blocks. */
203
224
  snapshotStory(story: FolioEditableDocumentStoryHandle): FolioAIEditSnapshot | null;
225
+ /**
226
+ * One story's tables, in the document order {@link snapshotStory} numbers
227
+ * them with, read through a reviewed view.
228
+ *
229
+ * A block snapshot says which table a paragraph sits in and nothing about
230
+ * the table itself. A caller that has to reproduce one — a comparison
231
+ * copying the target's table into the base, or checking that accepting its
232
+ * own redline reproduced it — needs the node: its `w:tblPr`, its `w:tblGrid`
233
+ * widths, and every row's and cell's properties.
234
+ */
235
+ storyTables({ story, view }?: FolioReadReviewedStoryOptions): readonly FolioStoryTable[];
236
+ /**
237
+ * Move the paired tables' own properties onto this story's tables, as
238
+ * tracked property changes.
239
+ *
240
+ * `w:tblPr`, `w:trPr` and `w:tcPr` belong to no block, so no block operation
241
+ * can carry them: a table that stayed in place while its widths, shading,
242
+ * borders or header row changed reads as unedited. Each difference is
243
+ * written as the target's property set plus a `w:tblPrChange` /
244
+ * `w:trPrChange` / `w:tcPrChange` holding the previous one, which is what a
245
+ * reject restores. A set that already agrees produces no revision.
246
+ */
247
+ matchStoryTableGeometry({ story, targetTables, pairings, revisionStamp }: FolioMatchStoryTableGeometryOptions): number;
204
248
  /** Read one story through an immutable reviewed-view projection. */
205
249
  readReviewedStory(options?: FolioReadReviewedStoryOptions): FolioReviewedStory | null;
206
250
  /** Resolve one editable story to its original or final state. */
@@ -219,7 +263,7 @@ declare class FolioDocxReviewer {
219
263
  */
220
264
  applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
221
265
  /** Apply a versioned operation batch to one editable document story. */
222
- applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
266
+ applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
223
267
  private applyDocumentOperationsInternal;
224
268
  /** Undo the latest unchanged document-operation batch and its created comments. */
225
269
  undoDocumentOperations(undoHandle: FolioDocumentOperationUndoHandle): FolioDocumentOperationUndoResult;
@@ -382,4 +426,4 @@ type ApplyFolioAIEditsToBufferResult = FolioAIEditApplyResult & {
382
426
  */
383
427
  declare const applyFolioAIEditsToBuffer: (buffer: ArrayBuffer, operations: FolioAIEditOperation[], options?: ApplyFolioAIEditsToBufferOptions) => Promise<ApplyFolioAIEditsToBufferResult>;
384
428
  //#endregion
385
- export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, FolioNumberingLevel, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, type FolioRevisionStamp, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView };
429
+ export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, FolioMatchStoryTableGeometryOptions, FolioNumberingLevel, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, type FolioRevisionStamp, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView };
@@ -14,7 +14,8 @@ import { schema, singletonManager } from "../prosemirror/schema/index.js";
14
14
  import { deterministicHexId } from "../utils/hexId.js";
15
15
  import { buildAnnotatedBlockText } from "./clean-text.js";
16
16
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
17
- import { createFolioAIEditSnapshot, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
17
+ import { createFolioAIEditSnapshot, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
18
+ import { matchTableGeometry } from "./table-geometry.js";
18
19
  import { TaggedError } from "better-result";
19
20
  import { Fragment } from "prosemirror-model";
20
21
  import { EditorState } from "prosemirror-state";
@@ -314,6 +315,53 @@ var FolioDocxReviewer = class FolioDocxReviewer {
314
315
  const state = this.getEditableStoryState(story);
315
316
  return state ? createFolioAIEditSnapshot(state.doc) : null;
316
317
  }
318
+ /**
319
+ * One story's tables, in the document order {@link snapshotStory} numbers
320
+ * them with, read through a reviewed view.
321
+ *
322
+ * A block snapshot says which table a paragraph sits in and nothing about
323
+ * the table itself. A caller that has to reproduce one — a comparison
324
+ * copying the target's table into the base, or checking that accepting its
325
+ * own redline reproduced it — needs the node: its `w:tblPr`, its `w:tblGrid`
326
+ * widths, and every row's and cell's properties.
327
+ */
328
+ storyTables({ story = MAIN_STORY, view = "final" } = {}) {
329
+ if (!isFolioReviewedView(view)) throw new UnsupportedFolioReviewedViewError({
330
+ message: "Unsupported reviewed document view.",
331
+ receivedView: view
332
+ });
333
+ const sourceState = this.getEditableStoryState(story);
334
+ return sourceState ? folioStoryTables(resolveReviewedState(sourceState, view).doc) : [];
335
+ }
336
+ /**
337
+ * Move the paired tables' own properties onto this story's tables, as
338
+ * tracked property changes.
339
+ *
340
+ * `w:tblPr`, `w:trPr` and `w:tcPr` belong to no block, so no block operation
341
+ * can carry them: a table that stayed in place while its widths, shading,
342
+ * borders or header row changed reads as unedited. Each difference is
343
+ * written as the target's property set plus a `w:tblPrChange` /
344
+ * `w:trPrChange` / `w:tcPrChange` holding the previous one, which is what a
345
+ * reject restores. A set that already agrees produces no revision.
346
+ */
347
+ matchStoryTableGeometry({ story = MAIN_STORY, targetTables, pairings, revisionStamp }) {
348
+ const state = this.getEditableStoryState(story);
349
+ if (!state || pairings.length === 0) return revisionStamp.idSeed;
350
+ const transaction = state.tr;
351
+ const { nextRevisionId } = matchTableGeometry({
352
+ tr: transaction,
353
+ baseTables: folioStoryTables(state.doc),
354
+ targetTables,
355
+ pairings,
356
+ revision: {
357
+ author: this.author,
358
+ date: revisionStamp.date,
359
+ idSeed: revisionStamp.idSeed
360
+ }
361
+ });
362
+ if (transaction.docChanged) this.setEditableStoryState(story, state.apply(transaction));
363
+ return nextRevisionId;
364
+ }
317
365
  /** Read one story through an immutable reviewed-view projection. */
318
366
  readReviewedStory(options = {}) {
319
367
  const story = options.story ?? MAIN_STORY;
@@ -388,17 +436,18 @@ var FolioDocxReviewer = class FolioDocxReviewer {
388
436
  });
389
437
  }
390
438
  /** Apply a versioned operation batch to one editable document story. */
391
- applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff }) {
439
+ applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates }) {
392
440
  return this.applyDocumentOperationsInternal({
393
441
  story,
394
442
  batch,
395
443
  ...snapshot !== void 0 && { snapshot },
396
444
  ...revisionStamp !== void 0 && { revisionStamp },
397
445
  ...wordDiff !== void 0 && { wordDiff },
446
+ ...tableTemplates !== void 0 && { tableTemplates },
398
447
  createUndoEntry: true
399
448
  });
400
449
  }
401
- applyDocumentOperationsInternal({ story, batch, snapshot, revisionStamp, wordDiff, createUndoEntry }) {
450
+ applyDocumentOperationsInternal({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, createUndoEntry }) {
402
451
  const beforeState = this.requireEditableStoryState(story);
403
452
  const createdCommentsLengthBefore = this.createdComments.length;
404
453
  const view = {
@@ -415,6 +464,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
415
464
  author: this.author,
416
465
  ...revisionStamp !== void 0 && { revisionStamp },
417
466
  ...wordDiff !== void 0 && { wordDiff },
467
+ ...tableTemplates !== void 0 && { tableTemplates },
418
468
  createCommentId: (text) => {
419
469
  const comment = createReviewerComment(this.nextCommentId(), text, this.author);
420
470
  this.createdComments.push(comment);