@kaitify/core 0.0.1-beta.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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/examples/App.vue +342 -0
  4. package/examples/content.js +1 -0
  5. package/examples/main.ts +4 -0
  6. package/examples/test.html +23 -0
  7. package/lib/extensions/Extension.d.ts +172 -0
  8. package/lib/extensions/align/index.d.ts +10 -0
  9. package/lib/extensions/attachment/index.d.ts +29 -0
  10. package/lib/extensions/back-color/index.d.ts +9 -0
  11. package/lib/extensions/blockquote/index.d.ts +12 -0
  12. package/lib/extensions/bold/index.d.ts +9 -0
  13. package/lib/extensions/code/index.d.ts +12 -0
  14. package/lib/extensions/code-block/hljs.d.ts +12 -0
  15. package/lib/extensions/code-block/index.d.ts +15 -0
  16. package/lib/extensions/color/index.d.ts +9 -0
  17. package/lib/extensions/font-family/index.d.ts +9 -0
  18. package/lib/extensions/font-size/index.d.ts +9 -0
  19. package/lib/extensions/heading/index.d.ts +13 -0
  20. package/lib/extensions/history/index.d.ts +10 -0
  21. package/lib/extensions/horizontal/index.d.ts +7 -0
  22. package/lib/extensions/image/index.d.ts +26 -0
  23. package/lib/extensions/indent/index.d.ts +8 -0
  24. package/lib/extensions/index.d.ts +29 -0
  25. package/lib/extensions/italic/index.d.ts +9 -0
  26. package/lib/extensions/line-height/index.d.ts +9 -0
  27. package/lib/extensions/link/index.d.ts +27 -0
  28. package/lib/extensions/list/index.d.ts +18 -0
  29. package/lib/extensions/math/index.d.ts +11 -0
  30. package/lib/extensions/strikethrough/index.d.ts +9 -0
  31. package/lib/extensions/subscript/index.d.ts +9 -0
  32. package/lib/extensions/superscript/index.d.ts +9 -0
  33. package/lib/extensions/table/index.d.ts +21 -0
  34. package/lib/extensions/task/index.d.ts +12 -0
  35. package/lib/extensions/text/index.d.ts +14 -0
  36. package/lib/extensions/underline/index.d.ts +9 -0
  37. package/lib/extensions/video/index.d.ts +27 -0
  38. package/lib/index.d.ts +3 -0
  39. package/lib/kaitify-core.es.js +38337 -0
  40. package/lib/kaitify-core.umd.js +2 -0
  41. package/lib/model/Editor.d.ts +504 -0
  42. package/lib/model/History.d.ts +42 -0
  43. package/lib/model/KNode.d.ts +258 -0
  44. package/lib/model/Selection.d.ts +29 -0
  45. package/lib/model/config/dom-observe.d.ts +10 -0
  46. package/lib/model/config/event-handler.d.ts +33 -0
  47. package/lib/model/config/format-patch.d.ts +25 -0
  48. package/lib/model/config/format-rules.d.ts +37 -0
  49. package/lib/model/config/function.d.ts +84 -0
  50. package/lib/model/index.d.ts +6 -0
  51. package/lib/tools/index.d.ts +49 -0
  52. package/lib/view/index.d.ts +21 -0
  53. package/lib/view/js-render/dom-patch.d.ts +65 -0
  54. package/lib/view/js-render/index.d.ts +5 -0
  55. package/package.json +52 -0
  56. package/src/css/style.less +56 -0
  57. package/src/css/var.less +45 -0
  58. package/src/extensions/Extension.ts +200 -0
  59. package/src/extensions/align/index.ts +115 -0
  60. package/src/extensions/attachment/icon.svg +1 -0
  61. package/src/extensions/attachment/index.ts +293 -0
  62. package/src/extensions/attachment/style.less +25 -0
  63. package/src/extensions/back-color/index.ts +56 -0
  64. package/src/extensions/blockquote/index.ts +144 -0
  65. package/src/extensions/blockquote/style.less +16 -0
  66. package/src/extensions/bold/index.ts +77 -0
  67. package/src/extensions/code/index.ts +295 -0
  68. package/src/extensions/code/style.less +14 -0
  69. package/src/extensions/code-block/hljs.less +183 -0
  70. package/src/extensions/code-block/hljs.ts +95 -0
  71. package/src/extensions/code-block/index.ts +308 -0
  72. package/src/extensions/code-block/style.less +20 -0
  73. package/src/extensions/color/index.ts +56 -0
  74. package/src/extensions/font-family/index.ts +80 -0
  75. package/src/extensions/font-size/index.ts +56 -0
  76. package/src/extensions/heading/index.ts +164 -0
  77. package/src/extensions/heading/style.less +42 -0
  78. package/src/extensions/history/index.ts +96 -0
  79. package/src/extensions/horizontal/index.ts +45 -0
  80. package/src/extensions/horizontal/style.less +13 -0
  81. package/src/extensions/image/index.ts +242 -0
  82. package/src/extensions/image/style.less +8 -0
  83. package/src/extensions/indent/index.ts +98 -0
  84. package/src/extensions/index.ts +29 -0
  85. package/src/extensions/italic/index.ts +77 -0
  86. package/src/extensions/line-height/index.ts +113 -0
  87. package/src/extensions/link/index.ts +184 -0
  88. package/src/extensions/link/style.less +19 -0
  89. package/src/extensions/list/index.ts +410 -0
  90. package/src/extensions/list/style.less +19 -0
  91. package/src/extensions/math/index.ts +233 -0
  92. package/src/extensions/math/style.less +21 -0
  93. package/src/extensions/strikethrough/index.ts +78 -0
  94. package/src/extensions/subscript/index.ts +77 -0
  95. package/src/extensions/superscript/index.ts +77 -0
  96. package/src/extensions/table/index.ts +1148 -0
  97. package/src/extensions/table/style.less +71 -0
  98. package/src/extensions/task/index.ts +243 -0
  99. package/src/extensions/task/style.less +59 -0
  100. package/src/extensions/text/index.ts +359 -0
  101. package/src/extensions/underline/index.ts +78 -0
  102. package/src/extensions/video/index.ts +273 -0
  103. package/src/extensions/video/style.less +8 -0
  104. package/src/index.ts +9 -0
  105. package/src/model/Editor.ts +1963 -0
  106. package/src/model/History.ts +115 -0
  107. package/src/model/KNode.ts +677 -0
  108. package/src/model/Selection.ts +39 -0
  109. package/src/model/config/dom-observe.ts +184 -0
  110. package/src/model/config/event-handler.ts +237 -0
  111. package/src/model/config/format-patch.ts +215 -0
  112. package/src/model/config/format-rules.ts +218 -0
  113. package/src/model/config/function.ts +1018 -0
  114. package/src/model/index.ts +6 -0
  115. package/src/tools/index.ts +156 -0
  116. package/src/view/index.ts +46 -0
  117. package/src/view/js-render/dom-patch.ts +324 -0
  118. package/src/view/js-render/index.ts +210 -0
  119. package/vite-env.d.ts +2 -0
