@stll/folio-core 0.4.0 → 0.5.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 (99) hide show
  1. package/dist/ai-edits/apply.d.ts +3 -9
  2. package/dist/ai-edits/apply.js +94 -14
  3. package/dist/ai-edits/blockRange.d.ts +33 -0
  4. package/dist/ai-edits/blockRange.js +58 -0
  5. package/dist/ai-edits/headless.d.ts +33 -4
  6. package/dist/ai-edits/headless.js +83 -15
  7. package/dist/ai-edits/index.d.ts +5 -3
  8. package/dist/ai-edits/index.js +3 -2
  9. package/dist/ai-edits/snapshot.d.ts +14 -2
  10. package/dist/ai-edits/snapshot.js +12 -1
  11. package/dist/ai-edits/types.d.ts +42 -3
  12. package/dist/compat/eigenpal.d.ts +3 -2
  13. package/dist/compat/eigenpal.js +6 -5
  14. package/dist/controller/layoutPipeline.d.ts +1 -0
  15. package/dist/controller/layoutPipeline.js +35 -137
  16. package/dist/controller/layoutScheduler.d.ts +2 -1
  17. package/dist/controller/layoutScheduler.js +6 -0
  18. package/dist/document-operations.d.ts +122 -0
  19. package/dist/document-operations.js +575 -0
  20. package/dist/docx/blockContentParser.js +17 -7
  21. package/dist/docx/encryption/agileDecryption.js +2 -1
  22. package/dist/docx/encryption/compoundFile.js +4 -2
  23. package/dist/docx/footnoteParser.d.ts +1 -17
  24. package/dist/docx/footnoteParser.js +1 -27
  25. package/dist/docx/groupDrawingParser.d.ts +8 -0
  26. package/dist/docx/groupDrawingParser.js +131 -0
  27. package/dist/docx/numberingParser.d.ts +3 -1
  28. package/dist/docx/numberingParser.js +8 -2
  29. package/dist/docx/paragraphParser.js +9 -3
  30. package/dist/docx/runParser.js +8 -1
  31. package/dist/docx/selectiveXmlPatch.js +4 -10
  32. package/dist/docx/serializer/numberingSerializer.js +3 -1
  33. package/dist/docx/settingsParser.d.ts +6 -3
  34. package/dist/docx/settingsParser.js +2 -0
  35. package/dist/docx/xmlParser.js +11 -4
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.js +6 -5
  38. package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
  39. package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
  40. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -7
  41. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -7
  42. package/dist/layout-bridge/convert/toFlowBlocks.js +56 -19
  43. package/dist/layout-engine/index.d.ts +6 -5
  44. package/dist/layout-engine/index.js +89 -26
  45. package/dist/layout-engine/keep-together.d.ts +13 -8
  46. package/dist/layout-engine/keep-together.js +40 -25
  47. package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
  48. package/dist/layout-engine/layoutInstrumentation.js +10 -1
  49. package/dist/layout-engine/measure/measureBlocks.js +28 -18
  50. package/dist/layout-engine/measure/measureParagraph.js +61 -15
  51. package/dist/layout-engine/paginator.d.ts +2 -1
  52. package/dist/layout-engine/paginator.js +9 -3
  53. package/dist/layout-engine/textBoxGroup.d.ts +7 -0
  54. package/dist/layout-engine/textBoxGroup.js +8 -0
  55. package/dist/layout-engine/types.d.ts +30 -9
  56. package/dist/layout-engine/types.js +20 -2
  57. package/dist/layout-painter/renderPage.d.ts +15 -1
  58. package/dist/layout-painter/renderPage.js +27 -11
  59. package/dist/layout-painter/renderParagraph.js +18 -8
  60. package/dist/layout-painter/renderTable.js +10 -7
  61. package/dist/managers/DocumentLoaderManager.js +12 -5
  62. package/dist/managers/TableSelectionManager.js +1 -1
  63. package/dist/model.js +1 -1
  64. package/dist/paged-layout/pageText.d.ts +14 -0
  65. package/dist/paged-layout/pageText.js +21 -0
  66. package/dist/prosemirror/attrs/index.js +2 -0
  67. package/dist/prosemirror/commands/comments.js +75 -5
  68. package/dist/prosemirror/commands/hyperlink.js +4 -2
  69. package/dist/prosemirror/commands/image.js +2 -1
  70. package/dist/prosemirror/commands/index.js +1 -1
  71. package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
  72. package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
  73. package/dist/prosemirror/conversion/fromProseDoc.js +14 -3
  74. package/dist/prosemirror/conversion/toProseDoc.js +29 -8
  75. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  76. package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
  77. package/dist/prosemirror/extensions/nodes/TableExtension.js +10 -11
  78. package/dist/prosemirror/findReplaceSelection.js +2 -1
  79. package/dist/prosemirror/index.js +1 -1
  80. package/dist/prosemirror/insertOperations.js +1 -1
  81. package/dist/prosemirror/schema/nodes.d.ts +10 -5
  82. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  83. package/dist/prosemirror/utils/tabCalculator.js +4 -2
  84. package/dist/redline.d.ts +20 -0
  85. package/dist/redline.js +127 -0
  86. package/dist/server.d.ts +7 -3
  87. package/dist/server.js +6 -2
  88. package/dist/symbols.d.ts +35 -0
  89. package/dist/symbols.js +512 -0
  90. package/dist/utils/clipboard.js +3 -3
  91. package/dist/utils/fontLoader.js +3 -0
  92. package/dist/utils/fontResolver.js +48 -1
  93. package/dist/utils/zoom.d.ts +21 -0
  94. package/dist/utils/zoom.js +21 -0
  95. package/dist/version-comparison.d.ts +92 -0
  96. package/dist/version-comparison.js +436 -0
  97. package/package.json +3 -3
  98. package/dist/paged-layout/headerFooterMargins.d.ts +0 -75
  99. package/dist/paged-layout/headerFooterMargins.js +0 -122
