quill-table-up 3.1.2 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/dist/index.css +1 -1
- package/dist/index.d.ts +168 -146
- package/dist/index.js +47 -47
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +52 -52
- package/dist/index.umd.js.map +1 -1
- package/package.json +22 -24
- package/src/__tests__/e2e/custom-creator.test.ts +44 -44
- package/src/__tests__/e2e/editor-page.ts +77 -77
- package/src/__tests__/e2e/table-align.test.ts +104 -104
- package/src/__tests__/e2e/table-blots.test.ts +169 -169
- package/src/__tests__/e2e/table-caption.test.ts +134 -134
- package/src/__tests__/e2e/table-clipboard.test.ts +20 -20
- package/src/__tests__/e2e/table-hack.test.ts +151 -151
- package/src/__tests__/e2e/table-keyboard-handler.test.ts +20 -4
- package/src/__tests__/e2e/table-menu.test.ts +172 -172
- package/src/__tests__/e2e/table-resize.test.ts +654 -9
- package/src/__tests__/e2e/table-scrollbar.test.ts +144 -144
- package/src/__tests__/e2e/table-selection.test.ts +563 -563
- package/src/__tests__/e2e/types.d.ts +8 -7
- package/src/__tests__/e2e/utils.ts +52 -52
- package/src/__tests__/unit/table-blots.test.ts +720 -720
- package/src/__tests__/unit/table-caption.test.ts +234 -234
- package/src/__tests__/unit/table-cell-merge.test.ts +713 -724
- package/src/__tests__/unit/table-clipboard.test.ts +2176 -2176
- package/src/__tests__/unit/table-hack.test.ts +1014 -1014
- package/src/__tests__/unit/table-insert.test.ts +915 -926
- package/src/__tests__/unit/table-redo-undo.test.ts +2429 -2429
- package/src/__tests__/unit/table-remove.test.ts +313 -343
- package/src/__tests__/unit/utils.test-d.ts +49 -49
- package/src/__tests__/unit/utils.test.ts +711 -711
- package/src/__tests__/unit/utils.ts +307 -307
- package/src/__tests__/unit/vitest.d.ts +14 -14
- package/src/formats/container-format.ts +107 -107
- package/src/formats/overrides/block-embed.ts +72 -72
- package/src/formats/overrides/block.ts +95 -95
- package/src/formats/overrides/index.ts +3 -3
- package/src/formats/overrides/scroll.ts +70 -70
- package/src/formats/table-body-format.ts +52 -52
- package/src/formats/table-caption-format.ts +116 -116
- package/src/formats/table-cell-format.ts +304 -304
- package/src/formats/table-cell-inner-format.ts +403 -398
- package/src/formats/table-colgroup-format.ts +136 -136
- package/src/formats/table-foot-format.ts +7 -7
- package/src/formats/table-head-format.ts +7 -7
- package/src/formats/table-main-format.ts +1 -1
- package/src/formats/table-row-format.ts +218 -210
- package/src/formats/utils.ts +6 -6
- package/src/index.ts +19 -19
- package/src/modules/index.ts +7 -7
- package/src/modules/table-align.ts +131 -131
- package/src/modules/table-clipboard/table-clipboard.ts +6 -8
- package/src/modules/table-dom-selector.ts +33 -33
- package/src/modules/table-menu/constants.ts +223 -223
- package/src/modules/table-menu/index.ts +4 -4
- package/src/modules/table-menu/table-menu-common.ts +330 -329
- package/src/modules/table-menu/table-menu-contextmenu.ts +111 -118
- package/src/modules/table-menu/table-menu-select.ts +96 -94
- package/src/modules/table-resize/index.ts +5 -5
- package/src/modules/table-resize/table-resize-box.ts +714 -363
- package/src/modules/table-resize/table-resize-common.ts +246 -382
- package/src/modules/table-resize/table-resize-drag.ts +241 -0
- package/src/modules/table-resize/table-resize-line.ts +244 -182
- package/src/modules/table-resize/table-resize-scale.ts +174 -173
- package/src/modules/table-resize/utils.ts +84 -3
- package/src/modules/table-scrollbar.ts +292 -292
- package/src/modules/table-selection.ts +613 -669
- package/src/style/button.less +45 -45
- package/src/style/color-picker.less +136 -136
- package/src/style/dialog.less +53 -53
- package/src/style/functions.less +9 -9
- package/src/style/index.less +120 -120
- package/src/style/input.less +64 -64
- package/src/style/select-box.less +52 -52
- package/src/style/table-creator.less +56 -56
- package/src/style/table-menu.less +125 -125
- package/src/style/table-resize-scale.less +31 -31
- package/src/style/table-resize.less +249 -202
- package/src/style/table-scrollbar.less +49 -49
- package/src/style/table-selection.less +23 -23
- package/src/style/tooltip.less +19 -19
- package/src/style/variables.less +1 -1
- package/src/svg/arrow-up-down.svg +11 -11
- package/src/svg/convert-cell.svg +7 -7
- package/src/table-up.ts +1363 -1360
- package/src/types.d.ts +4 -4
- package/src/utils/bem.ts +23 -23
- package/src/utils/blot-helper.ts +101 -105
- package/src/utils/color.ts +109 -109
- package/src/utils/components/button.ts +22 -22
- package/src/utils/components/color-picker.ts +236 -236
- package/src/utils/components/dialog.ts +83 -41
- package/src/utils/components/index.ts +6 -6
- package/src/utils/components/input.ts +74 -74
- package/src/utils/components/table/creator.ts +89 -89
- package/src/utils/components/table/index.ts +2 -2
- package/src/utils/components/table/select-box.ts +78 -78
- package/src/utils/components/tooltip.ts +179 -189
- package/src/utils/constants.ts +125 -124
- package/src/utils/drag-helper.ts +112 -0
- package/src/utils/index.ts +15 -14
- package/src/utils/is.ts +9 -9
- package/src/utils/position.ts +60 -60
- package/src/utils/resize-observer-helper.ts +47 -47
- package/src/utils/scroll.ts +145 -47
- package/src/utils/style-helper.ts +47 -47
- package/src/utils/transformer.ts +10 -10
- package/src/utils/transition-event-helper.ts +8 -8
- package/src/utils/types.ts +156 -157
- package/src/utils/utils.ts +12 -12
|
@@ -1,304 +1,304 @@
|
|
|
1
|
-
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
-
import type { TableBodyTag, TableCellValue } from '../utils';
|
|
3
|
-
import { blotName, ensureArray, findParentBlot, getInlineStyles, toCamelCase } from '../utils';
|
|
4
|
-
import { ContainerFormat } from './container-format';
|
|
5
|
-
import { TableBodyFormat } from './table-body-format';
|
|
6
|
-
import { TableCellInnerFormat } from './table-cell-inner-format';
|
|
7
|
-
import { TableRowFormat } from './table-row-format';
|
|
8
|
-
import { getValidCellspan } from './utils';
|
|
9
|
-
|
|
10
|
-
export class TableCellFormat extends ContainerFormat {
|
|
11
|
-
static blotName = blotName.tableCell;
|
|
12
|
-
static tagName = 'td';
|
|
13
|
-
static className = 'ql-table-cell';
|
|
14
|
-
static allowAttrs = new Set(['rowspan', 'colspan']);
|
|
15
|
-
static allowDataAttrs = new Set(['table-id', 'row-id', 'col-id', 'empty-row', 'wrap-tag']);
|
|
16
|
-
|
|
17
|
-
// keep `isAllowStyle` and `allowStyle` same with TableCellInnerFormat
|
|
18
|
-
static allowStyle = new Set(['background-color', 'border', 'height']);
|
|
19
|
-
static isAllowStyle(str: string): boolean {
|
|
20
|
-
const cssAttrName = toCamelCase(str);
|
|
21
|
-
for (const style of this.allowStyle) {
|
|
22
|
-
// cause `cssTextToObject` will transform css string to camel case style name
|
|
23
|
-
if (cssAttrName.startsWith(toCamelCase(style))) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static create(value: TableCellValue) {
|
|
31
|
-
const {
|
|
32
|
-
tableId,
|
|
33
|
-
rowId,
|
|
34
|
-
colId,
|
|
35
|
-
rowspan,
|
|
36
|
-
colspan,
|
|
37
|
-
style,
|
|
38
|
-
emptyRow,
|
|
39
|
-
tag = 'td',
|
|
40
|
-
wrapTag = 'tbody',
|
|
41
|
-
} = value;
|
|
42
|
-
const node = document.createElement(tag);
|
|
43
|
-
node.classList.add(...ensureArray(this.className));
|
|
44
|
-
node.dataset.tableId = tableId;
|
|
45
|
-
node.dataset.rowId = rowId;
|
|
46
|
-
node.dataset.colId = colId;
|
|
47
|
-
node.dataset.wrapTag = wrapTag;
|
|
48
|
-
node.setAttribute('rowspan', String(getValidCellspan(rowspan)));
|
|
49
|
-
node.setAttribute('colspan', String(getValidCellspan(colspan)));
|
|
50
|
-
style && (node.style.cssText = style);
|
|
51
|
-
try {
|
|
52
|
-
emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
|
|
53
|
-
}
|
|
54
|
-
catch {}
|
|
55
|
-
return node;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static formats(domNode: HTMLElement) {
|
|
59
|
-
const { tableId, rowId, colId, emptyRow, wrapTag = 'tbody' } = domNode.dataset;
|
|
60
|
-
const rowspan = Number(domNode.getAttribute('rowspan'));
|
|
61
|
-
const colspan = Number(domNode.getAttribute('colspan'));
|
|
62
|
-
const value: Record<string, any> = {
|
|
63
|
-
tableId,
|
|
64
|
-
rowId,
|
|
65
|
-
colId,
|
|
66
|
-
rowspan: getValidCellspan(rowspan),
|
|
67
|
-
colspan: getValidCellspan(colspan),
|
|
68
|
-
tag: domNode.tagName.toLowerCase(),
|
|
69
|
-
wrapTag,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const inlineStyles = getInlineStyles(domNode);
|
|
73
|
-
const entries = Object.entries(inlineStyles).filter(([, value]) => {
|
|
74
|
-
return !['initial', 'inherit'].includes(value);
|
|
75
|
-
});
|
|
76
|
-
if (entries.length > 0) {
|
|
77
|
-
value.style = entries.map(([key, value]) => `${key}: ${value}`).join(';');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
emptyRow && (value.emptyRow = JSON.parse(emptyRow));
|
|
82
|
-
}
|
|
83
|
-
catch {}
|
|
84
|
-
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
isChildHeadTableCellInner() {
|
|
89
|
-
const headChild = this.children.head;
|
|
90
|
-
return headChild && headChild.statics.blotName === blotName.tableCellInner;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
setFormatValue(name: string, value?: any) {
|
|
94
|
-
if (this.statics.allowAttrs.has(name) || this.statics.allowDataAttrs.has(name)) {
|
|
95
|
-
let attrName = name;
|
|
96
|
-
if (this.statics.allowDataAttrs.has(name)) {
|
|
97
|
-
attrName = `data-${name}`;
|
|
98
|
-
}
|
|
99
|
-
if (value) {
|
|
100
|
-
this.domNode.setAttribute(attrName, value);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
this.domNode.removeAttribute(attrName);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else if (this.statics.isAllowStyle(name)) {
|
|
107
|
-
Object.assign(this.domNode.style, {
|
|
108
|
-
[name]: value,
|
|
109
|
-
});
|
|
110
|
-
if (name.startsWith('border')) {
|
|
111
|
-
this.setStyleBoder(name, value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const headChild = this.children.head!;
|
|
116
|
-
if (
|
|
117
|
-
this.isChildHeadTableCellInner()
|
|
118
|
-
&& this.domNode.style.cssText
|
|
119
|
-
// only update if data not match. avoid optimize circular updates
|
|
120
|
-
&& this.domNode.style.cssText !== (headChild.domNode as HTMLElement).dataset.style
|
|
121
|
-
) {
|
|
122
|
-
(headChild.domNode as HTMLElement).dataset.style = this.domNode.style.cssText;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (this.parent && this.parent.statics.blotName === blotName.tableRow) {
|
|
126
|
-
(this.parent as TableRowFormat).setFormatValue(name, value);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
setStyleBoder(name: string, value?: any) {
|
|
131
|
-
// eslint-disable-next-line no-extra-boolean-cast
|
|
132
|
-
const setValue = Boolean(value) ? value : null;
|
|
133
|
-
const isMergeBorder = !['left', 'right', 'top', 'bottom'].some(direction => name.includes(direction)) && name.startsWith('border-');
|
|
134
|
-
if (!isMergeBorder) return;
|
|
135
|
-
|
|
136
|
-
const leftCellInners = this.getNearByCell('left').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
|
|
137
|
-
for (const cell of leftCellInners) {
|
|
138
|
-
cell.setFormatValue(name.replace('border-', 'border-right-'), setValue, true);
|
|
139
|
-
}
|
|
140
|
-
const topCellInners = this.getNearByCell('top').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
|
|
141
|
-
for (const cell of topCellInners) {
|
|
142
|
-
cell.setFormatValue(name.replace('border-', 'border-bottom-'), setValue, true);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
getNearByCell(direction: 'left' | 'top'): TableCellFormat[] {
|
|
147
|
-
const colIds: string[] = [];
|
|
148
|
-
try {
|
|
149
|
-
const tableMain = findParentBlot(this, blotName.tableMain);
|
|
150
|
-
colIds.push(...tableMain.getColIds());
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
console.error(`Cell is not in table! ${error}`);
|
|
154
|
-
}
|
|
155
|
-
if (colIds.length === 0) return [];
|
|
156
|
-
|
|
157
|
-
if (direction === 'left') {
|
|
158
|
-
const nearByCell = new Set<TableCellFormat>();
|
|
159
|
-
let row = this.parent;
|
|
160
|
-
for (let i = 0; i < this.rowspan; i++) {
|
|
161
|
-
if (!(row instanceof TableRowFormat)) break;
|
|
162
|
-
const next = row.children.iterator();
|
|
163
|
-
let cur: null | TableCellFormat = null;
|
|
164
|
-
while ((cur = next())) {
|
|
165
|
-
const i = colIds.indexOf(cur.colId) + cur.colspan;
|
|
166
|
-
if (this.colId === colIds[i]) {
|
|
167
|
-
nearByCell.add(cur);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
row = row.next as TableRowFormat;
|
|
171
|
-
}
|
|
172
|
-
return Array.from(nearByCell);
|
|
173
|
-
}
|
|
174
|
-
else if (direction === 'top') {
|
|
175
|
-
if (!(this.parent instanceof TableRowFormat) || !this.parent.prev) return [];
|
|
176
|
-
const nearByCell = new Set<TableCellFormat>();
|
|
177
|
-
|
|
178
|
-
const startColIndex = this.getColumnIndex();
|
|
179
|
-
const endColIndex = startColIndex + this.colspan;
|
|
180
|
-
const borderColIds = new Set(colIds.filter((_, i) => i >= startColIndex && i < endColIndex));
|
|
181
|
-
|
|
182
|
-
let rowspan = 1;
|
|
183
|
-
let row = this.parent.prev as TableRowFormat;
|
|
184
|
-
while (row) {
|
|
185
|
-
let trReachCurrent = false;
|
|
186
|
-
const next = row.children.iterator();
|
|
187
|
-
let cur: null | TableCellFormat = null;
|
|
188
|
-
let colspan = 0;
|
|
189
|
-
while ((cur = next())) {
|
|
190
|
-
if (borderColIds.has(cur.colId) && cur.rowspan >= rowspan) {
|
|
191
|
-
nearByCell.add(cur);
|
|
192
|
-
borderColIds.delete(cur.colId);
|
|
193
|
-
}
|
|
194
|
-
colspan += cur.colspan;
|
|
195
|
-
cur.rowspan >= rowspan && (trReachCurrent = true);
|
|
196
|
-
}
|
|
197
|
-
if (!trReachCurrent && colspan === colIds.length) break;
|
|
198
|
-
row = row.prev as TableRowFormat;
|
|
199
|
-
rowspan += 1;
|
|
200
|
-
}
|
|
201
|
-
return Array.from(nearByCell);
|
|
202
|
-
}
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
get tableId() {
|
|
207
|
-
return this.domNode.dataset.tableId!;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
get rowId() {
|
|
211
|
-
return this.domNode.dataset.rowId!;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
get colId() {
|
|
215
|
-
return this.domNode.dataset.colId!;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
get rowspan() {
|
|
219
|
-
return Number(this.domNode.getAttribute('rowspan'));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
get colspan() {
|
|
223
|
-
return Number(this.domNode.getAttribute('colspan'));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
get emptyRow(): string[] {
|
|
227
|
-
try {
|
|
228
|
-
return JSON.parse(this.domNode.dataset.emptyRow!);
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
return [];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
get wrapTag() {
|
|
236
|
-
return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
getColumnIndex() {
|
|
240
|
-
const table = findParentBlot(this, blotName.tableMain);
|
|
241
|
-
return table.getColIds().indexOf(this.colId);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
getCellInner() {
|
|
245
|
-
return this.children.head as TableCellInnerFormat;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
convertTableCell() {
|
|
249
|
-
const value = this.statics.formats(this.domNode);
|
|
250
|
-
const tag = value.tag === 'td' ? 'th' : 'td';
|
|
251
|
-
|
|
252
|
-
const headChild = this.children.head!;
|
|
253
|
-
if (
|
|
254
|
-
this.isChildHeadTableCellInner()
|
|
255
|
-
// only update if data not match. avoid optimize circular updates
|
|
256
|
-
&& (headChild.domNode as HTMLElement).dataset.tag !== tag
|
|
257
|
-
) {
|
|
258
|
-
(headChild.domNode as HTMLElement).dataset.tag = tag;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.replaceWith(blotName.tableCell, {
|
|
262
|
-
...value,
|
|
263
|
-
tag,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
checkMerge(): boolean {
|
|
268
|
-
const { colId, rowId, colspan, rowspan } = this;
|
|
269
|
-
const next = this.next as TableCellFormat;
|
|
270
|
-
return (
|
|
271
|
-
next !== null
|
|
272
|
-
&& next.statics.blotName === this.statics.blotName
|
|
273
|
-
&& next.rowId === rowId
|
|
274
|
-
&& next.colId === colId
|
|
275
|
-
&& next.colspan === colspan
|
|
276
|
-
&& next.rowspan === rowspan
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
optimize(context: Record<string, any>) {
|
|
281
|
-
const { tableId, rowId, wrapTag } = this;
|
|
282
|
-
if (this.parent !== null && this.parent.statics.blotName !== blotName.tableRow) {
|
|
283
|
-
this.wrap(blotName.tableRow, { tableId, rowId, wrapTag });
|
|
284
|
-
}
|
|
285
|
-
// when `replaceWith` called to replace cell. wrapTag may change. so row wrapTag also need to update
|
|
286
|
-
if (this.parent.statics.blotName === blotName.tableRow && (this.parent as TableRowFormat).wrapTag !== wrapTag) {
|
|
287
|
-
(this.parent as TableRowFormat).setFormatValue('wrap-tag', wrapTag);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (this.emptyRow.length > 0) {
|
|
291
|
-
const tableBody = this.parent.parent;
|
|
292
|
-
if (tableBody instanceof TableBodyFormat) {
|
|
293
|
-
let insertBefore: TypeParchment.Blot | null = this.parent.next;
|
|
294
|
-
for (const rowId of this.emptyRow) {
|
|
295
|
-
const row = this.scroll.create(blotName.tableRow, { tableId, rowId, wrapTag }) as TableRowFormat;
|
|
296
|
-
tableBody.insertBefore(row, insertBefore);
|
|
297
|
-
insertBefore = row.next;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
super.optimize(context);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
1
|
+
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
+
import type { TableBodyTag, TableCellValue } from '../utils';
|
|
3
|
+
import { blotName, ensureArray, findParentBlot, getInlineStyles, toCamelCase } from '../utils';
|
|
4
|
+
import { ContainerFormat } from './container-format';
|
|
5
|
+
import { TableBodyFormat } from './table-body-format';
|
|
6
|
+
import { TableCellInnerFormat } from './table-cell-inner-format';
|
|
7
|
+
import { TableRowFormat } from './table-row-format';
|
|
8
|
+
import { getValidCellspan } from './utils';
|
|
9
|
+
|
|
10
|
+
export class TableCellFormat extends ContainerFormat {
|
|
11
|
+
static blotName = blotName.tableCell;
|
|
12
|
+
static tagName = 'td';
|
|
13
|
+
static className = 'ql-table-cell';
|
|
14
|
+
static allowAttrs = new Set(['rowspan', 'colspan']);
|
|
15
|
+
static allowDataAttrs = new Set(['table-id', 'row-id', 'col-id', 'empty-row', 'wrap-tag']);
|
|
16
|
+
|
|
17
|
+
// keep `isAllowStyle` and `allowStyle` same with TableCellInnerFormat
|
|
18
|
+
static allowStyle = new Set(['background-color', 'border', 'height']);
|
|
19
|
+
static isAllowStyle(str: string): boolean {
|
|
20
|
+
const cssAttrName = toCamelCase(str);
|
|
21
|
+
for (const style of this.allowStyle) {
|
|
22
|
+
// cause `cssTextToObject` will transform css string to camel case style name
|
|
23
|
+
if (cssAttrName.startsWith(toCamelCase(style))) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static create(value: TableCellValue) {
|
|
31
|
+
const {
|
|
32
|
+
tableId,
|
|
33
|
+
rowId,
|
|
34
|
+
colId,
|
|
35
|
+
rowspan,
|
|
36
|
+
colspan,
|
|
37
|
+
style,
|
|
38
|
+
emptyRow,
|
|
39
|
+
tag = 'td',
|
|
40
|
+
wrapTag = 'tbody',
|
|
41
|
+
} = value;
|
|
42
|
+
const node = document.createElement(tag);
|
|
43
|
+
node.classList.add(...ensureArray(this.className));
|
|
44
|
+
node.dataset.tableId = tableId;
|
|
45
|
+
node.dataset.rowId = rowId;
|
|
46
|
+
node.dataset.colId = colId;
|
|
47
|
+
node.dataset.wrapTag = wrapTag;
|
|
48
|
+
node.setAttribute('rowspan', String(getValidCellspan(rowspan)));
|
|
49
|
+
node.setAttribute('colspan', String(getValidCellspan(colspan)));
|
|
50
|
+
style && (node.style.cssText = style);
|
|
51
|
+
try {
|
|
52
|
+
emptyRow && (node.dataset.emptyRow = JSON.stringify(emptyRow));
|
|
53
|
+
}
|
|
54
|
+
catch {}
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static formats(domNode: HTMLElement) {
|
|
59
|
+
const { tableId, rowId, colId, emptyRow, wrapTag = 'tbody' } = domNode.dataset;
|
|
60
|
+
const rowspan = Number(domNode.getAttribute('rowspan'));
|
|
61
|
+
const colspan = Number(domNode.getAttribute('colspan'));
|
|
62
|
+
const value: Record<string, any> = {
|
|
63
|
+
tableId,
|
|
64
|
+
rowId,
|
|
65
|
+
colId,
|
|
66
|
+
rowspan: getValidCellspan(rowspan),
|
|
67
|
+
colspan: getValidCellspan(colspan),
|
|
68
|
+
tag: domNode.tagName.toLowerCase(),
|
|
69
|
+
wrapTag,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const inlineStyles = getInlineStyles(domNode);
|
|
73
|
+
const entries = Object.entries(inlineStyles).filter(([, value]) => {
|
|
74
|
+
return !['initial', 'inherit'].includes(value);
|
|
75
|
+
});
|
|
76
|
+
if (entries.length > 0) {
|
|
77
|
+
value.style = entries.map(([key, value]) => `${key}: ${value}`).join(';');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
emptyRow && (value.emptyRow = JSON.parse(emptyRow));
|
|
82
|
+
}
|
|
83
|
+
catch {}
|
|
84
|
+
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
isChildHeadTableCellInner() {
|
|
89
|
+
const headChild = this.children.head;
|
|
90
|
+
return headChild && headChild.statics.blotName === blotName.tableCellInner;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setFormatValue(name: string, value?: any) {
|
|
94
|
+
if (this.statics.allowAttrs.has(name) || this.statics.allowDataAttrs.has(name)) {
|
|
95
|
+
let attrName = name;
|
|
96
|
+
if (this.statics.allowDataAttrs.has(name)) {
|
|
97
|
+
attrName = `data-${name}`;
|
|
98
|
+
}
|
|
99
|
+
if (value) {
|
|
100
|
+
this.domNode.setAttribute(attrName, value);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.domNode.removeAttribute(attrName);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (this.statics.isAllowStyle(name)) {
|
|
107
|
+
Object.assign(this.domNode.style, {
|
|
108
|
+
[name]: value,
|
|
109
|
+
});
|
|
110
|
+
if (name.startsWith('border')) {
|
|
111
|
+
this.setStyleBoder(name, value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const headChild = this.children.head!;
|
|
116
|
+
if (
|
|
117
|
+
this.isChildHeadTableCellInner()
|
|
118
|
+
&& this.domNode.style.cssText
|
|
119
|
+
// only update if data not match. avoid optimize circular updates
|
|
120
|
+
&& this.domNode.style.cssText !== (headChild.domNode as HTMLElement).dataset.style
|
|
121
|
+
) {
|
|
122
|
+
(headChild.domNode as HTMLElement).dataset.style = this.domNode.style.cssText;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.parent && this.parent.statics.blotName === blotName.tableRow) {
|
|
126
|
+
(this.parent as TableRowFormat).setFormatValue(name, value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setStyleBoder(name: string, value?: any) {
|
|
131
|
+
// eslint-disable-next-line no-extra-boolean-cast
|
|
132
|
+
const setValue = Boolean(value) ? value : null;
|
|
133
|
+
const isMergeBorder = !['left', 'right', 'top', 'bottom'].some(direction => name.includes(direction)) && name.startsWith('border-');
|
|
134
|
+
if (!isMergeBorder) return;
|
|
135
|
+
|
|
136
|
+
const leftCellInners = this.getNearByCell('left').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
|
|
137
|
+
for (const cell of leftCellInners) {
|
|
138
|
+
cell.setFormatValue(name.replace('border-', 'border-right-'), setValue, true);
|
|
139
|
+
}
|
|
140
|
+
const topCellInners = this.getNearByCell('top').map(td => td.descendant(TableCellInnerFormat, 0)[0]).filter(Boolean) as TableCellInnerFormat[];
|
|
141
|
+
for (const cell of topCellInners) {
|
|
142
|
+
cell.setFormatValue(name.replace('border-', 'border-bottom-'), setValue, true);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getNearByCell(direction: 'left' | 'top'): TableCellFormat[] {
|
|
147
|
+
const colIds: string[] = [];
|
|
148
|
+
try {
|
|
149
|
+
const tableMain = findParentBlot(this, blotName.tableMain);
|
|
150
|
+
colIds.push(...tableMain.getColIds());
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(`Cell is not in table! ${error}`);
|
|
154
|
+
}
|
|
155
|
+
if (colIds.length === 0) return [];
|
|
156
|
+
|
|
157
|
+
if (direction === 'left') {
|
|
158
|
+
const nearByCell = new Set<TableCellFormat>();
|
|
159
|
+
let row = this.parent;
|
|
160
|
+
for (let i = 0; i < this.rowspan; i++) {
|
|
161
|
+
if (!(row instanceof TableRowFormat)) break;
|
|
162
|
+
const next = row.children.iterator();
|
|
163
|
+
let cur: null | TableCellFormat = null;
|
|
164
|
+
while ((cur = next())) {
|
|
165
|
+
const i = colIds.indexOf(cur.colId) + cur.colspan;
|
|
166
|
+
if (this.colId === colIds[i]) {
|
|
167
|
+
nearByCell.add(cur);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
row = row.next as TableRowFormat;
|
|
171
|
+
}
|
|
172
|
+
return Array.from(nearByCell);
|
|
173
|
+
}
|
|
174
|
+
else if (direction === 'top') {
|
|
175
|
+
if (!(this.parent instanceof TableRowFormat) || !this.parent.prev) return [];
|
|
176
|
+
const nearByCell = new Set<TableCellFormat>();
|
|
177
|
+
|
|
178
|
+
const startColIndex = this.getColumnIndex();
|
|
179
|
+
const endColIndex = startColIndex + this.colspan;
|
|
180
|
+
const borderColIds = new Set(colIds.filter((_, i) => i >= startColIndex && i < endColIndex));
|
|
181
|
+
|
|
182
|
+
let rowspan = 1;
|
|
183
|
+
let row = this.parent.prev as TableRowFormat;
|
|
184
|
+
while (row) {
|
|
185
|
+
let trReachCurrent = false;
|
|
186
|
+
const next = row.children.iterator();
|
|
187
|
+
let cur: null | TableCellFormat = null;
|
|
188
|
+
let colspan = 0;
|
|
189
|
+
while ((cur = next())) {
|
|
190
|
+
if (borderColIds.has(cur.colId) && cur.rowspan >= rowspan) {
|
|
191
|
+
nearByCell.add(cur);
|
|
192
|
+
borderColIds.delete(cur.colId);
|
|
193
|
+
}
|
|
194
|
+
colspan += cur.colspan;
|
|
195
|
+
cur.rowspan >= rowspan && (trReachCurrent = true);
|
|
196
|
+
}
|
|
197
|
+
if (!trReachCurrent && colspan === colIds.length) break;
|
|
198
|
+
row = row.prev as TableRowFormat;
|
|
199
|
+
rowspan += 1;
|
|
200
|
+
}
|
|
201
|
+
return Array.from(nearByCell);
|
|
202
|
+
}
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get tableId() {
|
|
207
|
+
return this.domNode.dataset.tableId!;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get rowId() {
|
|
211
|
+
return this.domNode.dataset.rowId!;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get colId() {
|
|
215
|
+
return this.domNode.dataset.colId!;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get rowspan() {
|
|
219
|
+
return Number(this.domNode.getAttribute('rowspan'));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
get colspan() {
|
|
223
|
+
return Number(this.domNode.getAttribute('colspan'));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get emptyRow(): string[] {
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(this.domNode.dataset.emptyRow!);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get wrapTag() {
|
|
236
|
+
return this.domNode.dataset.wrapTag as TableBodyTag || 'tbody';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getColumnIndex() {
|
|
240
|
+
const table = findParentBlot(this, blotName.tableMain);
|
|
241
|
+
return table.getColIds().indexOf(this.colId);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getCellInner() {
|
|
245
|
+
return this.children.head as TableCellInnerFormat;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
convertTableCell() {
|
|
249
|
+
const value = this.statics.formats(this.domNode);
|
|
250
|
+
const tag = value.tag === 'td' ? 'th' : 'td';
|
|
251
|
+
|
|
252
|
+
const headChild = this.children.head!;
|
|
253
|
+
if (
|
|
254
|
+
this.isChildHeadTableCellInner()
|
|
255
|
+
// only update if data not match. avoid optimize circular updates
|
|
256
|
+
&& (headChild.domNode as HTMLElement).dataset.tag !== tag
|
|
257
|
+
) {
|
|
258
|
+
(headChild.domNode as HTMLElement).dataset.tag = tag;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.replaceWith(blotName.tableCell, {
|
|
262
|
+
...value,
|
|
263
|
+
tag,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
checkMerge(): boolean {
|
|
268
|
+
const { colId, rowId, colspan, rowspan } = this;
|
|
269
|
+
const next = this.next as TableCellFormat;
|
|
270
|
+
return (
|
|
271
|
+
next !== null
|
|
272
|
+
&& next.statics.blotName === this.statics.blotName
|
|
273
|
+
&& next.rowId === rowId
|
|
274
|
+
&& next.colId === colId
|
|
275
|
+
&& next.colspan === colspan
|
|
276
|
+
&& next.rowspan === rowspan
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
optimize(context: Record<string, any>) {
|
|
281
|
+
const { tableId, rowId, wrapTag } = this;
|
|
282
|
+
if (this.parent !== null && this.parent.statics.blotName !== blotName.tableRow) {
|
|
283
|
+
this.wrap(blotName.tableRow, { tableId, rowId, wrapTag });
|
|
284
|
+
}
|
|
285
|
+
// when `replaceWith` called to replace cell. wrapTag may change. so row wrapTag also need to update
|
|
286
|
+
if (this.parent.statics.blotName === blotName.tableRow && (this.parent as TableRowFormat).wrapTag !== wrapTag) {
|
|
287
|
+
(this.parent as TableRowFormat).setFormatValue('wrap-tag', wrapTag);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (this.emptyRow.length > 0) {
|
|
291
|
+
const tableBody = this.parent.parent;
|
|
292
|
+
if (tableBody instanceof TableBodyFormat) {
|
|
293
|
+
let insertBefore: TypeParchment.Blot | null = this.parent.next;
|
|
294
|
+
for (const rowId of this.emptyRow) {
|
|
295
|
+
const row = this.scroll.create(blotName.tableRow, { tableId, rowId, wrapTag }) as TableRowFormat;
|
|
296
|
+
tableBody.insertBefore(row, insertBefore);
|
|
297
|
+
insertBefore = row.next;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
super.optimize(context);
|
|
303
|
+
}
|
|
304
|
+
}
|