@@ -0,0 +1,1148 @@
1
+ import interact from 'interactjs'
2
+ import { element as DapElement, event as DapEvent, data as DapData } from 'dap-util'
3
+ import { Editor, KNode, KNodeCreateOptionType, KNodeMarksType, KNodeStylesType } from '@/model'
4
+ import { Extension } from '../Extension'
5
+ import './style.less'
6
+
7
+ export type TableCellsMergeDirectionType = 'left' | 'top' | 'right' | 'bottom'
8
+
9
+ declare module '../../model' {
10
+ interface EditorCommandsType {
11
+ getTable?: () => KNode | null
12
+ hasTable?: () => boolean
13
+ canMergeTableCells?: (direction: TableCellsMergeDirectionType) => boolean
14
+ setTable?: ({ rows, columns }: { rows: number; columns: number }) => Promise<void>
15
+ unsetTable?: () => Promise<void>
16
+ mergeTableCell?: (direction: TableCellsMergeDirectionType) => Promise<void>
17
+ addTableRow?: (direction: 'top' | 'bottom') => Promise<void>
18
+ deleteTableRow?: () => Promise<void>
19
+ addTableColumn?: (direction: 'left' | 'right') => Promise<void>
20
+ deleteTableColumn?: () => Promise<void>
21
+ }
22
+ }
23
+
24
+ /**
25
+ * 是否隐藏单元格
26
+ */
27
+ const isHideCell = (cell: KNode) => {
28
+ return cell.hasStyles() && cell.styles!.display == 'none'
29
+ }
30
+ /**
31
+ * 创建一个隐藏的单元格
32
+ */
33
+ const createHideCellNode = () => {
34
+ return KNode.create({
35
+ type: 'block',
36
+ tag: 'td',
37
+ nested: true,
38
+ fixed: true,
39
+ void: true,
40
+ styles: {
41
+ display: 'none'
42
+ },
43
+ children: [
44
+ {
45
+ type: 'closed',
46
+ tag: 'br'
47
+ }
48
+ ]
49
+ })
50
+ }
51
+ /**
52
+ * 获取单元格节点的rowspan和colspan
53
+ */
54
+ const getCellSize = (cell: KNode) => {
55
+ let rowCount = 1
56
+ let colCount = 1
57
+ if (cell.hasMarks()) {
58
+ if (cell.marks!['rowspan']) rowCount = Number(cell.marks!['rowspan']) || 1
59
+ if (cell.marks!['colspan']) colCount = Number(cell.marks!['colspan']) || 1
60
+ }
61
+ return { rowCount, colCount }
62
+ }
63
+ /**
64
+ * 获取表格正确的行数和列数
65
+ */
66
+ const getTableSize = (rows: KNode[]) => {
67
+ //表格实际列数
68
+ let colCount = 0
69
+ //遍历每一行
70
+ for (let i = 0; i < rows.length; i++) {
71
+ //当前行
72
+ const currentRow = rows[i]
73
+ //当前行的列数组
74
+ const cells = currentRow.children!
75
+ //当前行的实际列数
76
+ let currentColCount = 0
77
+ //遍历这一行的单元格
78
+ for (let j = 0; j < cells.length; j++) {
79
+ //当前单元格
80
+ const currentCell = cells[j]
81
+ //跳过隐藏单元格
82
+ if (isHideCell(currentCell)) {
83
+ continue
84
+ }
85
+ //获取单元格的大小
86
+ const cellSize = getCellSize(currentCell)
87
+ //当前行的实际列数是所有单元格的列数累加
88
+ currentColCount += cellSize.colCount
89
+ }
90
+ //总列数是所有行的实际列数最大的那个值
91
+ if (currentColCount > colCount) {
92
+ colCount = currentColCount
93
+ }
94
+ }
95
+ return { rowCount: rows.length, colCount }
96
+ }
97
+ /**
98
+ * 过滤表格中的隐藏单元格
99
+ */
100
+ const filterHideCells = (rows: KNode[]) => {
101
+ let rowIndex = 0
102
+ while (rowIndex < rows.length) {
103
+ const currentRow = rows[rowIndex]
104
+ let colIndex = 0
105
+ while (colIndex < currentRow.children!.length) {
106
+ const currentCell = currentRow.children![colIndex]
107
+ //如果是隐藏单元格,则去除,跳过本次循环
108
+ if (isHideCell(currentCell)) {
109
+ currentRow.children!.splice(colIndex, 1)
110
+ continue
111
+ }
112
+ colIndex++
113
+ }
114
+ rowIndex++
115
+ }
116
+ }
117
+ /**
118
+ * 给表格重新设置隐藏单元格
119
+ */
120
+ const resetTableHideCells = (editor: Editor, rows: KNode[]) => {
121
+ let rowIndex = 0
122
+ while (rowIndex < rows.length) {
123
+ const currentRow = rows[rowIndex]
124
+ let cellIndex = 0
125
+ while (cellIndex < currentRow.children!.length) {
126
+ const currentCell = currentRow.children![cellIndex]
127
+ //跳过隐藏单元格
128
+ if (isHideCell(currentCell)) {
129
+ cellIndex++
130
+ continue
131
+ }
132
+ //获取单元格的跨行和跨列数
133
+ const { rowCount, colCount } = getCellSize(currentCell)
134
+ //跨列
135
+ if (colCount > 1) {
136
+ for (let i = colCount - 1; i > 0; i--) {
137
+ const cell = createHideCellNode()
138
+ editor.addNodeAfter(cell, currentCell)
139
+ }
140
+ }
141
+ //跨行
142
+ if (rowCount > 1) {
143
+ //遍历后面受影响的行
144
+ for (let i = rowIndex + 1; i < rowIndex + rowCount; i++) {
145
+ //下一行
146
+ const nextRow = rows[i]
147
+ //下一行不存在则跳过
148
+ if (!nextRow) {
149
+ continue
150
+ }
151
+ //处理下一行的对应列,可能是多列,因为可能跨行的同时也跨列,所以使用for循环处理
152
+ for (let j = cellIndex; j < cellIndex + colCount; j++) {
153
+ //获取对应的单元格
154
+ const nextCell = nextRow.children![j]
155
+ //单元格不存在,需要补充隐藏的单元格
156
+ if (!nextCell) {
157
+ const hideCell = createHideCellNode()
158
+ editor.addNode(hideCell, nextRow, j)
159
+ }
160
+ //单元格非隐藏,则添加隐藏的单元格到它前面
161
+ else if (!isHideCell(nextCell)) {
162
+ const hideCell = createHideCellNode()
163
+ editor.addNodeBefore(hideCell, nextCell)
164
+ }
165
+ }
166
+ }
167
+ }
168
+ cellIndex++
169
+ }
170
+ rowIndex++
171
+ }
172
+ }
173
+ /**
174
+ * 获取单元格指定方向的最近的一个非隐藏单元格
175
+ */
176
+ const getTargetNotHideCell = (cell: KNode, direction: TableCellsMergeDirectionType) => {
177
+ if (direction == 'right') {
178
+ const nextCell = cell.getNext(cell.parent!.children!)
179
+ if (nextCell) {
180
+ if (isHideCell(nextCell)) {
181
+ return getTargetNotHideCell(nextCell, direction)
182
+ }
183
+ return nextCell
184
+ }
185
+ } else if (direction == 'left') {
186
+ const previousCell = cell.getPrevious(cell.parent!.children!)
187
+ if (previousCell) {
188
+ if (isHideCell(previousCell)) {
189
+ return getTargetNotHideCell(previousCell, direction)
190
+ }
191
+ return previousCell
192
+ }
193
+ } else if (direction == 'top') {
194
+ const row = cell.parent!
195
+ const index = row.children!.findIndex(item => item.isEqual(cell))
196
+ const previousRow = row.getPrevious(row.parent!.children!)
197
+ if (previousRow) {
198
+ const previousCell = previousRow.children![index]
199
+ if (previousCell) {
200
+ if (isHideCell(previousCell)) {
201
+ return getTargetNotHideCell(previousCell, direction)
202
+ }
203
+ return previousCell
204
+ }
205
+ }
206
+ } else if (direction == 'bottom') {
207
+ const row = cell.parent!
208
+ const index = row.children!.findIndex(item => item.isEqual(cell))
209
+ const nextRow = row.getNext(row.parent!.children!)
210
+ if (nextRow) {
211
+ const nextCell = nextRow.children![index]
212
+ if (nextCell) {
213
+ if (isHideCell(nextCell)) {
214
+ return getTargetNotHideCell(nextCell, direction)
215
+ }
216
+ return nextCell
217
+ }
218
+ }
219
+ }
220
+ return null
221
+ }
222
+ /**
223
+ * 针对新行,判断是否需要隐藏部分单元格
224
+ */
225
+ const hideCellWhereInCross = (newRow: KNode) => {
226
+ const rows = newRow.parent!.children!
227
+ const newRowIndex = rows.findIndex(item => item.isEqual(newRow))
228
+ for (let i = 0; i < newRow.children!.length; i++) {
229
+ //新行的单元格
230
+ const cell = newRow.children![i]
231
+ //跳过已经隐藏的单元格
232
+ if (isHideCell(cell)) {
233
+ continue
234
+ }
235
+ //获取上面的最近的一个非隐藏单元格
236
+ const targetCell = getTargetNotHideCell(cell, 'top')
237
+ if (targetCell) {
238
+ const { rowCount, colCount } = getCellSize(targetCell)
239
+ const rowIndex = rows.findIndex(item => item.isEqual(targetCell.parent!))
240
+ const colIndex = targetCell.parent!.children!.findIndex(item => item.isEqual(targetCell))
241
+ //该列在跨行的单元格范围内
242
+ if (rowIndex + rowCount - 1 >= newRowIndex) {
243
+ //需要考虑这个跨行的单元格是否跨列,所以采用循环遍历
244
+ for (let j = colIndex; j < colIndex + colCount; j++) {
245
+ setCellToHide(newRow.children![j])
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * 设置单元格隐藏
253
+ */
254
+ const setCellToHide = (cell: KNode) => {
255
+ if (isHideCell(cell)) {
256
+ return
257
+ }
258
+ if (cell.hasStyles()) {
259
+ cell.styles!.display = 'none'
260
+ } else {
261
+ cell.styles = { display: 'none' }
262
+ }
263
+ if (cell.hasMarks()) {
264
+ const marks: KNodeMarksType = {}
265
+ Object.keys(cell.marks!).forEach(markName => {
266
+ if (markName != 'rowspan' && markName != 'colspan') {
267
+ marks[markName] = cell.marks![markName]
268
+ }
269
+ })
270
+ cell.marks = { ...marks }
271
+ }
272
+ cell.void = true
273
+ const placeholderNode = KNode.createPlaceholder()
274
+ cell.children = [placeholderNode]
275
+ placeholderNode.parent = cell
276
+ }
277
+ /**
278
+ * 隐藏的单元格恢复显示
279
+ */
280
+ const setCellNotHide = (cell: KNode) => {
281
+ if (!isHideCell(cell)) {
282
+ return
283
+ }
284
+ const styles: KNodeStylesType = {}
285
+ Object.keys(cell.styles!).forEach(styleName => {
286
+ if (styleName != 'display') {
287
+ styles[styleName] = cell.styles![styleName]
288
+ }
289
+ })
290
+ cell.styles = { ...styles }
291
+ cell.void = false
292
+ const placeholderNode = KNode.createPlaceholder()
293
+ cell.children = [placeholderNode]
294
+ placeholderNode.parent = cell
295
+ }
296
+ /**
297
+ * 合并两个单元格
298
+ */
299
+ const mergeTwoCell = (cell: KNode, targetCell: KNode, direction: TableCellsMergeDirectionType) => {
300
+ const cellSize = getCellSize(cell)
301
+ const targetCellSize = getCellSize(targetCell)
302
+ if (direction == 'left') {
303
+ const children = cell.children!.map(item => {
304
+ item.parent = targetCell
305
+ return item
306
+ })
307
+ targetCell.children = [...targetCell.children!, ...children]
308
+ if (targetCell.hasMarks()) {
309
+ targetCell.marks!['colspan'] = targetCellSize.colCount + cellSize.colCount
310
+ } else {
311
+ targetCell.marks = {
312
+ colspan: targetCellSize.colCount + cellSize.colCount
313
+ }
314
+ }
315
+ setCellToHide(cell)
316
+ } else if (direction == 'right') {
317
+ const children = targetCell.children!.map(item => {
318
+ item.parent = cell
319
+ return item
320
+ })
321
+ cell.children = [...cell.children!, ...children]
322
+ if (cell.hasMarks()) {
323
+ cell.marks!['colspan'] = cellSize.colCount + targetCellSize.colCount
324
+ } else {
325
+ cell.marks = {
326
+ colspan: cellSize.colCount + targetCellSize.colCount
327
+ }
328
+ }
329
+ setCellToHide(targetCell)
330
+ } else if (direction == 'top') {
331
+ const children = cell.children!.map(item => {
332
+ item.parent = targetCell
333
+ return item
334
+ })
335
+ targetCell.children = [...targetCell.children!, ...children!]
336
+ if (targetCell.hasMarks()) {
337
+ targetCell.marks!['rowspan'] = targetCellSize.rowCount + cellSize.rowCount
338
+ } else {
339
+ targetCell.marks = {
340
+ rowspan: targetCellSize.rowCount + cellSize.rowCount
341
+ }
342
+ }
343
+ setCellToHide(cell)
344
+ } else if (direction == 'bottom') {
345
+ const children = targetCell.children!.map(item => {
346
+ item.parent = cell
347
+ return item
348
+ })
349
+ cell.children = [...cell.children!, ...children]
350
+ if (cell.hasMarks()) {
351
+ cell.marks!['rowspan'] = cellSize.rowCount + targetCellSize.rowCount
352
+ } else {
353
+ cell.marks = {
354
+ rowspan: cellSize.rowCount + targetCellSize.rowCount
355
+ }
356
+ }
357
+ setCellToHide(targetCell)
358
+ }
359
+ }
360
+ /**
361
+ * 获取最大宽度
362
+ */
363
+ const getMaxWidth = (element: HTMLElement): number => {
364
+ const parentElement = element.parentElement!
365
+ let maxWidth = DapElement.width(parentElement)
366
+ if (!maxWidth) {
367
+ maxWidth = getMaxWidth(parentElement)
368
+ }
369
+ return maxWidth
370
+ }
371
+ /**
372
+ * 设置表格拖拽改变列宽
373
+ */
374
+ const tableResizable = (editor: Editor) => {
375
+ //设置拖拽改变大小的功能
376
+ interact('.Kaitify table td').unset()
377
+ interact('.Kaitify table td').resizable({
378
+ //是否启用
379
+ enabled: true,
380
+ //指定可以调整大小的边缘
381
+ edges: { left: false, right: true, bottom: false, top: false },
382
+ //设置鼠标样式
383
+ cursorChecker(_action, _interactable, element, _interacting) {
384
+ return editor.isEditable() && element.nextElementSibling ? 'ew-resize' : 'default'
385
+ },
386
+ //启用惯性效果
387
+ inertia: false,
388
+ //调整大小时的自动滚动功能
389
+ autoScroll: true,
390
+ //保持宽高比
391
+ preserveAspectRatio: true,
392
+ //水平调整
393
+ axis: 'x',
394
+ //事件
395
+ listeners: {
396
+ //开始拖拽
397
+ start(event) {
398
+ //最后一列不能拖拽、不可编辑状态下不能拖拽
399
+ if (!event.target.nextElementSibling || !editor.isEditable()) {
400
+ event.interaction.stop()
401
+ return
402
+ }
403
+ //禁用dragstart
404
+ DapEvent.on(event.target, 'dragstart', e => e.preventDefault())
405
+ //获取单元格节点
406
+ const node = editor.findNode(event.target)
407
+ //获取单元格在父节点中的序列
408
+ const index = node.parent!.children!.findIndex(item => item.isEqual(node))
409
+ //获取单元格所在的表格
410
+ const table = node.getMatchNode({ tag: 'table' })!
411
+ //获取表格的colgroup节点
412
+ const colgroup = table.children!.find(item => item.isMatch({ tag: 'colgroup' }))!
413
+ //获取对应的col节点
414
+ const col = colgroup.children![index]
415
+ //获取对应的真实dom
416
+ const colDom = editor.findDom(col)
417
+ //暂存
418
+ DapData.set(event.target, 'col', col)
419
+ DapData.set(event.target, 'colDom', colDom)
420
+ },
421
+ //拖拽
422
+ move(event) {
423
+ //获取宽度
424
+ const { width } = event.rect
425
+ //获取暂存的col元素
426
+ const colDom = DapData.get(event.target, 'colDom') as HTMLElement
427
+ //设置宽度
428
+ colDom.setAttribute('width', `${width}px`)
429
+ },
430
+ //结束拖拽
431
+ end(event) {
432
+ //恢复dragstart
433
+ DapEvent.off(event.target, 'dragstart')
434
+ //获取宽度
435
+ const { width } = event.rect
436
+ //设置百分比宽度
437
+ const percentWidth = Number(((width / event.target.parentElement.offsetWidth) * 100).toFixed(2))
438
+ //获取暂存的col节点
439
+ const col = DapData.get(event.target, 'col') as KNode
440
+ //设置节点的styles
441
+ if (col.hasStyles()) {
442
+ col.marks!.width = `${percentWidth}%`
443
+ } else {
444
+ col.marks = {
445
+ width: `${percentWidth}%`
446
+ }
447
+ }
448
+ //更新视图
449
+ editor.updateView()
450
+ }
451
+ }
452
+ })
453
+ }
454
+
455
+ export const TableExtension = () =>
456
+ Extension.create({
457
+ name: 'table',
458
+ extraKeepTags: ['table', 'tfoot', 'tbody', 'thead', 'tr', 'th', 'td', 'col', 'colgroup'],
459
+ domParseNodeCallback(node) {
460
+ if (node.isMatch({ tag: 'table' })) {
461
+ node.type = 'block'
462
+ }
463
+ if (node.isMatch({ tag: 'tfoot' }) || node.isMatch({ tag: 'tbody' }) || node.isMatch({ tag: 'thead' }) || node.isMatch({ tag: 'tr' })) {
464
+ node.type = 'block'
465
+ node.fixed = true
466
+ node.nested = true
467
+ }
468
+ if (node.isMatch({ tag: 'th' }) || node.isMatch({ tag: 'td' })) {
469
+ node.type = 'block'
470
+ node.fixed = true
471
+ node.nested = true
472
+ if (isHideCell(node)) {
473
+ node.void = true
474
+ }
475
+ }
476
+ if (node.isMatch({ tag: 'colgroup' })) {
477
+ node.type = 'block'
478
+ node.fixed = true
479
+ node.nested = true
480
+ node.void = true
481
+ }
482
+ if (node.isMatch({ tag: 'col' })) {
483
+ node.type = 'closed'
484
+ node.void = true
485
+ }
486
+ return node
487
+ },
488
+ pasteKeepStyles(node) {
489
+ const styles: KNodeStylesType = {}
490
+ //表格单元格保留display样式
491
+ if (node.isMatch({ tag: 'td' }) || node.isMatch({ tag: 'th' })) {
492
+ if (node.styles!.hasOwnProperty('display')) styles.display = node.styles!.display
493
+ }
494
+ return styles
495
+ },
496
+ pasteKeepMarks(node) {
497
+ const marks: KNodeMarksType = {}
498
+ //表格单元格rowspan和colspan属性保留
499
+ if (node.isMatch({ tag: 'td' }) || node.isMatch({ tag: 'th' })) {
500
+ if (node.marks!.hasOwnProperty('rowspan')) marks['rowspan'] = node.marks!['rowspan']
501
+ if (node.marks!.hasOwnProperty('colspan')) marks['colspan'] = node.marks!['colspan']
502
+ }
503
+ return marks
504
+ },
505
+ formatRules: [
506
+ //表格相关节点类型设置
507
+ ({ node }) => {
508
+ if (node.isMatch({ tag: 'table' })) {
509
+ node.type = 'block'
510
+ }
511
+ if (node.isMatch({ tag: 'tfoot' }) || node.isMatch({ tag: 'tbody' }) || node.isMatch({ tag: 'thead' }) || node.isMatch({ tag: 'tr' })) {
512
+ node.type = 'block'
513
+ node.fixed = true
514
+ node.nested = true
515
+ }
516
+ if (node.isMatch({ tag: 'th' }) || node.isMatch({ tag: 'td' })) {
517
+ node.type = 'block'
518
+ node.fixed = true
519
+ node.nested = true
520
+ if (isHideCell(node)) {
521
+ node.void = true
522
+ }
523
+ }
524
+ if (node.isMatch({ tag: 'colgroup' })) {
525
+ node.type = 'block'
526
+ node.fixed = true
527
+ node.nested = true
528
+ node.void = true
529
+ }
530
+ if (node.isMatch({ tag: 'col' })) {
531
+ node.type = 'closed'
532
+ node.void = true
533
+ }
534
+ },
535
+ //表格结构改造
536
+ ({ editor, node }) => {
537
+ if (node.isMatch({ tag: 'table' })) {
538
+ //获取表格下所有的节点
539
+ const nodes = KNode.flat(node.children!)
540
+ //获取tbody节点
541
+ let tbody = nodes.find(item => item.isMatch({ tag: 'tbody' }))
542
+ //如果tbody节点不存在,则创建该节点
543
+ if (!tbody) {
544
+ tbody = KNode.create({
545
+ type: 'block',
546
+ tag: 'tbody',
547
+ nested: true,
548
+ fixed: true,
549
+ children: []
550
+ })
551
+ }
552
+ //获取所有的表格行节点并设置为tbody的子节点
553
+ const rows = nodes
554
+ .filter(item => item.isMatch({ tag: 'tr' }))
555
+ .map(item => {
556
+ item.parent = tbody
557
+ //过滤非法的子节点
558
+ if (item.hasChildren()) {
559
+ item.children = item.children!.filter(it => it.isMatch({ tag: 'td' }) || it.isMatch({ tag: 'th' }))
560
+ }
561
+ return item
562
+ })
563
+ tbody.children = [...rows]
564
+ //获取表格列数
565
+ const { colCount } = getTableSize(rows)
566
+ //获取colgroup节点
567
+ let colgroup = nodes.find(item => item.isMatch({ tag: 'colgroup' }))
568
+ //colgroup节点存在
569
+ if (colgroup) {
570
+ //过滤非法的子节点
571
+ colgroup.children = colgroup.children!.filter(item => item.isMatch({ tag: 'col' }))
572
+ //遍历每个col节点
573
+ colgroup.children!.forEach(col => {
574
+ //没有标记
575
+ if (!col.hasMarks()) {
576
+ col.marks = {
577
+ width: 'auto'
578
+ }
579
+ }
580
+ //没有width标记
581
+ else if (!col.marks!['width']) {
582
+ col.marks!['width'] = 'auto'
583
+ }
584
+ })
585
+ //对缺少的col元素进行补全
586
+ const length = colgroup.children!.length
587
+ for (let i = 0; i < colCount - length; i++) {
588
+ const col = KNode.create({
589
+ type: 'closed',
590
+ tag: 'col',
591
+ marks: {
592
+ width: 'auto'
593
+ },
594
+ void: true
595
+ })
596
+ editor.addNode(col, colgroup, colgroup.children!.length)
597
+ }
598
+ }
599
+ //colgroup节点不存在,则创建该节点
600
+ else {
601
+ const children: KNodeCreateOptionType[] = []
602
+ for (let i = colCount - 1; i >= 0; i--) {
603
+ children.push({
604
+ type: 'closed',
605
+ tag: 'col',
606
+ marks: {
607
+ width: 'auto'
608
+ },
609
+ void: true
610
+ })
611
+ }
612
+ colgroup = KNode.create({
613
+ type: 'block',
614
+ tag: 'colgroup',
615
+ fixed: true,
616
+ nested: true,
617
+ void: true,
618
+ children: children
619
+ })
620
+ }
621
+ //将colgroup和tbody设为表格的子节点
622
+ node.children = [colgroup, tbody]
623
+ colgroup.parent = node
624
+ tbody.parent = node
625
+ }
626
+ },
627
+ //th转td
628
+ ({ node }) => {
629
+ if (node.isMatch({ tag: 'th' })) {
630
+ node.tag = 'td'
631
+ }
632
+ },
633
+ //针对跨行跨列的单元格,增加隐藏单元格
634
+ ({ editor, node }) => {
635
+ if (node.isMatch({ tag: 'table' })) {
636
+ //所有行
637
+ const rows = node.children!.find(item => item.isMatch({ tag: 'tbody' }))!.children!
638
+ //过滤表格中的隐藏单元格
639
+ filterHideCells(rows)
640
+ //重新设置表格的隐藏单元格
641
+ resetTableHideCells(editor, rows)
642
+ }
643
+ }
644
+ ],
645
+ afterUpdateView() {
646
+ //表格拖拽改变列宽
647
+ tableResizable(this)
648
+ },
649
+ addCommands() {
650
+ /**
651
+ * 获取光标所在的表格节点,如果光标不在一个表格节点内,返回null
652
+ */
653
+ const getTable = () => {
654
+ return this.getMatchNodeBySelection({
655
+ tag: 'table'
656
+ })
657
+ }
658
+
659
+ /**
660
+ * 判断光标范围内是否有表格节点
661
+ */
662
+ const hasTable = () => {
663
+ return this.isSelectionNodesSomeMatch({
664
+ tag: 'table'
665
+ })
666
+ }
667
+
668
+ /**
669
+ * 是否可以合并单元格
670
+ */
671
+ const canMergeTableCells = (direction: TableCellsMergeDirectionType) => {
672
+ if (!this.selection.focused()) {
673
+ return false
674
+ }
675
+ //光标所在的单元格
676
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })
677
+ //光标在一个单元格内
678
+ if (cell && !isHideCell(cell)) {
679
+ //获取指定的合并的单元格
680
+ const targetCell = getTargetNotHideCell(cell, direction)
681
+ //单元格存在
682
+ if (targetCell) {
683
+ if (direction == 'left' || direction == 'right') {
684
+ const rows = cell.parent!.parent!.children!.filter(row => row.children!.some(n => !isHideCell(n)))
685
+ //只有一行
686
+ if (rows.length == 1) {
687
+ return true
688
+ }
689
+ const targetIndex = targetCell.parent!.children!.findIndex(item => item.isEqual(targetCell))
690
+ const cellIndex = cell.parent!.children!.findIndex(item => item.isEqual(cell))
691
+ if (direction == 'left') {
692
+ const size = getCellSize(targetCell)
693
+ //这两个单元格不相邻,也就是中间有隐藏的单元格并且隐藏单元格是跨行单元格的
694
+ if (cellIndex - targetIndex > size.colCount) {
695
+ return false
696
+ }
697
+ }
698
+ if (direction == 'right') {
699
+ const size = getCellSize(cell)
700
+ //这两个单元格不相邻,也就是中间有隐藏的单元格并且隐藏单元格是跨行单元格的
701
+ if (targetIndex - cellIndex > size.colCount) {
702
+ return false
703
+ }
704
+ }
705
+ return getCellSize(targetCell).rowCount == getCellSize(cell).rowCount
706
+ }
707
+ if (direction == 'top' || direction == 'bottom') {
708
+ const totalRows = cell.parent!.parent!.children!
709
+ const rows = totalRows.filter(row => row.children!.some(n => !isHideCell(n)))
710
+ const onlyOneCell = rows.every(row => row.children!.filter(item => !isHideCell(item)).length == 1)
711
+ //只有一列
712
+ if (onlyOneCell) {
713
+ return true
714
+ }
715
+ const targetIndex = totalRows.findIndex(item => item.isEqual(targetCell.parent!))
716
+ const cellIndex = totalRows.findIndex(item => item.isEqual(cell.parent!))
717
+ if (direction == 'top') {
718
+ const size = getCellSize(targetCell)
719
+ //这两个单元格不相邻,也就是中间有隐藏的单元格并且隐藏单元格是跨列单元格的
720
+ if (cellIndex - targetIndex > size.rowCount) {
721
+ return false
722
+ }
723
+ }
724
+ if (direction == 'bottom') {
725
+ const size = getCellSize(cell)
726
+ //这两个单元格不相邻,也就是中间有隐藏的单元格并且隐藏单元格是跨列单元格的
727
+ if (targetIndex - cellIndex > size.rowCount) {
728
+ return false
729
+ }
730
+ }
731
+ return getCellSize(targetCell).colCount == getCellSize(cell).colCount
732
+ }
733
+ }
734
+ }
735
+ return false
736
+ }
737
+
738
+ /**
739
+ * 插入表格
740
+ */
741
+ const setTable = async (options: { rows: number; columns: number }) => {
742
+ if (!this.selection.focused() || hasTable()) {
743
+ return
744
+ }
745
+ const rowNodes: KNodeCreateOptionType[] = []
746
+ const colNodes: KNodeCreateOptionType[] = []
747
+ for (let i = 0; i < options.rows; i++) {
748
+ const cellNodes: KNodeCreateOptionType[] = []
749
+ for (let j = 0; j < options.columns; j++) {
750
+ cellNodes.push({
751
+ type: 'block',
752
+ tag: 'td',
753
+ nested: true,
754
+ fixed: true,
755
+ children: [
756
+ {
757
+ type: 'block',
758
+ tag: this.blockRenderTag,
759
+ children: [
760
+ {
761
+ type: 'closed',
762
+ tag: 'br'
763
+ }
764
+ ]
765
+ }
766
+ ]
767
+ })
768
+ }
769
+ rowNodes.push({
770
+ type: 'block',
771
+ tag: 'tr',
772
+ nested: true,
773
+ fixed: true,
774
+ children: cellNodes
775
+ })
776
+ }
777
+ for (let i = 0; i < options.columns; i++) {
778
+ colNodes.push({
779
+ type: 'closed',
780
+ tag: 'col',
781
+ marks: {
782
+ width: 'auto'
783
+ },
784
+ void: true
785
+ })
786
+ }
787
+ const tableNode = KNode.create({
788
+ type: 'block',
789
+ tag: 'table',
790
+ children: [
791
+ {
792
+ type: 'block',
793
+ tag: 'colgroup',
794
+ fixed: true,
795
+ nested: true,
796
+ void: true,
797
+ children: colNodes
798
+ },
799
+ {
800
+ type: 'block',
801
+ tag: 'tbody',
802
+ fixed: true,
803
+ nested: true,
804
+ children: rowNodes
805
+ }
806
+ ]
807
+ })
808
+ this.insertNode(tableNode, true)
809
+ this.setSelectionBefore(tableNode, 'all')
810
+ await this.updateView()
811
+ }
812
+
813
+ /**
814
+ * 取消表格
815
+ */
816
+ const unsetTable = async () => {
817
+ const tableNode = getTable()
818
+ if (!tableNode) {
819
+ return
820
+ }
821
+ tableNode.toEmpty()
822
+ await this.updateView()
823
+ }
824
+
825
+ /**
826
+ * 合并单元格
827
+ */
828
+ const mergeTableCell = async (direction: TableCellsMergeDirectionType) => {
829
+ if (!canMergeTableCells(direction)) {
830
+ return
831
+ }
832
+ //光标所在的单元格
833
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })!
834
+ //目标单元格
835
+ const targetCell = getTargetNotHideCell(cell, direction)!
836
+ //进行合并
837
+ mergeTwoCell(cell, targetCell, direction)
838
+ //视图更新
839
+ await this.updateView()
840
+ }
841
+
842
+ /**
843
+ * 添加行
844
+ */
845
+ const addTableRow = async (direction: 'top' | 'bottom') => {
846
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })
847
+ //光标在某个非隐藏的单元格内
848
+ if (cell && !isHideCell(cell)) {
849
+ const row = cell.parent!
850
+ const rows = row.parent!.children!
851
+ const tableSize = getTableSize(rows)
852
+ const newRow = KNode.create({
853
+ type: 'block',
854
+ tag: 'tr',
855
+ nested: true,
856
+ fixed: true,
857
+ children: []
858
+ })
859
+ for (let i = 0; i < tableSize.colCount; i++) {
860
+ const newCell = KNode.create({
861
+ type: 'block',
862
+ tag: 'td',
863
+ nested: true,
864
+ fixed: true,
865
+ children: [
866
+ {
867
+ type: 'closed',
868
+ tag: 'br'
869
+ }
870
+ ]
871
+ })
872
+ this.addNode(newCell, newRow, newRow.children!.length)
873
+ }
874
+ //上面插入一行
875
+ if (direction == 'top') {
876
+ this.addNodeBefore(newRow, row)
877
+ }
878
+ //下面插入一行
879
+ else {
880
+ //获取单元格尺寸
881
+ const cellSize = getCellSize(cell)
882
+ let index = 1
883
+ let targetRow = row
884
+ //处理单元格跨行的情况,获取目标行
885
+ while (cellSize.rowCount > 1 && index < cellSize.rowCount) {
886
+ const nextRow = targetRow.getNext(rows)
887
+ if (!nextRow) {
888
+ break
889
+ }
890
+ targetRow = nextRow
891
+ index++
892
+ }
893
+ //在目标行后插入新行
894
+ this.addNodeAfter(newRow, targetRow)
895
+ //针对新行,判断是否需要隐藏部分单元格
896
+ hideCellWhereInCross(newRow)
897
+ }
898
+ this.setSelectionBefore(newRow, 'all')
899
+ }
900
+ await this.updateView()
901
+ }
902
+
903
+ /**
904
+ * 删除行
905
+ */
906
+ const deleteTableRow = async () => {
907
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })
908
+ //光标在某个非隐藏的单元格内
909
+ if (cell && !isHideCell(cell)) {
910
+ //所在行
911
+ const row = cell.parent!
912
+ //表格全部行
913
+ const rows = row.parent!.children!
914
+ //表格节点
915
+ const table = row.parent!.parent!
916
+ //只有一行,删除表格
917
+ if (rows.length == 1) {
918
+ table.toEmpty()
919
+ }
920
+ //正常删除
921
+ else {
922
+ //上一行
923
+ const previousRow = row.getPrevious(rows)
924
+ //下一行
925
+ const nextRow = row.getNext(rows)
926
+ //当前行的序列
927
+ const rowIndex = rows.findIndex(item => item.isEqual(row))
928
+ //遍历该行的每一个单元格
929
+ row.children!.forEach((currentCell, index) => {
930
+ //获取单元格尺寸
931
+ const cellSize = getCellSize(currentCell)
932
+ //是隐藏单元格
933
+ if (isHideCell(currentCell)) {
934
+ //获取上方最近的非隐藏单元格
935
+ const upCell = getTargetNotHideCell(currentCell, 'top')
936
+ //存在非隐藏单元格
937
+ if (upCell) {
938
+ //获取非隐藏单元格所在行在所有行中的序列
939
+ const upIndex = rows.findIndex(item => item.isEqual(upCell.parent!))
940
+ //获取非隐藏单元格的大小
941
+ const { rowCount } = getCellSize(upCell)
942
+ //当前隐藏单元格被上方非隐藏单元格所覆盖
943
+ if (rowIndex - upIndex < rowCount) {
944
+ upCell.marks!['rowspan'] = rowCount - 1
945
+ }
946
+ }
947
+ }
948
+ //是跨行单元格并且下一行存在
949
+ else if (cellSize.rowCount > 1 && nextRow) {
950
+ let i = index
951
+ while (i < index + cellSize.colCount) {
952
+ //获取下一行对应的单元格
953
+ const nextRowCell = nextRow.children![i]
954
+ if (isHideCell(nextRowCell)) {
955
+ setCellNotHide(nextRowCell)
956
+ if (nextRowCell.hasMarks()) {
957
+ nextRowCell.marks!['rowspan'] = cellSize.rowCount - 1
958
+ } else {
959
+ nextRowCell.marks = {
960
+ rowspan: cellSize.rowCount - 1
961
+ }
962
+ }
963
+ }
964
+ i++
965
+ }
966
+ }
967
+ })
968
+ if (previousRow) {
969
+ this.setSelectionAfter(previousRow, 'all')
970
+ } else if (nextRow) {
971
+ this.setSelectionBefore(nextRow, 'all')
972
+ }
973
+ row.toEmpty()
974
+ }
975
+ await this.updateView()
976
+ }
977
+ }
978
+
979
+ /**
980
+ * 添加列
981
+ */
982
+ const addTableColumn = async (direction: 'left' | 'right') => {
983
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })
984
+ //光标在某个非隐藏的单元格内
985
+ if (cell && !isHideCell(cell)) {
986
+ const row = cell.parent!
987
+ const tbody = row.parent!
988
+ const table = tbody.parent!
989
+ const rows = tbody.children!
990
+ //单元格在行中的序列
991
+ let cellIndex = -1
992
+ //左侧插入列
993
+ if (direction == 'left') {
994
+ cellIndex = row.children!.findIndex(item => {
995
+ return item.isEqual(cell)
996
+ })
997
+ }
998
+ //右侧插入列
999
+ if (direction == 'right') {
1000
+ //获取单元格尺寸
1001
+ const cellSize = getCellSize(cell)
1002
+ let index = 1
1003
+ let targetCell = cell
1004
+ //处理单元格跨列的情况,获取目标列
1005
+ while (cellSize.colCount > 1 && index < cellSize.colCount) {
1006
+ const nextCell = targetCell.getNext(row.children!)
1007
+ if (!nextCell) {
1008
+ break
1009
+ }
1010
+ targetCell = nextCell
1011
+ index++
1012
+ }
1013
+ cellIndex = row.children!.findIndex(item => {
1014
+ return item.isEqual(targetCell)
1015
+ })
1016
+ }
1017
+ rows!.forEach(item => {
1018
+ const newCell = KNode.create({
1019
+ type: 'block',
1020
+ tag: 'td',
1021
+ fixed: true,
1022
+ nested: true,
1023
+ children: [
1024
+ {
1025
+ type: 'closed',
1026
+ tag: 'br'
1027
+ }
1028
+ ]
1029
+ })
1030
+ this.addNode(newCell, item, direction == 'left' ? cellIndex : cellIndex + 1)
1031
+ if (item.isEqual(row)) {
1032
+ this.setSelectionBefore(newCell, 'all')
1033
+ }
1034
+ })
1035
+ //插入col
1036
+ const colgroup = table.children!.find(item => item.isMatch({ tag: 'colgroup' }))!
1037
+ const col = KNode.create({
1038
+ type: 'closed',
1039
+ tag: 'col',
1040
+ marks: {
1041
+ width: 'auto'
1042
+ },
1043
+ void: true
1044
+ })
1045
+ this.addNode(col, colgroup, direction == 'left' ? cellIndex : cellIndex + 1)
1046
+ await this.updateView()
1047
+ }
1048
+ }
1049
+
1050
+ /**
1051
+ * 删除列
1052
+ */
1053
+ const deleteTableColumn = async () => {
1054
+ const cell = this.getMatchNodeBySelection({ tag: 'td' })
1055
+ //光标在某个非隐藏的单元格内
1056
+ if (cell && !isHideCell(cell)) {
1057
+ //所在行
1058
+ const row = cell.parent!
1059
+ //表格全部行
1060
+ const rows = row.parent!.children!
1061
+ //表格节点
1062
+ const table = row.parent!.parent!
1063
+ //光标所在行只有一个单元格则删除表格
1064
+ if (row.children!.length == 1) {
1065
+ table.toEmpty()
1066
+ }
1067
+ //正常删除
1068
+ else {
1069
+ //前一个单元格
1070
+ const previousCell = cell.getPrevious(row.children!)
1071
+ //后一个单元格
1072
+ const nextCell = cell.getNext(row.children!)
1073
+ //光标所在的单元格在行中的序列
1074
+ const cellIndex = row.children!.findIndex(item => {
1075
+ return item.isEqual(cell)
1076
+ })
1077
+ //遍历所有的行
1078
+ rows.forEach((item, index) => {
1079
+ //当前行对应序列的单元格
1080
+ const currentCell = item.children![cellIndex]
1081
+ //获取对应单元格的大小
1082
+ const cellSize = getCellSize(currentCell)
1083
+ //是隐藏的单元格
1084
+ if (isHideCell(currentCell)) {
1085
+ //获取左侧最近的非隐藏单元格
1086
+ const leftCell = getTargetNotHideCell(currentCell, 'left')
1087
+ //左侧存在非隐藏单元格
1088
+ if (leftCell) {
1089
+ //获取单元格的序列
1090
+ const leftIndex = item.children!.findIndex(n => n.isEqual(leftCell))
1091
+ //获取单元格的大小
1092
+ const { colCount } = getCellSize(leftCell)
1093
+ //当前隐藏单元格被左侧非隐藏单元格所覆盖
1094
+ if (cellIndex - leftIndex < colCount) {
1095
+ leftCell.marks!['colspan'] = colCount - 1
1096
+ }
1097
+ }
1098
+ }
1099
+ //是跨列的单元格
1100
+ else if (cellSize.colCount > 1) {
1101
+ let i = index
1102
+ while (i < index + cellSize.rowCount) {
1103
+ //获取每一行对应单元格的下一个单元格
1104
+ const nextCell = rows[i].children![cellIndex].getNext(rows[i].children!)
1105
+ if (nextCell && isHideCell(nextCell)) {
1106
+ setCellNotHide(nextCell)
1107
+ if (nextCell.hasMarks()) {
1108
+ nextCell.marks!['colspan'] = cellSize.colCount - 1
1109
+ } else {
1110
+ nextCell.marks = {
1111
+ colspan: cellSize.colCount - 1
1112
+ }
1113
+ }
1114
+ }
1115
+ i++
1116
+ }
1117
+ }
1118
+ currentCell.toEmpty()
1119
+ })
1120
+ //删除col
1121
+ const colgroup = table.children!.find(item => item.isMatch({ tag: 'colgroup' }))!
1122
+ colgroup.children![cellIndex].toEmpty()
1123
+ //重置光标
1124
+ if (previousCell) {
1125
+ this.setSelectionAfter(previousCell, 'all')
1126
+ } else if (nextCell) {
1127
+ this.setSelectionBefore(nextCell, 'all')
1128
+ }
1129
+ }
1130
+ //渲染
1131
+ await this.updateView()
1132
+ }
1133
+ }
1134
+
1135
+ return {
1136
+ getTable,
1137
+ hasTable,
1138
+ canMergeTableCells,
1139
+ setTable,
1140
+ unsetTable,
1141
+ mergeTableCell,
1142
+ addTableRow,
1143
+ deleteTableRow,
1144
+ addTableColumn,
1145
+ deleteTableColumn
1146
+ }
1147
+ }
1148
+ })