@@ -1,3 +1,4 @@
1
+ import { tableColumnsArePinned } from "../layout-engine/types.js";
1
2
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
2
3
  import { getAutomaticTextColorForBackground } from "./documentColors.js";
3
4
  import { renderParagraphFragment } from "./renderParagraph.js";
@@ -137,6 +138,7 @@ function renderNestedTable(block, measure, context, doc) {
137
138
  }
138
139
  rowYPositions.push(yPos);
139
140
  const spanningCells = /* @__PURE__ */ new Map();
141
+ const columnsPinned = tableColumnsArePinned(block);
140
142
  let y = 0;
141
143
  for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
142
144
  const row = block.rows[rowIndex];
@@ -146,7 +148,7 @@ function renderNestedTable(block, measure, context, doc) {
146
148
  y += rowMeasure.height;
147
149
  continue;
148
150
  }
149
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi);
151
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, void 0, block.bidi, columnsPinned);
150
152
  tableEl.append(rowEl);
151
153
  y += rowMeasure.height;
152
154
  }
@@ -169,7 +171,7 @@ function applyBorder(el, side, border) {
169
171
  /**
170
172
  * Render a single table cell
171
173
  */
172
- function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context, doc) {
174
+ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, columnsPinned, context, doc) {
173
175
  const cellEl = doc.createElement("div");
174
176
  cellEl.className = TABLE_CLASS_NAMES.cell;
175
177
  cellEl.style.position = "absolute";
@@ -195,7 +197,7 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context,
195
197
  const automaticTextColor = getAutomaticTextColorForBackground(cell.background);
196
198
  if (automaticTextColor) cellEl.style.color = automaticTextColor;
197
199
  }
198
- if (cell.noWrap) cellEl.style.whiteSpace = "nowrap";
200
+ if (cell.noWrap && !columnsPinned) cellEl.style.whiteSpace = "nowrap";
199
201
  if (cell.verticalAlign) {
200
202
  cellEl.style.display = "flex";
201
203
  cellEl.style.flexDirection = "column";
@@ -226,7 +228,7 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, context,
226
228
  /**
227
229
  * Render a table row with rowSpan support
228
230
  */
229
- function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false) {
231
+ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false) {
230
232
  const rowEl = doc.createElement("div");
231
233
  rowEl.className = TABLE_CLASS_NAMES.row;
232
234
  const tableWidth = bidi ? columnWidths.reduce((sum, columnWidth) => sum + columnWidth, 0) : 0;
@@ -270,7 +272,7 @@ function renderTableRow(row, rowMeasure, rowIndex, y, columnWidths, totalRows, c
270
272
  isLastRow,
271
273
  isFirstCol: bidi ? atLogicalEnd : atLogicalStart,
272
274
  isLastCol: bidi ? atLogicalStart : atLogicalEnd
273
- }, context, doc);
275
+ }, columnsPinned, context, doc);
274
276
  cellEl.dataset["cellIndex"] = String(cellIndex);
