@milkdown/preset-gfm 6.1.5 → 6.2.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 (72) hide show
  1. package/lib/auto-link.d.ts.map +1 -1
  2. package/lib/footnote/definition.d.ts +1 -5
  3. package/lib/footnote/definition.d.ts.map +1 -1
  4. package/lib/footnote/reference.d.ts +1 -5
  5. package/lib/footnote/reference.d.ts.map +1 -1
  6. package/lib/index.d.ts +1 -3
  7. package/lib/index.d.ts.map +1 -1
  8. package/lib/index.es.js +1671 -16
  9. package/lib/index.es.js.map +1 -1
  10. package/lib/strike-through.d.ts +1 -5
  11. package/lib/strike-through.d.ts.map +1 -1
  12. package/lib/table/command.d.ts +1 -1
  13. package/lib/table/command.d.ts.map +1 -1
  14. package/lib/table/nodes/cell-selection.d.ts +38 -0
  15. package/lib/table/nodes/cell-selection.d.ts.map +1 -0
  16. package/lib/table/nodes/column-resizing.d.ts +10 -0
  17. package/lib/table/nodes/column-resizing.d.ts.map +1 -0
  18. package/lib/table/nodes/commands.d.ts +30 -0
  19. package/lib/table/nodes/commands.d.ts.map +1 -0
  20. package/lib/table/nodes/copy-paste.d.ts +13 -0
  21. package/lib/table/nodes/copy-paste.d.ts.map +1 -0
  22. package/lib/table/nodes/fix-tables.d.ts +6 -0
  23. package/lib/table/nodes/fix-tables.d.ts.map +1 -0
  24. package/lib/table/nodes/index.d.ts +5 -23
  25. package/lib/table/nodes/index.d.ts.map +1 -1
  26. package/lib/table/nodes/schema.d.ts +3 -1
  27. package/lib/table/nodes/schema.d.ts.map +1 -1
  28. package/lib/table/nodes/table-editing.d.ts +9 -0
  29. package/lib/table/nodes/table-editing.d.ts.map +1 -0
  30. package/lib/table/nodes/table-map.d.ts +44 -0
  31. package/lib/table/nodes/table-map.d.ts.map +1 -0
  32. package/lib/table/nodes/table-view.d.ts +15 -0
  33. package/lib/table/nodes/table-view.d.ts.map +1 -0
  34. package/lib/table/nodes/types.d.ts +15 -0
  35. package/lib/table/nodes/types.d.ts.map +1 -0
  36. package/lib/table/nodes/util.d.ts +16 -0
  37. package/lib/table/nodes/util.d.ts.map +1 -0
  38. package/lib/table/operator-plugin/actions.d.ts +1 -1
  39. package/lib/table/operator-plugin/actions.d.ts.map +1 -1
  40. package/lib/table/operator-plugin/calc-pos.d.ts.map +1 -1
  41. package/lib/table/operator-plugin/helper.d.ts +1 -1
  42. package/lib/table/operator-plugin/helper.d.ts.map +1 -1
  43. package/lib/table/operator-plugin/index.d.ts +1 -1
  44. package/lib/table/operator-plugin/index.d.ts.map +1 -1
  45. package/lib/table/operator-plugin/widget.d.ts +4 -4
  46. package/lib/table/operator-plugin/widget.d.ts.map +1 -1
  47. package/lib/table/utils.d.ts +4 -4
  48. package/lib/table/utils.d.ts.map +1 -1
  49. package/lib/task-list-item.d.ts +1 -5
  50. package/lib/task-list-item.d.ts.map +1 -1
  51. package/package.json +6 -6
  52. package/src/auto-link.ts +4 -3
  53. package/src/table/command.ts +3 -3
  54. package/src/table/nodes/cell-selection.ts +352 -0
  55. package/src/table/nodes/column-resizing.ts +260 -0
  56. package/src/table/nodes/commands.ts +551 -0
  57. package/src/table/nodes/copy-paste.ts +306 -0
  58. package/src/table/nodes/fix-tables.ts +117 -0
  59. package/src/table/nodes/index.ts +7 -1
  60. package/src/table/nodes/schema.ts +100 -2
  61. package/src/table/nodes/table-editing.ts +275 -0
  62. package/src/table/nodes/table-map.ts +280 -0
  63. package/src/table/nodes/table-view.ts +76 -0
  64. package/src/table/nodes/types.ts +16 -0
  65. package/src/table/nodes/util.ts +107 -0
  66. package/src/table/operator-plugin/actions.ts +4 -4
  67. package/src/table/operator-plugin/calc-pos.ts +2 -1
  68. package/src/table/operator-plugin/helper.ts +2 -1
  69. package/src/table/operator-plugin/index.ts +1 -1
  70. package/src/table/operator-plugin/widget.ts +4 -14
  71. package/src/table/utils.ts +5 -2
  72. package/src/task-list-item.ts +2 -1
