@stll/folio-core 0.53.0 → 0.54.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 (148) hide show
  1. package/dist/ai-edits/apply.js +390 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +3 -1
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/fonts/embeddedFonts.js +1 -1
  41. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  42. package/dist/i18n/messages/catalogs.gen.js +34 -34
  43. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  44. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  45. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  46. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  47. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  48. package/dist/markdown/escape.d.ts +2 -2
  49. package/dist/markdown/escape.js +4 -5
  50. package/dist/markdown/renderBlock.js +91 -25
  51. package/dist/markdown/renderRuns.d.ts +10 -1
  52. package/dist/markdown/renderRuns.js +320 -59
  53. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  54. package/dist/prosemirror/clearRunColor.js +46 -0
  55. package/dist/prosemirror/commands/comments.js +56 -21
  56. package/dist/prosemirror/commands/formatPainter.js +1 -1
  57. package/dist/prosemirror/commands/hyperlink.js +2 -1
  58. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  59. package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
  60. package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
  61. package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
  62. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
  63. package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
  64. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  65. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  66. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  67. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  68. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
  69. package/dist/prosemirror/conversion/toProseDoc.js +31 -150
  70. package/dist/prosemirror/documentSchema.d.ts +8 -0
  71. package/dist/prosemirror/documentSchema.js +18 -0
  72. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  73. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  74. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  75. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  76. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  77. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  78. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  79. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  80. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  81. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  82. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  83. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  84. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  85. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  86. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  87. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  88. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  89. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  90. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  91. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  92. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  94. package/dist/prosemirror/extensions/types.d.ts +7 -0
  95. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  96. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  97. package/dist/prosemirror/index.d.ts +2 -1
  98. package/dist/prosemirror/index.js +2 -1
  99. package/dist/prosemirror/listNumbering.d.ts +3 -21
  100. package/dist/prosemirror/listNumbering.js +10 -25
  101. package/dist/prosemirror/listRendering.d.ts +27 -0
  102. package/dist/prosemirror/listRendering.js +27 -0
  103. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  104. package/dist/prosemirror/markupViewNotes.js +49 -0
  105. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  106. package/dist/prosemirror/markupViewProjection.js +7 -2
  107. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  108. package/dist/prosemirror/noteReferenceReview.js +332 -0
  109. package/dist/prosemirror/paragraphIndentation.js +5 -2
  110. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  111. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  112. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  113. package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
  114. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  115. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  116. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  117. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  118. package/dist/prosemirror/plugins/index.d.ts +2 -1
  119. package/dist/prosemirror/plugins/index.js +2 -1
  120. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  121. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  122. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  123. package/dist/prosemirror/plugins/suggestionMode.js +391 -28
  124. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  125. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  126. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  127. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  128. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  129. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  130. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  131. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  132. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  133. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  134. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  135. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  136. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  137. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  138. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  139. package/dist/prosemirror/tableCellPaste.js +525 -0
  140. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  141. package/dist/prosemirror/tableGridMutation.js +166 -18
  142. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  143. package/dist/prosemirror/tableRunIn.js +165 -0
  144. package/dist/prosemirror/textInput.js +11 -2
  145. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  146. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  147. package/dist/server.d.ts +2 -2
  148. package/package.json +5 -5