275
277
  cellEl.dataset["columnIndex"] = String(columnIndex);
276
278
  if (rowSpan > 1) cellEl.dataset["rowSpan"] = String(rowSpan);
@@ -348,6 +350,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
348
350
  }
349
351
  rowYPositions.push(yPos);
350
352
  const spanningCells = /* @__PURE__ */ new Map();
353
+ const columnsPinned = tableColumnsArePinned(block);
351
354
  const headerRowCount = fragment.headerRowCount ?? 0;
352
355
  let y = 0;
353
356
  if (headerRowCount > 0 && fragment.continuesFromPrev) for (let hdrIdx = 0; hdrIdx < headerRowCount; hdrIdx++) {
@@ -358,7 +361,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
358
361
  y += hdrRowMeasure.height;
359
362
  continue;
360
363
  }
361
- const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi);
364
+ const rowEl = renderTableRow(hdrRow, hdrRowMeasure, hdrIdx, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, hdrIdx === 0, block.bidi, columnsPinned);
362
365
  rowEl.dataset["repeatedHeader"] = "true";
363
366
  tableEl.append(rowEl);
364
367
  y += hdrRowMeasure.height;
@@ -386,7 +389,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
386
389
  continue;
387
390
  }
388
391
  const isFirstRowInFragment = headerRowCount > 0 && fragment.continuesFromPrev ? false : fragment.continuesFromPrev && rowIndex === fragment.fromRow;
389
- const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi);
392
+ const rowEl = renderTableRow(row, rowMeasure, rowIndex, y, measure.columnWidths, block.rows.length, context, doc, spanningCells, rowYPositions, isFirstRowInFragment, block.bidi, columnsPinned);
390
393
  contentParent.append(rowEl);
391
394
  y += rowMeasure.height;
392
395
  }
@@ -1,6 +1,7 @@
1
1
  import { resetAuthorColors } from "../utils/authorColors.js";
2
2
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
3
  import { parseDocx } from "../docx/parser.js";
4
+ import { recordDocumentLoadPhase } from "../layout-engine/layoutInstrumentation.js";
4
5
  import { inspectDocxCompatibility } from "../docx/compatibility.js";
5
6
  //#region src/managers/DocumentLoaderManager.ts
6
7
  /**
@@ -48,11 +49,17 @@ var DocumentLoaderManager = class {
48
49
  const generation = ++this.loadGeneration;
49
50
  if (!(history.state !== null)) setDocumentLoadState({ status: "loading" });
50
51
  try {
51
- const doc = await parseDocx(buffer, {
52
- detectVariables: false,
53
- preloadFonts: false,
54
- password: options.password
55
- });
52
+ const parseStartedAt = performance.now();
53
+ let doc;
54
+ try {
55
+ doc = await parseDocx(buffer, {
56
+ detectVariables: false,
57
+ preloadFonts: false,
58
+ password: options.password
59
+ });
60
+ } finally {
61
+ recordDocumentLoadPhase("docx-parse", performance.now() - parseStartedAt);
62
+ }
56
63
  if (this.loadGeneration !== generation) return;
57
64
  this.loadParsedDocument(doc);
58
65
  } catch (error) {
@@ -1,5 +1,5 @@
1
- import { Subscribable } from "./Subscribable.js";
2
1
  import { addColumn, addRow, createTableContext, deleteColumn, deleteRow, getColumnCount, mergeCells, splitCell } from "../utils/tableOperations.js";
2
+ import { Subscribable } from "./Subscribable.js";
3
3
  //#region src/managers/TableSelectionManager.ts
4
4
  /** Data attributes for table elements in the rendered DOM */