@@ -0,0 +1,306 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { Fragment, Node, NodeType, Schema, Slice } from '@milkdown/prose/model';
4
+ import { EditorState, Transaction } from '@milkdown/prose/state';
5
+ import { Transform } from '@milkdown/prose/transform';
6
+
7
+ import { CellSelection } from './cell-selection';
8
+ import { tableNodeTypes } from './schema';
9
+ import { Rect, TableMap } from './table-map';
10
+ import { removeColSpan, setAttr } from './util';
11
+
12
+ // Utilities to help with copying and pasting table cells
13
+
14
+ // : (Slice) → ?{width: number, height: number, rows: [Fragment]}
15
+ // Get a rectangular area of cells from a slice, or null if the outer
16
+ // nodes of the slice aren't table cells or rows.
17
+ export function pastedCells(slice: Slice) {
18
+ if (!slice.size) return null;
19
+ let { content, openStart, openEnd } = slice;
20
+ while (
21
+ content.childCount == 1 &&
22
+ ((openStart > 0 && openEnd > 0) || (content.firstChild as Node).type.spec['tableRole'] == 'table')
23
+ ) {
24
+ openStart--;
25
+ openEnd--;
26
+ content = (content.firstChild as Node).content;
27
+ }
28
+ const first = content.firstChild as Node,
29
+ role = first.type.spec['tableRole'];
30
+ const schema = first.type.schema,
31
+ rows = [];
32
+ if (role == 'row') {
33
+ for (let i = 0; i < content.childCount; i++) {
34
+ let cells = content.child(i).content;
35
+ const left = i ? 0 : Math.max(0, openStart - 1);
36
+ const right = i < content.childCount - 1 ? 0 : Math.max(0, openEnd - 1);
37
+ if (left || right) cells = fitSlice(tableNodeTypes(schema).row, new Slice(cells, left, right)).content;
38
+ rows.push(cells);
39
+ }
40
+ } else if (role == 'cell' || role == 'header_cell') {
41
+ rows.push(
42
+ openStart || openEnd
43
+ ? fitSlice(tableNodeTypes(schema).row, new Slice(content, openStart, openEnd)).content
44
+ : content,
45
+ );
46
+ } else {
47
+ return null;
48
+ }
49
+ return ensureRectangular(schema, rows);
50
+ }
51
+
52
+ // : (Schema, [Fragment]) → {width: number, height: number, rows: [Fragment]}
53
+ // Compute the width and height of a set of cells, and make sure each
54
+ // row has the same number of cells.
55
+ export type R = { width: number; height: number; rows: Fragment[] };
56
+ function ensureRectangular(schema: Schema, rows: Fragment[]): R {
57
+ const widths: number[] = [];
58
+ for (let i = 0; i < rows.length; i++) {
59
+ const row = rows[i] as Fragment;
60
+ for (let j = row.childCount - 1; j >= 0; j--) {
61
+ const { rowspan, colspan } = row.child(j).attrs;
62
+ for (let r = i; r < i + rowspan; r++) widths[r] = (widths[r] || 0) + colspan;
63
+ }
64
+ }
65
+ let width = 0;
66
+ for (let r = 0; r < widths.length; r++) width = Math.max(width, widths[r] as number);
67
+ for (let r = 0; r < widths.length; r++) {
68
+ if (r >= rows.length) rows.push(Fragment.empty);
69
+ if ((widths[r] as number) < width) {
70
+ const empty = tableNodeTypes(schema).cell.createAndFill(),
71
+ cells = [];
72
+ for (let i = widths[r] as number; i < width; i++) cells.push(empty);
73
+ rows[r] = (rows[r] as Fragment).append(Fragment.from(cells));
74
+ }
75
+ }
76
+ return { height: rows.length, width, rows };
77
+ }
78
+
79
+ export function fitSlice(nodeType: NodeType, slice: Slice) {
80
+ const node = nodeType.createAndFill() as Node;
81
+ const tr = new Transform(node).replace(0, node.content.size, slice);
82
+ return tr.doc;
83
+ }
84
+
85
+ // Clip or extend (repeat) the given set of cells to cover the given
86
+ // width and height. Will clip rowspan/colspan cells at the edges when
87
+ // they stick out.
88
+ export function clipCells({ width, height, rows }: R, newWidth: number, newHeight: number): R {
89
+ if (width != newWidth) {
90
+ const added: number[] = [],
91
+ newRows: Fragment[] = [];
92
+ for (let row = 0; row < rows.length; row++) {
93
+ const frag = rows[row] as Fragment,
94
+ cells = [];
95
+ for (let col = added[row] || 0, i = 0; col < newWidth; i++) {
96
+ let cell = frag.child(i % frag.childCount);
97
+ if (col + cell.attrs['colspan'] > newWidth)
98
+ cell = cell.type.create(
99
+ removeColSpan(cell.attrs, cell.attrs['colspan'], col + cell.attrs['colspan'] - newWidth),
100
+ cell.content,
101
+ );
102
+ cells.push(cell);
103
+ col += cell.attrs['colspan'];
104
+ for (let j = 1; j < cell.attrs['rowspan']; j++)
105
+ added[row + j] = (added[row + j] || 0) + cell.attrs['colspan'];
106
+ }
107
+ newRows.push(Fragment.from(cells));
108
+ }
109
+ rows = newRows;
110
+ width = newWidth;
111
+ }
112
+
113
+ if (height != newHeight) {
114
+ const newRows = [];
115
+ for (let row = 0, i = 0; row < newHeight; row++, i++) {
116
+ const cells = [],
117
+ source = rows[i % height] as Fragment;
118
+ for (let j = 0; j < source.childCount; j++) {
119
+ let cell = source.child(j);
120
+ if (row + cell.attrs['rowspan'] > newHeight)
121
+ cell = cell.type.create(
122
+ setAttr(cell.attrs, 'rowspan', Math.max(1, newHeight - cell.attrs['rowspan'])),
123
+ cell.content,
124
+ );
125
+ cells.push(cell);
126
+ }
127
+ newRows.push(Fragment.from(cells));
128
+ }
129
+ rows = newRows;
130
+ height = newHeight;
131
+ }
132
+
133
+ return { width, height, rows };
134
+ }
135
+
136
+ // Make sure a table has at least the given width and height. Return
137
+ // true if something was changed.
138
+ function growTable(
139
+ tr: Transaction,
140
+ map: TableMap,
141
+ table: Node,
142
+ start: number,
143
+ width: number,
144
+ height: number,
145
+ mapFrom: number,
146
+ ) {
147
+ const schema = tr.doc.type.schema,
148
+ types = tableNodeTypes(schema);
149
+ let empty, emptyHead;
150
+ if (width > map.width) {
151
+ for (let row = 0, rowEnd = 0; row < map.height; row++) {
152
+ const rowNode = table.child(row);
153
+ rowEnd += rowNode.nodeSize;
154
+ const cells = [];
155
+ let add;
156
+ if (rowNode.lastChild == null || rowNode.lastChild.type == types.cell)
157
+ add = empty || (empty = types.cell.createAndFill());
158
+ else add = emptyHead || (emptyHead = types.header_cell.createAndFill());
159
+ for (let i = map.width; i < width; i++) cells.push(add);
160
+ tr.insert(tr.mapping.slice(mapFrom).map(rowEnd - 1 + start), cells);
161
+ }
162
+ }
163
+ if (height > map.height) {
164
+ const cells = [];
165
+ for (let i = 0, start = (map.height - 1) * map.width; i < Math.max(map.width, width); i++) {
166
+ const header =
167
+ i >= map.width ? false : (table.nodeAt(map.map[start + i] as number) as Node).type == types.header_cell;
168
+ cells.push(
169
+ header
170
+ ? emptyHead || (emptyHead = types.header_cell.createAndFill())
171
+ : empty || (empty = types.cell.createAndFill()),
172
+ );
173
+ }
174
+
175
+ const emptyRow = types.row.create(null, Fragment.from(cells)),
176
+ rows = [];
177
+ for (let i = map.height; i < height; i++) rows.push(emptyRow);
178
+ tr.insert(tr.mapping.slice(mapFrom).map(start + table.nodeSize - 2), rows);
179
+ }
180
+ return !!(empty || emptyHead);
181
+ }
182
+
183
+ // Make sure the given line (left, top) to (right, top) doesn't cross
184
+ // any rowspan cells by splitting cells that cross it. Return true if
185
+ // something changed.
186
+ function isolateHorizontal(
187
+ tr: Transaction,
188
+ map: TableMap,
189
+ table: Node,
190
+ start: number,
191
+ left: number,
192
+ right: number,
193
+ top: number,
194
+ mapFrom: number,
195
+ ) {
196
+ if (top == 0 || top == map.height) return false;
197
+ let found = false;
198
+ for (let col = left; col < right; col++) {
199
+ const index = top * map.width + col,
200
+ pos = map.map[index] as number;
201
+ if (map.map[index - map.width] == pos) {
202
+ found = true;
203
+ const cell = table.nodeAt(pos) as Node;
204
+ const { top: cellTop, left: cellLeft } = map.findCell(pos);
205
+ tr.setNodeMarkup(
206
+ tr.mapping.slice(mapFrom).map(pos + start),
207
+ null,
208
+ setAttr(cell.attrs, 'rowspan', top - cellTop),
209
+ );
210
+ tr.insert(
211
+ tr.mapping.slice(mapFrom).map(map.positionAt(top, cellLeft, table)),
212
+ cell.type.createAndFill(setAttr(cell.attrs, 'rowspan', cellTop + cell.attrs['rowspan'] - top)) as Node,
213
+ );
214
+ col += cell.attrs['colspan'] - 1;
215
+ }
216
+ }
217
+ return found;
218
+ }
219
+
220
+ // Make sure the given line (left, top) to (left, bottom) doesn't
221
+ // cross any colspan cells by splitting cells that cross it. Return
222
+ // true if something changed.
223
+ function isolateVertical(
224
+ tr: Transaction,
225
+ map: TableMap,
226
+ table: Node,
227
+ start: number,
228
+ top: number,
229
+ bottom: number,
230
+ left: number,
231
+ mapFrom: number,
232
+ ) {
233
+ if (left == 0 || left == map.width) return false;
234
+ let found = false;
235
+ for (let row = top; row < bottom; row++) {
236
+ const index = row * map.width + left,
237
+ pos = map.map[index] as number;
238
+ if (map.map[index - 1] == pos) {
239
+ found = true;
240
+ const cell = table.nodeAt(pos) as Node,
241
+ cellLeft = map.colCount(pos);
242
+ const updatePos = tr.mapping.slice(mapFrom).map(pos + start);
243
+ tr.setNodeMarkup(
244
+ updatePos,
245
+ null,
246
+ removeColSpan(cell.attrs, left - cellLeft, cell.attrs['colspan'] - (left - cellLeft)),
247
+ );
248
+ tr.insert(
249
+ updatePos + cell.nodeSize,
250
+ cell.type.createAndFill(removeColSpan(cell.attrs, 0, left - cellLeft)) as Node,
251
+ );
252
+ row += cell.attrs['rowspan'] - 1;
253
+ }
254
+ }
255
+ return found;
256
+ }
257
+
258
+ // Insert the given set of cells (as returned by `pastedCells`) into a
259
+ // table, at the position pointed at by rect.
260
+ export function insertCells(
261
+ state: EditorState,
262
+ dispatch: (tr: Transaction) => void,
263
+ tableStart: number,
264
+ rect: Rect,
265
+ cells: R,
266
+ ) {
267
+ let table = (tableStart ? state.doc.nodeAt(tableStart - 1) : state.doc) as Node,
268
+ map = TableMap.get(table);
269
+ const { top, left } = rect;
270
+ const right = left + cells.width,
271
+ bottom = top + cells.height;
272
+ const tr = state.tr;
273
+ let mapFrom = 0;
274
+ function recomp() {
275
+ table = (tableStart ? tr.doc.nodeAt(tableStart - 1) : tr.doc) as Node;
276
+ map = TableMap.get(table);
277
+ mapFrom = tr.mapping.maps.length;
278
+ }
279
+ // Prepare the table to be large enough and not have any cells
280
+ // crossing the boundaries of the rectangle that we want to
281
+ // insert into. If anything about it changes, recompute the table
282
+ // map so that subsequent operations can see the current shape.
283
+ if (growTable(tr, map, table, tableStart, right, bottom, mapFrom)) recomp();
284
+ if (isolateHorizontal(tr, map, table, tableStart, left, right, top, mapFrom)) recomp();
285
+ if (isolateHorizontal(tr, map, table, tableStart, left, right, bottom, mapFrom)) recomp();
286
+ if (isolateVertical(tr, map, table, tableStart, top, bottom, left, mapFrom)) recomp();
287
+ if (isolateVertical(tr, map, table, tableStart, top, bottom, right, mapFrom)) recomp();
288
+
289
+ for (let row = top; row < bottom; row++) {
290
+ const from = map.positionAt(row, left, table),
291
+ to = map.positionAt(row, right, table);
292
+ tr.replace(
293
+ tr.mapping.slice(mapFrom).map(from + tableStart),
294
+ tr.mapping.slice(mapFrom).map(to + tableStart),
295
+ new Slice(cells.rows[row - top] as Fragment, 0, 0),
296
+ );
297
+ }
298
+ recomp();
299
+ tr.setSelection(
300
+ new CellSelection(
301
+ tr.doc.resolve(tableStart + map.positionAt(top, left, table)),
302
+ tr.doc.resolve(tableStart + map.positionAt(bottom - 1, right - 1, table)),
303
+ ),
304
+ );
305
+ dispatch(tr);
306
+ }
@@ -0,0 +1,117 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { Node } from '@milkdown/prose/model';
4
+ import { EditorState, PluginKey, Transaction } from '@milkdown/prose/state';
5
+
6
+ import { tableNodeTypes } from './schema';
7
+ import { Problem, TableMap } from './table-map';
8
+ import { removeColSpan, setAttr } from './util';
9
+
10
+ export const fixTablesKey = new PluginKey('fix-tables');
11
+
12
+ // Helper for iterating through the nodes in a document that changed
13
+ // compared to the given previous document. Useful for avoiding
14
+ // duplicate work on each transaction.
15
+ function changedDescendants(old: Node, cur: Node, offset: number, f: (node: Node, pos: number) => void | boolean) {
16
+ const oldSize = old.childCount,
17
+ curSize = cur.childCount;
18
+ outer: for (let i = 0, j = 0; i < curSize; i++) {
19
+ const child = cur.child(i);
20
+ for (let scan = j, e = Math.min(oldSize, i + 3); scan < e; scan++) {
21
+ if (old.child(scan) == child) {
22
+ j = scan + 1;
23
+ offset += child.nodeSize;
24
+ continue outer;
25
+ }
26
+ }
27
+ f(child, offset);
28
+ if (j < oldSize && old.child(j).sameMarkup(child)) changedDescendants(old.child(j), child, offset + 1, f);
29
+ else child.nodesBetween(0, child.content.size, f, offset + 1);
30
+ offset += child.nodeSize;
31
+ }
32
+ }
33
+
34
+ // :: (EditorState, ?EditorState) → ?Transaction
35
+ // Inspect all tables in the given state's document and return a
36
+ // transaction that fixes them, if necessary. If `oldState` was
37
+ // provided, that is assumed to hold a previous, known-good state,
38
+ // which will be used to avoid re-scanning unchanged parts of the
39
+ // document.
40
+ export function fixTables(state: EditorState, oldState: EditorState) {
41
+ let tr: undefined | Transaction;
42
+ const check = (node: Node, pos: number) => {
43
+ if (node.type.spec['tableRole'] == 'table') tr = fixTable(state, node, pos, tr);
44
+ };
45
+ if (!oldState) state.doc.descendants(check);
46
+ else if (oldState.doc != state.doc) changedDescendants(oldState.doc, state.doc, 0, check);
47
+ return tr;
48
+ }
49
+
50
+ // : (EditorState, Node, number, ?Transaction) → ?Transaction
51
+ // Fix the given table, if necessary. Will append to the transaction
52
+ // it was given, if non-null, or create a new one if necessary.
53
+ export function fixTable(state: EditorState, table: Node, tablePos: number, tr?: Transaction) {
54
+ const map = TableMap.get(table);
55
+ if (!map.problems) return tr;
56
+ if (!tr) tr = state.tr;
57
+
58
+ // Track which rows we must add cells to, so that we can adjust that
59
+ // when fixing collisions.
60
+ const mustAdd = [];
61
+ for (let i = 0; i < map.height; i++) mustAdd.push(0);
62
+ for (let i = 0; i < map.problems.length; i++) {
63
+ const prob = map.problems[i] as Problem;
64
+ if (prob.type == 'collision') {
65
+ const cell = table.nodeAt(prob.pos) as Node;
66
+ for (let j = 0; j < cell.attrs['rowspan']; j++) mustAdd[prob.row + j] += prob.n;
67
+ tr.setNodeMarkup(
68
+ tr.mapping.map(tablePos + 1 + prob.pos),
69
+ null,
70
+ removeColSpan(cell.attrs, cell.attrs['colspan'] - prob.n, prob.n),
71
+ );
72
+ } else if (prob.type == 'missing') {
73
+ mustAdd[prob.row] += prob.n;
74
+ } else if (prob.type == 'overlong_rowspan') {
75
+ const cell = table.nodeAt(prob.pos) as Node;
76
+ tr.setNodeMarkup(
77
+ tr.mapping.map(tablePos + 1 + prob.pos),
78
+ null,
79
+ setAttr(cell.attrs, 'rowspan', cell.attrs['rowspan'] - prob.n),
80
+ );
81
+ } else if (prob.type == 'colwidth mismatch') {
82
+ const cell = table.nodeAt(prob.pos) as Node;
83
+ tr.setNodeMarkup(
84
+ tr.mapping.map(tablePos + 1 + prob.pos),
85
+ null,
86
+ setAttr(cell.attrs, 'colwidth', prob.colwidth),
87
+ );
88
+ }
89
+ }
90
+ let first, last;
91
+ for (let i = 0; i < mustAdd.length; i++)
92
+ if (mustAdd[i]) {
93
+ if (first == null) first = i;
94
+ last = i;
95
+ }
96
+ // Add the necessary cells, using a heuristic for whether to add the
97
+ // cells at the start or end of the rows (if it looks like a 'bite'
98
+ // was taken out of the table, add cells at the start of the row
99
+ // after the bite. Otherwise add them at the end).
100
+ for (let i = 0, pos = tablePos + 1; i < map.height; i++) {
101
+ const row = table.child(i);
102
+ const end = pos + row.nodeSize;
103
+ const add = mustAdd[i] as number;
104
+ if (add > 0) {
105
+ let tableNodeType = 'cell';
106
+ if (row.firstChild) {
107
+ tableNodeType = row.firstChild.type.spec['tableRole'];
108
+ }
109
+ const nodes = [];
110
+ for (let j = 0; j < add; j++) nodes.push(tableNodeTypes(state.schema)[tableNodeType].createAndFill());
111
+ const side = (i == 0 || first == i - 1) && last == i ? pos + 1 : end - 1;
112
+ tr.insert(tr.mapping.map(side), nodes);
113
+ }
114
+ pos = end;
115
+ }
116
+ return tr.setMeta(fixTablesKey, { fixTables: true });
117
+ }
@@ -3,13 +3,15 @@ import { createCmd, createCmdKey, MarkdownNode, schemaCtx } from '@milkdown/core
3
3
  import { InputRule } from '@milkdown/prose/inputrules';
