@ebl-vue/editor-full 1.0.8

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 (101) hide show
  1. package/.postcssrc.yml +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.mjs +2565 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +55 -0
  8. package/postcss.config.js +15 -0
  9. package/src/components/Editor/Editor.vue +209 -0
  10. package/src/components/index.ts +27 -0
  11. package/src/constants/index.ts +1 -0
  12. package/src/i18n/zh-cn.ts +151 -0
  13. package/src/icons/index.ts +78 -0
  14. package/src/index.ts +11 -0
  15. package/src/installer.ts +22 -0
  16. package/src/plugins/alert/index.css +150 -0
  17. package/src/plugins/alert/index.ts +463 -0
  18. package/src/plugins/block-alignment/index.css +9 -0
  19. package/src/plugins/block-alignment/index.ts +116 -0
  20. package/src/plugins/block-alignment/readme.md +1 -0
  21. package/src/plugins/code/LICENSE +21 -0
  22. package/src/plugins/code/index.css +120 -0
  23. package/src/plugins/code/index.ts +530 -0
  24. package/src/plugins/code/utils/string.ts +34 -0
  25. package/src/plugins/color-picker/index.ts +138 -0
  26. package/src/plugins/color-picker/styles.css +27 -0
  27. package/src/plugins/delimiter/index.css +14 -0
  28. package/src/plugins/delimiter/index.ts +122 -0
  29. package/src/plugins/drag-drop/index.css +19 -0
  30. package/src/plugins/drag-drop/index.ts +151 -0
  31. package/src/plugins/drag-drop/readme.md +1 -0
  32. package/src/plugins/header/H1.ts +405 -0
  33. package/src/plugins/header/H2.ts +403 -0
  34. package/src/plugins/header/H3.ts +404 -0
  35. package/src/plugins/header/H4.ts +405 -0
  36. package/src/plugins/header/H5.ts +405 -0
  37. package/src/plugins/header/H6.ts +406 -0
  38. package/src/plugins/header/index.css +20 -0
  39. package/src/plugins/header/index.ts +15 -0
  40. package/src/plugins/header/types.d.ts +46 -0
  41. package/src/plugins/indent/index.css +86 -0
  42. package/src/plugins/indent/index.ts +697 -0
  43. package/src/plugins/inline-code/index.css +11 -0
  44. package/src/plugins/inline-code/index.ts +205 -0
  45. package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +211 -0
  46. package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
  47. package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
  48. package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
  49. package/src/plugins/list/ListRenderer/index.ts +6 -0
  50. package/src/plugins/list/ListTabulator/index.ts +1179 -0
  51. package/src/plugins/list/index.ts +502 -0
  52. package/src/plugins/list/styles/CssPrefix.ts +4 -0
  53. package/src/plugins/list/styles/icons/index.ts +10 -0
  54. package/src/plugins/list/styles/input.css +36 -0
  55. package/src/plugins/list/styles/list.css +165 -0
  56. package/src/plugins/list/types/Elements.ts +14 -0
  57. package/src/plugins/list/types/ItemMeta.ts +40 -0
  58. package/src/plugins/list/types/ListParams.ts +102 -0
  59. package/src/plugins/list/types/ListRenderer.ts +6 -0
  60. package/src/plugins/list/types/OlCounterType.ts +63 -0
  61. package/src/plugins/list/types/index.ts +14 -0
  62. package/src/plugins/list/utils/focusItem.ts +18 -0
  63. package/src/plugins/list/utils/getChildItems.ts +40 -0
  64. package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
  65. package/src/plugins/list/utils/getItemContentElement.ts +10 -0
  66. package/src/plugins/list/utils/getSiblings.ts +52 -0
  67. package/src/plugins/list/utils/isLastItem.ts +9 -0
  68. package/src/plugins/list/utils/itemHasSublist.ts +10 -0
  69. package/src/plugins/list/utils/normalizeData.ts +84 -0
  70. package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
  71. package/src/plugins/list/utils/renderToolboxInput.ts +105 -0
  72. package/src/plugins/list/utils/stripNumbers.ts +7 -0
  73. package/src/plugins/list/utils/type-guards.ts +8 -0
  74. package/src/plugins/list.md +15 -0
  75. package/src/plugins/marker/index.css +4 -0
  76. package/src/plugins/marker/index.ts +187 -0
  77. package/src/plugins/paragraph/index.css +23 -0
  78. package/src/plugins/paragraph/index.ts +380 -0
  79. package/src/plugins/paragraph/types/icons.d.ts +4 -0
  80. package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
  81. package/src/plugins/quote/index.css +26 -0
  82. package/src/plugins/quote/index.ts +206 -0
  83. package/src/plugins/table/index.ts +4 -0
  84. package/src/plugins/table/plugin.ts +254 -0
  85. package/src/plugins/table/style.css +388 -0
  86. package/src/plugins/table/table.ts +1192 -0
  87. package/src/plugins/table/toolbox.ts +165 -0
  88. package/src/plugins/table/utils/dom.ts +128 -0
  89. package/src/plugins/table/utils/popover.ts +172 -0
  90. package/src/plugins/table/utils/throttled.ts +22 -0
  91. package/src/plugins/underline/index.css +3 -0
  92. package/src/plugins/underline/index.ts +216 -0
  93. package/src/plugins/undo/index.ts +509 -0
  94. package/src/plugins/undo/observer.ts +101 -0
  95. package/src/style.css +89 -0
  96. package/src/utils/index.ts +15 -0
  97. package/src/utils/install.ts +19 -0
  98. package/tsconfig.json +37 -0
  99. package/types/index.d.ts +13 -0
  100. package/types/plugins/index.d.ts +0 -0
  101. package/vite.config.ts +79 -0
@@ -0,0 +1,1192 @@
1
+ import Toolbox from './toolbox';
2
+ import * as $ from './utils/dom';
3
+ import throttled from './utils/throttled';
4
+
5
+ import {
6
+ IconDirectionLeftDown,
7
+ IconDirectionRightDown,
8
+ IconDirectionUpRight,
9
+ IconDirectionDownRight,
10
+ IconCross,
11
+ IconPlus
12
+ } from '../../icons';
13
+ type TableData = {
14
+ /**
15
+ * - number of rows in the table
16
+ */
17
+ /**
18
+ * - number of rows in the table
19
+ */
20
+ rows: number;
21
+ /**
22
+ * - number of columns in the table
23
+ */
24
+ /**
25
+ * - number of columns in the table
26
+ */
27
+ cols: number;
28
+ colWidth: string[];
29
+ }
30
+ const CSS = {
31
+ wrapper: 'tc-wrap',
32
+ wrapperReadOnly: 'tc-wrap--readonly',
33
+ table: 'tc-table',
34
+ row: 'tc-row',
35
+ withHeadings: 'tc-table--heading',
36
+ rowSelected: 'tc-row--selected',
37
+ cell: 'tc-cell',
38
+ cellSelected: 'tc-cell--selected',
39
+ addRow: 'tc-add-row',
40
+ addRowDisabled: 'tc-add-row--disabled',
41
+ addColumn: 'tc-add-column',
42
+ addColumnDisabled: 'tc-add-column--disabled',
43
+ };
44
+
45
+ /**
46
+ * @typedef {object} TableConfig
47
+ * @description Tool's config from Editor
48
+ * @property {boolean} withHeadings — Uses the first line as headings
49
+ * @property {string[][]} withHeadings — two-dimensional array with table contents
50
+ */
51
+
52
+ /**
53
+ * @typedef {object} TableData - object with the data transferred to form a table
54
+ * @property {number} rows - number of rows in the table
55
+ * @property {number} cols - number of columns in the table
56
+ */
57
+
58
+
59
+ /**
60
+ * Generates and manages table contents.
61
+ */
62
+ export default class Table {
63
+
64
+ private readOnly: boolean;
65
+ private api: any;
66
+ private data: any;
67
+ private config: any;
68
+ private wrapper: any;
69
+ private table: any;
70
+ private toolboxColumn: any;
71
+ private toolboxRow: any;
72
+ private documentClicked: any;
73
+ private hoveredRow: any;
74
+ private hoveredColumn: any;
75
+ private selectedRow: any;
76
+ private selectedColumn: any;
77
+ private tunes: any;
78
+ private focusedCell: any;
79
+ private numberOfColumns: number;
80
+ private hoveredCell: any;
81
+ private isDragging: boolean;
82
+ private draggingRow: number;
83
+ private draggingColumn: number;
84
+ private draggingColArr: any[];
85
+ private startWidth: number;
86
+ private mouseStartX: number;
87
+ private colWidthArr:any[];
88
+ private minCellWidhth: number=50;
89
+ private defaultCellWidth = 100;
90
+ /**
91
+ * Creates
92
+ *
93
+ * @constructor
94
+ * @param {boolean} readOnly - read-only mode flag
95
+ * @param {object} api - Editor.js API
96
+ * @param {TableData} data - Editor.js API
97
+ * @param {TableConfig} config - Editor.js API
98
+ */
99
+ constructor(readOnly: boolean, api: any, data: TableData, config: any) {
100
+ this.readOnly = readOnly;
101
+ this.api = api;
102
+ this.data = data;
103
+ this.config = config;
104
+
105
+ /**
106
+ * DOM nodes
107
+ */
108
+ this.wrapper = null;
109
+ this.table = null;
110
+ this.hoveredCell = null;
111
+
112
+ this.isDragging = false;
113
+ this.draggingColArr = [];
114
+ this.draggingRow = 0;
115
+ this.draggingColumn = 0;
116
+ this.startWidth = 0;
117
+ this.mouseStartX = 0;
118
+ this.colWidthArr = [];
119
+
120
+
121
+ // 鼠标焦点数据
122
+ this.focusedCell = {
123
+ row: 0,
124
+ col: 0,
125
+ };
126
+ /**
127
+ * Toolbox for managing of columns
128
+ */
129
+ this.toolboxColumn = this.createColumnToolbox();
130
+ this.toolboxRow = this.createRowToolbox();
131
+
132
+ /**
133
+ * Create table and wrapper elements
134
+ */
135
+ this.createTableWrapper();
136
+
137
+ // Current hovered row index
138
+ this.hoveredRow = 0;
139
+
140
+ // Current hovered column index
141
+ this.hoveredColumn = 0;
142
+
143
+ // Index of last selected row via toolbox
144
+ this.selectedRow = 0;
145
+
146
+ // Index of last selected column via toolbox
147
+ this.selectedColumn = 0;
148
+
149
+ // Additional settings for the table
150
+ this.tunes = {
151
+ withHeadings: false
152
+ };
153
+
154
+ /**
155
+ * Resize table to match config/data size
156
+ */
157
+ this.resize();
158
+ if(this.data.colWidth.length > 0) {
159
+ this.colWidthArr = this.data.colWidth;
160
+ }
161
+ this.updateColWidth();
162
+ /**
163
+ * Fill the table with data
164
+ */
165
+ this.fill();
166
+
167
+ /**
168
+ * The cell in which the focus is currently located, if 0 and 0 then there is no focus
169
+ * Uses to switch between cells with buttons
170
+ */
171
+ this.focusedCell = {
172
+ row: 0,
173
+ column: 0
174
+ };
175
+
176
+ /**
177
+ * Global click listener allows to delegate clicks on some elements
178
+ */
179
+ this.documentClicked = (event:MouseEvent) => {
180
+ const clickedInsideTable = event.target.closest(`.${CSS.table}`) !== null;
181
+ const outsideTableClicked = event.target.closest(`.${CSS.wrapper}`) === null;
182
+ const clickedOutsideToolboxes = clickedInsideTable || outsideTableClicked;
183
+
184
+ if (clickedOutsideToolboxes) {
185
+ this.hideToolboxes();
186
+ }
187
+
188
+ const clickedOnAddRowButton = event.target.closest(`.${CSS.addRow}`);
189
+ const clickedOnAddColumnButton = event.target.closest(`.${CSS.addColumn}`);
190
+
191
+ /**
192
+ * Also, check if clicked in current table, not other (because documentClicked bound to the whole document)
193
+ */
194
+ if (clickedOnAddRowButton && clickedOnAddRowButton.parentNode === this.wrapper) {
195
+ this.addRow(undefined, true);
196
+ this.hideToolboxes();
197
+ } else if (clickedOnAddColumnButton && clickedOnAddColumnButton.parentNode === this.wrapper) {
198
+ this.addColumn(undefined, true);
199
+ this.hideToolboxes();
200
+ }
201
+ };
202
+
203
+ if (!this.readOnly) {
204
+ this.bindEvents();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Returns the rendered table wrapper
210
+ *
211
+ * @returns {Element}
212
+ */
213
+ getWrapper() {
214
+ return this.wrapper;
215
+ }
216
+
217
+ /**
218
+ * Hangs the necessary handlers to events
219
+ */
220
+ bindEvents() {
221
+ // set the listener to close toolboxes when click outside
222
+ document.addEventListener('click', this.documentClicked);
223
+ // 鼠标点击事件:判定是否在表格内部
224
+ document.addEventListener("mousedown", (e) =>
225
+ this.handleDocumentMousedown(e)
226
+ );
227
+ // Update toolboxes position depending on the mouse movements
228
+ this.table.addEventListener('mousemove', throttled(150, (event:MouseEvent) => this.onMouseMoveInTable(event)), { passive: true });
229
+
230
+ // Controls some of the keyboard buttons inside the table
231
+ this.table.onkeypress = (event:KeyboardEvent) => this.onKeyPressListener(event);
232
+
233
+ // Tab is executed by default before keypress, so it must be intercepted on keydown
234
+ this.table.addEventListener('keydown', (event:KeyboardEvent) => this.onKeyDownListener(event));
235
+
236
+ // Determine the position of the cell in focus
237
+ this.table.addEventListener('focusin', (event:FocusEvent) => this.focusInTableListener(event));
238
+ }
239
+ handleDocumentMousedown = (e:MouseEvent) => {
240
+ // 在表格内部按下鼠标并且处于拖拽位置
241
+ if (this.wrapper.contains(e.target)) {
242
+ const target = e.target as HTMLElement;
243
+ if (target.classList.contains("cell-resizable")) {
244
+ e.preventDefault();
245
+ //this.hideToolbox(); // 拖拽时隐藏工具栏
246
+ this.isDragging = true;
247
+ this.table.classList.add("table-resizing"); //为整个表格添加光标样式
248
+ document.addEventListener("mousemove", this.handleDocumentMousemove);
249
+ document.addEventListener("mouseup", this.handleDocumentMouseup);
250
+ this.draggingRow = this.hoveredRow;
251
+ this.draggingColumn = this.hoveredColumn;
252
+ this.draggingColArr = this.getCellInCol(this.draggingColumn);
253
+ this.startWidth = this.colWidthArr[this.draggingColumn - 1]; //获取初始宽度
254
+ this.mouseStartX = e.clientX;
255
+ }
256
+ } else {
257
+ //this.hideToolbox();
258
+ }
259
+ };
260
+ // 获取同一列的所有单元格
261
+ getCellInCol(col:number) {
262
+ const cells:any[] = [];
263
+ const rows = this.table.querySelectorAll(`.${CSS.row}`);
264
+ rows.forEach((row:Element) => {
265
+ const cell = row.querySelector(`.${CSS.cell}:nth-child(${col})`);
266
+ if (cell) cells.push(cell);
267
+ });
268
+ return cells;
269
+ }
270
+ // mousemove事件
271
+ handleDocumentMousemove = (e) => {
272
+ if (!this.isDragging) return;
273
+ const currentMouseX = e.clientX;
274
+ const deltaX = currentMouseX - this.mouseStartX;
275
+ const newWidth = Math.max(this.startWidth + deltaX, this.minCellWidhth);
276
+
277
+ this.colWidthArr[this.draggingColumn - 1] = newWidth;
278
+ this.updateColWidth();
279
+ };
280
+ // mouseup事件
281
+ handleDocumentMouseup = (e) => {
282
+ //this.showToolbox(); // 拖拽结束时重新显示工具栏
283
+ //this.updateToolboxPosition();
284
+ this.updateToolboxesPosition();
285
+ this.isDragging = false;
286
+ this.table.classList.remove("table-resizing");
287
+ document.removeEventListener("mousemove", this.handleDocumentMousemove);
288
+ document.removeEventListener("mouseup", this.handleDocumentMouseup);
289
+ };
290
+ /**
291
+ * Configures and creates the toolbox for manipulating with columns
292
+ *
293
+ * @returns {Toolbox}
294
+ */
295
+ createColumnToolbox() {
296
+ return new Toolbox({
297
+ api: this.api,
298
+ cssModifier: 'column',
299
+ items: [
300
+ {
301
+ label: this.api.i18n.t('Add column to left'),
302
+ icon: IconDirectionLeftDown,
303
+ hideIf: () => {
304
+ return this.numberOfColumns === this.config.maxcols
305
+ },
306
+ onClick: () => {
307
+ this.addColumn(this.selectedColumn, true);
308
+ this.hideToolboxes();
309
+ this.updateColWidth();
310
+ }
311
+ },
312
+ {
313
+ label: this.api.i18n.t('Add column to right'),
314
+ icon: IconDirectionRightDown,
315
+ hideIf: () => {
316
+ return this.numberOfColumns === this.config.maxcols
317
+ },
318
+ onClick: () => {
319
+ this.addColumn(this.selectedColumn + 1, true);
320
+ this.hideToolboxes();
321
+ this.updateColWidth();
322
+ }
323
+ },
324
+ {
325
+ label: this.api.i18n.t('Delete column'),
326
+ icon: IconCross,
327
+ hideIf: () => {
328
+ return this.numberOfColumns === 1;
329
+ },
330
+ confirmationRequired: true,
331
+ onClick: () => {
332
+ this.deleteColumn(this.selectedColumn);
333
+ this.hideToolboxes();
334
+ this.updateColWidth();
335
+ }
336
+ }
337
+ ],
338
+ onOpen: () => {
339
+ this.selectColumn(this.hoveredColumn);
340
+ this.hideRowToolbox();
341
+ },
342
+ onClose: () => {
343
+ this.unselectColumn();
344
+ }
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Configures and creates the toolbox for manipulating with rows
350
+ *
351
+ * @returns {Toolbox}
352
+ */
353
+ createRowToolbox() {
354
+ return new Toolbox({
355
+ api: this.api,
356
+ cssModifier: 'row',
357
+ items: [
358
+ {
359
+ label: this.api.i18n.t('Add row above'),
360
+ icon: IconDirectionUpRight,
361
+ hideIf: () => {
362
+ return this.numberOfRows === this.config.maxrows
363
+ },
364
+ onClick: () => {
365
+ this.addRow(this.selectedRow, true);
366
+ this.hideToolboxes();
367
+ }
368
+ },
369
+ {
370
+ label: this.api.i18n.t('Add row below'),
371
+ icon: IconDirectionDownRight,
372
+ hideIf: () => {
373
+ return this.numberOfRows === this.config.maxrows
374
+ },
375
+ onClick: () => {
376
+ this.addRow(this.selectedRow + 1, true);
377
+ this.hideToolboxes();
378
+ }
379
+ },
380
+ {
381
+ label: this.api.i18n.t('Delete row'),
382
+ icon: IconCross,
383
+ hideIf: () => {
384
+ return this.numberOfRows === 1;
385
+ },
386
+ confirmationRequired: true,
387
+ onClick: () => {
388
+ this.deleteRow(this.selectedRow);
389
+ this.hideToolboxes();
390
+ }
391
+ }
392
+ ],
393
+ onOpen: () => {
394
+ this.selectRow(this.hoveredRow);
395
+ this.hideColumnToolbox();
396
+ },
397
+ onClose: () => {
398
+ this.unselectRow();
399
+ }
400
+ });
401
+ }
402
+
403
+ /**
404
+ * When you press enter it moves the cursor down to the next row
405
+ * or creates it if the click occurred on the last one
406
+ */
407
+ moveCursorToNextRow() {
408
+ if (this.focusedCell.row !== this.numberOfRows) {
409
+ this.focusedCell.row += 1;
410
+ //this.focusCell(this.focusedCell);
411
+ this.focusCell();
412
+ } else {
413
+ this.addRow();
414
+ this.focusedCell.row += 1;
415
+ //this.focusCell(this.focusedCell);
416
+ this.focusCell();
417
+ this.updateToolboxesPosition(0, 0);
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Get table cell by row and col index
423
+ *
424
+ * @param {number} row - cell row coordinate
425
+ * @param {number} column - cell column coordinate
426
+ * @returns {HTMLElement}
427
+ */
428
+ getCell(row:number, column:number) {
429
+ return this.table.querySelectorAll(`.${CSS.row}:nth-child(${row}) .${CSS.cell}`)[column - 1];
430
+ }
431
+
432
+ /**
433
+ * Get table row by index
434
+ *
435
+ * @param {number} row - row coordinate
436
+ * @returns {HTMLElement}
437
+ */
438
+ getRow(row:number) {
439
+ return this.table.querySelector(`.${CSS.row}:nth-child(${row})`);
440
+ }
441
+
442
+ /**
443
+ * The parent of the cell which is the row
444
+ *
445
+ * @param {HTMLElement} cell - cell element
446
+ * @returns {HTMLElement}
447
+ */
448
+ getRowByCell(cell:Element) {
449
+ return cell.parentElement;
450
+ }
451
+
452
+ /**
453
+ * Ger row's first cell
454
+ *
455
+ * @param {Element} row - row to find its first cell
456
+ * @returns {Element}
457
+ */
458
+ getRowFirstCell(row:Element) {
459
+ return row.querySelector(`.${CSS.cell}:first-child`);
460
+ }
461
+
462
+ /**
463
+ * Set the sell's content by row and column numbers
464
+ *
465
+ * @param {number} row - cell row coordinate
466
+ * @param {number} column - cell column coordinate
467
+ * @param {string} content - cell HTML content
468
+ */
469
+ setCellContent(row:number, column:number, content:string) {
470
+ const cell = this.getCell(row, column);
471
+
472
+ cell.innerHTML = content;
473
+ }
474
+
475
+ /**
476
+ * Add column in table on index place
477
+ * Add cells in each row
478
+ *
479
+ * @param {number} columnIndex - number in the array of columns, where new column to insert, -1 if insert at the end
480
+ * @param {boolean} [setFocus] - pass true to focus the first cell
481
+ */
482
+ addColumn(columnIndex = -1, setFocus = false) {
483
+ let numOfColumns=this.numberOfColumns;
484
+ /**
485
+ * Check if the number of columns has reached the maximum allowed columns specified in the configuration,
486
+ * and if so, exit the function to prevent adding more columns beyond the limit.
487
+ */
488
+ if (this.config && this.config.maxcols && this.numberOfColumns >= this.config.maxcols) {
489
+ return;
490
+ }
491
+
492
+ /**
493
+ * Iterate all rows and add a new cell to them for creating a column
494
+ */
495
+ for (let rowIndex = 1; rowIndex <= this.numberOfRows; rowIndex++) {
496
+ let cell;
497
+ const cellElem = this.createCell();
498
+
499
+ if (columnIndex > 0 && columnIndex <= numOfColumns) {
500
+ cell = this.getCell(rowIndex, columnIndex);
501
+
502
+ $.insertBefore(cellElem, cell);
503
+ } else {
504
+ cell = this.getRow(rowIndex).appendChild(cellElem);
505
+ }
506
+
507
+ /**
508
+ * Autofocus first cell
509
+ */
510
+ if (rowIndex === 1) {
511
+ const firstCell = this.getCell(rowIndex, columnIndex > 0 ? columnIndex : numOfColumns + 1);
512
+
513
+ if (firstCell && setFocus) {
514
+ $.focus(firstCell);
515
+ }
516
+ }
517
+ }
518
+
519
+ const addColButton = this.wrapper.querySelector(`.${CSS.addColumn}`);
520
+ if (this.config?.maxcols && this.numberOfColumns > this.config.maxcols - 1 && addColButton ){
521
+ addColButton.classList.add(CSS.addColumnDisabled);
522
+ }
523
+ this.addHeadingAttrToFirstRow();
524
+ this.colWidthArr.splice(columnIndex - 1, 0, this.defaultCellWidth);
525
+ };
526
+
527
+ /**
528
+ * Add row in table on index place
529
+ *
530
+ * @param {number} index - number in the array of rows, where new column to insert, -1 if insert at the end
531
+ * @param {boolean} [setFocus] - pass true to focus the inserted row
532
+ * @returns {HTMLElement} row
533
+ */
534
+ addRow(index = -1, setFocus = false) {
535
+ let insertedRow;
536
+ let rowElem = $.make('div', CSS.row);
537
+
538
+ if (this.tunes.withHeadings) {
539
+ this.removeHeadingAttrFromFirstRow();
540
+ }
541
+
542
+ /**
543
+ * We remember the number of columns, because it is calculated
544
+ * by the number of cells in the first row
545
+ * It is necessary that the first line is filled in correctly
546
+ */
547
+ let numberOfColumns = this.numberOfColumns;
548
+ /**
549
+ * Check if the number of rows has reached the maximum allowed rows specified in the configuration,
550
+ * and if so, exit the function to prevent adding more columns beyond the limit.
551
+ */
552
+ if (this.config && this.config.maxrows && this.numberOfRows >= this.config.maxrows && addRowButton) {
553
+ return;
554
+ }
555
+
556
+ if (index > 0 && index <= this.numberOfRows) {
557
+ let row = this.getRow(index);
558
+
559
+ insertedRow = $.insertBefore(rowElem, row);
560
+ } else {
561
+ insertedRow = this.table.appendChild(rowElem);
562
+ }
563
+
564
+ this.fillRow(insertedRow, numberOfColumns);
565
+
566
+ if (this.tunes.withHeadings) {
567
+ this.addHeadingAttrToFirstRow();
568
+ }
569
+
570
+ const insertedRowFirstCell = this.getRowFirstCell(insertedRow);
571
+
572
+ if (insertedRowFirstCell && setFocus) {
573
+ $.focus(insertedRowFirstCell);
574
+ }
575
+
576
+ const addRowButton = this.wrapper.querySelector(`.${CSS.addRow}`);
577
+ if (this.config && this.config.maxrows && this.numberOfRows >= this.config.maxrows && addRowButton) {
578
+ addRowButton.classList.add(CSS.addRowDisabled);
579
+ }
580
+ return insertedRow;
581
+ };
582
+
583
+ /**
584
+ * Delete a column by index
585
+ *
586
+ * @param {number} index
587
+ */
588
+ deleteColumn(index: number) {
589
+ for (let i = 1; i <= this.numberOfRows; i++) {
590
+ const cell = this.getCell(i, index);
591
+
592
+ if (!cell) {
593
+ return;
594
+ }
595
+
596
+ cell.remove();
597
+ }
598
+ const addColButton = this.wrapper.querySelector(`.${CSS.addColumn}`);
599
+ if (addColButton) {
600
+ addColButton.classList.remove(CSS.addColumnDisabled);
601
+ }
602
+ this.colWidthArr.splice(index - 1, 1);
603
+ }
604
+
605
+ /**
606
+ * Delete a row by index
607
+ *
608
+ * @param {number} index
609
+ */
610
+ deleteRow(index) {
611
+ this.getRow(index).remove();
612
+ const addRowButton = this.wrapper.querySelector(`.${CSS.addRow}`);
613
+ if (addRowButton) {
614
+ addRowButton.classList.remove(CSS.addRowDisabled);
615
+ }
616
+
617
+ this.addHeadingAttrToFirstRow();
618
+ }
619
+
620
+ /**
621
+ * Create a wrapper containing a table, toolboxes
622
+ * and buttons for adding rows and columns
623
+ *
624
+ * @returns {HTMLElement} wrapper - where all buttons for a table and the table itself will be
625
+ */
626
+ createTableWrapper() {
627
+ this.wrapper = $.make('div', CSS.wrapper);
628
+ this.table = $.make('div', CSS.table);
629
+
630
+ if (this.readOnly) {
631
+ this.wrapper.classList.add(CSS.wrapperReadOnly);
632
+ }
633
+
634
+ this.wrapper.appendChild(this.toolboxRow.element);
635
+ this.wrapper.appendChild(this.toolboxColumn.element);
636
+ this.wrapper.appendChild(this.table);
637
+
638
+ // if (!this.readOnly) {
639
+ // const addColumnButton = $.make('div', CSS.addColumn, {
640
+ // innerHTML: IconPlus
641
+ // });
642
+ // const addRowButton = $.make('div', CSS.addRow, {
643
+ // innerHTML: IconPlus
644
+ // });
645
+
646
+ // this.wrapper.appendChild(addColumnButton);
647
+ // this.wrapper.appendChild(addRowButton);
648
+ // }
649
+ }
650
+
651
+ /**
652
+ * Returns the size of the table based on initial data or config "size" property
653
+ *
654
+ * @return {{rows: number, cols: number}} - number of cols and rows
655
+ */
656
+ computeInitialSize() {
657
+ const content = this.data && this.data.content;
658
+ const isValidArray = Array.isArray(content);
659
+ const isNotEmptyArray = isValidArray ? content.length : false;
660
+ const contentRows = isValidArray ? content.length : undefined;
661
+ const contentCols = isNotEmptyArray ? content[0].length : undefined;
662
+ const parsedRows = Number.parseInt(this.config && this.config.rows);
663
+ const parsedCols = Number.parseInt(this.config && this.config.cols);
664
+
665
+ /**
666
+ * Value of config have to be positive number
667
+ */
668
+ const configRows = !isNaN(parsedRows) && parsedRows > 0 ? parsedRows : undefined;
669
+ const configCols = !isNaN(parsedCols) && parsedCols > 0 ? parsedCols : undefined;
670
+ const defaultRows = 2;
671
+ const defaultCols = 2;
672
+ const rows = contentRows || configRows || defaultRows;
673
+ const cols = contentCols || configCols || defaultCols;
674
+
675
+ return {
676
+ rows: rows,
677
+ cols: cols
678
+ };
679
+ }
680
+
681
+ /**
682
+ * Resize table to match config size or transmitted data size
683
+ *
684
+ * @return {{rows: number, cols: number}} - number of cols and rows
685
+ */
686
+ resize() {
687
+ const { rows, cols } = this.computeInitialSize();
688
+
689
+ for (let i = 0; i < rows; i++) {
690
+ this.addRow();
691
+ }
692
+
693
+ for (let i = 0; i < cols; i++) {
694
+ this.addColumn();
695
+ }
696
+
697
+
698
+ }
699
+ updateColWidth() {
700
+ //设置各列宽度
701
+ let colWidth = "";
702
+ if (this.colWidthArr && this.colWidthArr.length > 0) {
703
+ for (let i = 0; i < this.colWidthArr.length; i++) {
704
+ colWidth += " " + this.colWidthArr[i] + "px";
705
+ }
706
+ }
707
+ if (colWidth) {
708
+ this.wrapper.style.setProperty('--col-width', colWidth);
709
+ }
710
+
711
+ }
712
+
713
+ /**
714
+ * Fills the table with data passed to the constructor
715
+ *
716
+ * @returns {void}
717
+ */
718
+ fill() {
719
+ const data = this.data;
720
+
721
+ if (data && data.content) {
722
+ for (let i = 0; i < data.content.length; i++) {
723
+ for (let j = 0; j < data.content[i].length; j++) {
724
+ this.setCellContent(i + 1, j + 1, data.content[i][j]);
725
+ }
726
+ }
727
+ }
728
+ }
729
+
730
+ /**
731
+ * Fills a row with cells
732
+ *
733
+ * @param {HTMLElement} row - row to fill
734
+ * @param {number} numberOfColumns - how many cells should be in a row
735
+ */
736
+ fillRow(row, numberOfColumns) {
737
+ for (let i = 1; i <= numberOfColumns; i++) {
738
+ const newCell = this.createCell();
739
+
740
+ row.appendChild(newCell);
741
+ }
742
+ }
743
+
744
+ /**
745
+ * Creating a cell element
746
+ *
747
+ * @return {Element}
748
+ */
749
+ createCell() {
750
+ return $.make('div', CSS.cell, {
751
+ contentEditable: !this.readOnly
752
+ });
753
+ }
754
+
755
+ /**
756
+ * Get number of rows in the table
757
+ */
758
+ get numberOfRows() {
759
+ return this.table.childElementCount;
760
+ }
761
+
762
+ /**
763
+ * Get number of columns in the table
764
+ */
765
+ get numberOfColumns() {
766
+ if (this.numberOfRows) {
767
+ return this.table.querySelectorAll(`.${CSS.row}:first-child .${CSS.cell}`).length;
768
+ }
769
+
770
+ return 0;
771
+ }
772
+
773
+ /**
774
+ * Is the column toolbox menu displayed or not
775
+ *
776
+ * @returns {boolean}
777
+ */
778
+ get isColumnMenuShowing() {
779
+ return this.selectedColumn !== 0;
780
+ }
781
+
782
+ /**
783
+ * Is the row toolbox menu displayed or not
784
+ *
785
+ * @returns {boolean}
786
+ */
787
+ get isRowMenuShowing() {
788
+ return this.selectedRow !== 0;
789
+ }
790
+
791
+ /**
792
+ * Recalculate position of toolbox icons
793
+ *
794
+ * @param {Event} event - mouse move event
795
+ */
796
+ onMouseMoveInTable(event) {
797
+ const { row, column, deltaXCell, deltaYCell } = this.getHoveredCell(event);
798
+
799
+ this.hoveredColumn = column;
800
+ this.hoveredRow = row;
801
+
802
+ this.updateToolboxesPosition();
803
+
804
+ if (this.hoveredCell !== null && this.hoveredCell.classList.contains("cell-resizable")) {
805
+ this.hoveredCell.classList.remove("cell-resizable");
806
+ }
807
+ this.hoveredCell = this.getCell(row, column);
808
+
809
+ // 判断是否可以调整列宽
810
+ if (this.cellIsResizable(this.hoveredCell, deltaXCell)) {
811
+ // 将鼠标改为拖拽光标
812
+ if (!this.hoveredCell.classList.contains("cell-resizable"))
813
+ this.hoveredCell.classList.add("cell-resizable");
814
+ }
815
+
816
+ }
817
+ cellIsResizable(cell: HTMLElement, deltaX: number) {
818
+ // 判断光标是否在单元格右侧10px的位置
819
+ const cellWidth = cell.clientWidth;
820
+ return cellWidth - deltaX <= 10;
821
+ }
822
+ /**
823
+ * Prevents default Enter behaviors
824
+ * Adds Shift+Enter processing
825
+ *
826
+ * @param {KeyboardEvent} event - keypress event
827
+ */
828
+ onKeyPressListener(event) {
829
+ if (event.key === 'Enter') {
830
+ if (event.shiftKey) {
831
+ return true;
832
+ }
833
+
834
+ this.moveCursorToNextRow();
835
+ }
836
+
837
+ return event.key !== 'Enter';
838
+ };
839
+
840
+ /**
841
+ * Prevents tab keydown event from bubbling
842
+ * so that it only works inside the table
843
+ *
844
+ * @param {KeyboardEvent} event - keydown event
845
+ */
846
+ onKeyDownListener(event) {
847
+ if (event.key === 'Tab') {
848
+ event.stopPropagation();
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Set the coordinates of the cell that the focus has moved to
854
+ *
855
+ * @param {FocusEvent} event - focusin event
856
+ */
857
+ focusInTableListener(event) {
858
+ const cell = event.target;
859
+ const row = this.getRowByCell(cell);
860
+
861
+ this.focusedCell = {
862
+ row: Array.from(this.table.querySelectorAll(`.${CSS.row}`)).indexOf(row) + 1,
863
+ column: Array.from(row.querySelectorAll(`.${CSS.cell}`)).indexOf(cell) + 1
864
+ };
865
+ }
866
+
867
+ /**
868
+ * Unselect row/column
869
+ * Close toolbox menu
870
+ * Hide toolboxes
871
+ *
872
+ * @returns {void}
873
+ */
874
+ hideToolboxes() {
875
+ this.hideRowToolbox();
876
+ this.hideColumnToolbox();
877
+ this.updateToolboxesPosition();
878
+ }
879
+
880
+ /**
881
+ * Unselect row, close toolbox
882
+ *
883
+ * @returns {void}
884
+ */
885
+ hideRowToolbox() {
886
+ this.unselectRow();
887
+ this.toolboxRow.hide();
888
+ }
889
+ /**
890
+ * Unselect column, close toolbox
891
+ *
892
+ * @returns {void}
893
+ */
894
+ hideColumnToolbox() {
895
+ this.unselectColumn();
896
+
897
+ this.toolboxColumn.hide();
898
+ }
899
+
900
+ /**
901
+ * Set the cursor focus to the focused cell
902
+ *
903
+ * @returns {void}
904
+ */
905
+ focusCell() {
906
+ this.focusedCellElem.focus();
907
+ }
908
+
909
+ /**
910
+ * Get current focused element
911
+ *
912
+ * @returns {HTMLElement} - focused cell
913
+ */
914
+ get focusedCellElem() {
915
+ const { row, column } = this.focusedCell;
916
+
917
+ return this.getCell(row, column);
918
+ }
919
+
920
+ /**
921
+ * Update toolboxes position
922
+ *
923
+ * @param {number} row - hovered row
924
+ * @param {number} column - hovered column
925
+ */
926
+ updateToolboxesPosition(row = this.hoveredRow, column = this.hoveredColumn) {
927
+ if (!this.isColumnMenuShowing) {
928
+ if (column > 0 && column <= this.numberOfColumns) { // not sure this statement is needed. Maybe it should be fixed in getHoveredCell()
929
+ this.toolboxColumn.show(() => {
930
+ return {
931
+ left: `calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${column} - 1) * 2))`
932
+ };
933
+ });
934
+ }
935
+ }
936
+
937
+ if (!this.isRowMenuShowing) {
938
+ if (row > 0 && row <= this.numberOfRows) { // not sure this statement is needed. Maybe it should be fixed in getHoveredCell()
939
+ this.toolboxRow.show(() => {
940
+ const hoveredRowElement = this.getRow(row);
941
+ const { fromTopBorder } = $.getRelativeCoordsOfTwoElems(this.table, hoveredRowElement);
942
+ const { height } = hoveredRowElement.getBoundingClientRect();
943
+
944
+ return {
945
+ top: `${Math.ceil(fromTopBorder + height / 2)}px`
946
+ };
947
+ });
948
+ }
949
+ }
950
+ }
951
+
952
+ /**
953
+ * Makes the first row headings
954
+ *
955
+ * @param {boolean} withHeadings - use headings row or not
956
+ */
957
+ setHeadingsSetting(withHeadings) {
958
+ this.tunes.withHeadings = withHeadings;
959
+
960
+ if (withHeadings) {
961
+ this.table.classList.add(CSS.withHeadings);
962
+ this.addHeadingAttrToFirstRow();
963
+ } else {
964
+ this.table.classList.remove(CSS.withHeadings);
965
+ this.removeHeadingAttrFromFirstRow();
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Adds an attribute for displaying the placeholder in the cell
971
+ */
972
+ addHeadingAttrToFirstRow() {
973
+ for (let cellIndex = 1; cellIndex <= this.numberOfColumns; cellIndex++) {
974
+ let cell = this.getCell(1, cellIndex);
975
+
976
+ if (cell) {
977
+ cell.setAttribute('heading', this.api.i18n.t('Heading'));
978
+ }
979
+ }
980
+ }
981
+
982
+ /**
983
+ * Removes an attribute for displaying the placeholder in the cell
984
+ */
985
+ removeHeadingAttrFromFirstRow() {
986
+ for (let cellIndex = 1; cellIndex <= this.numberOfColumns; cellIndex++) {
987
+ let cell = this.getCell(1, cellIndex);
988
+
989
+ if (cell) {
990
+ cell.removeAttribute('heading');
991
+ }
992
+ }
993
+ }
994
+
995
+ /**
996
+ * Add effect of a selected row
997
+ *
998
+ * @param {number} index
999
+ */
1000
+ selectRow(index) {
1001
+ const row = this.getRow(index);
1002
+
1003
+ if (row) {
1004
+ this.selectedRow = index;
1005
+ row.classList.add(CSS.rowSelected);
1006
+ }
1007
+ }
1008
+
1009
+ /**
1010
+ * Remove effect of a selected row
1011
+ */
1012
+ unselectRow() {
1013
+ if (this.selectedRow <= 0) {
1014
+ return;
1015
+ }
1016
+
1017
+ const row = this.table.querySelector(`.${CSS.rowSelected}`);
1018
+
1019
+ if (row) {
1020
+ row.classList.remove(CSS.rowSelected);
1021
+ }
1022
+
1023
+ this.selectedRow = 0;
1024
+ }
1025
+
1026
+ /**
1027
+ * Add effect of a selected column
1028
+ *
1029
+ * @param {number} index
1030
+ */
1031
+ selectColumn(index) {
1032
+ for (let i = 1; i <= this.numberOfRows; i++) {
1033
+ const cell = this.getCell(i, index);
1034
+
1035
+ if (cell) {
1036
+ cell.classList.add(CSS.cellSelected);
1037
+ }
1038
+ }
1039
+
1040
+ this.selectedColumn = index;
1041
+ }
1042
+
1043
+ /**
1044
+ * Remove effect of a selected column
1045
+ */
1046
+ unselectColumn() {
1047
+ if (this.selectedColumn <= 0) {
1048
+ return;
1049
+ }
1050
+
1051
+ let cells = this.table.querySelectorAll(`.${CSS.cellSelected}`);
1052
+
1053
+ Array.from(cells).forEach(column => {
1054
+ column.classList.remove(CSS.cellSelected);
1055
+ });
1056
+
1057
+ this.selectedColumn = 0;
1058
+ }
1059
+
1060
+ /**
1061
+ * Calculates the row and column that the cursor is currently hovering over
1062
+ * The search was optimized from O(n) to O (log n) via bin search to reduce the number of calculations
1063
+ *
1064
+ * @param {Event} event - mousemove event
1065
+ * @returns hovered cell coordinates as an integer row and column
1066
+ */
1067
+ getHoveredCell(event: MouseEvent) {
1068
+ let hoveredRow = this.hoveredRow;
1069
+ let hoveredColumn = this.hoveredColumn;
1070
+ const { width, height, x, y } = $.getCursorPositionRelativeToElement(this.table, event);
1071
+
1072
+
1073
+
1074
+ // Looking for hovered column
1075
+ if (x >= 0) {
1076
+ const beforeTheLeftBorder1 = ({ fromLeftBorder }: { fromLeftBorder: number }) => x < fromLeftBorder;
1077
+ const afterTheRightBorder1 = ({ fromRightBorder }: { fromRightBorder: number }) => x > (width - fromRightBorder);
1078
+ hoveredColumn = this.binSearch(
1079
+ this.numberOfColumns,
1080
+ (mid) => this.getCell(1, mid),
1081
+ beforeTheLeftBorder1,
1082
+ afterTheRightBorder1
1083
+ );
1084
+ }
1085
+
1086
+ // Looking for hovered row
1087
+ if (y >= 0) {
1088
+ const beforeTheLeftBorder2 = ({ fromTopBorder }: { fromTopBorder: number }) => y < fromTopBorder;
1089
+ const afterTheRightBorder2 = ({ fromBottomBorder }: { fromBottomBorder: number }) => y > (height - fromBottomBorder);
1090
+ hoveredRow = this.binSearch(
1091
+ this.numberOfRows,
1092
+ (mid) => this.getCell(mid, 1),
1093
+ beforeTheLeftBorder2,
1094
+ afterTheRightBorder2
1095
+ );
1096
+ }
1097
+ const row = hoveredRow || this.hoveredRow;
1098
+ const column = hoveredColumn || this.hoveredColumn;
1099
+ // 获取鼠标在单元格内的坐标
1100
+ const { deltaXCell, deltaYCell } = this.getMousePositionRelateToCell(
1101
+ row,
1102
+ column,
1103
+ x,
1104
+ y
1105
+ );
1106
+
1107
+ return {
1108
+ row,
1109
+ column,
1110
+ deltaXCell,
1111
+ deltaYCell
1112
+ };
1113
+ }
1114
+ // 获取鼠标相对于单元格的坐标
1115
+ getMousePositionRelateToCell(row:number, col:number, deltaX:number, deltaY:number) {
1116
+ const cell = this.getCell(row, col);
1117
+ const { fromTopBorder, fromLeftBorder } = $.getRelativeCoordsOfTwoElems(
1118
+ this.table,
1119
+ cell
1120
+ );
1121
+ return {
1122
+ deltaXCell: deltaX - fromLeftBorder,
1123
+ deltaYCell: deltaY - fromTopBorder,
1124
+ };
1125
+ }
1126
+ /**
1127
+ * Looks for the index of the cell the mouse is hovering over.
1128
+ * Cells can be represented as ordered intervals with left and
1129
+ * right (upper and lower for rows) borders inside the table, if the mouse enters it, then this is our index
1130
+ *
1131
+ * @param {number} numberOfCells - upper bound of binary search
1132
+ * @param {function} getCell - function to take the currently viewed cell
1133
+ * @param {function} beforeTheLeftBorder - determines the cursor position, to the left of the cell or not
1134
+ * @param {function} afterTheRightBorder - determines the cursor position, to the right of the cell or not
1135
+ * @returns {number}
1136
+ */
1137
+ binSearch(numberOfCells, getCell, beforeTheLeftBorder, afterTheRightBorder) {
1138
+ let leftBorder = 0;
1139
+ let rightBorder = numberOfCells + 1;
1140
+ let totalIterations = 0;
1141
+ let mid;
1142
+
1143
+ while (leftBorder < rightBorder - 1 && totalIterations < 10) {
1144
+ mid = Math.ceil((leftBorder + rightBorder) / 2);
1145
+
1146
+ const cell = getCell(mid);
1147
+ const relativeCoords = $.getRelativeCoordsOfTwoElems(this.table, cell);
1148
+
1149
+ if (beforeTheLeftBorder(relativeCoords)) {
1150
+ rightBorder = mid;
1151
+ } else if (afterTheRightBorder(relativeCoords)) {
1152
+ leftBorder = mid;
1153
+ } else {
1154
+ break;
1155
+ }
1156
+
1157
+ totalIterations++;
1158
+ }
1159
+
1160
+ return mid;
1161
+ }
1162
+
1163
+ /**
1164
+ * Collects data from cells into a two-dimensional array
1165
+ *
1166
+ * @returns {string[][]}
1167
+ */
1168
+ getData() {
1169
+ const data = [];
1170
+
1171
+ for (let i = 1; i <= this.numberOfRows; i++) {
1172
+ const row = this.table.querySelector(`.${CSS.row}:nth-child(${i})`);
1173
+ const cells = Array.from(row.querySelectorAll(`.${CSS.cell}`));
1174
+ const isEmptyRow = cells.every(cell => !cell.textContent.trim());
1175
+
1176
+ if (isEmptyRow) {
1177
+ continue;
1178
+ }
1179
+
1180
+ data.push(cells.map(cell => cell.innerHTML));
1181
+ }
1182
+
1183
+ return data;
1184
+ }
1185
+
1186
+ /**
1187
+ * Remove listeners on the document
1188
+ */
1189
+ destroy() {
1190
+ document.removeEventListener('click', this.documentClicked);
1191
+ }
1192
+ }