quill-table-up 2.2.2 → 2.2.3
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/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/unit/table-blots.test.ts +58 -1
- package/src/__tests__/unit/table-clipboard.test.ts +97 -1
- package/src/formats/table-main-format.ts +44 -1
- package/src/modules/table-clipboard.ts +12 -4
- package/src/table-up.ts +17 -0
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import Quill from 'quill';
|
|
1
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import { TableCellFormat } from '../../formats';
|
|
3
4
|
import { TableUp } from '../../table-up';
|
|
4
|
-
import { createQuillWithTableModule } from './utils';
|
|
5
|
+
import { createQuillWithTableModule, createTableBodyHTML, createTableCaptionHTML, createTaleColHTML, expectDelta } from './utils';
|
|
6
|
+
|
|
7
|
+
const Delta = Quill.import('delta');
|
|
5
8
|
|
|
6
9
|
beforeEach(() => {
|
|
7
10
|
vi.useFakeTimers();
|
|
@@ -221,3 +224,57 @@ describe('test tableCell getNearByCell', () => {
|
|
|
221
224
|
expect(tds[2].getNearByCell('top')).toEqual([tds[1]]);
|
|
222
225
|
});
|
|
223
226
|
});
|
|
227
|
+
|
|
228
|
+
describe('test table child sort', () => {
|
|
229
|
+
it('table child should sort by correct order event delta not', async () => {
|
|
230
|
+
const quill = createQuillWithTableModule(`<p><br></p>`);
|
|
231
|
+
quill.setContents([
|
|
232
|
+
{ insert: '\n' },
|
|
233
|
+
{ insert: '1' },
|
|
234
|
+
{ attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
235
|
+
{ insert: '2' },
|
|
236
|
+
{ attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
237
|
+
{ insert: '3' },
|
|
238
|
+
{ attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '3', rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
239
|
+
{ insert: { 'table-up-col': { tableId: '1', colId: '1', full: false, width: 100 } } },
|
|
240
|
+
{ insert: { 'table-up-col': { tableId: '1', colId: '2', full: false, width: 100 } } },
|
|
241
|
+
{ insert: { 'table-up-col': { tableId: '1', colId: '3', full: false, width: 100 } } },
|
|
242
|
+
{ insert: 'title' },
|
|
243
|
+
{ attributes: { 'table-up-caption': { tableId: '1', side: 'top' } }, insert: '\n' },
|
|
244
|
+
{ insert: '\n' },
|
|
245
|
+
]);
|
|
246
|
+
await vi.runAllTimersAsync();
|
|
247
|
+
|
|
248
|
+
expect(quill.root).toEqualHTML(
|
|
249
|
+
`
|
|
250
|
+
<p><br></p>
|
|
251
|
+
<div>
|
|
252
|
+
<table cellpadding="0" cellspacing="0" style="margin-right: auto; width: 300px;">
|
|
253
|
+
${createTableCaptionHTML({ text: 'title' })}
|
|
254
|
+
${createTaleColHTML(3, { full: false, width: 100 })}
|
|
255
|
+
${createTableBodyHTML(1, 3, { isEmpty: false })}
|
|
256
|
+
</table>
|
|
257
|
+
</div>
|
|
258
|
+
<p><br></p>
|
|
259
|
+
`,
|
|
260
|
+
{ ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
|
|
261
|
+
);
|
|
262
|
+
expectDelta(
|
|
263
|
+
new Delta([
|
|
264
|
+
{ insert: '\ntitle' },
|
|
265
|
+
{ attributes: { 'table-up-caption': { side: 'top' } }, insert: '\n' },
|
|
266
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
267
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
268
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
269
|
+
{ insert: '1' },
|
|
270
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
271
|
+
{ insert: '2' },
|
|
272
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
273
|
+
{ insert: '3' },
|
|
274
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
275
|
+
{ insert: '\n' },
|
|
276
|
+
]),
|
|
277
|
+
quill.getContents(),
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Quill from 'quill';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
import { TableUp } from '../../table-up';
|
|
4
|
-
import { createQuillWithTableModule, createTableDeltaOps, createTableHTML, createTaleColHTML, expectDelta, simulatePasteHTML } from './utils';
|
|
4
|
+
import { createQuillWithTableModule, createTableBodyHTML, createTableCaptionHTML, createTableDeltaOps, createTableHTML, createTaleColHTML, expectDelta, simulatePasteHTML } from './utils';
|
|
5
5
|
|
|
6
6
|
const Delta = Quill.import('delta');
|
|
7
7
|
|
|
@@ -244,6 +244,60 @@ describe('clipboard cell structure', () => {
|
|
|
244
244
|
);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
+
it('clipboard convert multiple merged cell 3', async () => {
|
|
248
|
+
const quill = createQuillWithTableModule(`<p><br></p>`);
|
|
249
|
+
quill.setContents(
|
|
250
|
+
quill.clipboard.convert({
|
|
251
|
+
html: '<table><tbody><tr><td rowspan="3">merge1</td><td colspan="2">merge2</td><td>3</td></tr><tr><td rowspan="2">merge3</td><td>1</td><td>4</td></tr><tr><td>2</td><td>5</td></tr></tbody></table>',
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
await vi.runAllTimersAsync();
|
|
255
|
+
expect(quill.root).toEqualHTML(
|
|
256
|
+
`
|
|
257
|
+
<p><br></p>
|
|
258
|
+
<div>
|
|
259
|
+
<table cellpadding="0" cellspacing="0">
|
|
260
|
+
${createTaleColHTML(4, { width: 100, full: false })}
|
|
261
|
+
<tbody>
|
|
262
|
+
<tr>
|
|
263
|
+
<td rowspan="3" colspan="1">
|
|
264
|
+
<div data-rowspan="3" data-colspan="1"><p>merge1</p></div>
|
|
265
|
+
</td>
|
|
266
|
+
<td rowspan="1" colspan="2">
|
|
267
|
+
<div data-rowspan="1" data-colspan="2"><p>merge2</p></div>
|
|
268
|
+
</td>
|
|
269
|
+
<td rowspan="1" colspan="1">
|
|
270
|
+
<div data-rowspan="1" data-colspan="1"><p>3</p></div>
|
|
271
|
+
</td>
|
|
272
|
+
</tr>
|
|
273
|
+
<tr>
|
|
274
|
+
<td rowspan="2" colspan="1">
|
|
275
|
+
<div data-rowspan="2" data-colspan="1"><p>merge3</p></div>
|
|
276
|
+
</td>
|
|
277
|
+
<td rowspan="1" colspan="1">
|
|
278
|
+
<div data-rowspan="1" data-colspan="1"><p>1</p></div>
|
|
279
|
+
</td>
|
|
280
|
+
<td rowspan="1" colspan="1">
|
|
281
|
+
<div data-rowspan="1" data-colspan="1"><p>4</p></div>
|
|
282
|
+
</td>
|
|
283
|
+
</tr>
|
|
284
|
+
<tr>
|
|
285
|
+
<td rowspan="1" colspan="1">
|
|
286
|
+
<div data-rowspan="1" data-colspan="1"><p>2</p></div>
|
|
287
|
+
</td>
|
|
288
|
+
<td rowspan="1" colspan="1">
|
|
289
|
+
<div data-rowspan="1" data-colspan="1"><p>5</p></div>
|
|
290
|
+
</td>
|
|
291
|
+
</tr>
|
|
292
|
+
</tbody>
|
|
293
|
+
</table>
|
|
294
|
+
</div>
|
|
295
|
+
<p><br></p>
|
|
296
|
+
`,
|
|
297
|
+
{ ignoreAttrs: ['class', 'style', 'data-table-id', 'data-row-id', 'data-col-id', 'contenteditable'] },
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
247
301
|
it('clipboard convert table without new line', async () => {
|
|
248
302
|
const quill = createQuillWithTableModule(`<p><br></p>`);
|
|
249
303
|
quill.setContents(
|
|
@@ -827,6 +881,48 @@ describe('clipboard cell structure', () => {
|
|
|
827
881
|
{ ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
|
|
828
882
|
);
|
|
829
883
|
});
|
|
884
|
+
|
|
885
|
+
it('clipboard convert should generate colgroup at correct position', async () => {
|
|
886
|
+
const quill = createQuillWithTableModule(`<p><br></p>`);
|
|
887
|
+
quill.setContents(
|
|
888
|
+
quill.clipboard.convert({
|
|
889
|
+
html: '<table class="ql-table" data-table-id="0u473v7j5th" cellpadding="0" cellspacing="0"><tbody data-table-id="0u473v7j5th"><tr class="ql-table-row" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj"><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="0025lmzkmn85x" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="0025lmzkmn85x" data-rowspan="1" data-colspan="1" contenteditable="true"><p>1</p></div></td><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="g8v4waukxpe" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="g8v4waukxpe" data-rowspan="1" data-colspan="1" contenteditable="true"><p>2</p></div></td><td class="ql-table-cell" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="7ubw564uue5" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="0u473v7j5th" data-row-id="1de78ie70hj" data-col-id="7ubw564uue5" data-rowspan="1" data-colspan="1" contenteditable="true"><p>3</p></div></td></tr></tbody><caption class="ql-table-caption" data-table-id="0u473v7j5th" contenteditable="true">title</caption></table>',
|
|
890
|
+
}),
|
|
891
|
+
);
|
|
892
|
+
await vi.runAllTimersAsync();
|
|
893
|
+
|
|
894
|
+
expect(quill.root).toEqualHTML(
|
|
895
|
+
`
|
|
896
|
+
<p><br></p>
|
|
897
|
+
<div>
|
|
898
|
+
<table cellpadding="0" cellspacing="0" style="margin-right: auto; width: 300px;">
|
|
899
|
+
${createTableCaptionHTML({ text: 'title' })}
|
|
900
|
+
${createTaleColHTML(3, { full: false, width: 100 })}
|
|
901
|
+
${createTableBodyHTML(1, 3, { isEmpty: false })}
|
|
902
|
+
</table>
|
|
903
|
+
</div>
|
|
904
|
+
<p><br></p>
|
|
905
|
+
`,
|
|
906
|
+
{ ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
|
|
907
|
+
);
|
|
908
|
+
expectDelta(
|
|
909
|
+
new Delta([
|
|
910
|
+
{ insert: '\ntitle' },
|
|
911
|
+
{ attributes: { 'table-up-caption': { side: 'top' } }, insert: '\n' },
|
|
912
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
913
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
914
|
+
{ insert: { 'table-up-col': { full: false, width: 100 } } },
|
|
915
|
+
{ insert: '1' },
|
|
916
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
917
|
+
{ insert: '2' },
|
|
918
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
919
|
+
{ insert: '3' },
|
|
920
|
+
{ attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
|
|
921
|
+
{ insert: '\n' },
|
|
922
|
+
]),
|
|
923
|
+
quill.getContents(),
|
|
924
|
+
);
|
|
925
|
+
});
|
|
830
926
|
});
|
|
831
927
|
|
|
832
928
|
describe('clipboard content format', () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type TypeScroll from 'quill/blots/scroll';
|
|
1
2
|
import type { TableValue } from '../utils';
|
|
2
3
|
import { blotName, tableUpSize } from '../utils';
|
|
3
4
|
import { ContainerFormat } from './container-format';
|
|
@@ -25,7 +26,7 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
25
26
|
return node;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
constructor(scroll:
|
|
29
|
+
constructor(public scroll: TypeScroll, domNode: HTMLElement, _value: unknown) {
|
|
29
30
|
super(scroll, domNode);
|
|
30
31
|
this.updateAlign();
|
|
31
32
|
}
|
|
@@ -159,4 +160,46 @@ export class TableMainFormat extends ContainerFormat {
|
|
|
159
160
|
|
|
160
161
|
super.optimize(context);
|
|
161
162
|
}
|
|
163
|
+
|
|
164
|
+
sortMergeChildren() {
|
|
165
|
+
// move same type children to the first child
|
|
166
|
+
const childs: Record<string, ContainerFormat[]> = {
|
|
167
|
+
[blotName.tableCaption]: [],
|
|
168
|
+
[blotName.tableColgroup]: [],
|
|
169
|
+
[blotName.tableBody]: [],
|
|
170
|
+
};
|
|
171
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
172
|
+
this.children.forEach((child) => {
|
|
173
|
+
if (childs[child.statics.blotName]) {
|
|
174
|
+
childs[child.statics.blotName].push(child as ContainerFormat);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
for (const formats of Object.values(childs)) {
|
|
178
|
+
for (let i = 1; i < formats.length; i++) {
|
|
179
|
+
formats[i].moveChildren(formats[0]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// check sort child
|
|
184
|
+
const tableCaption = childs[blotName.tableCaption][0];
|
|
185
|
+
const tableColgroup = childs[blotName.tableColgroup][0];
|
|
186
|
+
const tableBody = childs[blotName.tableBody][0];
|
|
187
|
+
|
|
188
|
+
const isCaptionFirst = tableCaption && this.children.head !== tableCaption;
|
|
189
|
+
const isColgroupSecond = tableColgroup && tableCaption && tableCaption.next !== tableColgroup;
|
|
190
|
+
const isColgroupFirst = tableColgroup && !tableCaption && this.children.head !== tableColgroup;
|
|
191
|
+
const isBodyLast = tableBody && this.children.tail !== tableBody;
|
|
192
|
+
|
|
193
|
+
// sort child
|
|
194
|
+
if (isCaptionFirst || isColgroupSecond || isColgroupFirst || isBodyLast) {
|
|
195
|
+
const tableMain = this.clone() as TableMainFormat;
|
|
196
|
+
tableCaption && tableMain.appendChild(tableCaption);
|
|
197
|
+
tableColgroup && tableMain.appendChild(tableColgroup);
|
|
198
|
+
tableBody && tableMain.appendChild(tableBody);
|
|
199
|
+
|
|
200
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
201
|
+
this.children.forEach(child => child.remove());
|
|
202
|
+
tableMain.moveChildren(this);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
162
205
|
}
|
|
@@ -82,7 +82,13 @@ export class TableClipboard extends Clipboard {
|
|
|
82
82
|
ops.push({ attributes: attrs, insert });
|
|
83
83
|
}
|
|
84
84
|
// record col insert index
|
|
85
|
-
if (
|
|
85
|
+
if (
|
|
86
|
+
!attrs?.[blotName.tableCellInner]
|
|
87
|
+
&& !attrs?.[blotName.tableCaption]
|
|
88
|
+
&& !hasCol
|
|
89
|
+
&& isString(insert)
|
|
90
|
+
&& insert.trim().length > 0
|
|
91
|
+
) {
|
|
86
92
|
bodyStartIndex = i;
|
|
87
93
|
}
|
|
88
94
|
}
|
|
@@ -200,13 +206,15 @@ export class TableClipboard extends Clipboard {
|
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
// skip the colspan of the cell in the previous row
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
for (let i = this.cellCount; i < this.rowspanCount.length; i++) {
|
|
210
|
+
const { rowspan, colspan } = this.rowspanCount[i];
|
|
211
|
+
if (rowspan === 0) break;
|
|
212
|
+
this.cellCount += colspan;
|
|
213
|
+
}
|
|
205
214
|
// add current cell rowspan in `rowspanCount` to calculate next row cell
|
|
206
215
|
if (cellFormat.rowspan > 1) {
|
|
207
216
|
this.rowspanCount[this.cellCount] = { rowspan: cellFormat.rowspan, colspan: cellFormat.colspan };
|
|
208
217
|
}
|
|
209
|
-
|
|
210
218
|
const colId = this.colIds[this.cellCount];
|
|
211
219
|
this.cellCount += cellFormat.colspan;
|
|
212
220
|
|
package/src/table-up.ts
CHANGED
|
@@ -956,6 +956,23 @@ export class TableUp {
|
|
|
956
956
|
}
|
|
957
957
|
|
|
958
958
|
listenBalanceCells() {
|
|
959
|
+
this.quill.on(
|
|
960
|
+
Quill.events.SCROLL_OPTIMIZE,
|
|
961
|
+
(mutations: MutationRecord[]) => {
|
|
962
|
+
mutations.some((mutation) => {
|
|
963
|
+
const mutationTarget = mutation.target as HTMLElement;
|
|
964
|
+
if (mutationTarget.tagName === 'TABLE') {
|
|
965
|
+
const tableMain = Quill.find(mutationTarget) as TableMainFormat;
|
|
966
|
+
if (tableMain) {
|
|
967
|
+
tableMain.sortMergeChildren();
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
});
|
|
973
|
+
},
|
|
974
|
+
);
|
|
975
|
+
|
|
959
976
|
this.quill.on(
|
|
960
977
|
Quill.events.SCROLL_OPTIMIZE,
|
|
961
978
|
(mutations: MutationRecord[]) => {
|