quill-table-up 2.4.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -40
- package/dist/index.css +1 -1
- package/dist/index.d.ts +172 -110
- package/dist/index.js +29 -28
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +29 -28
- package/dist/index.umd.js.map +1 -1
- package/dist/table-creator.css +1 -1
- package/package.json +1 -1
- package/src/__tests__/e2e/table-align.test.ts +1 -2
- package/src/__tests__/e2e/table-blots.test.ts +45 -35
- package/src/__tests__/e2e/table-clipboard.test.ts +20 -0
- package/src/__tests__/e2e/table-hack.test.ts +5 -5
- package/src/__tests__/e2e/table-keyboard-handler.test.ts +6 -6
- package/src/__tests__/e2e/table-menu.test.ts +23 -0
- package/src/__tests__/e2e/table-selection.test.ts +3 -3
- package/src/__tests__/unit/table-blots.test.ts +26 -26
- package/src/__tests__/unit/table-caption.test.ts +33 -36
- package/src/__tests__/unit/table-cell-merge.test.ts +114 -114
- package/src/__tests__/unit/table-clipboard.test.ts +383 -19
- package/src/__tests__/unit/table-hack.test.ts +202 -144
- package/src/__tests__/unit/table-insert.test.ts +79 -79
- package/src/__tests__/unit/table-redo-undo.test.ts +495 -64
- package/src/__tests__/unit/table-remove.test.ts +8 -11
- package/src/__tests__/unit/utils.test.ts +4 -4
- package/src/__tests__/unit/utils.ts +4 -3
- package/src/formats/container-format.ts +25 -2
- package/src/formats/index.ts +54 -8
- package/src/formats/overrides/block.ts +35 -30
- package/src/formats/table-body-format.ts +18 -58
- package/src/formats/table-cell-format.ts +296 -286
- package/src/formats/table-cell-inner-format.ts +384 -358
- package/src/formats/table-foot-format.ts +7 -0
- package/src/formats/table-head-format.ts +7 -0
- package/src/formats/table-main-format.ts +84 -5
- package/src/formats/table-row-format.ts +44 -14
- package/src/modules/index.ts +1 -0
- package/src/modules/table-align.ts +59 -53
- package/src/modules/table-clipboard.ts +60 -39
- package/src/modules/table-dom-selector.ts +33 -0
- package/src/modules/table-menu/constants.ts +21 -31
- package/src/modules/table-menu/table-menu-common.ts +72 -51
- package/src/modules/table-menu/table-menu-contextmenu.ts +22 -6
- package/src/modules/table-menu/table-menu-select.ts +75 -12
- package/src/modules/table-resize/table-resize-box.ts +148 -128
- package/src/modules/table-resize/table-resize-common.ts +37 -32
- package/src/modules/table-resize/table-resize-line.ts +53 -36
- package/src/modules/table-resize/table-resize-scale.ts +32 -27
- package/src/modules/table-scrollbar.ts +58 -32
- package/src/modules/table-selection.ts +102 -79
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/style/index.less +0 -1
- package/src/style/select-box.less +1 -0
- package/src/style/table-menu.less +4 -4
- package/src/style/table-resize.less +1 -0
- package/src/style/table-scrollbar.less +2 -2
- package/src/table-up.ts +161 -194
- package/src/utils/components/table/creator.ts +4 -1
- package/src/utils/components/tooltip.ts +6 -1
- package/src/utils/constants.ts +8 -2
- package/src/utils/index.ts +2 -1
- package/src/utils/scroll.ts +47 -0
- package/src/utils/style-helper.ts +47 -0
- package/src/utils/transformer.ts +0 -25
- package/src/utils/types.ts +15 -15
- package/src/utils/scroll-event-helper.ts +0 -22
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type TypeScroll from 'quill/blots/scroll';
|
|
2
2
|
import type { TableValue } from '../utils';
|
|
3
|
-
import {
|
|
3
|
+
import type { TableBodyFormat } from './table-body-format';
|
|
4
|
+
import { blotName, randomId, tableUpSize } from '../utils';
|
|
4
5
|
import { ContainerFormat } from './container-format';
|
|
5
6
|
import { TableCellInnerFormat } from './table-cell-inner-format';
|
|
6
7
|
import { TableColFormat } from './table-col-format';
|
|
@@ -128,8 +129,14 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
128
129
|
Object.assign(this.domNode.style, style);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
getBodys() {
|
|
133
|
+
return Array.from(this.domNode.querySelectorAll('thead, tbody, tfoot'))
|
|
134
|
+
.map(el => this.scroll.find(el) as TableBodyFormat)
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
|
|
131
138
|
getRows() {
|
|
132
|
-
return Array.from(this.domNode.querySelectorAll(
|
|
139
|
+
return Array.from(this.domNode.querySelectorAll('tr'))
|
|
133
140
|
.map(el => this.scroll.find(el) as TableRowFormat)
|
|
134
141
|
.filter(Boolean);
|
|
135
142
|
}
|
|
@@ -228,7 +235,7 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
228
235
|
else {
|
|
229
236
|
if (row.children.length === 0 && row.prev) {
|
|
230
237
|
// find the not empty row
|
|
231
|
-
let prev
|
|
238
|
+
let prev = row.prev as TableRowFormat;
|
|
232
239
|
while (prev && prev.children.length === 0) {
|
|
233
240
|
prev = prev.prev as TableRowFormat;
|
|
234
241
|
}
|
|
@@ -257,7 +264,9 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
257
264
|
const childs: Record<string, ContainerFormat[]> = {
|
|
258
265
|
[blotName.tableCaption]: [],
|
|
259
266
|
[blotName.tableColgroup]: [],
|
|
267
|
+
[blotName.tableHead]: [],
|
|
260
268
|
[blotName.tableBody]: [],
|
|
269
|
+
[blotName.tableFoot]: [],
|
|
261
270
|
};
|
|
262
271
|
// eslint-disable-next-line unicorn/no-array-for-each
|
|
263
272
|
this.children.forEach((child) => {
|
|
@@ -274,23 +283,93 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
274
283
|
// check sort child
|
|
275
284
|
const tableCaption = childs[blotName.tableCaption][0];
|
|
276
285
|
const tableColgroup = childs[blotName.tableColgroup][0];
|
|
286
|
+
const tableHead = childs[blotName.tableHead][0];
|
|
277
287
|
const tableBody = childs[blotName.tableBody][0];
|
|
288
|
+
const tableFoot = childs[blotName.tableFoot][0];
|
|
278
289
|
|
|
279
290
|
const isCaptionFirst = tableCaption && this.children.head !== tableCaption;
|
|
280
291
|
const isColgroupSecond = tableColgroup && tableCaption && tableCaption.next !== tableColgroup;
|
|
281
292
|
const isColgroupFirst = tableColgroup && !tableCaption && this.children.head !== tableColgroup;
|
|
282
|
-
const
|
|
293
|
+
const isHeadLast = tableHead && !tableBody && !tableFoot && this.children.tail !== tableHead;
|
|
294
|
+
const isBodyAfterHead = tableBody && tableHead && tableBody.prev !== tableHead;
|
|
295
|
+
const isBodyLast = tableBody && !tableFoot && this.children.tail !== tableBody;
|
|
296
|
+
const isBodyBeforeFoot = tableBody && tableFoot && tableBody.next !== tableFoot;
|
|
297
|
+
const isFootLast = tableFoot && this.children.tail !== tableFoot;
|
|
283
298
|
|
|
284
299
|
// sort child
|
|
285
|
-
if (isCaptionFirst || isColgroupSecond || isColgroupFirst || isBodyLast) {
|
|
300
|
+
if (isCaptionFirst || isColgroupSecond || isColgroupFirst || isHeadLast || isBodyAfterHead || isBodyLast || isBodyBeforeFoot || isFootLast) {
|
|
286
301
|
const tableMain = this.clone() as TableMainFormat;
|
|
287
302
|
tableCaption && tableMain.appendChild(tableCaption);
|
|
288
303
|
tableColgroup && tableMain.appendChild(tableColgroup);
|
|
304
|
+
tableHead && tableMain.appendChild(tableHead);
|
|
289
305
|
tableBody && tableMain.appendChild(tableBody);
|
|
306
|
+
tableFoot && tableMain.appendChild(tableFoot);
|
|
290
307
|
|
|
291
308
|
// eslint-disable-next-line unicorn/no-array-for-each
|
|
292
309
|
this.children.forEach(child => child.remove());
|
|
293
310
|
tableMain.moveChildren(this);
|
|
294
311
|
}
|
|
295
312
|
}
|
|
313
|
+
|
|
314
|
+
// insert row at index
|
|
315
|
+
insertRow(targetIndex: number) {
|
|
316
|
+
// get all column id. exclude the columns of the target index row with rowspan
|
|
317
|
+
const colIds = this.getColIds();
|
|
318
|
+
const rows = this.descendants(TableRowFormat);
|
|
319
|
+
const insertColIds = new Set(colIds);
|
|
320
|
+
let index = 0;
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
if (index === targetIndex) break;
|
|
323
|
+
row.foreachCellInner((cell) => {
|
|
324
|
+
if (index + cell.rowspan > targetIndex) {
|
|
325
|
+
cell.rowspan += 1;
|
|
326
|
+
insertColIds.delete(cell.colId);
|
|
327
|
+
// colspan cell need remove all includes colId
|
|
328
|
+
if (cell.colspan !== 1) {
|
|
329
|
+
const colIndex = colIds.indexOf(cell.colId);
|
|
330
|
+
for (let i = 0; i < cell.colspan - 1; i++) {
|
|
331
|
+
insertColIds.delete(colIds[colIndex + i + 1]);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
index += 1;
|
|
337
|
+
}
|
|
338
|
+
// append new row
|
|
339
|
+
const tableId = this.tableId;
|
|
340
|
+
const rowId = randomId();
|
|
341
|
+
const tableRow = this.scroll.create(blotName.tableRow, {
|
|
342
|
+
tableId,
|
|
343
|
+
rowId,
|
|
344
|
+
}) as ContainerFormat;
|
|
345
|
+
for (const colId of insertColIds) {
|
|
346
|
+
const breakBlot = this.scroll.create('break');
|
|
347
|
+
const block = breakBlot.wrap('block');
|
|
348
|
+
const tableCellInner = block.wrap(blotName.tableCellInner, {
|
|
349
|
+
tableId,
|
|
350
|
+
rowId,
|
|
351
|
+
colId,
|
|
352
|
+
rowspan: 1,
|
|
353
|
+
colspan: 1,
|
|
354
|
+
});
|
|
355
|
+
const tableCell = tableCellInner.wrap(blotName.tableCell, {
|
|
356
|
+
tableId,
|
|
357
|
+
rowId,
|
|
358
|
+
colId,
|
|
359
|
+
rowspan: 1,
|
|
360
|
+
colspan: 1,
|
|
361
|
+
});
|
|
362
|
+
tableRow.appendChild(tableCell);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// insert the new row at the target index
|
|
366
|
+
// if you insert a row at the thead, then the new row will be inserted in thead. Similarly for tbody and tfoot
|
|
367
|
+
const refRow = rows[targetIndex] || null;
|
|
368
|
+
if (!refRow) {
|
|
369
|
+
rows[rows.length - 1].parent.appendChild(tableRow);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
refRow.parent.insertBefore(tableRow, refRow);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
296
375
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
-
import type { TableCellValue, TableRowValue } from '../utils';
|
|
2
|
+
import type { TableBodyTag, TableCellValue, TableRowValue } from '../utils';
|
|
3
3
|
import type { TableCellFormat } from './table-cell-format';
|
|
4
4
|
import { blotName, findParentBlot } from '../utils';
|
|
5
5
|
import { ContainerFormat } from './container-format';
|
|
@@ -10,11 +10,21 @@ export class TableRowFormat extends ContainerFormat {
|
|
|
10
10
|
static blotName = blotName.tableRow;
|
|
11
11
|
static tagName = 'tr';
|
|
12
12
|
static className = 'ql-table-row';
|
|
13
|
+
static allowDataAttrs = new Set(['table-id', 'row-id', 'wrap-tag']);
|
|
14
|
+
static allowDataAttrsChangeHandler: Record<string, keyof TableRowFormat> = {
|
|
15
|
+
'wrap-tag': 'wrapParentTag',
|
|
16
|
+
};
|
|
13
17
|
|
|
14
18
|
static create(value: TableRowValue) {
|
|
19
|
+
const {
|
|
20
|
+
tableId,
|
|
21
|
+
rowId,
|
|
22
|
+
wrapTag = 'tbody',
|
|
23
|
+
} = value;
|
|
15
24
|
const node = super.create() as HTMLElement;
|
|
16
|
-
node.dataset.tableId =
|
|
17
|
-
node.dataset.rowId =
|
|
25
|
+
node.dataset.tableId = tableId;
|
|
26
|
+
node.dataset.rowId = rowId;
|
|
27
|
+
node.dataset.wrapTag = wrapTag;
|
|
18
28
|
return node;
|
|
19
29
|
}
|
|
20
30
|
|
|
@@ -28,6 +38,10 @@ export class TableRowFormat extends ContainerFormat {
|
|
|
28
38
|
return this.domNode.dataset.tableId!;
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
get wrapTag() {
|
|
42
|
+
return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
setHeight(value: string) {
|
|
32
46
|
this.foreachCellInner((cellInner) => {
|
|
33
47
|
cellInner.setFormatValue('height', value, true);
|
|
@@ -144,7 +158,7 @@ export class TableRowFormat extends ContainerFormat {
|
|
|
144
158
|
let cur: TableCellFormat | null;
|
|
145
159
|
while ((cur = next())) {
|
|
146
160
|
const [tableCell] = cur.descendants(TableCellInnerFormat);
|
|
147
|
-
if (func(tableCell, i++)) break;
|
|
161
|
+
if (tableCell && func(tableCell, i++)) break;
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
164
|
|
|
@@ -157,19 +171,35 @@ export class TableRowFormat extends ContainerFormat {
|
|
|
157
171
|
);
|
|
158
172
|
}
|
|
159
173
|
|
|
160
|
-
|
|
174
|
+
wrapParentTag() {
|
|
175
|
+
const tableBodyBlotNameMap: Record<string, string> = {
|
|
176
|
+
thead: blotName.tableHead,
|
|
177
|
+
tbody: blotName.tableBody,
|
|
178
|
+
tfoot: blotName.tableFoot,
|
|
179
|
+
};
|
|
180
|
+
|
|
161
181
|
const parent = this.parent;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
182
|
+
if (parent !== null && parent.statics.blotName !== tableBodyBlotNameMap[this.wrapTag]) {
|
|
183
|
+
if (Object.values(tableBodyBlotNameMap).includes(parent.statics.blotName)) {
|
|
184
|
+
const index = this.offset(this.parent);
|
|
185
|
+
const newParent = this.parent.split(index);
|
|
186
|
+
if (newParent && newParent.length() <= 0) {
|
|
187
|
+
newParent.remove();
|
|
188
|
+
}
|
|
189
|
+
const afterParent = (this.parent as any).splitAfter(this);
|
|
190
|
+
if (afterParent && afterParent.length() <= 0) {
|
|
191
|
+
afterParent.remove();
|
|
192
|
+
}
|
|
193
|
+
this.parent.replaceWith(tableBodyBlotNameMap[this.wrapTag], this.tableId);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.wrap(tableBodyBlotNameMap[this.wrapTag], this.tableId);
|
|
197
|
+
}
|
|
165
198
|
}
|
|
199
|
+
}
|
|
166
200
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
&& !(this.parent instanceof this.statics.requiredContainer)
|
|
170
|
-
) {
|
|
171
|
-
this.wrap(this.statics.requiredContainer.blotName);
|
|
172
|
-
}
|
|
201
|
+
optimize(_context: Record<string, any>) {
|
|
202
|
+
this.wrapParentTag();
|
|
173
203
|
|
|
174
204
|
this.enforceAllowedChildren();
|
|
175
205
|
if (this.children.length > 0 && this.next != null && this.checkMerge()) {
|
package/src/modules/index.ts
CHANGED
|
@@ -3,31 +3,38 @@ import type { TableUp } from '../table-up';
|
|
|
3
3
|
import { autoUpdate, computePosition, flip, limitShift, offset, shift } from '@floating-ui/dom';
|
|
4
4
|
import Quill from 'quill';
|
|
5
5
|
import { createBEM, createResizeObserver } from '../utils';
|
|
6
|
+
import { TableDomSelector } from './table-dom-selector';
|
|
6
7
|
|
|
7
|
-
export class TableAlign {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export class TableAlign extends TableDomSelector {
|
|
9
|
+
static moduleName: string = 'table-align';
|
|
10
|
+
|
|
11
|
+
tableBlot?: TableMainFormat;
|
|
12
|
+
tableWrapperBlot?: TableWrapperFormat;
|
|
13
|
+
alignBox: HTMLElement | null;
|
|
11
14
|
cleanup?: () => void;
|
|
12
15
|
bem = createBEM('align');
|
|
13
|
-
resizeObserver
|
|
16
|
+
resizeObserver?: ResizeObserver;
|
|
14
17
|
|
|
15
|
-
constructor(public tableModule: TableUp, public
|
|
16
|
-
|
|
17
|
-
this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
|
|
18
|
+
constructor(public tableModule: TableUp, public quill: Quill, _options: any) {
|
|
19
|
+
super(tableModule, quill);
|
|
18
20
|
|
|
19
|
-
this.alignBox = this.
|
|
20
|
-
this.
|
|
21
|
-
this.quill.on(Quill.events.
|
|
22
|
-
|
|
23
|
-
this.show();
|
|
21
|
+
this.alignBox = this.buildTools();
|
|
22
|
+
this.hide();
|
|
23
|
+
this.quill.on(Quill.events.EDITOR_CHANGE, this.updateWhenTextChange);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
updateWhenTextChange = () => {
|
|
27
|
-
|
|
26
|
+
updateWhenTextChange = (eventName: string) => {
|
|
27
|
+
if (eventName === Quill.events.TEXT_CHANGE) {
|
|
28
|
+
if (this.table && !this.quill.root.contains(this.table)) {
|
|
29
|
+
this.setSelectionTable(undefined);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.update();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
28
35
|
};
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
buildTools() {
|
|
31
38
|
const alignBox = this.tableModule.addContainer(this.bem.b());
|
|
32
39
|
const icons = Quill.import('ui/icons') as Record<string, any>;
|
|
33
40
|
const alignIcons = {
|
|
@@ -40,39 +47,21 @@ export class TableAlign {
|
|
|
40
47
|
item.dataset.align = align;
|
|
41
48
|
item.classList.add(this.bem.be('item'));
|
|
42
49
|
item.innerHTML = `<i class="icon">${iconStr}</i>`;
|
|
43
|
-
item.addEventListener('click', ()
|
|
44
|
-
const value = item.dataset.align;
|
|
45
|
-
if (value) {
|
|
46
|
-
this.setTableAlign(this.tableBlot, value);
|
|
47
|
-
|
|
48
|
-
this.quill.once(Quill.events.SCROLL_OPTIMIZE, () => {
|
|
49
|
-
if (this.tableModule.tableSelection) {
|
|
50
|
-
this.tableModule.tableSelection.hide();
|
|
51
|
-
}
|
|
52
|
-
if (this.tableModule.tableResize) {
|
|
53
|
-
this.tableModule.tableResize.update();
|
|
54
|
-
}
|
|
55
|
-
if (this.tableModule.tableResizeScale) {
|
|
56
|
-
this.tableModule.tableResizeScale.update();
|
|
57
|
-
}
|
|
58
|
-
if (this.tableModule.tableScrollbar) {
|
|
59
|
-
this.tableModule.tableScrollbar.update();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
});
|
|
50
|
+
item.addEventListener('click', this.handleAlignItemClick.bind(this));
|
|
64
51
|
alignBox.appendChild(item);
|
|
65
52
|
}
|
|
66
|
-
if (!this.cleanup) {
|
|
67
|
-
this.cleanup = autoUpdate(
|
|
68
|
-
this.tableWrapperBlot.domNode,
|
|
69
|
-
alignBox,
|
|
70
|
-
() => this.update(),
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
53
|
return alignBox;
|
|
74
54
|
}
|
|
75
55
|
|
|
56
|
+
handleAlignItemClick(e: MouseEvent) {
|
|
57
|
+
const item = e.currentTarget;
|
|
58
|
+
if (!item) return;
|
|
59
|
+
const value = (item as HTMLElement).dataset.align;
|
|
60
|
+
if (value && this.tableBlot) {
|
|
61
|
+
this.setTableAlign(this.tableBlot, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
76
65
|
setTableAlign(tableBlot: TableMainFormat, align: string) {
|
|
77
66
|
const cols = tableBlot.getCols();
|
|
78
67
|
for (const col of cols) {
|
|
@@ -81,14 +70,28 @@ export class TableAlign {
|
|
|
81
70
|
}
|
|
82
71
|
|
|
83
72
|
show() {
|
|
84
|
-
if (!this.alignBox) return;
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
73
|
+
if (!this.table || !this.alignBox) return;
|
|
74
|
+
this.tableBlot = Quill.find(this.table) as TableMainFormat;
|
|
75
|
+
this.tableWrapperBlot = this.tableBlot.parent as TableWrapperFormat;
|
|
76
|
+
this.alignBox.classList.remove(this.bem.is('hidden'));
|
|
77
|
+
this.resizeObserver = createResizeObserver(() => this.update(), { ignoreFirstBind: true });
|
|
78
|
+
this.resizeObserver.observe(this.table);
|
|
79
|
+
if (this.cleanup) {
|
|
80
|
+
this.cleanup();
|
|
81
|
+
}
|
|
82
|
+
this.cleanup = autoUpdate(
|
|
83
|
+
this.tableWrapperBlot.domNode,
|
|
84
|
+
this.alignBox,
|
|
85
|
+
() => this.update(),
|
|
86
|
+
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
hide() {
|
|
90
|
-
|
|
91
|
-
this.
|
|
90
|
+
this.tableBlot = undefined;
|
|
91
|
+
this.tableWrapperBlot = undefined;
|
|
92
|
+
if (this.alignBox) {
|
|
93
|
+
this.alignBox.classList.add(this.bem.is('hidden'));
|
|
94
|
+
}
|
|
92
95
|
if (this.cleanup) {
|
|
93
96
|
this.cleanup();
|
|
94
97
|
this.cleanup = undefined;
|
|
@@ -96,8 +99,8 @@ export class TableAlign {
|
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
update() {
|
|
99
|
-
if (!this.alignBox) return;
|
|
100
|
-
if (this.tableBlot.full || this.tableBlot.domNode.offsetWidth >= this.quill.root.offsetWidth) {
|
|
102
|
+
if (!this.alignBox || !this.tableBlot || !this.tableWrapperBlot) return;
|
|
103
|
+
if (!this.table || this.tableBlot.full || this.tableBlot.domNode.offsetWidth >= this.quill.root.offsetWidth) {
|
|
101
104
|
this.hide();
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
@@ -115,11 +118,14 @@ export class TableAlign {
|
|
|
115
118
|
|
|
116
119
|
destroy() {
|
|
117
120
|
this.hide();
|
|
118
|
-
this.resizeObserver
|
|
121
|
+
if (this.resizeObserver) {
|
|
122
|
+
this.resizeObserver.disconnect();
|
|
123
|
+
this.resizeObserver = undefined;
|
|
124
|
+
}
|
|
119
125
|
this.quill.off(Quill.events.TEXT_CHANGE, this.updateWhenTextChange);
|
|
120
126
|
if (this.alignBox) {
|
|
121
127
|
this.alignBox.remove();
|
|
122
|
-
this.alignBox =
|
|
128
|
+
this.alignBox = null;
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
}
|
|
@@ -53,7 +53,9 @@ export class TableClipboard extends Clipboard {
|
|
|
53
53
|
constructor(public quill: Quill, options: Partial<ClipboardOptions>) {
|
|
54
54
|
super(quill, options);
|
|
55
55
|
this.addMatcher('table', this.matchTable.bind(this));
|
|
56
|
+
this.addMatcher('thead', this.matchThead.bind(this));
|
|
56
57
|
this.addMatcher('tbody', this.matchTbody.bind(this));
|
|
58
|
+
this.addMatcher('tfoot', this.matchTfoot.bind(this));
|
|
57
59
|
this.addMatcher('colgroup', this.matchColgroup.bind(this));
|
|
58
60
|
this.addMatcher('col', this.matchCol.bind(this));
|
|
59
61
|
this.addMatcher('tr', this.matchTr.bind(this));
|
|
@@ -64,6 +66,22 @@ export class TableClipboard extends Clipboard {
|
|
|
64
66
|
this.addMatcher(Node.ELEMENT_NODE, this.matchTdAttributor.bind(this));
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
getStyleBackgroundColor(node: Node, delta: TypeDelta) {
|
|
70
|
+
const backgroundColor = (node as HTMLElement).style.backgroundColor;
|
|
71
|
+
if (backgroundColor) {
|
|
72
|
+
for (const op of delta.ops) {
|
|
73
|
+
if (op.attributes?.[blotName.tableCellInner]) {
|
|
74
|
+
const { style, ...value } = op.attributes[blotName.tableCellInner] as TableCellValue;
|
|
75
|
+
const styleObj = cssTextToObject(style || '');
|
|
76
|
+
if (!styleObj.backgroundColor) {
|
|
77
|
+
styleObj.backgroundColor = backgroundColor;
|
|
78
|
+
op.attributes[blotName.tableCellInner] = { ...value, style: objectToCssText(styleObj) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
matchTable(node: Node, delta: TypeDelta) {
|
|
68
86
|
if (delta.ops.length === 0) return delta;
|
|
69
87
|
|
|
@@ -73,7 +91,7 @@ export class TableClipboard extends Clipboard {
|
|
|
73
91
|
for (let i = 0; i < delta.ops.length; i++) {
|
|
74
92
|
const { attributes, insert } = delta.ops[i];
|
|
75
93
|
// if attribute doesn't have tableCellInner, treat it as a blank line(emptyRow)
|
|
76
|
-
if (!isObject(insert) && attributes
|
|
94
|
+
if (!isObject(insert) && (!attributes || (!attributes[blotName.tableCellInner] && !attributes[blotName.tableCaption]))) {
|
|
77
95
|
delta.ops.splice(i, 1);
|
|
78
96
|
i -= 1;
|
|
79
97
|
continue;
|
|
@@ -120,54 +138,66 @@ export class TableClipboard extends Clipboard {
|
|
|
120
138
|
}, [] as Record<string, any>[]);
|
|
121
139
|
ops.splice(bodyStartIndex + 1, 0, ...newCols);
|
|
122
140
|
|
|
141
|
+
const resultDelta = new Delta(ops);
|
|
142
|
+
this.getStyleBackgroundColor(node, resultDelta);
|
|
123
143
|
// reset variable to avoid conflict with other table
|
|
124
144
|
this.tableId = randomId();
|
|
125
145
|
this.colIds = [];
|
|
126
146
|
this.rowspanCount = [];
|
|
127
147
|
this.cellCount = 0;
|
|
128
148
|
this.colCount = 0;
|
|
129
|
-
return
|
|
149
|
+
return resultDelta;
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
matchTbody(node: Node, delta: TypeDelta) {
|
|
133
|
-
|
|
134
|
-
if (backgroundColor) {
|
|
135
|
-
for (const op of delta.ops) {
|
|
136
|
-
if (op.attributes?.[blotName.tableCellInner]) {
|
|
137
|
-
const { style, ...value } = op.attributes[blotName.tableCellInner] as TableCellValue;
|
|
138
|
-
const styleObj = cssTextToObject(style || '');
|
|
139
|
-
if (!styleObj.backgroundColor) {
|
|
140
|
-
styleObj.backgroundColor = backgroundColor;
|
|
141
|
-
op.attributes[blotName.tableCellInner] = { ...value, style: objectToCssText(styleObj) };
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
153
|
+
this.getStyleBackgroundColor(node, delta);
|
|
146
154
|
// add `emptyRow`
|
|
147
155
|
let emptyRows = [];
|
|
148
156
|
for (let i = delta.ops.length - 1; i >= 0; i--) {
|
|
149
157
|
const op = delta.ops[i];
|
|
150
|
-
if (op.attributes) {
|
|
151
|
-
|
|
158
|
+
if (!op.attributes?.[blotName.tableCellInner]) {
|
|
159
|
+
emptyRows = [];
|
|
160
|
+
emptyRows.push(randomId());
|
|
161
|
+
}
|
|
162
|
+
else if (op.attributes) {
|
|
163
|
+
if ((op.attributes[blotName.tableCellInner] as TableCellValue).rowspan === 1) {
|
|
152
164
|
emptyRows = [];
|
|
153
|
-
emptyRows.push(randomId());
|
|
154
165
|
}
|
|
155
|
-
else {
|
|
156
|
-
if ((op.attributes[blotName.tableCellInner] as TableCellValue).
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
else if (emptyRows.length > 0) {
|
|
160
|
-
if (!(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow) {
|
|
161
|
-
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow = [];
|
|
162
|
-
}
|
|
163
|
-
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow!.push(...emptyRows);
|
|
166
|
+
else if (emptyRows.length > 0) {
|
|
167
|
+
if (!(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow) {
|
|
168
|
+
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow = [];
|
|
164
169
|
}
|
|
170
|
+
(op.attributes[blotName.tableCellInner] as TableCellValue).emptyRow!.push(...emptyRows);
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
173
|
}
|
|
174
|
+
// clear rowspan. thead/tbody/tfoot will not share rowspan
|
|
175
|
+
this.rowspanCount = [];
|
|
168
176
|
return delta;
|
|
169
177
|
}
|
|
170
178
|
|
|
179
|
+
matchThead(node: Node, delta: TypeDelta) {
|
|
180
|
+
const deltaData = this.matchTbody(node, delta);
|
|
181
|
+
for (const op of deltaData.ops) {
|
|
182
|
+
if (op.attributes && op.attributes[blotName.tableCellInner]) {
|
|
183
|
+
const tableCellInner = op.attributes[blotName.tableCellInner] as TableCellValue;
|
|
184
|
+
tableCellInner.wrapTag = 'thead';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return deltaData;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
matchTfoot(node: Node, delta: TypeDelta) {
|
|
191
|
+
const deltaData = this.matchTbody(node, delta);
|
|
192
|
+
for (const op of deltaData.ops) {
|
|
193
|
+
if (op.attributes && op.attributes[blotName.tableCellInner]) {
|
|
194
|
+
const tableCellInner = op.attributes[blotName.tableCellInner] as TableCellValue;
|
|
195
|
+
tableCellInner.wrapTag = 'tfoot';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return deltaData;
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
matchColgroup(node: Node, delta: TypeDelta) {
|
|
172
202
|
const ops: Record<string, any>[] = [];
|
|
173
203
|
for (let i = 0; i < delta.ops.length; i++) {
|
|
@@ -213,18 +243,9 @@ export class TableClipboard extends Clipboard {
|
|
|
213
243
|
this.rowspanCount[i] = { rowspan: 0, colspan: 0 };
|
|
214
244
|
}
|
|
215
245
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (tableCellInner && background) {
|
|
220
|
-
const { style = '' } = tableCellInner as TableCellValue;
|
|
221
|
-
const styleObj = cssTextToObject(style);
|
|
222
|
-
styleObj.backgroundColor = background as string;
|
|
223
|
-
(op.attributes![blotName.tableCellInner] as TableCellValue).style = objectToCssText(styleObj);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return delta;
|
|
246
|
+
this.getStyleBackgroundColor(node, delta);
|
|
247
|
+
// if delta.ops is empty, return a new line. make sure emptyRow parse correctly in `matchTbody`
|
|
248
|
+
return delta.ops.length === 0 ? new Delta([{ insert: '\n' }]) : delta;
|
|
228
249
|
}
|
|
229
250
|
|
|
230
251
|
matchTd(node: Node, delta: TypeDelta) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type Quill from 'quill';
|
|
2
|
+
import type { TableUp } from '../table-up';
|
|
3
|
+
|
|
4
|
+
export class TableDomSelector {
|
|
5
|
+
table?: HTMLTableElement;
|
|
6
|
+
|
|
7
|
+
constructor(public tableModule: TableUp, public quill: Quill) {
|
|
8
|
+
this.quill.root.addEventListener('mousedown', this.tableSelectHandler.bind(this));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
tableSelectHandler(event: MouseEvent) {
|
|
12
|
+
const path = event.composedPath() as HTMLElement[];
|
|
13
|
+
if (event.button !== 0 || !path || path.length <= 0) return;
|
|
14
|
+
const tableNode = path.find(node => node.tagName && node.tagName.toUpperCase() === 'TABLE');
|
|
15
|
+
this.setSelectionTable(tableNode as HTMLTableElement);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setSelectionTable(table: HTMLTableElement | undefined) {
|
|
19
|
+
if (this.table === table) return;
|
|
20
|
+
this.hide();
|
|
21
|
+
this.table = table;
|
|
22
|
+
if (this.table) {
|
|
23
|
+
this.show();
|
|
24
|
+
}
|
|
25
|
+
this.update();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
hide() {}
|
|
29
|
+
|
|
30
|
+
show() {}
|
|
31
|
+
|
|
32
|
+
update() {}
|
|
33
|
+
}
|