5
5
  const TABLE_DATA_ATTRIBUTES = {
package/dist/model.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "./types/documentEnumValues.js";
2
1
  import { deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
2
+ import { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "./types/documentEnumValues.js";
3
3
  import { types_exports } from "./layout-engine/types.js";
4
4
  export { BORDER_STYLE_VALUES, CONDITIONAL_STYLE_TYPE_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FLOATING_TABLE_X_SPEC_VALUES, FLOATING_TABLE_Y_SPEC_VALUES, FONT_THEME_VALUES, FRAME_WRAP_VALUES, FRAME_X_ALIGN_VALUES, FRAME_Y_ALIGN_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LEVEL_SUFFIX_VALUES, LINE_SPACING_RULE_VALUES, types_exports as Layout, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, OUTLINE_STYLE_CSS_ALIASES, OUTLINE_STYLE_CSS_ALIAS_VALUES, PARAGRAPH_ALIGNMENT_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, SHAPE_OUTLINE_STYLE_VALUES, SHAPE_TYPE_VALUES, STYLE_TYPE_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES, deriveBlockId, getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId };
@@ -0,0 +1,14 @@
1
+ import { Layout } from "../layout-engine/types.js";
2
+ import { Node } from "prosemirror-model";
3
+
4
+ //#region src/paged-layout/pageText.d.ts
5
+ /**
6
+ * The plain text of a single rendered page (1-based), read by joining each of
7
+ * the page's layout fragments' `[pmStart, pmEnd)` document text with a
8
+ * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
9
+ * are skipped. Returns `null` when the layout hasn't been computed yet or the
10
+ * page number is out of range.
11
+ */
12
+ declare const getPageTextFromLayout: (layout: Layout | null, doc: Node, page: number) => string | null;
13
+ //#endregion
14
+ export { getPageTextFromLayout };
@@ -0,0 +1,21 @@
1
+ //#region src/paged-layout/pageText.ts
2
+ /**
3
+ * The plain text of a single rendered page (1-based), read by joining each of
4
+ * the page's layout fragments' `[pmStart, pmEnd)` document text with a
5
+ * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
6
+ * are skipped. Returns `null` when the layout hasn't been computed yet or the
7
+ * page number is out of range.
8
+ */
9
+ const getPageTextFromLayout = (layout, doc, page) => {
10
+ if (!layout || !Number.isInteger(page) || page < 1) return null;
11
+ const layoutPage = layout.pages[page - 1];
12
+ if (!layoutPage) return null;
13
+ const fragmentTexts = [];
14
+ for (const fragment of layoutPage.fragments) {
15
+ if (typeof fragment.pmStart !== "number" || typeof fragment.pmEnd !== "number") continue;
16
+ fragmentTexts.push(doc.textBetween(fragment.pmStart, fragment.pmEnd, "\n"));
17
+ }
18
+ return fragmentTexts.join("\n");
19
+ };
20
+ //#endregion
21
+ export { getPageTextFromLayout };
@@ -174,6 +174,7 @@ const readParagraphAttrs = (node) => {
174
174
  });
175
175
  optionalOneOf(attrs, "sectionBreakType", "paragraph.attrs.sectionBreakType", issues, SECTION_BREAK_TYPES);
176
176
  optionalStringArray(attrs, "listLevelNumFmts", "paragraph.attrs.listLevelNumFmts", issues);
177
+ optionalNumberArray(attrs, "listLevelStarts", "paragraph.attrs.listLevelStarts", issues);
177
178
  optionalNumber(attrs, "listAbstractNumId", "paragraph.attrs.listAbstractNumId", issues);
178
179
  optionalBorderMap(attrs, "borders", "paragraph.attrs.borders", issues, [
179
180
  "top",
@@ -186,6 +187,7 @@ const readParagraphAttrs = (node) => {
186
187
  optionalShading(attrs, "shading", "paragraph.attrs.shading", issues);
187
188
  optionalTabStops(attrs, "tabs", "paragraph.attrs.tabs", issues);
188
189
  optionalRecord(attrs, "spacingExplicit", "paragraph.attrs.spacingExplicit", issues);
190
+ optionalRecord(attrs, "spacingFromDocDefaults", "paragraph.attrs.spacingFromDocDefaults", issues);
189
191
  optionalTextFormatting(attrs, "defaultTextFormatting", "paragraph.attrs.defaultTextFormatting", issues);
190
192
  optionalRecord(attrs, "numPr", "paragraph.attrs.numPr", issues);
191
193
  validateNumPr(attrs["numPr"], issues);
@@ -1,3 +1,4 @@
1
+ import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
1
2
  //#region src/prosemirror/commands/comments.ts
2
3
  /**
3
4
  * Add a comment mark to the current selection.
@@ -59,21 +60,47 @@ function resolveChange(from, to, mode, revisionIds) {
59
60
  if (node.type.name === "paragraph") {
60
61
  const op = collectPPrMarkOp(node, pos, from, to, mode, revisionSet);
61
62
  if (op) pPrMarkOps.push(op);
63
+ const boundaryCovered = rangeCoversParagraphBoundary(from, to, pos, node);
64
+ let nextAttrs = null;
62
65
  const propertyChanges = node.attrs["_propertyChanges"];
63
- if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && rangeCoversParagraphBoundary(from, to, pos, node)) {
66
+ if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && boundaryCovered) {
64
67
  const matches = propertyChanges.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
65
68
  if (matches.length > 0) {
66
69
  const remaining = propertyChanges.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
67
- const nextAttrs = {
70
+ nextAttrs = {
68
71
  ...node.attrs,
69
72
  _propertyChanges: remaining.length > 0 ? remaining : null
70
73
  };
71
- if (mode === "reject") {
72
- for (const change of matches.toReversed()) if (change.previousFormatting) for (const [key, val] of Object.entries(change.previousFormatting)) nextAttrs[key] = val;
74
+ if (mode === "reject") for (const change of matches.toReversed()) {
75
+ Object.assign(nextAttrs, paragraphRejectAttrPatch(change.previousFormatting));
76
+ nextAttrs["_originalFormatting"] = paragraphRejectOriginalFormatting(change.previousFormatting, node.attrs["_originalFormatting"]);
73
77
  }
74
- tr.setNodeMarkup(pos, void 0, nextAttrs);
75
78
  }
76
79
  }
80
+ const sectionProperties = node.attrs["_sectionProperties"];
81
+ const sectionChanges = sectionProperties?.propertyChanges;
82
+ if (sectionProperties && Array.isArray(sectionChanges) && sectionChanges.length > 0 && boundaryCovered) {
83
+ const matches = sectionChanges.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
84
+ if (matches.length > 0) {
85
+ const remaining = sectionChanges.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
86
+ let restored = { ...sectionProperties };
87
+ if (mode === "reject") for (const change of matches.toReversed()) restored = sectionRejectProperties(restored, change.previousProperties);
88
+ delete restored.propertyChanges;
89
+ if (remaining.length > 0) restored.propertyChanges = remaining;
90
+ nextAttrs = nextAttrs ?? { ...node.attrs };
91
+ nextAttrs["_sectionProperties"] = restored;
92
+ nextAttrs["sectionBreakType"] = sectionBreakTypeFromSectionStart(restored.sectionStart);
93
+ }
94
+ }
95
+ if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
96
+ return true;
97
+ }
98
+ const tableChangeAttrName = TABLE_PROPERTY_CHANGE_ATTR_BY_NODE[node.type.name];
99
+ if (tableChangeAttrName !== void 0) {
100
+ if (rangeCoversNode(from, to, pos, node)) {
101
+ const nextAttrs = resolveTablePropertyChangeAttrs(node, tableChangeAttrName, mode, revisionSet);
102
+ if (nextAttrs) tr.setNodeMarkup(pos, void 0, nextAttrs);
103
+ }
77
104
  return true;
78
105
  }
79
106
  if (!node.isInline) return true;
@@ -124,6 +151,49 @@ function rangeCoversParagraphBoundary(from, to, pos, node) {
124
151
  const boundaryTo = pos + node.nodeSize;
125
152
  return from <= boundaryFrom && to >= boundaryTo;
126
153
  }
154
+ /** Whether [from, to] fully covers the node — the range property-change cards
155
+ * and accept-all / reject-all sweeps supply for table-level records. */
156
+ function rangeCoversNode(from, to, pos, node) {
157
+ return from <= pos && to >= pos + node.nodeSize;
158
+ }
159
+ const SECTION_BREAK_TYPE_VALUES = [
160
+ "nextPage",
161
+ "continuous",
162
+ "oddPage",
163
+ "evenPage"
164
+ ];
165
+ function sectionBreakTypeFromSectionStart(sectionStart) {
166
+ return SECTION_BREAK_TYPE_VALUES.find((value) => value === sectionStart) ?? null;
167
+ }
168
+ /** PM node type name → the attr its tracked property-change records live on. */
169
+ const TABLE_PROPERTY_CHANGE_ATTR_BY_NODE = {
170
+ table: "tblPrChange",
171
+ tableRow: "trPrChange",
172
+ tableCell: "tcPrChange",
173
+ tableHeader: "tcPrChange"
174
+ };
175
+ /**
176
+ * Resolve the tracked property-change records on one table / row / cell node.
177
+ * Accept keeps the live formatting and clears the matched records; reject
178
+ * additionally restores the stored previous formatting wholesale (the change
179
+ * element stores the complete old property set — see propertyChangeScope.ts).
180
+ * Returns the next attrs, or `null` when no record matches.
181
+ */
182
+ function resolveTablePropertyChangeAttrs(node, attrName, mode, revisionSet) {
183
+ const changes = node.attrs[attrName];
184
+ if (!Array.isArray(changes) || changes.length === 0) return null;
185
+ const matches = changes.filter((c) => revisionSet === null || c.info && revisionSet.has(c.info.id));
186
+ if (matches.length === 0) return null;
187
+ const remaining = changes.filter((c) => revisionSet !== null && (!c.info || !revisionSet.has(c.info.id)));
188
+ const nextAttrs = {
189
+ ...node.attrs,
190
+ [attrName]: remaining.length > 0 ? remaining : null
191
+ };
192
+ if (mode === "reject") for (const change of matches.toReversed()) if (attrName === "tblPrChange") Object.assign(nextAttrs, tableRejectAttrPatch(change.previousFormatting));
193
+ else if (attrName === "trPrChange") Object.assign(nextAttrs, tableRowRejectAttrPatch(change.previousFormatting));
194
+ else Object.assign(nextAttrs, tableCellRejectAttrPatch(change.previousFormatting, node.attrs["_originalFormatting"]));
195
+ return nextAttrs;
196
+ }
127
197
  function isPPrMarkAttr(value) {
128
198
  if (typeof value !== "object" || value === null) return false;
129
199
  const kind = value.kind;
@@ -39,7 +39,8 @@ const editHyperlinkAtCursor = (view, { displayText, href }) => {
39
39
  const { $from } = view.state.selection;
40
40
  const linkMark = $from.marks().find((m) => m.type === hlType);
41
41
  if (!linkMark) return true;
42
- const targetRange = rangeAtCursor(collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark)), $from.pos);
42
+ const ranges = collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark));
43
+ const targetRange = rangeAtCursor(ranges, $from.pos);
43
44
  if (!targetRange) return false;
44
45
  const { tooltip } = expectHyperlinkMarkAttrs(linkMark);
45
46
  const newMark = hlType.create({
@@ -69,7 +70,8 @@ const removeHyperlinkAtCursor = (view, { popupHref }) => {
69
70
  }
70
71
  });
71
72
  if (!linkMark) return false;
72
- const targetRange = rangeAtCursor(collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark)), $from.pos);
73
+ const ranges = collectHyperlinkRanges($from.parent, $from.start(), hlType, hyperlinkHref(linkMark));
74
+ const targetRange = rangeAtCursor(ranges, $from.pos);
73
75
  if (!targetRange) return false;
74
76
  const tr = view.state.tr.removeMark(targetRange.start, targetRange.end, hlType);
75
77
  view.dispatch(tr.scrollIntoView());
@@ -124,7 +124,8 @@ const applyImageWrapType = (view, pos, wrapType) => {
124
124
  const applyImageTransform = (view, pos, action) => {
125
125
  const node = imageNodeAt(view, pos);
126
126
  if (!node) return false;
127
- const transform = computeImageTransform(expectImageAttrs(node).transform ?? "", action);
127
+ const currentTransform = expectImageAttrs(node).transform ?? "";
128
+ const transform = computeImageTransform(currentTransform, action);
128
129
  const tr = view.state.tr.setNodeMarkup(pos, void 0, mergeImageAttrs(node, { transform }));
129
130
  view.dispatch(tr.scrollIntoView());
130
131
  return true;
@@ -5,9 +5,9 @@ import { getTableContext, isInTable as isInTableCell } from "../extensions/nodes
5
5
  import { getListInfo, isInList } from "../extensions/features/ListExtension.js";
6
6
  import { CLIPBOARD_READ_ERROR_EVENT, buildPlainTextSlice, pasteWithoutFormatting } from "./pastePlainText.js";
7
7
  import { getHyperlinkAttrs, getSelectedText, isHyperlinkActive } from "../extensions/marks/HyperlinkExtension.js";
8
- import { addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyTableStyle, autoFitContents, deleteColumn, deleteRow, deleteTable, distributeColumns, insertTable, mergeCells, removeTableBorders, selectColumn, selectRow, selectTable, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setInsideTableBorders, setOutsideTableBorders, setRowHeight, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, splitCell, toggleHeaderRow, toggleNoWrap } from "./table.js";
9
8
  import { clearFontFamily, clearFontSize, clearHighlight, clearTextColor, insertHyperlink, removeHyperlink, setFontFamily, setFontSize, setHighlight, setHyperlink, setTextColor, setUnderlineStyle, toggleBold, toggleItalic, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline } from "./formatting.js";
10
9
  import { addTabStop, alignCenter, alignJustify, alignLeft, alignRight, applyStyle, clearStyle, decreaseIndent, decreaseListLevel, doubleSpacing, generateTOC, increaseIndent, increaseListLevel, oneAndHalfSpacing, removeList, removeTabStop, setAlignment, setIndentFirstLine, setIndentLeft, setIndentRight, setLineSpacing, setLtr, setRtl, setSpaceAfter, setSpaceBefore, singleSpacing, toggleBidi, toggleBulletList, toggleNumberedList } from "./paragraph.js";
10
+ import { addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyTableStyle, autoFitContents, deleteColumn, deleteRow, deleteTable, distributeColumns, insertTable, mergeCells, removeTableBorders, selectColumn, selectRow, selectTable, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setInsideTableBorders, setOutsideTableBorders, setRowHeight, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, splitCell, toggleHeaderRow, toggleNoWrap } from "./table.js";
11
11
  import { PAINTABLE_MARK_NAMES, applyFormatMarks, captureFormatMarks } from "./formatPainter.js";
12
12
  import { insertPageBreak } from "./pageBreak.js";
13
13
  export { CLIPBOARD_READ_ERROR_EVENT, PAINTABLE_MARK_NAMES, acceptAIEditRevision, acceptAllChanges, acceptChange, addColumnLeft, addColumnRight, addCommentMark, addRowAbove, addRowBelow, addTabStop, alignCenter, alignJustify, alignLeft, alignRight, applyFormatMarks, applyStyle, applyTableStyle, autoFitContents, buildPlainTextSlice, captureFormatMarks, clearFontFamily, clearFontSize, clearFormatting, clearHighlight, clearStyle, clearTextColor, createRemoveMarkCommand, createSetMarkCommand, decreaseIndent, decreaseListLevel, deleteColumn, deleteRow, deleteTable, distributeColumns, doubleSpacing, findAIEditRevisionRange, findNextChange, findPreviousChange, generateTOC, getHyperlinkAttrs, getListInfo, getMarkAttr, getParagraphAlignment, getParagraphBidi, getSelectedText, getStyleId, getTableContext, increaseIndent, increaseListLevel, insertHyperlink, insertPageBreak, insertTable, isHyperlinkActive, isInList, isInTableCell as isInTable, isMarkActive, mergeCells, oneAndHalfSpacing, pasteWithoutFormatting, rejectAIEditRevision, rejectAllChanges, rejectChange, removeCommentMark, removeHyperlink, removeList, removeTabStop, removeTableBorders, selectColumn, selectRow, selectTable, setAlignment, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setFontFamily, setFontSize, setHighlight, setHyperlink, setIndentFirstLine, setIndentLeft, setIndentRight, setInsideTableBorders, setLineSpacing, setLtr, setOutsideTableBorders, setRowHeight, setRtl, setSpaceAfter, setSpaceBefore, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, setTextColor, setUnderlineStyle, singleSpacing, splitCell, toggleBidi, toggleBold, toggleBulletList, toggleHeaderRow, toggleItalic, toggleNoWrap, toggleNumberedList, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline };
@@ -0,0 +1,68 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+
3
+ //#region src/prosemirror/commands/propertyChangeScope.d.ts
4
+ type AttrPatch = Record<string, unknown>;
5
+ /**
6
+ * Paragraph attrs governed by a `w:pPrChange` — the attrs
7
+ * `paragraphFormattingToAttrs` (conversion/toProseDoc.ts) can produce from a
8
+ * parsed `CT_PPrBase` (ECMA-376 §17.13.5.29). Rejecting a pPrChange restores
9
+ * the stored old pPr wholesale for exactly these keys: a key absent from the
10
+ * stored record resets to the schema default (`null`).
11
+ *
12
+ * Deliberately OUT of scope (preserved across a reject):
13
+ * - identity/structure: `paraId`, `textId`, `sectionBreakType`,
14
+ * `_sectionProperties`, `_propertyChanges`, `pPrMark`, `bookmarks`,
15
+ * `_emptyHyperlinks`, `renderedPageBreakBefore`
16
+ * - paragraph-mark run properties — `pPr/rPr` is CT_PPr-only, not part of the
17
+ * CT_PPrBase payload a pPrChange stores: `defaultTextFormatting`,
18
+ * `runInWithNext` (`w:specVanish` lives in that rPr)
19
+ * - load-time style/numbering bookkeeping the command layer cannot recompute
20
+ * without a style resolver: `numPrFromStyle`, the `list*` rendering attrs,
21
+ * `spacingFromDocDefaults`
22
+ */
23
+ declare const PPR_CHANGE_SCOPED_ATTR_KEYS: readonly ["styleId", "numPr", "alignment", "spaceBefore", "spaceAfter", "lineSpacing", "lineSpacingRule", "spacingExplicit", "indentLeft", "indentRight", "indentFirstLine", "hangingIndent", "borders", "shading", "tabs", "pageBreakBefore", "keepNext", "keepLines", "widowControl", "contextualSpacing", "outlineLevel", "direction", "_autospacingBase"];
24
+ /**
25
+ * Build the attr patch that rejecting one pPrChange applies to a paragraph:
26
+ * every in-scope key set to the stored previous value, or reset to `null`
27
+ * when the stored old pPr does not carry it. Keys the record captured beyond
28
+ * the scoped set (editor-created list suggestions snapshot list-rendering
29
+ * bookkeeping attrs) merge through 1:1 so their pre-change values restore too.
30
+ */
31
+ declare function paragraphRejectAttrPatch(previousFormatting: Record<string, unknown> | null | undefined): AttrPatch;
32
+ /**
33
+ * Rebuild the paragraph's `_originalFormatting` (the serializer's pPr source)
34
+ * after a reject: the stored old pPr wholesale within CT_PPrBase scope, with
35
+ * the paragraph-mark rPr fields (`runProperties`, `runInWithNext`) preserved
36
+ * from the live original — a pPrChange cannot store them, so a reject must
37
+ * not drop them.
38
+ */
39
+ declare function paragraphRejectOriginalFormatting(previousFormatting: Record<string, unknown> | null | undefined, liveOriginal: unknown): document_d_exports.ParagraphFormatting | null;
40
+ /**
41
+ * Rejected section properties: the stored old sectPr wholesale, preserving
42
+ * the live header/footer references — `EG_HdrFtrReferences` is not part of
43
+ * the `CT_SectPrBase` payload a `w:sectPrChange` stores (ECMA-376
44
+ * §17.13.5.32), so those children survive a reject. The caller re-attaches
45
+ * whatever `propertyChanges` remain unresolved.
46
+ */
47
+ declare function sectionRejectProperties(live: document_d_exports.SectionProperties, previousProperties: document_d_exports.SectionProperties | undefined): document_d_exports.SectionProperties;
48
+ /**
49
+ * Attr patch for rejecting a `w:tblPrChange`: the stored old tblPr wholesale
50
+ * (`w:tblPrChange` stores the complete previous tblPr, so every tblPr-derived
51
+ * attr resets when absent). `columnWidths` comes from `w:tblGrid`, not tblPr,
52
+ * and is untouched.
53
+ */
54
+ declare function tableRejectAttrPatch(previousFormatting: document_d_exports.TableFormatting | undefined): AttrPatch;
55
+ /** Attr patch for rejecting a `w:trPrChange`: the stored old trPr wholesale. */
56
+ declare function tableRowRejectAttrPatch(previousFormatting: document_d_exports.TableRowFormatting | undefined): AttrPatch;
57
+ /**
58
+ * Attr patch for rejecting a `w:tcPrChange`: the stored old tcPr wholesale
59
+ * for the non-structural attrs. `gridSpan` / `vMerge` are structural in
60
+ * ProseMirror (`colspan` / `rowspan` shape the table grid); restoring them
61
+ * from the record would desync the grid without a full table restructure, so
62
+ * the live structure is kept and the rebuilt `_originalFormatting` inherits
63
+ * the live original's `gridSpan` / `vMerge` to stay consistent with it.
64
+ * (Word would also restore tracked merges; folio deliberately does not.)
65
+ */
66
+ declare function tableCellRejectAttrPatch(previousFormatting: document_d_exports.TableCellFormatting | undefined, liveOriginalFormatting: document_d_exports.TableCellFormatting | null | undefined): AttrPatch;
67
+ //#endregion
68
+ export { PPR_CHANGE_SCOPED_ATTR_KEYS, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch };