@milkdown/preset-gfm 6.5.4 → 7.0.0-next.1

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 (156) hide show
  1. package/lib/composed/commands.d.ts +8 -0
  2. package/lib/composed/commands.d.ts.map +1 -0
  3. package/lib/composed/index.d.ts +6 -0
  4. package/lib/composed/index.d.ts.map +1 -0
  5. package/lib/composed/inputrules.d.ts +3 -0
  6. package/lib/composed/inputrules.d.ts.map +1 -0
  7. package/lib/composed/keymap.d.ts +3 -0
  8. package/lib/composed/keymap.d.ts.map +1 -0
  9. package/lib/composed/plugins.d.ts +3 -0
  10. package/lib/composed/plugins.d.ts.map +1 -0
  11. package/lib/composed/schema.d.ts +3 -0
  12. package/lib/composed/schema.d.ts.map +1 -0
  13. package/lib/index.d.ts +4 -34
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.es.js +634 -2328
  16. package/lib/index.es.js.map +1 -1
  17. package/lib/mark/index.d.ts +2 -0
  18. package/lib/mark/index.d.ts.map +1 -0
  19. package/lib/mark/strike-through.d.ts +5 -0
  20. package/lib/mark/strike-through.d.ts.map +1 -0
  21. package/lib/node/footnote/definition.d.ts +2 -0
  22. package/lib/node/footnote/definition.d.ts.map +1 -0
  23. package/lib/{footnote → node/footnote}/index.d.ts +0 -0
  24. package/lib/node/footnote/index.d.ts.map +1 -0
  25. package/lib/node/footnote/reference.d.ts +2 -0
  26. package/lib/node/footnote/reference.d.ts.map +1 -0
  27. package/lib/node/index.d.ts +4 -0
  28. package/lib/node/index.d.ts.map +1 -0
  29. package/lib/node/table/index.d.ts +32 -0
  30. package/lib/node/table/index.d.ts.map +1 -0
  31. package/lib/node/table/utils.d.ts +26 -0
  32. package/lib/node/table/utils.d.ts.map +1 -0
  33. package/lib/node/task-list-item.d.ts +2 -0
  34. package/lib/node/task-list-item.d.ts.map +1 -0
  35. package/lib/plugin/auto-insert-zero-space-plugin.d.ts +2 -0
  36. package/lib/plugin/auto-insert-zero-space-plugin.d.ts.map +1 -0
  37. package/lib/plugin/column-resizing-plugin.d.ts +2 -0
  38. package/lib/plugin/column-resizing-plugin.d.ts.map +1 -0
  39. package/lib/plugin/index.d.ts +5 -0
  40. package/lib/plugin/index.d.ts.map +1 -0
  41. package/lib/plugin/remark-gfm-plugin.d.ts +2 -0
  42. package/lib/plugin/remark-gfm-plugin.d.ts.map +1 -0
  43. package/lib/plugin/table-editing-plugin.d.ts +2 -0
  44. package/lib/plugin/table-editing-plugin.d.ts.map +1 -0
  45. package/package.json +13 -10
  46. package/src/composed/commands.ts +24 -0
  47. package/src/composed/index.ts +6 -0
  48. package/src/composed/inputrules.ts +8 -0
  49. package/src/composed/keymap.ts +10 -0
  50. package/src/composed/plugins.ts +11 -0
  51. package/src/composed/schema.ts +20 -0
  52. package/src/index.ts +6 -89
  53. package/src/mark/index.ts +2 -0
  54. package/src/mark/strike-through.ts +46 -0
  55. package/src/node/footnote/definition.ts +70 -0
  56. package/src/{footnote → node/footnote}/index.ts +0 -0
  57. package/src/node/footnote/reference.ts +60 -0
  58. package/src/node/index.ts +4 -0
  59. package/src/node/table/index.ts +318 -0
  60. package/src/node/table/utils.ts +509 -0
  61. package/src/node/task-list-item.ts +88 -0
  62. package/src/{table/plugin/auto-insert-zero-space.ts → plugin/auto-insert-zero-space-plugin.ts} +14 -15
  63. package/src/plugin/column-resizing-plugin.ts +6 -0
  64. package/src/plugin/index.ts +5 -0
  65. package/src/plugin/remark-gfm-plugin.ts +6 -0
  66. package/src/plugin/table-editing-plugin.ts +6 -0
  67. package/lib/footnote/definition.d.ts +0 -3
  68. package/lib/footnote/definition.d.ts.map +0 -1
  69. package/lib/footnote/index.d.ts.map +0 -1
  70. package/lib/footnote/reference.d.ts +0 -3
  71. package/lib/footnote/reference.d.ts.map +0 -1
  72. package/lib/footnote/utils.d.ts +0 -3
  73. package/lib/footnote/utils.d.ts.map +0 -1
  74. package/lib/strike-through.d.ts +0 -3
  75. package/lib/strike-through.d.ts.map +0 -1
  76. package/lib/supported-keys.d.ts +0 -28
  77. package/lib/supported-keys.d.ts.map +0 -1
  78. package/lib/table/command.d.ts +0 -4
  79. package/lib/table/command.d.ts.map +0 -1
  80. package/lib/table/index.d.ts +0 -10
  81. package/lib/table/index.d.ts.map +0 -1
  82. package/lib/table/nodes/index.d.ts +0 -12
  83. package/lib/table/nodes/index.d.ts.map +0 -1
  84. package/lib/table/operator-plugin/actions.d.ts +0 -20
  85. package/lib/table/operator-plugin/actions.d.ts.map +0 -1
  86. package/lib/table/operator-plugin/calc-pos.d.ts +0 -3
  87. package/lib/table/operator-plugin/calc-pos.d.ts.map +0 -1
  88. package/lib/table/operator-plugin/constant.d.ts +0 -6
  89. package/lib/table/operator-plugin/constant.d.ts.map +0 -1
  90. package/lib/table/operator-plugin/helper.d.ts +0 -7
  91. package/lib/table/operator-plugin/helper.d.ts.map +0 -1
  92. package/lib/table/operator-plugin/index.d.ts +0 -6
  93. package/lib/table/operator-plugin/index.d.ts.map +0 -1
  94. package/lib/table/operator-plugin/style.d.ts +0 -3
  95. package/lib/table/operator-plugin/style.d.ts.map +0 -1
  96. package/lib/table/operator-plugin/widget.d.ts +0 -8
  97. package/lib/table/operator-plugin/widget.d.ts.map +0 -1
  98. package/lib/table/plugin/auto-insert-zero-space.d.ts +0 -3
  99. package/lib/table/plugin/auto-insert-zero-space.d.ts.map +0 -1
  100. package/lib/table/plugin/cell-selection.d.ts +0 -40
  101. package/lib/table/plugin/cell-selection.d.ts.map +0 -1
  102. package/lib/table/plugin/column-resizing.d.ts +0 -18
  103. package/lib/table/plugin/column-resizing.d.ts.map +0 -1
  104. package/lib/table/plugin/commands.d.ts +0 -30
  105. package/lib/table/plugin/commands.d.ts.map +0 -1
  106. package/lib/table/plugin/copy-paste.d.ts +0 -14
  107. package/lib/table/plugin/copy-paste.d.ts.map +0 -1
  108. package/lib/table/plugin/fix-tables.d.ts +0 -7
  109. package/lib/table/plugin/fix-tables.d.ts.map +0 -1
  110. package/lib/table/plugin/index.d.ts +0 -4
  111. package/lib/table/plugin/index.d.ts.map +0 -1
  112. package/lib/table/plugin/schema.d.ts +0 -4
  113. package/lib/table/plugin/schema.d.ts.map +0 -1
  114. package/lib/table/plugin/table-editing.d.ts +0 -9
  115. package/lib/table/plugin/table-editing.d.ts.map +0 -1
  116. package/lib/table/plugin/table-map.d.ts +0 -44
  117. package/lib/table/plugin/table-map.d.ts.map +0 -1
  118. package/lib/table/plugin/table-view.d.ts +0 -15
  119. package/lib/table/plugin/table-view.d.ts.map +0 -1
  120. package/lib/table/plugin/types.d.ts +0 -15
  121. package/lib/table/plugin/types.d.ts.map +0 -1
  122. package/lib/table/plugin/util.d.ts +0 -16
  123. package/lib/table/plugin/util.d.ts.map +0 -1
  124. package/lib/table/utils.d.ts +0 -21
  125. package/lib/table/utils.d.ts.map +0 -1
  126. package/lib/task-list-item.d.ts +0 -9
  127. package/lib/task-list-item.d.ts.map +0 -1
  128. package/src/footnote/definition.ts +0 -187
  129. package/src/footnote/reference.ts +0 -178
  130. package/src/footnote/utils.ts +0 -4
  131. package/src/strike-through.ts +0 -43
  132. package/src/supported-keys.ts +0 -13
  133. package/src/table/command.ts +0 -20
  134. package/src/table/index.ts +0 -13
  135. package/src/table/nodes/index.ts +0 -189
  136. package/src/table/operator-plugin/actions.ts +0 -116
  137. package/src/table/operator-plugin/calc-pos.ts +0 -36
  138. package/src/table/operator-plugin/constant.ts +0 -7
  139. package/src/table/operator-plugin/helper.ts +0 -39
  140. package/src/table/operator-plugin/index.ts +0 -110
  141. package/src/table/operator-plugin/style.ts +0 -123
  142. package/src/table/operator-plugin/widget.ts +0 -57
  143. package/src/table/plugin/cell-selection.ts +0 -381
  144. package/src/table/plugin/column-resizing.ts +0 -288
  145. package/src/table/plugin/commands.ts +0 -594
  146. package/src/table/plugin/copy-paste.ts +0 -322
  147. package/src/table/plugin/fix-tables.ts +0 -132
  148. package/src/table/plugin/index.ts +0 -4
  149. package/src/table/plugin/schema.ts +0 -120
  150. package/src/table/plugin/table-editing.ts +0 -369
  151. package/src/table/plugin/table-map.ts +0 -345
  152. package/src/table/plugin/table-view.ts +0 -80
  153. package/src/table/plugin/types.ts +0 -16
  154. package/src/table/plugin/util.ts +0 -119
  155. package/src/table/utils.ts +0 -165
  156. package/src/task-list-item.ts +0 -159
