@ebl-vue/editor-full 2.31.35 → 2.31.37

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