@@ -0,0 +1,525 @@
1
+ import { decodeTableCellParagraphSourcePayload, transportTableCellsWithParagraphPropertySources } from "../docx/paragraphPropertySource.js";
2
+ import { expectTableAttrs, mergeTableAttrs } from "./attrs/index.js";
3
+ import { Fragment, Slice } from "prosemirror-model";
4
+ import { CellSelection, TableMap, cellAround, tableNodeTypes } from "prosemirror-tables";
5
+ import { Transform } from "prosemirror-transform";
6
+ //#region src/prosemirror/tableCellPaste.ts
7
+ /**
8
+ * Pasting table cells into a table: the pasted cells overwrite the cells they
9
+ * land on, merges the pasted block cuts through are split first, and the table
10
+ * grows when the block reaches past its edge. A paste into a cell selection
11
+ * fills every selected cell instead and leaves the table's shape alone.
12
+ *
13
+ * `prosemirror-tables` ships the same operation as its `handlePaste`, but its
14
+ * merge splitting inserts the lower half of a vertical merge at a position
15
+ * relative to the table rather than to the document, so in any table that does
16
+ * not open the document the new cell lands inside an earlier cell and the
17
+ * paste either throws or leaves rows that no longer tile the grid. Its split
18
+ * halves also copy every attribute of the cell they came from, so both halves
19
+ * claim the same source cell and the same stored merge continuation.
20
+ *
21
+ * With suggestions on, the paste is tracked: each overwritten cell keeps its
22
+ * content as a deletion before the pasted content, a vertical merge the block
23
+ * cuts through is split as a tracked cell merge, and the rows and cells the
24
+ * table grows by are tracked insertions, so rejecting every change gives the
25
+ * table back. A horizontal merge has no tracked split, so one the block cuts
26
+ * through is split directly, as the structural table commands do.
27
+ */
28
+ const fitSlice = (nodeType, slice) => {
29
+ const node = nodeType.createAndFill();
30
+ if (!node) throw new RangeError(`Cannot create an empty ${nodeType.name}`);
31
+ return new Transform(node).replace(0, node.content.size, slice).doc;
32
+ };
33
+ /**
34
+ * A cell's attributes with `count` of its columns, from the `from`th on, taken
35
+ * out of its span and its column widths.
36
+ */
37
+ const withoutColumns = (attrs, from, count) => {
38
+ const colwidth = attrs["colwidth"];
39
+ const widths = Array.isArray(colwidth) ? colwidth.toSpliced(from, count) : null;
40
+ return {
41
+ ...attrs,
42
+ colspan: Math.max(1, Number(attrs["colspan"]) || 1) - count,
43
+ colwidth: widths?.some((width) => Number(width) > 0) ? widths : null
44
+ };
45
+ };
46
+ const spanOf = (cell, name) => Math.max(1, Number(cell.attrs[name]) || 1);
47
+ /** Pad short rows with empty cells so the block is rectangular. */
48
+ const ensureRectangular = (schema, rows) => {
49
+ const widths = [];
50
+ for (const [index, row] of rows.entries()) for (let child = row.childCount - 1; child >= 0; child--) {
51
+ const cell = row.child(child);
52
+ for (let covered = index; covered < index + spanOf(cell, "rowspan"); covered++) widths[covered] = (widths[covered] ?? 0) + spanOf(cell, "colspan");
53
+ }
54
+ const width = Math.max(0, ...widths.map((value) => value ?? 0));
55
+ for (let index = 0; index < widths.length; index++) {
56
+ if (index >= rows.length) rows.push(Fragment.empty);
57
+ const rowWidth = widths[index] ?? 0;
58
+ if (rowWidth < width) {
59
+ const cells = [];
60
+ for (let column = rowWidth; column < width; column++) {
61
+ const empty = tableNodeTypes(schema).cell.createAndFill();
62
+ if (empty) cells.push(empty);
63
+ }
64
+ rows[index] = (rows[index] ?? Fragment.empty).append(Fragment.from(cells));
65
+ }
66
+ }
67
+ return {
68
+ height: rows.length,
69
+ width,
70
+ rows
71
+ };
72
+ };
73
+ /**
74
+ * The attributes of a cell made from another one, split off it or pasted from
75
+ * a copy of it: the same formatting, but not the other cell's identity, its
76
+ * revisions, or the continuation cells its merge stored, whose paragraphs
77
+ * belong to the other cell.
78
+ */
79
+ const newCellAttrs = (attrs) => ({
80
+ ...attrs,
81
+ _docxCellId: null,
82
+ cellMarker: null,
83
+ tcPrChange: null,
84
+ _preserveVMergeRestart: null,
85
+ _docxVMergeContinuationCells: null
86
+ });
87
+ const withNewTableCells = (node) => {
88
+ if (node.isTextblock || node.isLeaf) return node;
89
+ const content = withNewTableCellsIn(node.content);
90
+ const role = node.type.spec["tableRole"];
91
+ const isCell = role === "cell" || role === "header_cell";
92
+ return content === node.content && !isCell ? node : node.type.create(isCell ? newCellAttrs(node.attrs) : node.attrs, content, node.marks);
93
+ };
94
+ const withNewTableCellsIn = (fragment) => {
95
+ let changed = false;
96
+ const nodes = [];
97
+ fragment.forEach((node) => {
98
+ const next = withNewTableCells(node);
99
+ changed ||= next !== node;
100
+ nodes.push(next);
101
+ });
102
+ return changed ? Fragment.from(nodes) : fragment;
103
+ };
104
+ /**
105
+ * A pasted slice whose table cells are new cells: a copy of a cell must not
106
+ * claim the source cell's identity, revisions, or the continuation cells its
107
+ * merge stored, whatever the paste lands on.
108
+ */
109
+ const pastedSliceWithNewTableCells = (slice) => {
110
+ const content = withNewTableCellsIn(slice.content);
111
+ return content === slice.content ? slice : new Slice(content, slice.openStart, slice.openEnd);
112
+ };
113
+ /** The cells a slice holds, or null when it holds none (it is not table content). */
114
+ const pastedCells = (slice) => {
115
+ if (slice.size === 0) return null;
116
+ let { content, openStart, openEnd } = slice;
117
+ while (content.childCount === 1 && (openStart > 0 && openEnd > 0 || content.child(0).type.spec["tableRole"] === "table")) {
118
+ openStart--;
119
+ openEnd--;
120
+ content = content.child(0).content;
121
+ }
122
+ const first = content.firstChild;
123
+ if (!first) return null;
124
+ const role = first.type.spec["tableRole"];
125
+ const { schema } = first.type;
126
+ const rows = [];
127
+ if (role === "row") for (let index = 0; index < content.childCount; index++) {
128
+ let cells = content.child(index).content;
129
+ const left = index ? 0 : Math.max(0, openStart - 1);
130
+ const right = index < content.childCount - 1 ? 0 : Math.max(0, openEnd - 1);
131
+ if (left || right) cells = fitSlice(tableNodeTypes(schema).row, new Slice(cells, left, right)).content;
132
+ rows.push(cells);
133
+ }
134
+ else if (role === "cell" || role === "header_cell") rows.push(openStart || openEnd ? fitSlice(tableNodeTypes(schema).row, new Slice(content, openStart, openEnd)).content : content);
135
+ else return null;
136
+ const newCells = rows.map((row) => {
137
+ const cells = [];
138
+ row.forEach((cell) => {
139
+ cells.push(cell.type.create(newCellAttrs(cell.attrs), cell.content, cell.marks));
140
+ });
141
+ return Fragment.from(cells);
142
+ });
143
+ return ensureRectangular(schema, newCells);
144
+ };
145
+ const readTable = (tr, tableStart) => {
146
+ const table = tr.doc.nodeAt(tableStart - 1);
147
+ if (!table || table.type.spec["tableRole"] !== "table") throw new RangeError("The pasted-into table is gone");
148
+ return {
149
+ table,
150
+ map: TableMap.get(table),
151
+ tableStart,
152
+ mapFrom: tr.mapping.maps.length
153
+ };
154
+ };
155
+ const mapPosition = (tr, context, pos) => tr.mapping.slice(context.mapFrom).map(pos);
156
+ /** A new, empty cell of the same kind as `like`, with nothing that names another cell. */
157
+ const emptyCellLike = (like, schema) => {
158
+ const types = tableNodeTypes(schema);
159
+ const cell = (like?.type === types.header_cell ? types.header_cell : types.cell).createAndFill();
160
+ if (!cell) throw new RangeError("Cannot create an empty table cell");
161
+ return cell;
162
+ };
163
+ const isOmittedGridSlot = (cell) => cell.attrs["_omittedGridSlot"] != null;
164
+ const hasOmittedGridSlots = (table) => {
165
+ let found = false;
166
+ table.forEach((row) => {
167
+ row.forEach((cell) => {
168
+ found ||= isOmittedGridSlot(cell);
169
+ });
170
+ });
171
+ return found;
172
+ };
173
+ /**
174
+ * The `w:vMerge` continuation cells a merged cell stored for the rows below its
175
+ * first, one per row, empty ones standing in for any it did not store.
176
+ */
177
+ const continuationCells = (cell, rowspan) => {
178
+ const stored = cell.attrs["_docxVMergeContinuationCells"];
179
+ const cells = stored === void 0 || stored === null ? [] : [...decodeTableCellParagraphSourcePayload(stored).cells];
180
+ while (cells.length < rowspan - 1) cells.push({
181
+ type: "tableCell",
182
+ formatting: { vMerge: "continue" },
183
+ content: [{
184
+ type: "paragraph",
185
+ content: []
186
+ }]
187
+ });
188
+ return cells;
189
+ };
190
+ /** Stored continuation cells to carry, or null for none. */
191
+ const continuationAttr = (cells) => cells.length > 0 ? transportTableCellsWithParagraphPropertySources(cells) : null;
192
+ /** Add columns and rows so the table reaches `width` × `height`. */
193
+ const growTable = (tr, context, width, height, revision) => {
194
+ const { table, map, tableStart } = context;
195
+ const { schema } = tr.doc.type;
196
+ let grown = false;
197
+ if (width > map.width) {
198
+ let rowEnd = 0;
199
+ for (let row = 0; row < map.height; row++) {
200
+ const rowNode = table.child(row);
201
+ rowEnd += rowNode.nodeSize;
202
+ const cells = [];
203
+ for (let column = map.width; column < width; column++) {
204
+ const cell = emptyCellLike(rowNode.lastChild, schema);
205
+ cells.push(revision ? cell.type.create({
206
+ ...cell.attrs,
207
+ cellMarker: {
208
+ kind: "ins",
209
+ info: { ...revision }
210
+ }
211
+ }, cell.content) : cell);
212
+ }
213
+ tr.insert(mapPosition(tr, context, rowEnd - 1 + tableStart), cells);
214
+ }
215
+ const widths = expectTableAttrs(table).columnWidths;
216
+ const lastWidth = widths?.at(-1);
217
+ tr.setNodeMarkup(mapPosition(tr, context, tableStart - 1), void 0, mergeTableAttrs(table, { columnWidths: widths?.length === map.width && lastWidth !== void 0 ? [...widths, ...Array.from({ length: width - map.width }, () => lastWidth)] : void 0 }));
218
+ grown = true;
219
+ }
220
+ if (height > map.height) {
221
+ const lastRowStart = (map.height - 1) * map.width;
222
+ const cells = [];
223
+ for (let column = 0; column < Math.max(map.width, width); column++) {
224
+ const above = column < map.width ? table.nodeAt(map.map[lastRowStart + column] ?? -1) : null;
225
+ cells.push(emptyCellLike(above ?? null, schema));
226
+ }
227
+ const rowType = tableNodeTypes(schema).row;
228
+ const rows = [];
229
+ for (let row = map.height; row < height; row++) rows.push(rowType.create(revision ? { trIns: { ...revision } } : null, cells));
230
+ tr.insert(mapPosition(tr, context, tableStart + table.nodeSize - 2), rows);
231
+ grown = true;
232
+ }
233
+ return grown;
234
+ };
235
+ /** Split every vertical merge that crosses the line above row `top`, within [left, right). */
236
+ const isolateHorizontal = (tr, context, left, right, top, revision) => {
237
+ const { table, map, tableStart } = context;
238
+ if (top === 0 || top === map.height) return false;
239
+ let found = false;
240
+ for (let column = left; column < right; column++) {
241
+ const index = top * map.width + column;
242
+ const pos = map.map[index];
243
+ if (pos === void 0 || map.map[index - map.width] !== pos) continue;
244
+ found = true;
245
+ const cell = table.nodeAt(pos);
246
+ if (!cell) continue;
247
+ const { top: cellTop, left: cellLeft } = map.findCell(pos);
248
+ const rowspan = spanOf(cell, "rowspan");
249
+ const upperRows = top - cellTop;
250
+ const lowerRows = rowspan - upperRows;
251
+ const continuations = continuationCells(cell, rowspan);
252
+ tr.setNodeMarkup(mapPosition(tr, context, tableStart + pos), null, {
253
+ ...cell.attrs,
254
+ rowspan: upperRows,
255
+ _docxVMergeContinuationCells: continuationAttr(continuations.slice(0, upperRows - 1))
256
+ });
257
+ const lower = cell.type.createAndFill({
258
+ ...newCellAttrs(cell.attrs),
259
+ rowspan: lowerRows,
260
+ _docxVMergeContinuationCells: continuationAttr(continuations.slice(upperRows)),
261
+ cellMarker: revision ? {
262
+ kind: "merge",
263
+ info: { ...revision },
264
+ verticalMergeOriginal: "continue"
265
+ } : null
266
+ });
267
+ if (!lower) throw new RangeError("Cannot split a merged table cell");
268
+ tr.insert(mapPosition(tr, context, tableStart + map.positionAt(top, cellLeft, table)), lower);
269
+ column += spanOf(cell, "colspan") - 1;
270
+ }
271
+ return found;
272
+ };
273
+ /** Split every horizontal merge that crosses the line left of column `left`, within [top, bottom). */
274
+ const isolateVertical = (tr, context, top, bottom, left) => {
275
+ const { table, map, tableStart } = context;
276
+ if (left === 0 || left === map.width) return false;
277
+ let found = false;
278
+ for (let row = top; row < bottom; row++) {
279
+ const index = row * map.width + left;
280
+ const pos = map.map[index];
281
+ if (pos === void 0 || map.map[index - 1] !== pos) continue;
282
+ found = true;
283
+ const cell = table.nodeAt(pos);
284
+ if (!cell) continue;
285
+ const cellLeft = map.colCount(pos);
286
+ const colspan = spanOf(cell, "colspan");
287
+ const updatePos = mapPosition(tr, context, tableStart + pos);
288
+ tr.setNodeMarkup(updatePos, null, withoutColumns(cell.attrs, left - cellLeft, colspan - (left - cellLeft)));
289
+ const right = cell.type.createAndFill(withoutColumns(newCellAttrs(cell.attrs), 0, left - cellLeft));
290
+ if (!right) throw new RangeError("Cannot split a merged table cell");
291
+ tr.insert(updatePos + cell.nodeSize, right);
292
+ row += spanOf(cell, "rowspan") - 1;
293
+ }
294
+ return found;
295
+ };
296
+ /**
297
+ * The range of a cell's content a paste replaces. A block of whole paragraphs
298
+ * (a pasted cell's content) replaces all of it; open content (text, or
299
+ * paragraphs cut from a longer run) goes from the start of the first paragraph
300
+ * to the end of the last, so it takes on the cell's paragraph formatting the
301
+ * way a paste into a paragraph does.
302
+ */
303
+ const cellPasteRange = (cell, cellPos, slice) => {
304
+ const from = cellPos + 1;
305
+ const to = cellPos + cell.nodeSize - 1;
306
+ if (slice.openStart === 0 && slice.openEnd === 0 && slice.content.firstChild?.isBlock) return {
307
+ from,
308
+ to
309
+ };
310
+ return {
311
+ from: cell.firstChild?.isTextblock ? from + 1 : from,
312
+ to: cell.lastChild?.isTextblock ? to - 1 : to
313
+ };
314
+ };
315
+ /** Replace a cell's content outright. */
316
+ const replaceCellContentDirectly = (tr, cellPos, slice) => {
317
+ const cell = tr.doc.nodeAt(cellPos);
318
+ if (!cell) throw new RangeError("The pasted-into table cell is gone");
319
+ const { from, to } = cellPasteRange(cell, cellPos, slice);
320
+ tr.replace(from, to, slice);
321
+ };
322
+ /** Keep overwritten cells live until review resolves the pasted vertical merges. */
323
+ const verticalPasteTargets = ({ context: { map, table, tableStart }, cells, placement: { top, left } }) => {
324
+ const targets = [];
325
+ const covered = /* @__PURE__ */ new Set();
326
+ for (const [offset, fragment] of cells.rows.entries()) {
327
+ const row = top + offset;
328
+ let column = left;
329
+ for (let child = 0; child < fragment.childCount; child++) {
330
+ const pasted = fragment.child(child);
331
+ while (covered.has(row * map.width + column)) column++;
332
+ const right = column + spanOf(pasted, "colspan");
333
+ const bottom = row + spanOf(pasted, "rowspan");
334
+ for (let nextRow = row; nextRow < bottom;) {
335
+ const pos = map.map[nextRow * map.width + column];
336
+ const cell = pos === void 0 ? null : table.nodeAt(pos);
337
+ if (pos === void 0 || !cell || cell.attrs["cellMarker"] != null) return null;
338
+ const rect = map.findCell(pos);
339
+ if (rect.top !== nextRow || rect.left !== column || rect.right !== right || rect.bottom > bottom) return null;
340
+ const anchor = nextRow === row;
341
+ targets.push({
342
+ pos: tableStart + pos,
343
+ slice: anchor ? new Slice(pasted.content, 0, 0) : new Slice(Fragment.from(table.type.schema.node("paragraph")), 0, 0),
344
+ merge: anchor ? "anchor" : "continuation"
345
+ });
346
+ nextRow = rect.bottom;
347
+ }
348
+ for (let r = row; r < bottom; r++) for (let c = column; c < right; c++) covered.add(r * map.width + c);
349
+ column = right;
350
+ }
351
+ }
352
+ return targets;
353
+ };
354
+ /**
355
+ * The table cell each pasted cell lands on, when every one lands on a cell of
356
+ * its own shape; null when the block's cells are shaped differently.
357
+ */
358
+ const matchingCells = ({ map, tableStart }, cells, top, left) => {
359
+ const matches = [];
360
+ const covered = /* @__PURE__ */ new Set();
361
+ for (const [offset, fragment] of cells.rows.entries()) {
362
+ const row = top + offset;
363
+ let column = left;
364
+ for (let child = 0; child < fragment.childCount; child++) {
365
+ const pasted = fragment.child(child);
366
+ while (covered.has(row * map.width + column)) column++;
367
+ const pos = map.map[row * map.width + column];
368
+ if (pos === void 0) return null;
369
+ const rect = map.findCell(pos);
370
+ if (rect.top !== row || rect.left !== column || rect.right - rect.left !== spanOf(pasted, "colspan") || rect.bottom - rect.top !== spanOf(pasted, "rowspan")) return null;
371
+ for (let r = rect.top; r < rect.bottom; r++) for (let c = rect.left; c < rect.right; c++) covered.add(r * map.width + c);
372
+ matches.push({
373
+ pos: tableStart + pos,
374
+ slice: new Slice(pasted.content, 0, 0)
375
+ });
376
+ column = rect.right;
377
+ }
378
+ }
379
+ return matches;
380
+ };
381
+ /** The pasted cell covering (`row`, `column`) of the block, repeating the block past its edge. */
382
+ const pastedCellAt = (cells, row, column) => {
383
+ const wantedRow = row % cells.height;
384
+ const wantedColumn = column % cells.width;
385
+ const covered = [];
386
+ for (const [index, fragment] of cells.rows.entries()) {
387
+ let at = 0;
388
+ for (let child = 0; child < fragment.childCount; child++) {
389
+ const cell = fragment.child(child);
390
+ while (covered[index]?.[at]) at++;
391
+ for (let r = index; r < index + spanOf(cell, "rowspan"); r++) for (let c = at; c < at + spanOf(cell, "colspan"); c++) (covered[r] ??= [])[c] = 1;
392
+ if (wantedRow >= index && wantedRow < index + spanOf(cell, "rowspan") && wantedColumn >= at && wantedColumn < at + spanOf(cell, "colspan")) return cell;
393
+ at += spanOf(cell, "colspan");
394
+ }
395
+ }
396
+ return null;
397
+ };
398
+ /**
399
+ * Every cell of the table touching `rect`, each taking the pasted cell at its
400
+ * place in the block (the block repeating to cover the rectangle), or the
401
+ * pasted content itself when it is not cells. The table's shape is untouched.
402
+ */
403
+ const fillTargets = ({ map, table, tableStart }, rect, cells, slice) => {
404
+ const targets = [];
405
+ for (const pos of map.cellsInRect(rect)) {
406
+ const cellRect = map.findCell(pos);
407
+ const row = Math.max(cellRect.top, rect.top) - rect.top;
408
+ const column = Math.max(cellRect.left, rect.left) - rect.left;
409
+ const pasted = cells ? pastedCellAt(cells, row, column) : null;
410
+ const cell = table.nodeAt(pos);
411
+ if (cells && !pasted || !cell || isOmittedGridSlot(cell)) continue;
412
+ targets.push({
413
+ pos: tableStart + pos,
414
+ slice: pasted ? new Slice(pasted.content, 0, 0) : slice
415
+ });
416
+ }
417
+ return targets;
418
+ };
419
+ const replaceTargets = (tr, targets, replaceCellContent) => {
420
+ for (const { pos, slice } of targets.toSorted((a, b) => b.pos - a.pos)) replaceCellContent(tr, pos, slice);
421
+ };
422
+ const selectBlock = (tr, tableStart, rect) => {
423
+ const { map, table } = readTable(tr, tableStart);
424
+ tr.setSelection(new CellSelection(tr.doc.resolve(tableStart + map.positionAt(rect.top, rect.left, table)), tr.doc.resolve(tableStart + map.positionAt(rect.bottom - 1, rect.right - 1, table))));
425
+ };
426
+ /**
427
+ * Paste a block of cells into the table at `tableStart` from (`top`, `left`):
428
+ * grow the table to hold it, split the merges its edges cut through, then give
429
+ * each cell the block covers its pasted cell. Where the block's cells have the
430
+ * table's shapes the table keeps its cells, with their formatting and
431
+ * identity, and takes the pasted content; otherwise the block's cells replace
432
+ * them, except in a tracked paste, which keeps the table's shape. Selects the
433
+ * pasted block.
434
+ */
435
+ const insertTableCells = (tr, tableStart, { top, left }, cells, { revision, replaceCellContent = replaceCellContentDirectly } = {}) => {
436
+ let rect = {
437
+ top,
438
+ left,
439
+ right: left + cells.width,
440
+ bottom: top + cells.height
441
+ };
442
+ let context = readTable(tr, tableStart);
443
+ if (hasOmittedGridSlots(context.table)) {
444
+ rect = {
445
+ ...rect,
446
+ right: Math.min(rect.right, context.map.width),
447
+ bottom: Math.min(rect.bottom, context.map.height)
448
+ };
449
+ replaceTargets(tr, fillTargets(context, rect, cells, Slice.empty), replaceCellContent);
450
+ selectBlock(tr, tableStart, rect);
451
+ return tr;
452
+ }
453
+ const recompute = (changed) => {
454
+ if (changed) context = readTable(tr, tableStart);
455
+ };
456
+ recompute(growTable(tr, context, rect.right, rect.bottom, revision));
457
+ recompute(isolateHorizontal(tr, context, rect.left, rect.right, rect.top, revision));
458
+ recompute(isolateHorizontal(tr, context, rect.left, rect.right, rect.bottom, revision));
459
+ recompute(isolateVertical(tr, context, rect.top, rect.bottom, rect.left));
460
+ recompute(isolateVertical(tr, context, rect.top, rect.bottom, rect.right));
461
+ const matches = matchingCells(context, cells, rect.top, rect.left);
462
+ if (matches) replaceTargets(tr, matches, replaceCellContent);
463
+ else if (revision) {
464
+ const vertical = verticalPasteTargets({
465
+ context,
466
+ cells,
467
+ placement: rect
468
+ });
469
+ if (vertical) for (const { pos, slice, merge } of vertical.toSorted((a, b) => b.pos - a.pos)) {
470
+ replaceCellContent(tr, pos, slice);
471
+ if (merge === "continuation") tr.setNodeAttribute(pos, "cellMarker", {
472
+ kind: "merge",
473
+ info: { ...revision },
474
+ verticalMerge: "continue",
475
+ verticalMergeOriginal: "rest"
476
+ });
477
+ }
478
+ else replaceTargets(tr, fillTargets(context, rect, cells, Slice.empty), replaceCellContent);
479
+ } else for (let row = rect.top; row < rect.bottom; row++) {
480
+ const from = context.map.positionAt(row, rect.left, context.table);
481
+ const to = context.map.positionAt(row, rect.right, context.table);
482
+ tr.replace(mapPosition(tr, context, from + tableStart), mapPosition(tr, context, to + tableStart), new Slice(cells.rows[row - rect.top] ?? Fragment.empty, 0, 0));
483
+ }
484
+ selectBlock(tr, tableStart, rect);
485
+ return tr;
486
+ };
487
+ /**
488
+ * Paste into every cell of a cell selection: each takes the pasted cell at its
489
+ * place in the block, the block repeating to cover the selection, or the
490
+ * pasted content itself when it holds no cells. The table keeps its shape.
491
+ */
492
+ const fillSelectedCells = (tr, selection, slice, { replaceCellContent = replaceCellContentDirectly } = {}) => {
493
+ const tableStart = selection.$anchorCell.start(-1);
494
+ const context = readTable(tr, tableStart);
495
+ const rect = context.map.rectBetween(selection.$anchorCell.pos - tableStart, selection.$headCell.pos - tableStart);
496
+ replaceTargets(tr, fillTargets(context, rect, pastedCells(slice), slice), replaceCellContent);
497
+ const after = readTable(tr, tableStart);
498
+ tr.setSelection(new CellSelection(tr.doc.resolve(tableStart + after.map.positionAt(rect.top, rect.left, after.table)), tr.doc.resolve(tableStart + after.map.positionAt(rect.bottom - 1, rect.right - 1, after.table))));
499
+ return tr;
500
+ };
501
+ /**
502
+ * Paste `slice` into the table the selection is in: into every selected cell
503
+ * of a cell selection, or, when the slice holds cells, as a block of cells
504
+ * from the cell holding the cursor. False otherwise, so the paste falls
505
+ * through to the ordinary one.
506
+ */
507
+ const pasteTableCells = (state, slice, dispatch, hooks) => {
508
+ const { selection } = state;
509
+ let run = null;
510
+ if (selection instanceof CellSelection) run = (tr) => fillSelectedCells(tr, selection, slice, hooks);
511
+ else {
512
+ const cells = pastedCells(slice);
513
+ const $cell = cells ? cellAround(selection.$head) : null;
514
+ if (cells && $cell) {
515
+ const tableStart = $cell.start(-1);
516
+ const rect = TableMap.get($cell.node(-1)).findCell($cell.pos - tableStart);
517
+ run = (tr) => insertTableCells(tr, tableStart, rect, cells, hooks);
518
+ }
519
+ }
520
+ if (!run) return false;
521
+ if (dispatch) dispatch(run(state.tr).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
522
+ return true;
523
+ };
524
+ //#endregion
525
+ export { cellPasteRange, fillSelectedCells, insertTableCells, pasteTableCells, pastedCells, pastedSliceWithNewTableCells };
@@ -19,19 +19,35 @@ type RemoveTableRowOptions = {
19
19
  * and the table is left one row shorter than that merge.
20
20
  */
21
21
  declare const removeTableRow: (tr: Transaction, { map, table, tableStart }: RemoveTableRowOptions, row: number) => void;
22
- /**
23
- * Remove every row a structural edit left without a cell of its own, closing
24
- * the vertical merges that reached through it over one row fewer.
25
- *
26
- * Deleting the only column a row still had a cell in, or merging whole rows
27
- * together, leaves a row holding nothing but the merges from above. The
28
- * editable model can carry such a row, but nothing else agrees with it: a
29
- * package spells it as a row of `w:vMerge` continuations, which the reader
30
- * gives a cell of its own by splitting the merge apart, so the reopened table
31
- * differs from the one the operation reported. Accepting the same deletion
32
- * tracked removes the cells one at a time and the row with its last one,
33
- * which is this result; the two modes have to leave the same table.
34
- */
22
+ /** Whether a batch of structural deletions may have emptied rows. */
23
+ type RowsEmptiedInBatch = {
24
+ pending: boolean;
25
+ };
26
+ type MarkRowEmptiedInBatchOptions = {
27
+ tr: Transaction;
28
+ rowPosition: number;
29
+ emptied: RowsEmptiedInBatch;
30
+ };
31
+ /** A transient row attribute follows node moves; conversion never serializes it. */
32
+ declare const markRowEmptiedInBatch: ({ tr, rowPosition, emptied }: MarkRowEmptiedInBatchOptions) => void;
33
+ type MarkRowsEmptiedInBatchOptions = {
34
+ tr: Transaction;
35
+ tablePosition: number;
36
+ previousTable: Node;
37
+ emptied: RowsEmptiedInBatch;
38
+ };
39
+ /** Column removal keeps row indices stable: mark only newly cell-less rows. */
40
+ declare const markRowsEmptiedInBatch: ({ tr, tablePosition, previousTable, emptied }: MarkRowsEmptiedInBatchOptions) => void;
41
+ /** Sweep current tables in reverse so moves cannot invalidate cleanup targets. */
42
+ declare const removeRowsWithoutCellsAfterBatch: (tr: Transaction, emptied: RowsEmptiedInBatch) => void;
43
+ type RemoveRowsWithoutCellsInRangeOptions = {
44
+ tr: Transaction;
45
+ from: number;
46
+ to: number;
47
+ };
48
+ /** Close marked rows before their owning cell is captured into a continuation payload. */
49
+ declare const removeRowsWithoutCellsInRange: ({ tr, from, to }: RemoveRowsWithoutCellsInRangeOptions) => void;
50
+ /** Close all cell-less rows after an immediate structural edit. */
35
51
  declare const removeRowsWithoutCells: (tr: Transaction, tablePosition: number) => void;
36
52
  type ReconcileTableGridAfterColumnRemovalOptions = {
37
53
  tr: Transaction;
@@ -62,5 +78,28 @@ type ReconcileTableGridAfterColumnInsertionOptions = {
62
78
  * more than `w:tblGrid` declares.
63
79
  */
64
80
  declare const reconcileTableGridAfterColumnInsertion: ({ tr, tablePosition, previousTable, insertedColumn }: ReconcileTableGridAfterColumnInsertionOptions) => void;
81
+ type InsertTableColumnOptions = {
82
+ tr: Transaction;
83
+ tablePosition: number;
84
+ /** The grid column the new column takes; the columns from it on move right. */
85
+ column: number;
86
+ /** The grid column whose cells the new cells copy their formatting from. */
87
+ templateColumn: number;
88
+ /** A new, empty cell shaped after `template`, the row's cell in the template column. */
89
+ createCell: (template: Node | null) => Node;
90
+ };
91
+ /**
92
+ * Insert a grid column, walking the table map: each row gains a cell at the
93
+ * column's place in the grid (after any cell a merge from a row above holds
94
+ * there), a cell merged across that place widens instead, and `w:tblGrid`
95
+ * gains the column.
96
+ */
97
+ declare const insertTableColumn: ({ tr, tablePosition, column, templateColumn, createCell }: InsertTableColumnOptions) => void;
98
+ /**
99
+ * Remove grid columns [`left`, `right`), walking the table map: a cell only
100
+ * in them goes, a cell merged across them narrows, `w:tblGrid` loses them,
101
+ * and a row left without a cell of its own goes too.
102
+ */
103
+ declare const removeTableColumns: (tr: Transaction, tablePosition: number, left: number, right: number) => void;
65
104
  //#endregion
66
- export { reconcileTableGridAfterColumnInsertion, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCells, removeTableRow };
105
+ export { RowsEmptiedInBatch, insertTableColumn, markRowEmptiedInBatch, markRowsEmptiedInBatch, reconcileTableGridAfterColumnInsertion, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCells, removeRowsWithoutCellsAfterBatch, removeRowsWithoutCellsInRange, removeTableColumns, removeTableRow };