@@ -1,369 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
-
3
- import { keydownHandler } from '@milkdown/prose/keymap'
4
- import type { ResolvedPos } from '@milkdown/prose/model'
5
- import { Fragment, Slice } from '@milkdown/prose/model'
6
- import type { Command, EditorState, Transaction } from '@milkdown/prose/state'
7
- import {
8
- Plugin,
9
- PluginKey,
10
- Selection,
11
- TextSelection,
12
- } from '@milkdown/prose/state'
13
- import type { EditorView } from '@milkdown/prose/view'
14
-
15
- import {
16
- CellSelection,
17
- drawCellSelection,
18
- normalizeSelection,
19
- } from './cell-selection'
20
- import { clipCells, fitSlice, insertCells, pastedCells } from './copy-paste'
21
- import { fixTables } from './fix-tables'
22
- import { tableNodeTypes } from './schema'
23
- import { TableMap } from './table-map'
24
- import {
25
- cellAround,
26
- inSameTable,
27
- isInTable,
28
- nextCell,
29
- selectionCell,
30
- } from './util'
31
-
32
- export const tableEditingKey = new PluginKey('selectingCells')
33
-
34
- function domInCell(view: EditorView, dom: EventTarget | null) {
35
- for (; dom && dom != view.dom; dom = (dom as Element).parentNode as Element) {
36
- if ((dom as Element).nodeName == 'TD' || (dom as Element).nodeName == 'TH')
37
- return dom
38
- }
39
-
40
- return undefined
41
- }
42
-
43
- function cellUnderMouse(view: EditorView, event: MouseEvent) {
44
- const mousePos = view.posAtCoords({
45
- left: event.clientX,
46
- top: event.clientY,
47
- })
48
- if (!mousePos)
49
- return null
50
- return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null
51
- }
52
-
53
- function handleMouseDown(view: EditorView, event: Event) {
54
- const startEvent = event as MouseEvent
55
- if (startEvent.ctrlKey || startEvent.metaKey)
56
- return
57
-
58
- const startDOMCell = domInCell(view, startEvent.target as Element)
59
- let $anchor
60
- if (startEvent.shiftKey && view.state.selection instanceof CellSelection) {
61
- // Adding to an existing cell selection
62
- setCellSelection(view.state.selection.$anchorCell, startEvent)
63
- startEvent.preventDefault()
64
- }
65
- else if (
66
- startEvent.shiftKey
67
- && startDOMCell
68
- // eslint-disable-next-line no-cond-assign
69
- && ($anchor = cellAround(view.state.selection.$anchor)) != null
70
- && cellUnderMouse(view, startEvent)!.pos != $anchor.pos
71
- ) {
72
- // Adding to a selection that starts in another cell (causing a
73
- // cell selection to be created).
74
- setCellSelection($anchor, startEvent)
75
- startEvent.preventDefault()
76
- }
77
- else if (!startDOMCell) {
78
- // Not in a cell, let the default behavior happen.
79
- return
80
- }
81
-
82
- // Create and dispatch a cell selection between the given anchor and
83
- // the position under the mouse.
84
- function setCellSelection($anchor: ResolvedPos, event: MouseEvent) {
85
- let $head = cellUnderMouse(view, event)
86
- const starting = tableEditingKey.getState(view.state) == null
87
- if (!$head || !inSameTable($anchor, $head)) {
88
- if (starting)
89
- $head = $anchor
90
- else return
91
- }
92
- const selection = new CellSelection($anchor, $head)
93
- if (starting || !view.state.selection.eq(selection)) {
94
- const tr = view.state.tr.setSelection(selection)
95
- if (starting)
96
- tr.setMeta(tableEditingKey, $anchor.pos)
97
- view.dispatch(tr)
98
- }
99
- }
100
-
101
- // Stop listening to mouse motion events.
102
- function stop() {
103
- view.root.removeEventListener('mouseup', stop)
104
- view.root.removeEventListener('dragstart', stop)
105
- view.root.removeEventListener('mousemove', move)
106
- if (tableEditingKey.getState(view.state) != null)
107
- view.dispatch(view.state.tr.setMeta(tableEditingKey, -1))
108
- }
109
-
110
- function move(event: Event) {
111
- const anchor = tableEditingKey.getState(view.state)
112
- let $anchor
113
- if (anchor != null) {
114
- // Continuing an existing cross-cell selection
115
- $anchor = view.state.doc.resolve(anchor)
116
- }
117
- else if (domInCell(view, event.target) != startDOMCell) {
118
- // Moving out of the initial cell -- start a new cell selection
119
- $anchor = cellUnderMouse(view, startEvent)
120
- if (!$anchor)
121
- return stop()
122
- }
123
- if ($anchor)
124
- setCellSelection($anchor, event as MouseEvent)
125
- }
126
- view.root.addEventListener('mouseup', stop)
127
- view.root.addEventListener('dragstart', stop)
128
- view.root.addEventListener('mousemove', move)
129
- }
130
-
131
- function handleTripleClick(view: EditorView, pos: number) {
132
- const doc = view.state.doc
133
- const $cell = cellAround(doc.resolve(pos))
134
- if (!$cell)
135
- return false
136
- view.dispatch(view.state.tr.setSelection(new CellSelection($cell)))
137
- return true
138
- }
139
-
140
- function maybeSetSelection(
141
- state: EditorState,
142
- dispatch: undefined | ((tr: Transaction) => void),
143
- selection: Selection,
144
- ) {
145
- if (selection.eq(state.selection))
146
- return false
147
- if (dispatch)
148
- dispatch(state.tr.setSelection(selection).scrollIntoView())
149
- return true
150
- }
151
-
152
- function atEndOfCell(view: EditorView, axis: string, dir: number) {
153
- if (!(view.state.selection instanceof TextSelection))
154
- return null
155
- const { $head } = view.state.selection
156
- for (let d = $head.depth - 1; d >= 0; d--) {
157
- const parent = $head.node(d)
158
- const index = dir < 0 ? $head.index(d) : $head.indexAfter(d)
159
- if (index != (dir < 0 ? 0 : parent.childCount))
160
- return null
161
- if (
162
- parent.type.spec.tableRole == 'cell'
163
- || parent.type.spec.tableRole == 'header_cell'
164
- ) {
165
- const cellPos = $head.before(d)
166
- const dirStr
167
- = axis == 'vert' ? (dir > 0 ? 'down' : 'up') : dir > 0 ? 'right' : 'left'
168
- return view.endOfTextblock(dirStr) ? cellPos : null
169
- }
170
- }
171
- return null
172
- }
173
-
174
- function arrow(axis: string, dir: number): Command {
175
- return (state, dispatch, view) => {
176
- const sel = state.selection
177
- if (sel instanceof CellSelection) {
178
- return maybeSetSelection(
179
- state,
180
- dispatch,
181
- Selection.near(sel.$headCell, dir),
182
- )
183
- }
184
-
185
- if (axis != 'horiz' && !sel.empty)
186
- return false
187
- const end = atEndOfCell(view as EditorView, axis, dir)
188
- if (end == null)
189
- return false
190
- if (axis == 'horiz') {
191
- return maybeSetSelection(
192
- state,
193
- dispatch,
194
- Selection.near(state.doc.resolve(sel.head + dir), dir),
195
- )
196
- }
197
- else {
198
- const $cell = state.doc.resolve(end)
199
- const $next = nextCell($cell, axis, dir)
200
- let newSel
201
- if ($next)
202
- newSel = Selection.near($next, 1)
203
- else if (dir < 0)
204
- newSel = Selection.near(state.doc.resolve($cell.before(-1)), -1)
205
- else newSel = Selection.near(state.doc.resolve($cell.after(-1)), 1)
206
- return maybeSetSelection(state, dispatch, newSel)
207
- }
208
- }
209
- }
210
-
211
- function shiftArrow(axis: string, dir: number): Command {
212
- return (state, dispatch, view) => {
213
- let sel = state.selection
214
- if (!(sel instanceof CellSelection)) {
215
- const end = atEndOfCell(view as EditorView, axis, dir)
216
- if (end == null)
217
- return false
218
- sel = new CellSelection(state.doc.resolve(end))
219
- }
220
- const $head = nextCell((sel as CellSelection).$headCell, axis, dir)
221
- if (!$head)
222
- return false
223
- return maybeSetSelection(
224
- state,
225
- dispatch,
226
- new CellSelection((sel as CellSelection).$anchorCell, $head),
227
- )
228
- }
229
- }
230
-
231
- function deleteCellSelection(
232
- state: EditorState,
233
- dispatch?: (tr: Transaction) => void,
234
- ) {
235
- const sel = state.selection
236
- if (!(sel instanceof CellSelection))
237
- return false
238
- if (dispatch) {
239
- const tr = state.tr
240
- const baseContent = tableNodeTypes(state.schema).cell.createAndFill()
241
- .content
242
- sel.forEachCell((cell, pos) => {
243
- if (!cell.content.eq(baseContent)) {
244
- tr.replace(
245
- tr.mapping.map(pos + 1),
246
- tr.mapping.map(pos + cell.nodeSize - 1),
247
- new Slice(baseContent, 0, 0),
248
- )
249
- }
250
- })
251
- if (tr.docChanged)
252
- dispatch(tr)
253
- }
254
- return true
255
- }
256
-
257
- const handleKeyDown = keydownHandler({
258
- 'ArrowLeft': arrow('horiz', -1),
259
- 'ArrowRight': arrow('horiz', 1),
260
- 'ArrowUp': arrow('vert', -1),
261
- 'ArrowDown': arrow('vert', 1),
262
-
263
- 'Shift-ArrowLeft': shiftArrow('horiz', -1),
264
- 'Shift-ArrowRight': shiftArrow('horiz', 1),
265
- 'Shift-ArrowUp': shiftArrow('vert', -1),
266
- 'Shift-ArrowDown': shiftArrow('vert', 1),
267
-
268
- 'Backspace': deleteCellSelection,
269
- 'Mod-Backspace': deleteCellSelection,
270
- 'Delete': deleteCellSelection,
271
- 'Mod-Delete': deleteCellSelection,
272
- })
273
-
274
- export function handlePaste(view: EditorView, _: Event, slice: Slice) {
275
- if (!isInTable(view.state))
276
- return false
277
- let cells = pastedCells(slice)
278
- const sel = view.state.selection
279
- if (sel instanceof CellSelection) {
280
- if (!cells) {
281
- cells = {
282
- width: 1,
283
- height: 1,
284
- rows: [
285
- Fragment.from(
286
- fitSlice(tableNodeTypes(view.state.schema).cell, slice),
287
- ),
288
- ],
289
- }
290
- }
291
- const table = sel.$anchorCell.node(-1)
292
- const start = sel.$anchorCell.start(-1)
293
- const rect = TableMap.get(table).rectBetween(
294
- sel.$anchorCell.pos - start,
295
- sel.$headCell.pos - start,
296
- )
297
- cells = clipCells(cells, rect.right - rect.left, rect.bottom - rect.top)
298
- insertCells(view.state, view.dispatch, start, rect, cells)
299
- return true
300
- }
301
- else if (cells) {
302
- const $cell = selectionCell(view.state) as ResolvedPos
303
- const start = $cell.start(-1)
304
- insertCells(
305
- view.state,
306
- view.dispatch,
307
- start,
308
- TableMap.get($cell.node(-1)).findCell($cell.pos - start),
309
- cells,
310
- )
311
- return true
312
- }
313
- else {
314
- return false
315
- }
316
- }
317
-
318
- export function tableEditing({ allowTableNodeSelection = false } = {}) {
319
- return new Plugin({
320
- key: tableEditingKey,
321
-
322
- // This piece of state is used to remember when a mouse-drag
323
- // cell-selection is happening, so that it can continue even as
324
- // transactions (which might move its anchor cell) come in.
325
- state: {
326
- init() {
327
- return null
328
- },
329
- apply(tr, cur) {
330
- const set = tr.getMeta(tableEditingKey)
331
- if (set != null)
332
- return set == -1 ? null : set
333
- if (cur == null || !tr.docChanged)
334
- return cur
335
- const { deleted, pos } = tr.mapping.mapResult(cur)
336
- return deleted ? null : pos
337
- },
338
- },
339
-
340
- props: {
341
- decorations: drawCellSelection,
342
-
343
- handleDOMEvents: {
344
- mousedown: handleMouseDown,
345
- },
346
-
347
- createSelectionBetween(view) {
348
- if (tableEditingKey.getState(view.state) != null)
349
- return view.state.selection
350
-
351
- return null
352
- },
353
-
354
- handleTripleClick,
355
-
356
- handleKeyDown,
357
-
358
- handlePaste,
359
- },
360
-
361
- appendTransaction(_, oldState, state) {
362
- return normalizeSelection(
363
- state,
364
- fixTables(state, oldState),
365
- allowTableNodeSelection,
366
- )
367
- },
368
- })
369
- }
@@ -1,345 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
-
3
- import type { Attrs, Node } from '@milkdown/prose/model'
4
-
5
- const cache = new WeakMap<Node, TableMap>()
6
- const readFromCache = (key: Node) => cache.get(key)
7
- const addToCache = (key: Node, value: TableMap) => {
8
- cache.set(key, value)
9
- return value
10
- }
11
-
12
- export class Rect {
13
- public tableStart?: number
14
- public map?: TableMap
15
- public table?: Node
16
- constructor(
17
- public left: number,
18
- public top: number,
19
- public right: number,
20
- public bottom: number,
21
- ) {}
22
- }
23
-
24
- // ::- A table map describes the structore of a given table. To avoid
25
- // recomputing them all the time, they are cached per table node. To
26
- // be able to do that, positions saved in the map are relative to the
27
- // start of the table, rather than the start of the document.
28
- export class TableMap {
29
- constructor(
30
- public width: number,
31
- public height: number,
32
- public map: number[],
33
- public problems?: Problem[],
34
- ) {
35
- // :: number The width of the table
36
- this.width = width
37
- // :: number The table's height
38
- this.height = height
39
- // :: [number] A width * height array with the start position of
40
- // the cell covering that part of the table in each slot
41
- this.map = map
42
- // An optional array of problems (cell overlap or non-rectangular
43
- // shape) for the table, used by the table normalizer.
44
- this.problems = problems
45
- }
46
-
47
- // Find the dimensions of the cell at the given position.
48
- findCell(pos: number): Rect {
49
- for (let i = 0; i < this.map.length; i++) {
50
- const curPos = this.map[i]
51
- if (curPos != pos)
52
- continue
53
- const left = i % this.width
54
- const top = (i / this.width) | 0
55
- let right = left + 1
56
- let bottom = top + 1
57
- for (let j = 1; right < this.width && this.map[i + j] == curPos; j++)
58
- right++
59
- for (
60
- let j = 1;
61
- bottom < this.height && this.map[i + this.width * j] == curPos;
62
- j++
63
- )
64
- bottom++
65
- return new Rect(left, top, right, bottom)
66
- }
67
- throw new RangeError(`No cell with offset ${pos} found`)
68
- }
69
-
70
- colCount(pos: number): number {
71
- for (let i = 0; i < this.map.length; i++) {
72
- if (this.map[i] == pos)
73
- return i % this.width
74
- }
75
-
76
- throw new RangeError(`No cell with offset ${pos} found`)
77
- }
78
-
79
- // :: (number, string, number) → ?number
80
- // Find the next cell in the given direction, starting from the cell
81
- // at `pos`, if any.
82
- nextCell(pos: number, axis: string, dir: number): number | undefined {
83
- const { left, right, top, bottom } = this.findCell(pos)
84
- if (axis == 'horiz') {
85
- if (dir < 0 ? left == 0 : right == this.width)
86
- return undefined
87
- return this.map[top * this.width + (dir < 0 ? left - 1 : right)]
88
- }
89
- else {
90
- if (dir < 0 ? top == 0 : bottom == this.height)
91
- return undefined
92
- return this.map[left + this.width * (dir < 0 ? top - 1 : bottom)]
93
- }
94
- }
95
-
96
- // :: (number, number) → Rect
97
- // Get the rectangle spanning the two given cells.
98
- rectBetween(a: number, b: number): Rect {
99
- const {
100
- left: leftA,
101
- right: rightA,
102
- top: topA,
103
- bottom: bottomA,
104
- } = this.findCell(a)
105
- const {
106
- left: leftB,
107
- right: rightB,
108
- top: topB,
109
- bottom: bottomB,
110
- } = this.findCell(b)
111
- return new Rect(
112
- Math.min(leftA, leftB),
113
- Math.min(topA, topB),
114
- Math.max(rightA, rightB),
115
- Math.max(bottomA, bottomB),
116
- )
117
- }
118
-
119
- // :: (Rect) → [number]
120
- // Return the position of all cells that have the top left corner in
121
- // the given rectangle.
122
- cellsInRect(rect: Rect): number[] {
123
- const result: number[] = []
124
- const seen: Record<number, boolean> = {}
125
- for (let row = rect.top; row < rect.bottom; row++) {
126
- for (let col = rect.left; col < rect.right; col++) {
127
- const index = row * this.width + col
128
- const pos = this.map[index] as number
129
- if (seen[pos])
130
- continue
131
- seen[pos] = true
132
- if (
133
- (col != rect.left || !col || this.map[index - 1] != pos)
134
- && (row != rect.top || !row || this.map[index - this.width] != pos)
135
- )
136
- result.push(pos)
137
- }
138
- }
139
- return result
140
- }
141
-
142
- // :: (number, number, Node) → number
143
- // Return the position at which the cell at the given row and column
144
- // starts, or would start, if a cell started there.
145
- positionAt(row: number, col: number, table: Node): number {
146
- for (let i = 0, rowStart = 0; ; i++) {
147
- const rowEnd = rowStart + table.child(i).nodeSize
148
- if (i == row) {
149
- let index = col + row * this.width
150
- const rowEndIndex = (row + 1) * this.width
151
- // Skip past cells from previous rows (via rowspan)
152
- while (index < rowEndIndex && (this.map[index] as number) < rowStart)
153
- index++
154
- return index == rowEndIndex ? rowEnd - 1 : (this.map[index] as number)
155
- }
156
- rowStart = rowEnd
157
- }
158
- }
159
-
160
- // Find the table map for the given table node.
161
- static get(table: Node): TableMap {
162
- return readFromCache(table) || addToCache(table, computeMap(table))
163
- }
164
- }
165
-
166
- export type Problem =
167
- | {
168
- type: 'missing'
169
- row: number
170
- n: number
171
- }
172
- | {
173
- type: 'overlong_rowspan'
174
- pos: number
175
- n: number
176
- }
177
- | {
178
- type: 'collision'
179
- row: number
180
- pos: number
181
- n: number
182
- }
183
- | {
184
- type: 'colwidth mismatch'
185
- pos: number
186
- colwidth: boolean
187
- }
188
-
189
- // Compute a table map.
190
- function computeMap(table: Node) {
191
- if (table.type.spec.tableRole != 'table')
192
- throw new RangeError(`Not a table node: ${table.type.name}`)
193
- const width = findWidth(table)
194
- const height = table.childCount
195
- const map: number[] = []
196
- const colWidths: number[] = []
197
- let mapPos = 0
198
- let problems: Problem[] | undefined
199
- for (let i = 0, e = width * height; i < e; i++) map[i] = 0
200
-
201
- for (let row = 0, pos = 0; row < height; row++) {
202
- const rowNode = table.child(row)
203
- pos++
204
- for (let i = 0; ; i++) {
205
- while (mapPos < map.length && map[mapPos] != 0) mapPos++
206
- if (i == rowNode.childCount)
207
- break
208
- const cellNode = rowNode.child(i)
209
- const { colspan, rowspan, colwidth } = cellNode.attrs
210
- for (let h = 0; h < rowspan; h++) {
211
- if (h + row >= height) {
212
- (problems || (problems = [])).push({
213
- type: 'overlong_rowspan',
214
- pos,
215
- n: rowspan - h,
216
- })
217
- break
218
- }
219
- const start = mapPos + h * width
220
- for (let w = 0; w < colspan; w++) {
221
- if (map[start + w] == 0) {
222
- map[start + w] = pos
223
- }
224
- else {
225
- (problems || (problems = [])).push({
226
- type: 'collision',
227
- row,
228
- pos,
229
- n: colspan - w,
230
- })
231
- }
232
- const colW = colwidth && colwidth[w]
233
- if (colW) {
234
- const widthIndex = ((start + w) % width) * 2
235
- const prev = colWidths[widthIndex]
236
- if (
237
- prev == null
238
- || (prev != colW && colWidths[widthIndex + 1] == 1)
239
- ) {
240
- colWidths[widthIndex] = colW
241
- colWidths[widthIndex + 1] = 1
242
- }
243
- else if (prev == colW) {
244
- colWidths[widthIndex + 1]++
245
- }
246
- }
247
- }
248
- }
249
- mapPos += colspan
250
- pos += cellNode.nodeSize
251
- }
252
- const expectedPos = (row + 1) * width
253
- let missing = 0
254
- while (mapPos < expectedPos) {
255
- if (map[mapPos++] == 0)
256
- missing++
257
- }
258
-
259
- if (missing)
260
- (problems || (problems = [])).push({ type: 'missing', row, n: missing })
261
- pos++
262
- }
263
-
264
- const tableMap = new TableMap(width, height, map, problems)
265
- let badWidths = false
266
-
267
- // For columns that have defined widths, but whose widths disagree
268
- // between rows, fix up the cells whose width doesn't match the
269
- // computed one.
270
- for (let i = 0; !badWidths && i < colWidths.length; i += 2) {
271
- if (colWidths[i] != null && (colWidths[i + 1] as number) < height)
272
- badWidths = true
273
- }
274
- if (badWidths)
275
- findBadColWidths(tableMap, colWidths, table)
276
-
277
- return tableMap
278
- }
279
-
280
- function findWidth(table: Node) {
281
- let width = -1
282
- let hasRowSpan = false
283
- for (let row = 0; row < table.childCount; row++) {
284
- const rowNode = table.child(row)
285
- let rowWidth = 0
286
- if (hasRowSpan) {
287
- for (let j = 0; j < row; j++) {
288
- const prevRow = table.child(j)
289
- for (let i = 0; i < prevRow.childCount; i++) {
290
- const cell = prevRow.child(i)
291
- if (j + cell.attrs.rowspan > row)
292
- rowWidth += cell.attrs.colspan
293
- }
294
- }
295
- }
296
- for (let i = 0; i < rowNode.childCount; i++) {
297
- const cell = rowNode.child(i)
298
- rowWidth += cell.attrs.colspan
299
- if (cell.attrs.rowspan > 1)
300
- hasRowSpan = true
301
- }
302
- if (width == -1)
303
- width = rowWidth
304
- else if (width != rowWidth)
305
- width = Math.max(width, rowWidth)
306
- }
307
- return width
308
- }
309
-
310
- function findBadColWidths(map: TableMap, colWidths: number[], table: Node) {
311
- if (!map.problems)
312
- map.problems = []
313
- for (let i = 0, seen: Record<number, boolean> = {}; i < map.map.length; i++) {
314
- const pos = map.map[i] as number
315
- if (seen[pos])
316
- continue
317
- seen[pos] = true
318
- const node = table.nodeAt(pos) as Node
319
- let updated = null
320
- for (let j = 0; j < node.attrs.colspan; j++) {
321
- const col = (i + j) % map.width
322
- const colWidth = colWidths[col * 2]
323
- if (
324
- colWidth != null
325
- && (!node.attrs.colwidth || node.attrs.colwidth[j] != colWidth)
326
- )
327
- (updated || (updated = freshColWidth(node.attrs)))[j] = colWidth
328
- }
329
- if (updated) {
330
- map.problems.unshift({
331
- type: 'colwidth mismatch',
332
- pos,
333
- colwidth: updated,
334
- })
335
- }
336
- }
337
- }
338
-
339
- function freshColWidth(attrs: Attrs) {
340
- if (attrs.colwidth)
341
- return attrs.colwidth.slice()
342
- const result = []
343
- for (let i = 0; i < attrs.colspan; i++) result.push(0)
344
- return result
345
- }