@milkdown/preset-gfm 6.1.3 → 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,352 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { Fragment, Node, ResolvedPos, Slice } from '@milkdown/prose/model';
4
+ import {
5
+ EditorState,
6
+ NodeSelection,
7
+ Selection,
8
+ SelectionRange,
9
+ TextSelection,
10
+ Transaction,
11
+ } from '@milkdown/prose/state';
12
+ import { Mappable } from '@milkdown/prose/transform';
13
+ import { Decoration, DecorationSet } from '@milkdown/prose/view';
14
+
15
+ import { TableMap } from './table-map';
16
+ import { inSameTable, pointsAtCell, removeColSpan, setAttr } from './util';
17
+
18
+ // ::- A [`Selection`](http://prosemirror.net/docs/ref/#state.Selection)
19
+ // subclass that represents a cell selection spanning part of a table.
20
+ // With the plugin enabled, these will be created when the user
21
+ // selects across cells, and will be drawn by giving selected cells a
22
+ // `selectedCell` CSS class.
23
+ export class CellSelection extends Selection {
24
+ // :: (ResolvedPos, ?ResolvedPos)
25
+ // A table selection is identified by its anchor and head cells. The
26
+ // positions given to this constructor should point _before_ two
27
+ // cells in the same table. They may be the same, to select a single
28
+ // cell.
29
+ constructor(public $anchorCell: ResolvedPos, public $headCell = $anchorCell) {
30
+ const table = $anchorCell.node(-1),
31
+ map = TableMap.get(table),
32
+ start = $anchorCell.start(-1);
33
+ const rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
34
+ const doc = $anchorCell.node(0);
35
+ const cells = map.cellsInRect(rect).filter((p) => p != $headCell.pos - start);
36
+ // Make the head cell the first range, so that it counts as the
37
+ // primary part of the selection
38
+ cells.unshift($headCell.pos - start);
39
+ const ranges = cells.map((pos) => {
40
+ const cell = table.nodeAt(pos) as Node,
41
+ from = pos + start + 1;
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ return new (SelectionRange as any)(doc.resolve(from), doc.resolve(from + cell.content.size));
44
+ });
45
+ super(ranges[0].$from, ranges[0].$to, ranges);
46
+ // :: ResolvedPos
47
+ // A resolved position pointing _in front of_ the anchor cell (the one
48
+ // that doesn't move when extending the selection).
49
+ this.$anchorCell = $anchorCell;
50
+ // :: ResolvedPos
51
+ // A resolved position pointing in front of the head cell (the one
52
+ // moves when extending the selection).
53
+ this.$headCell = $headCell;
54
+ }
55
+
56
+ map(doc: Node, mapping: Mappable): Selection {
57
+ const $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos));
58
+ const $headCell = doc.resolve(mapping.map(this.$headCell.pos));
59
+ if (pointsAtCell($anchorCell) && pointsAtCell($headCell) && inSameTable($anchorCell, $headCell)) {
60
+ const tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1);
61
+ if (tableChanged && this.isRowSelection()) return CellSelection.rowSelection($anchorCell, $headCell);
62
+ else if (tableChanged && this.isColSelection()) return CellSelection.colSelection($anchorCell, $headCell);
63
+ else return new CellSelection($anchorCell, $headCell);
64
+ }
65
+ return TextSelection.between($anchorCell, $headCell);
66
+ }
67
+
68
+ // :: () → Slice
69
+ // Returns a rectangular slice of table rows containing the selected
70
+ // cells.
71
+ override content(): Slice {
72
+ const table = this.$anchorCell.node(-1),
73
+ map = TableMap.get(table),
74
+ start = this.$anchorCell.start(-1);
75
+ const rect = map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start);
76
+ const seen: Record<number, boolean> = {},
77
+ rows = [];
78
+ for (let row = rect.top; row < rect.bottom; row++) {
79
+ const rowContent = [];
80
+ for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
81
+ const pos = map.map[index] as number;
82
+ if (!seen[pos]) {
83
+ seen[pos] = true;
84
+ const cellRect = map.findCell(pos);
85
+ let cell = table.nodeAt(pos) as Node;
86
+ const extraLeft = rect.left - cellRect.left,
87
+ extraRight = cellRect.right - rect.right;
88
+ if (extraLeft > 0 || extraRight > 0) {
89
+ let attrs = cell.attrs;
90
+ if (extraLeft > 0) attrs = removeColSpan(attrs, 0, extraLeft);
91
+ if (extraRight > 0) attrs = removeColSpan(attrs, attrs['colspan'] - extraRight, extraRight);
92
+ if (cellRect.left < rect.left) cell = cell.type.createAndFill(attrs) as Node;
93
+ else cell = cell.type.create(attrs, cell.content);
94
+ }
95
+ if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
96
+ const attrs = setAttr(
97
+ cell.attrs,
98
+ 'rowspan',
99
+ Math.min(cellRect.bottom, rect.bottom) - Math.max(cellRect.top, rect.top),
100
+ );
101
+ if (cellRect.top < rect.top) cell = cell.type.createAndFill(attrs) as Node;
102
+ else cell = cell.type.create(attrs, cell.content);
103
+ }
104
+ rowContent.push(cell);
105
+ }
106
+ }
107
+ rows.push(table.child(row).copy(Fragment.from(rowContent)));
108
+ }
109
+
110
+ const fragment = this.isColSelection() && this.isRowSelection() ? table : rows;
111
+ return new Slice(Fragment.from(fragment), 1, 1);
112
+ }
113
+
114
+ override replace(tr: Transaction, content = Slice.empty) {
115
+ const mapFrom = tr.steps.length,
116
+ ranges = this.ranges;
117
+ for (let i = 0; i < ranges.length; i++) {
118
+ const { $from, $to } = ranges[i] as SelectionRange,
119
+ mapping = tr.mapping.slice(mapFrom);
120
+ tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
121
+ }
122
+ const sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1);
123
+ if (sel) tr.setSelection(sel);
124
+ }
125
+
126
+ override replaceWith(tr: Transaction, node: Node) {
127
+ this.replace(tr, new Slice(Fragment.from(node), 0, 0));
128
+ }
129
+
130
+ forEachCell(f: (node: Node, index: number) => void) {
131
+ const table = this.$anchorCell.node(-1),
132
+ map = TableMap.get(table),
133
+ start = this.$anchorCell.start(-1);
134
+ const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start));
135
+ for (let i = 0; i < cells.length; i++)
136
+ f(table.nodeAt(cells[i] as number) as Node, start + (cells[i] as number));
137
+ }
138
+
139
+ // :: () → bool
140
+ // True if this selection goes all the way from the top to the
141
+ // bottom of the table.
142
+ isColSelection() {
143
+ const anchorTop = this.$anchorCell.index(-1),
144
+ headTop = this.$headCell.index(-1);
145
+ if (Math.min(anchorTop, headTop) > 0) return false;
146
+ const anchorBot = anchorTop + (this.$anchorCell.nodeAfter as Node).attrs['rowspan'],
147
+ headBot = headTop + (this.$headCell.nodeAfter as Node).attrs['rowspan'];
148
+ return Math.max(anchorBot, headBot) == this.$headCell.node(-1).childCount;
149
+ }
150
+
151
+ // :: (ResolvedPos, ?ResolvedPos) → CellSelection
152
+ // Returns the smallest column selection that covers the given anchor
153
+ // and head cell.
154
+ static colSelection($anchorCell: ResolvedPos, $headCell = $anchorCell) {
155
+ const map = TableMap.get($anchorCell.node(-1)),
156
+ start = $anchorCell.start(-1);
157
+ const anchorRect = map.findCell($anchorCell.pos - start),
158
+ headRect = map.findCell($headCell.pos - start);
159
+ const doc = $anchorCell.node(0);
160
+ if (anchorRect.top <= headRect.top) {
161
+ if (anchorRect.top > 0) {
162
+ const left = map.map[anchorRect.left] as number;
163
+ $anchorCell = doc.resolve(start + left);
164
+ }
165
+ if (headRect.bottom < map.height) {
166
+ const pos = map.map[map.width * (map.height - 1) + headRect.right - 1] as number;
167
+ $headCell = doc.resolve(start + pos);
168
+ }
169
+ } else {
170
+ if (headRect.top > 0) {
171
+ const left = map.map[anchorRect.left] as number;
172
+ $headCell = doc.resolve(start + left);
173
+ }
174
+ if (anchorRect.bottom < map.height) {
175
+ const pos = map.map[map.width * (map.height - 1) + anchorRect.right - 1] as number;
176
+ $anchorCell = doc.resolve(start + pos);
177
+ }
178
+ }
179
+ return new CellSelection($anchorCell, $headCell);
180
+ }
181
+
182
+ // :: () → bool
183
+ // True if this selection goes all the way from the left to the
184
+ // right of the table.
185
+ isRowSelection() {
186
+ const map = TableMap.get(this.$anchorCell.node(-1)),
187
+ start = this.$anchorCell.start(-1);
188
+ const anchorLeft = map.colCount(this.$anchorCell.pos - start),
189
+ headLeft = map.colCount(this.$headCell.pos - start);
190
+ if (Math.min(anchorLeft, headLeft) > 0) return false;
191
+ const anchorRight = anchorLeft + (this.$anchorCell.nodeAfter as Node).attrs['colspan'],
192
+ headRight = headLeft + (this.$headCell.nodeAfter as Node).attrs['colspan'];
193
+ return Math.max(anchorRight, headRight) == map.width;
194
+ }
195
+
196
+ eq(other: Selection): boolean {
197
+ return (
198
+ other instanceof CellSelection &&
199
+ other.$anchorCell.pos == this.$anchorCell.pos &&
200
+ other.$headCell.pos == this.$headCell.pos
201
+ );
202
+ }
203
+
204
+ // :: (ResolvedPos, ?ResolvedPos) → CellSelection
205
+ // Returns the smallest row selection that covers the given anchor
206
+ // and head cell.
207
+ static rowSelection($anchorCell: ResolvedPos, $headCell = $anchorCell) {
208
+ const map = TableMap.get($anchorCell.node(-1)),
209
+ start = $anchorCell.start(-1);
210
+ const anchorRect = map.findCell($anchorCell.pos - start),
211
+ headRect = map.findCell($headCell.pos - start);
212
+ const doc = $anchorCell.node(0);
213
+ if (anchorRect.left <= headRect.left) {
214
+ if (anchorRect.left > 0) {
215
+ const pos = map.map[anchorRect.top * map.width] as number;
216
+ $anchorCell = doc.resolve(start + pos);
217
+ }
218
+ if (headRect.right < map.width) {
219
+ const pos = map.map[map.width * (headRect.top + 1) - 1] as number;
220
+ $headCell = doc.resolve(start + pos);
221
+ }
222
+ } else {
223
+ if (headRect.left > 0) {
224
+ const pos = map.map[headRect.top * map.width] as number;
225
+ $headCell = doc.resolve(start + pos);
226
+ }
227
+ if (anchorRect.right < map.width) {
228
+ const pos = map.map[map.width * (anchorRect.top + 1) - 1] as number;
229
+
230
+ $anchorCell = doc.resolve(start + pos);
231
+ }
232
+ }
233
+ return new CellSelection($anchorCell, $headCell);
234
+ }
235
+
236
+ toJSON() {
237
+ return {
238
+ type: 'cell',
239
+ anchor: this.$anchorCell.pos,
240
+ head: this.$headCell.pos,
241
+ };
242
+ }
243
+
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ static override fromJSON(doc: Node, json: any) {
246
+ return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head));
247
+ }
248
+
249
+ // :: (Node, number, ?number) → CellSelection
250
+ static create(doc: Node, anchorCell: number, headCell = anchorCell) {
251
+ return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell));
252
+ }
253
+
254
+ override getBookmark() {
255
+ return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos);
256
+ }
257
+ }
258
+
259
+ CellSelection.prototype.visible = false;
260
+
261
+ Selection.jsonID('cell', CellSelection);
262
+
263
+ class CellBookmark {
264
+ constructor(public anchor: number, public head: number) {
265
+ this.anchor = anchor;
266
+ this.head = head;
267
+ }
268
+ map(mapping: Mappable) {
269
+ return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head));
270
+ }
271
+ resolve(doc: Node): Selection {
272
+ const $anchorCell = doc.resolve(this.anchor),
273
+ $headCell = doc.resolve(this.head);
274
+ if (
275
+ $anchorCell.parent.type.spec['tableRole'] == 'row' &&
276
+ $headCell.parent.type.spec['tableRole'] == 'row' &&
277
+ $anchorCell.index() < $anchorCell.parent.childCount &&
278
+ $headCell.index() < $headCell.parent.childCount &&
279
+ inSameTable($anchorCell, $headCell)
280
+ )
281
+ return new CellSelection($anchorCell, $headCell);
282
+ else return Selection.near($headCell, 1);
283
+ }
284
+ }
285
+
286
+ export function drawCellSelection(state: EditorState) {
287
+ if (!(state.selection instanceof CellSelection)) return null;
288
+ const cells: Decoration[] = [];
289
+ state.selection.forEachCell((node, pos) => {
290
+ cells.push(Decoration.node(pos, pos + node.nodeSize, { class: 'selectedCell' }));
291
+ });
292
+ return DecorationSet.create(state.doc, cells);
293
+ }
294
+
295
+ function isCellBoundarySelection({ $from, $to }: Selection) {
296
+ if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false; // Cheap elimination
297
+ let afterFrom = $from.pos,
298
+ beforeTo = $to.pos,
299
+ depth = $from.depth;
300
+ for (; depth >= 0; depth--, afterFrom++) if ($from.after(depth + 1) < $from.end(depth)) break;
301
+ for (let d = $to.depth; d >= 0; d--, beforeTo--) if ($to.before(d + 1) > $to.start(d)) break;
302
+ return afterFrom == beforeTo && /row|table/.test($from.node(depth).type.spec['tableRole']);
303
+ }
304
+
305
+ function isTextSelectionAcrossCells({ $from, $to }: Selection) {
306
+ let fromCellBoundaryNode;
307
+ let toCellBoundaryNode;
308
+
309
+ for (let i = $from.depth; i > 0; i--) {
310
+ const node = $from.node(i);
311
+ if (node.type.spec['tableRole'] === 'cell' || node.type.spec['tableRole'] === 'header_cell') {
312
+ fromCellBoundaryNode = node;
313
+ break;
314
+ }
315
+ }
316
+
317
+ for (let i = $to.depth; i > 0; i--) {
318
+ const node = $to.node(i);
319
+ if (node.type.spec['tableRole'] === 'cell' || node.type.spec['tableRole'] === 'header_cell') {
320
+ toCellBoundaryNode = node;
321
+ break;
322
+ }
323
+ }
324
+
325
+ return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0;
326
+ }
327
+
328
+ export function normalizeSelection(state: EditorState, tr: Transaction | undefined, allowTableNodeSelection: boolean) {
329
+ const sel = (tr || state).selection,
330
+ doc = (tr || state).doc;
331
+ let normalize, role;
332
+ if (sel instanceof NodeSelection && (role = sel.node.type.spec['tableRole'])) {
333
+ if (role == 'cell' || role == 'header_cell') {
334
+ normalize = CellSelection.create(doc, sel.from);
335
+ } else if (role == 'row') {
336
+ const $cell = doc.resolve(sel.from + 1);
337
+ normalize = CellSelection.rowSelection($cell, $cell);
338
+ } else if (!allowTableNodeSelection) {
339
+ const map = TableMap.get(sel.node),
340
+ start = sel.from + 1;
341
+ const pos = map.map[map.width * map.height - 1] as number;
342
+ const lastCell = start + pos;
343
+ normalize = CellSelection.create(doc, start + 1, lastCell);
344
+ }
345
+ } else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
346
+ normalize = TextSelection.create(doc, sel.from);
347
+ } else if (sel instanceof TextSelection && isTextSelectionAcrossCells(sel)) {
348
+ normalize = TextSelection.create(doc, sel.$from.start(), sel.$from.end());
349
+ }
350
+ if (normalize) (tr || (tr = state.tr)).setSelection(normalize);
351
+ return tr;
352
+ }
@@ -0,0 +1,260 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { Attrs, Node } from '@milkdown/prose/model';
3
+ import { EditorState, Plugin, PluginKey, Transaction } from '@milkdown/prose/state';
4
+ import { Decoration, DecorationSet, EditorView } from '@milkdown/prose/view';
5
+
6
+ import { tableNodeTypes } from './schema';
7
+ import { TableMap } from './table-map';
8
+ import { TableView, updateColumns } from './table-view';
9
+ import { cellAround, pointsAtCell, setAttr } from './util';
10
+
11
+ export const key = new PluginKey('tableColumnResizing');
12
+
13
+ export function columnResizing({
14
+ handleWidth = 5,
15
+ cellMinWidth = 25,
16
+ View = TableView,
17
+ lastColumnResizable = true,
18
+ } = {}) {
19
+ const plugin = new Plugin({
20
+ key,
21
+ state: {
22
+ init(this: Plugin, _, state) {
23
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
24
+ this.spec.props!.nodeViews![tableNodeTypes(state.schema).table.name] = (node) =>
25
+ new View(node, cellMinWidth);
26
+ return new ResizeState(-1, false);
27
+ },
28
+ apply(tr, prev) {
29
+ return prev.apply(tr);
30
+ },
31
+ },
32
+ props: {
33
+ attributes(state) {
34
+ const pluginState = key.getState(state);
35
+ return pluginState.activeHandle > -1 ? { class: 'resize-cursor' } : (undefined as unknown as Attrs);
36
+ },
37
+
38
+ handleDOMEvents: {
39
+ mousemove(view, event) {
40
+ handleMouseMove(view, event as MouseEvent, handleWidth, lastColumnResizable);
41
+ },
42
+ mouseleave(view) {
43
+ handleMouseLeave(view);
44
+ },
45
+ mousedown(view, event) {
46
+ handleMouseDown(view, event as MouseEvent, cellMinWidth);
47
+ },
48
+ },
49
+
50
+ decorations(state) {
51
+ const pluginState = key.getState(state);
52
+ if (pluginState.activeHandle > -1) return handleDecorations(state, pluginState.activeHandle);
53
+
54
+ return null;
55
+ },
56
+
57
+ nodeViews: {},
58
+ },
59
+ });
60
+ return plugin;
61
+ }
62
+
63
+ class ResizeState {
64
+ constructor(public activeHandle: number, public dragging: null | boolean) {
65
+ this.activeHandle = activeHandle;
66
+ this.dragging = dragging;
67
+ }
68
+
69
+ apply(this: ResizeState, tr: Transaction) {
70
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
71
+ let state = this;
72
+ const action = tr.getMeta(key);
73
+ if (action && action.setHandle != null) return new ResizeState(action.setHandle, null);
74
+ if (action && action.setDragging !== undefined) return new ResizeState(state.activeHandle, action.setDragging);
75
+ if (state.activeHandle > -1 && tr.docChanged) {
76
+ let handle = tr.mapping.map(state.activeHandle, -1);
77
+ if (!pointsAtCell(tr.doc.resolve(handle))) handle = 0;
78
+ state = new ResizeState(handle, state.dragging);
79
+ }
80
+ return state;
81
+ }
82
+ }
83
+
84
+ function handleMouseMove(view: EditorView, event: MouseEvent, handleWidth: number, lastColumnResizable: boolean) {
85
+ const pluginState = key.getState(view.state);
86
+
87
+ if (!pluginState.dragging) {
88
+ const target = domCellAround(event.target as Element);
89
+ let cell = -1;
90
+ if (target) {
91
+ const { left, right } = target.getBoundingClientRect();
92
+ if (event.clientX - left <= handleWidth) cell = edgeCell(view, event, 'left');
93
+ else if (right - event.clientX <= handleWidth) cell = edgeCell(view, event, 'right');
94
+ }
95
+
96
+ if (cell != pluginState.activeHandle) {
97
+ if (!lastColumnResizable && cell !== -1) {
98
+ const $cell = view.state.doc.resolve(cell);
99
+ const table = $cell.node(-1),
100
+ map = TableMap.get(table),
101
+ start = $cell.start(-1);
102
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
103
+ const col = map.colCount($cell.pos - start) + $cell.nodeAfter!.attrs['colspan'] - 1;
104
+
105
+ if (col == map.width - 1) {
106
+ return;
107
+ }
108
+ }
109
+
110
+ updateHandle(view, cell);
111
+ }
112
+ }
113
+ }
114
+
115
+ function handleMouseLeave(view: EditorView) {
116
+ const pluginState = key.getState(view.state);
117
+ if (pluginState.activeHandle > -1 && !pluginState.dragging) updateHandle(view, -1);
118
+ }
119
+
120
+ function handleMouseDown(view: EditorView, event: MouseEvent, cellMinWidth: number) {
121
+ const pluginState = key.getState(view.state);
122
+ if (pluginState.activeHandle == -1 || pluginState.dragging) return false;
123
+
124
+ const cell = view.state.doc.nodeAt(pluginState.activeHandle) as Node;
125
+ const width = currentColWidth(view, pluginState.activeHandle, cell.attrs);
126
+ view.dispatch(
127
+ view.state.tr.setMeta(key, {
128
+ setDragging: { startX: event.clientX, startWidth: width },
129
+ }),
130
+ );
131
+
132
+ function finish(event: MouseEvent) {
133
+ window.removeEventListener('mouseup', finish);
134
+ window.removeEventListener('mousemove', move);
135
+ const pluginState = key.getState(view.state);
136
+ if (pluginState.dragging) {
137
+ updateColumnWidth(view, pluginState.activeHandle, draggedWidth(pluginState.dragging, event, cellMinWidth));
138
+ view.dispatch(view.state.tr.setMeta(key, { setDragging: null }));
139
+ }
140
+ }
141
+ function move(event: MouseEvent) {
142
+ if (!event.which) return finish(event);
143
+ const pluginState = key.getState(view.state);
144
+ const dragged = draggedWidth(pluginState.dragging, event, cellMinWidth);
145
+ displayColumnWidth(view, pluginState.activeHandle, dragged, cellMinWidth);
146
+ }
147
+
148
+ window.addEventListener('mouseup', finish);
149
+ window.addEventListener('mousemove', move);
150
+ event.preventDefault();
151
+ return true;
152
+ }
153
+
154
+ function currentColWidth(view: EditorView, cellPos: number, { colspan, colwidth }: Attrs) {
155
+ const width = colwidth && colwidth[colwidth.length - 1];
156
+ if (width) return width;
157
+ const dom = view.domAtPos(cellPos);
158
+ const node = dom.node.childNodes[dom.offset] as HTMLElement;
159
+ let domWidth = node.offsetWidth,
160
+ parts = colspan;
161
+ if (colwidth)
162
+ for (let i = 0; i < colspan; i++)
163
+ if (colwidth[i]) {
164
+ domWidth -= colwidth[i];
165
+ parts--;
166
+ }
167
+ return domWidth / parts;
168
+ }
169
+
170
+ function domCellAround(target: Element | null) {
171
+ while (target && target.nodeName != 'TD' && target.nodeName != 'TH')
172
+ target = target.classList.contains('ProseMirror') ? null : (target.parentNode as Element);
173
+ return target;
174
+ }
175
+
176
+ function edgeCell(view: EditorView, event: MouseEvent, side: 'left' | 'right') {
177
+ const found = view.posAtCoords({ left: event.clientX, top: event.clientY });
178
+ if (!found) return -1;
179
+ const { pos } = found;
180
+ const $cell = cellAround(view.state.doc.resolve(pos));
181
+ if (!$cell) return -1;
182
+ if (side == 'right') return $cell.pos;
183
+ const map = TableMap.get($cell.node(-1)),
184
+ start = $cell.start(-1);
185
+ const index = map.map.indexOf($cell.pos - start);
186
+ return index % map.width == 0 ? -1 : start + (map.map[index - 1] as number);
187
+ }
188
+
189
+ function draggedWidth(dragging: { startX: number; startWidth: number }, event: MouseEvent, cellMinWidth: number) {
190
+ const offset = event.clientX - dragging.startX;
191
+ return Math.max(cellMinWidth, dragging.startWidth + offset);
192
+ }
193
+
194
+ function updateHandle(view: EditorView, value: number) {
195
+ view.dispatch(view.state.tr.setMeta(key, { setHandle: value }));
196
+ }
197
+
198
+ function updateColumnWidth(view: EditorView, cell: number, width: number) {
199
+ const $cell = view.state.doc.resolve(cell);
200
+ const table = $cell.node(-1),
201
+ map = TableMap.get(table),
202
+ start = $cell.start(-1);
203
+ const col = map.colCount($cell.pos - start) + ($cell.nodeAfter as Node).attrs['colspan'] - 1;
204
+ const tr = view.state.tr;
205
+ for (let row = 0; row < map.height; row++) {
206
+ const mapIndex = row * map.width + col;
207
+ // Rowspanning cell that has already been handled
208
+ if (row && map.map[mapIndex] == map.map[mapIndex - map.width]) continue;
209
+ const pos = map.map[mapIndex] as number,
210
+ { attrs } = table.nodeAt(pos) as Node;
211
+ const index = attrs['colspan'] == 1 ? 0 : col - map.colCount(pos);
212
+ if (attrs['colwidth'] && attrs['colwidth'][index] == width) continue;
213
+ const colwidth = attrs['colwidth'] ? attrs['colwidth'].slice() : zeroes(attrs['colspan']);
214
+ colwidth[index] = width;
215
+ tr.setNodeMarkup(start + pos, null, setAttr(attrs, 'colwidth', colwidth));
216
+ }
217
+ if (tr.docChanged) view.dispatch(tr);
218
+ }
219
+
220
+ function displayColumnWidth(view: EditorView, cell: number, width: number, cellMinWidth: number) {
221
+ const $cell = view.state.doc.resolve(cell);
222
+ const table = $cell.node(-1),
223
+ start = $cell.start(-1);
224
+ const col = TableMap.get(table).colCount($cell.pos - start) + ($cell.nodeAfter as Node).attrs['colspan'] - 1;
225
+ let dom = view.domAtPos($cell.start(-1)).node as HTMLElement;
226
+ while (dom.nodeName != 'TABLE') dom = dom.parentNode as HTMLElement;
227
+ updateColumns(table, dom.firstChild as HTMLTableColElement, dom as HTMLTableElement, cellMinWidth, col, width);
228
+ }
229
+
230
+ function zeroes(n: number) {
231
+ const result = [];
232
+ for (let i = 0; i < n; i++) result.push(0);
233
+ return result;
234
+ }
235
+
236
+ function handleDecorations(state: EditorState, cell: number) {
237
+ const decorations = [];
238
+ const $cell = state.doc.resolve(cell);
239
+ const table = $cell.node(-1),
240
+ map = TableMap.get(table),
241
+ start = $cell.start(-1);
242
+ const col = map.colCount($cell.pos - start) + ($cell.nodeAfter as Node).attrs['colspan'];
243
+ for (let row = 0; row < map.height; row++) {
244
+ const index = col + row * map.width - 1;
245
+ // For positions that are have either a different cell or the end
246
+ // of the table to their right, and either the top of the table or
247
+ // a different cell above them, add a decoration
248
+ if (
249
+ (col == map.width || map.map[index] != map.map[index + 1]) &&
250
+ (row == 0 || map.map[index - 1] != map.map[index - 1 - map.width])
251
+ ) {
252
+ const cellPos = map.map[index] as number;
253
+ const pos = start + cellPos + (table.nodeAt(cellPos) as Node).nodeSize - 1;
254
+ const dom = document.createElement('div');
255
+ dom.className = 'column-resize-handle';
256
+ decorations.push(Decoration.widget(pos, dom));
257
+ }
258
+ }
259
+ return DecorationSet.create(state.doc, decorations);
260
+ }