4
4
  import { NodeType } from '@milkdown/prose/model';
5
5
  import { Plugin, PluginKey, Selection, TextSelection } from '@milkdown/prose/state';
6
- import { columnResizing, goToNextCell, tableEditing } from '@milkdown/prose/tables';
7
6
  import { createPlugin, createShortcut } from '@milkdown/utils';
8
7
 
9
8
  import { exitTable } from '../command';
10
9
  import { operatorPlugin } from '../operator-plugin';
11
10
  import { createTable } from '../utils';
11
+ import { columnResizing } from './column-resizing';
12
+ import { goToNextCell } from './commands';
12
13
  import { schema } from './schema';
14
+ import { tableEditing } from './table-editing';
13
15
 
14
16
  export const SupportedKeys = {
15
17
  NextCell: 'NextCell',
@@ -195,3 +197,7 @@ export const table = createPlugin<Keys, Record<string, unknown>, keyof typeof sc
195
197
  },
196
198
  };
197
199
  });
200
+
201
+ export * from './cell-selection';
202
+ export * from './commands';
203
+ export * from './util';
@@ -1,5 +1,103 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import { tableNodes as tableNodesSpecCreator } from '@milkdown/prose/tables';
2
+ import { Attrs, Node, NodeSpec, Schema } from '@milkdown/prose/model';
3
+
4
+ import { TableNodesOptions } from './types';
5
+
6
+ function getCellAttrs(dom: HTMLElement, extraAttrs: Attrs) {
7
+ const widthAttr = dom.getAttribute('data-colwidth');
8
+ const widths = widthAttr && /^\d+(,\d+)*$/.test(widthAttr) ? widthAttr.split(',').map((s) => Number(s)) : null;
9
+ const colspan = Number(dom.getAttribute('colspan') || 1);
10
+ const result = {
11
+ colspan,
12
+ rowspan: Number(dom.getAttribute('rowspan') || 1),
13
+ colwidth: widths && widths.length == colspan ? widths : null,
14
+ };
15
+ for (const prop in extraAttrs) {
16
+ const getter = extraAttrs[prop].getFromDOM;
17
+ const value = getter && getter(dom);
18
+ if (value != null) result[prop as keyof typeof result] = value;
19
+ }
20
+ return result;
21
+ }
22
+
23
+ function setCellAttrs(node: Node, extraAttrs: Attrs) {
24
+ const attrs: Record<string, unknown> = {};
25
+ if (node.attrs['colspan'] != 1) attrs['colspan'] = node.attrs['colspan'];
26
+ if (node.attrs['rowspan'] != 1) attrs['rowspan'] = node.attrs['rowspan'];
27
+ if (node.attrs['colwidth']) attrs['data-colwidth'] = node.attrs['colwidth'].join(',');
28
+ for (const prop in extraAttrs) {
29
+ const setter = extraAttrs[prop].setDOMAttr;
30
+ if (setter) setter(node.attrs[prop], attrs);
31
+ }
32
+ return attrs;
33
+ }
34
+
35
+ function tableNodesSpecCreator(options: TableNodesOptions) {
36
+ const extraAttrs: Attrs = options.cellAttributes || {};
37
+ const cellAttrs: Record<string, unknown> = {
38
+ colspan: { default: 1 },
39
+ rowspan: { default: 1 },
40
+ colwidth: { default: null },
41
+ };
42
+ for (const prop in extraAttrs) cellAttrs[prop] = { default: extraAttrs[prop].default };
43
+ const finalAttrs = cellAttrs as Attrs;
44
+
45
+ const schema: Record<'table' | 'table_row' | 'table_cell' | 'table_header', NodeSpec> = {
46
+ table: {
47
+ content: 'table_row+',
48
+ tableRole: 'table',
49
+ isolating: true,
50
+ group: options.tableGroup,
51
+ parseDOM: [{ tag: 'table' }],
52
+ toDOM() {
53
+ return ['table', ['tbody', 0]];
54
+ },
55
+ },
56
+ table_row: {
57
+ content: '(table_cell | table_header)*',
58
+ tableRole: 'row',
59
+ parseDOM: [{ tag: 'tr' }],
60
+ toDOM() {
61
+ return ['tr', 0];
62
+ },
63
+ },
64
+ table_cell: {
65
+ content: options.cellContent,
66
+ attrs: finalAttrs,
67
+ tableRole: 'cell',
68
+ isolating: true,
69
+ parseDOM: [{ tag: 'td', getAttrs: (dom) => getCellAttrs(dom as HTMLElement, extraAttrs) }],
70
+ toDOM(node) {
71
+ return ['td', setCellAttrs(node, extraAttrs), 0];
72
+ },
73
+ },
74
+ table_header: {
75
+ content: options.cellContent,
76
+ attrs: finalAttrs,
77
+ tableRole: 'header_cell',
78
+ isolating: true,
79
+ parseDOM: [{ tag: 'th', getAttrs: (dom) => getCellAttrs(dom as HTMLElement, extraAttrs) }],
80
+ toDOM(node) {
81
+ return ['th', setCellAttrs(node, extraAttrs), 0];
82
+ },
83
+ },
84
+ };
85
+
86
+ return schema;
87
+ }
88
+
89
+ export function tableNodeTypes(schema: Schema) {
90
+ let result = schema.cached['tableNodeTypes'];
91
+ if (!result) {
92
+ result = schema.cached['tableNodeTypes'] = {};
93
+ for (const name in schema.nodes) {
94
+ const type = schema.nodes[name],
95
+ role = type?.spec['tableRole'];
96
+ if (role) result[role] = type;
97
+ }
98
+ }
99
+ return result;
100
+ }
3
101
 
4
102
  export const schema = tableNodesSpecCreator({
5
103
  tableGroup: 'block',
@@ -9,7 +107,7 @@ export const schema = tableNodesSpecCreator({
9
107
  default: 'left',
10
108
  getFromDOM: (dom) => (dom as HTMLElement).style.textAlign || 'left',
11
109
  setDOMAttr: (value, attrs) => {
12
- attrs.style = `text-align: ${value || 'left'}`;
110
+ attrs['style'] = `text-align: ${value || 'left'}`;
13
111
  },
14
112
  },
15
113
  },