quill-table-up 2.2.4 → 2.3.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "quill-table-up",
3
3
  "type": "module",
4
- "version": "2.2.4",
4
+ "version": "2.3.0",
5
5
  "packageManager": "pnpm@9.9.0",
6
6
  "description": "A table module for quill2.x",
7
7
  "author": "zzxming",
@@ -115,7 +115,7 @@ test('test TableSelection set indent format', async ({ page }) => {
115
115
  expect(await page.locator('#editor1 .ql-table-cell-inner p.ql-indent-1').count()).toBe(4);
116
116
  });
117
117
 
118
- test('test TableSelection set multiple format', async ({ page }) => {
118
+ test('test TableSelection set format header', async ({ page }) => {
119
119
  await createTableBySelect(page, 'container1', 2, 2);
120
120
  const cell = page.locator('#editor1 .ql-editor .ql-table td').nth(0);
121
121
  const cellBounding = (await cell.boundingBox())!;
@@ -125,13 +125,38 @@ test('test TableSelection set multiple format', async ({ page }) => {
125
125
  await page.mouse.move(cellBounding.x + cellBounding.width * 2 - 10, cellBounding.y + cellBounding.height * 2 - 10);
126
126
  await page.mouse.up();
127
127
 
128
- await page.locator('#editor1 .ql-editor p').nth(0).type('1');
129
- const cells = page.locator('#editor1 .ql-editor .ql-table-cell-inner');
130
- await cells.all().then(async (elements) => {
131
- for (const element of elements) {
132
- await element.type('1');
133
- }
134
- });
128
+ await page.locator('#container1 .ql-toolbar .ql-header').nth(0).click();
129
+ await page.locator('#container1 .ql-toolbar .ql-header .ql-picker-options .ql-picker-item').nth(0).click();
130
+
131
+ expect(await page.locator('#editor1 .ql-table-cell-inner h1').count()).toBe(4);
132
+ });
133
+
134
+ extendTest('test TableSelection set multiple format', async ({ page, editorPage }) => {
135
+ editorPage.index = 0;
136
+ await editorPage.setContents([
137
+ { insert: '\n' },
138
+ { insert: { 'table-up-col': { tableId: '1', colId: '1', full: false, width: 100 } } },
139
+ { insert: { 'table-up-col': { tableId: '1', colId: '2', full: false, width: 100 } } },
140
+ { insert: '1' },
141
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
142
+ { insert: '2' },
143
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
144
+ { insert: '3' },
145
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
146
+ { insert: '4' },
147
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
148
+ { insert: '\n' },
149
+ ]);
150
+ await page.waitForTimeout(1000);
151
+ const cell = page.locator('#editor1 .ql-editor .ql-table td').nth(0);
152
+ const cellBounding = (await cell.boundingBox())!;
153
+ expect(cellBounding).not.toBeNull();
154
+ await cell.click();
155
+ await page.mouse.down();
156
+ await page.mouse.move(cellBounding.x + cellBounding.width * 1.5, cellBounding.y + cellBounding.height * 1.5);
157
+ await page.mouse.up();
158
+ await editorPage.blur();
159
+ await editorPage.focus();
135
160
 
136
161
  await page.locator('.ql-toolbar .ql-bold').nth(0).click();
137
162
  await page.locator('.ql-toolbar .ql-italic').nth(0).click();
@@ -145,7 +170,6 @@ test('test TableSelection set multiple format', async ({ page }) => {
145
170
  expect(await page.locator('#editor1 .ql-table-cell-inner em').count()).toBe(4);
146
171
  expect(await page.locator('#editor1 .ql-table-cell-inner s').count()).toBe(4);
147
172
  expect(await page.locator('#editor1 .ql-table-cell-inner u').count()).toBe(4);
148
-
149
173
  await strongEl.all().then(async (elements) => {
150
174
  for (const element of elements) {
151
175
  await expect(element).toHaveCSS('background-color', 'rgb(230, 0, 0)');
@@ -175,6 +199,53 @@ test('test TableSelection clean format', async ({ page }) => {
175
199
  expect(await cleanEl.count()).toBe(0);
176
200
  });
177
201
 
202
+ extendTest('test TableSelection set format in part of cell text', async ({ page, editorPage }) => {
203
+ editorPage.index = 0;
204
+ await editorPage.setContents([
205
+ { insert: '\n' },
206
+ { insert: { 'table-up-col': { tableId: '1', colId: '1', full: false, width: 100 } } },
207
+ { insert: { 'table-up-col': { tableId: '1', colId: '2', full: false, width: 100 } } },
208
+ { insert: '1' },
209
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
210
+ { insert: '1' },
211
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
212
+ { insert: '1' },
213
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
214
+ { insert: '2' },
215
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
216
+ { insert: '2' },
217
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
218
+ { insert: '2' },
219
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
220
+ { insert: '3' },
221
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
222
+ { insert: '4' },
223
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
224
+ { insert: '\n' },
225
+ ]);
226
+ await page.waitForTimeout(1000);
227
+ await page.locator('#editor1 .ql-editor .ql-table td').nth(0).click();
228
+
229
+ await editorPage.setSelection(4, 0);
230
+ await page.locator('#container1 .ql-toolbar .ql-header').nth(0).click();
231
+ await page.locator('#container1 .ql-toolbar .ql-header .ql-picker-options .ql-picker-item').nth(0).click();
232
+ const selectionAfterHeader = await editorPage.getSelection();
233
+ expect(selectionAfterHeader).toEqual({ index: 4, length: 0 });
234
+
235
+ await editorPage.setSelection(6, 0);
236
+ await page.locator('.ql-toolbar .ql-list[value="bullet"]').nth(0).click();
237
+ const selectionAfterList = await editorPage.getSelection();
238
+ expect(selectionAfterList).toEqual({ index: 6, length: 0 });
239
+
240
+ await page.locator('#editor1 .ql-editor .ql-table td').nth(1).click();
241
+ await editorPage.setSelection(9, 3);
242
+ await page.locator('.ql-toolbar .ql-bold').nth(0).click();
243
+ await page.locator('.ql-toolbar .ql-italic').nth(0).click();
244
+
245
+ expect(await page.locator('#editor1 .ql-table-cell-inner strong').count()).toBe(2);
246
+ expect(await page.locator('#editor1 .ql-table-cell-inner em').count()).toBe(2);
247
+ });
248
+
178
249
  extendTest('test TableSelection should update when text change', async ({ page, editorPage }) => {
179
250
  editorPage.index = 0;
180
251
  await createTableBySelect(page, 'container1', 3, 3);
@@ -576,3 +647,20 @@ extendTest('toolbox bounds should same with quill.root', async ({ page, editorPa
576
647
  expect(quillRootBoundingAfter).not.toBeNull();
577
648
  expect(toolboxBoundingAfter).toEqual(quillRootBoundingAfter);
578
649
  });
650
+
651
+ extendTest('TableSelection should not update when input composition', async ({ page, editorPage }) => {
652
+ editorPage.index = 0;
653
+ await createTableBySelect(page, 'container1', 3, 3);
654
+
655
+ await page.locator('#editor1 .ql-table .ql-table-cell').nth(0).click();
656
+ const selectionLine = page.locator('#container1 .table-up-selection .table-up-selection__line');
657
+ await expect(selectionLine).toBeVisible();
658
+ const bounding = (await selectionLine.boundingBox())!;
659
+ expect(bounding).not.toBeNull();
660
+
661
+ await page.dispatchEvent('#editor1 .ql-editor .ql-table-cell', 'compositionstart');
662
+ await page.type('#editor1 .ql-editor .ql-table-cell', 'zhongwen');
663
+
664
+ const composingBounding = (await selectionLine.boundingBox())!;
665
+ expect(composingBounding).toEqual(bounding);
666
+ });
@@ -923,6 +923,187 @@ describe('clipboard cell structure', () => {
923
923
  quill.getContents(),
924
924
  );
925
925
  });
926
+
927
+ it('clipboard convert rowspan and struct have empty tr', async () => {
928
+ const quill = createQuillWithTableModule(`<p><br></p>`);
929
+ quill.setContents(
930
+ quill.clipboard.convert({
931
+ html: `
932
+ <body link=blue vlink=purple>
933
+
934
+ <table border=0 cellpadding=0 cellspacing=0 width=126 style='border-collapse:
935
+ collapse;width:94pt'>
936
+ <!--StartFragment-->
937
+ <col width=63 span=2 style='width:47pt'>
938
+ <tr height=19 style='height:14.4pt'>
939
+ <td rowspan=6 height=114 class=xl65 width=63 style='border-bottom:.5pt solid black;
940
+ height:86.4pt;width:47pt'>合并1</td>
941
+ <td rowspan=2 class=xl65 width=63 style='border-bottom:.5pt solid black;
942
+ width:47pt'>合并2</td>
943
+ </tr>
944
+ <tr height=19 style='height:14.4pt'>
945
+ </tr>
946
+ <tr height=19 style='height:14.4pt'>
947
+ <td rowspan=2 height=38 class=xl65 style='border-bottom:.5pt solid black;
948
+ height:28.8pt;border-top:none'>合并3</td>
949
+ </tr>
950
+ <tr height=19 style='height:14.4pt'>
951
+ </tr>
952
+ <tr height=19 style='height:14.4pt'>
953
+ <td rowspan=2 height=38 class=xl65 style='border-bottom:.5pt solid black;
954
+ height:28.8pt;border-top:none'>合并4</td>
955
+ </tr>
956
+ <tr height=19 style='height:14.4pt'>
957
+ </tr>
958
+ <!--EndFragment-->
959
+ </table>
960
+
961
+ </body>
962
+ `,
963
+ }),
964
+ );
965
+ await vi.runAllTimersAsync();
966
+
967
+ expect(quill.root).toEqualHTML(
968
+ `
969
+ <p><br></p>
970
+ <div>
971
+ <table cellpadding="0" cellspacing="0">
972
+ ${createTaleColHTML(2, { full: false, width: 63 })}
973
+ <tbody>
974
+ <tr>
975
+ <td rowspan="3" colspan="1">
976
+ <div><p>合并1</p></div>
977
+ </td>
978
+ <td rowspan="1" colspan="1">
979
+ <div><p>合并2</p></div>
980
+ </td>
981
+ </tr>
982
+ <tr>
983
+ <td rowspan="1" colspan="1">
984
+ <div><p>合并3</p></div>
985
+ </td>
986
+ </tr>
987
+ <tr>
988
+ <td rowspan="1" colspan="1">
989
+ <div><p>合并4</p></div>
990
+ </td>
991
+ </tr>
992
+ </tbody>
993
+ </table>
994
+ </div>
995
+ <p><br></p>
996
+ `,
997
+ { ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'] },
998
+ );
999
+ expectDelta(
1000
+ new Delta([
1001
+ { insert: '\n' },
1002
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1003
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1004
+ { insert: '合并1' },
1005
+ { attributes: { 'table-up-cell-inner': { rowspan: 3, colspan: 1 } }, insert: '\n' },
1006
+ { insert: '合并2' },
1007
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1008
+ { insert: '合并3' },
1009
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1010
+ { insert: '合并4' },
1011
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1012
+ { insert: '\n' },
1013
+ ]),
1014
+ quill.getContents(),
1015
+ );
1016
+ });
1017
+
1018
+ it('clipboard convert col with span attribute', async () => {
1019
+ const quill = createQuillWithTableModule(`<p><br></p>`);
1020
+ quill.setContents(
1021
+ quill.clipboard.convert({
1022
+ html: `
1023
+ <body link=blue vlink=purple>
1024
+
1025
+ <table border=0 cellpadding=0 cellspacing=0 width=252 style='border-collapse:
1026
+ collapse;width:188pt'>
1027
+ <!--StartFragment-->
1028
+ <col width=63 span=4 style='width:47pt'>
1029
+ <tr height=19 style='height:14.4pt'>
1030
+ <td colspan=2 rowspan=6 height=114 class=xl65 width=126 style='height:86.4pt;
1031
+ width:94pt'>1</td>
1032
+ <td colspan=2 rowspan=2 class=xl65 width=126 style='width:94pt'>2</td>
1033
+ </tr>
1034
+ <tr height=19 style='height:14.4pt'>
1035
+ </tr>
1036
+ <tr height=19 style='height:14.4pt'>
1037
+ <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>3</td>
1038
+ </tr>
1039
+ <tr height=19 style='height:14.4pt'>
1040
+ </tr>
1041
+ <tr height=19 style='height:14.4pt'>
1042
+ <td colspan=2 rowspan=2 height=38 class=xl65 style='height:28.8pt'>4</td>
1043
+ </tr>
1044
+ <tr height=19 style='height:14.4pt'>
1045
+ </tr>
1046
+ <!--EndFragment-->
1047
+ </table>
1048
+
1049
+ </body>
1050
+ `,
1051
+ }),
1052
+ );
1053
+ await vi.runAllTimersAsync();
1054
+
1055
+ expect(quill.root).toEqualHTML(
1056
+ `
1057
+ <p><br></p>
1058
+ <div>
1059
+ <table cellpadding="0" cellspacing="0">
1060
+ ${createTaleColHTML(4, { full: false, width: 63 })}
1061
+ <tbody>
1062
+ <tr>
1063
+ <td rowspan="3" colspan="2">
1064
+ <div><p>1</p></div>
1065
+ </td>
1066
+ <td rowspan="1" colspan="2">
1067
+ <div><p>2</p></div>
1068
+ </td>
1069
+ </tr>
1070
+ <tr>
1071
+ <td rowspan="1" colspan="2">
1072
+ <div><p>3</p></div>
1073
+ </td>
1074
+ </tr>
1075
+ <tr>
1076
+ <td rowspan="1" colspan="2">
1077
+ <div><p>4</p></div>
1078
+ </td>
1079
+ </tr>
1080
+ </tbody>
1081
+ </table>
1082
+ </div>
1083
+ <p><br></p>
1084
+ `,
1085
+ { ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'] },
1086
+ );
1087
+ expectDelta(
1088
+ new Delta([
1089
+ { insert: '\n' },
1090
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1091
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1092
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1093
+ { insert: { 'table-up-col': { full: false, width: 63 } } },
1094
+ { insert: '1' },
1095
+ { attributes: { 'table-up-cell-inner': { rowspan: 3, colspan: 2 } }, insert: '\n' },
1096
+ { insert: '2' },
1097
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1098
+ { insert: '3' },
1099
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1100
+ { insert: '4' },
1101
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1102
+ { insert: '\n' },
1103
+ ]),
1104
+ quill.getContents(),
1105
+ );
1106
+ });
926
1107
  });
927
1108
 
928
1109
  describe('clipboard content format', () => {
@@ -320,7 +320,7 @@ describe('hack format cell', () => {
320
320
  expect(quill.getSelection()).toEqual({ index: 18, length: 0 });
321
321
  });
322
322
 
323
- it('select length is 0 and selectedTds not empty should format all text in cell', async () => {
323
+ it('selection not in cell and selectedTds not empty should format all text in cell', async () => {
324
324
  const quill = createQuillWithTableModule('<p></p>', { selection: TableSelection });
325
325
  quill.setContents(createTableDeltaOps(2, 2, { full: false }));
326
326
  quill.updateContents(
@@ -333,14 +333,15 @@ describe('hack format cell', () => {
333
333
  .insert('123456789'),
334
334
  );
335
335
 
336
+ quill.setSelection({ index: 3, length: 0 });
337
+ quill.blur();
338
+ quill.focus();
336
339
  const tableUp = quill.getModule(TableUp.moduleName) as TableUp;
337
340
  const tds = quill.scroll.descendants(TableCellInnerFormat, 0);
338
341
  tableUp.tableSelection!.selectedTds = [tds[0], tds[2]];
339
- quill.setSelection(18, 0, Quill.sources.SILENT);
340
342
  quill.format('bold', true);
341
343
  // simulate `getBoundingClientRect` will effect selectedTd computed position. need manual set
342
344
  tableUp.tableSelection!.selectedTds = [tds[0], tds[2]];
343
- quill.setSelection(18, 0, Quill.sources.SILENT);
344
345
  quill.format('list', 'bullet');
345
346
  expectDelta(
346
347
  quill.getContents(),
@@ -363,10 +364,9 @@ describe('hack format cell', () => {
363
364
  { insert: '\n' },
364
365
  ]),
365
366
  );
366
- expect(quill.getSelection()).toBeNull();
367
367
  });
368
368
 
369
- it('selection not in cell but have selectedTds. should format all text in selected cell', async () => {
369
+ it('selection can get format tableCellInner. should format like origin', async () => {
370
370
  const quill = createQuillWithTableModule('<p></p>', { selection: TableSelection });
371
371
  quill.setContents([
372
372
  { insert: '12345\n' },
@@ -383,33 +383,29 @@ describe('hack format cell', () => {
383
383
  { insert: '\n' },
384
384
  ]);
385
385
 
386
- const tableUp = quill.getModule(TableUp.moduleName) as TableUp;
387
- const tds = quill.scroll.descendants(TableCellInnerFormat, 0);
388
- tableUp.tableSelection!.selectedTds = [tds[0], tds[1]];
389
- quill.setSelection(1, 3, Quill.sources.SILENT);
390
- quill.format('bold', true);
391
- // simulate `getBoundingClientRect` will effect selectedTd computed position. need manual set
392
- tableUp.tableSelection!.selectedTds = [tds[0], tds[1]];
393
- quill.setSelection(1, 3, Quill.sources.SILENT);
386
+ quill.setSelection(9, 0, Quill.sources.SILENT);
394
387
  quill.format('list', 'bullet');
388
+ // simulate `getBoundingClientRect` will effect selectedTd computed position. need manual set
389
+ quill.setSelection(14, 1, Quill.sources.SILENT);
390
+ quill.format('bold', true);
395
391
  expectDelta(
396
392
  quill.getContents(),
397
393
  new Delta([
398
394
  { insert: '12345\n' },
399
395
  { insert: { 'table-up-col': { tableId: '1', colId: '1', full: false, width: 100 } } },
400
396
  { insert: { 'table-up-col': { tableId: '1', colId: '2', full: false, width: 100 } } },
401
- { insert: '1', attributes: { bold: true } },
397
+ { insert: '1' },
402
398
  { attributes: { 'list': 'bullet', 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
403
- { insert: '2', attributes: { bold: true } },
404
- { attributes: { 'list': 'bullet', 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
399
+ { insert: '2' },
400
+ { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '1', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
405
401
  { insert: '3' },
406
402
  { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '1', rowspan: 1, colspan: 1 } }, insert: '\n' },
407
- { insert: '4' },
403
+ { attributes: { bold: true }, insert: '4' },
408
404
  { attributes: { 'table-up-cell-inner': { tableId: '1', rowId: '2', colId: '2', rowspan: 1, colspan: 1 } }, insert: '\n' },
409
405
  { insert: '\n' },
410
406
  ]),
411
407
  );
412
- expect(quill.getSelection()).toBeNull();
408
+ expect(quill.getSelection()).toEqual({ index: 14, length: 1 });
413
409
  });
414
410
  });
415
411
 
@@ -685,6 +685,57 @@ describe('table undo', () => {
685
685
  { ignoreAttrs: ['class', 'style', 'data-table-id', 'contenteditable'] },
686
686
  );
687
687
  });
688
+
689
+ it('undo and redo remove last column', async () => {
690
+ const quill = await createTable(2, 2, { full: false });
691
+ const tableModule = quill.getModule(TableUp.moduleName) as TableUp;
692
+ const tds = quill.scroll.descendants(TableCellInnerFormat, 0);
693
+ tableModule.removeCol([tds[1]]);
694
+ await vi.runAllTimersAsync();
695
+ const afterColHtml = `
696
+ <p><br></p>
697
+ <div contenteditable="false">
698
+ <table cellpadding="0" cellspacing="0" style="margin-right: auto; width: 200px;">
699
+ <colgroup contenteditable="false">
700
+ <col width="100px" data-col-id="1">
701
+ </colgroup>
702
+ <tbody>
703
+ <tr>
704
+ <td rowspan="1" colspan="1" data-col-id="1">
705
+ <div data-col-id="1"><p>1</p></div>
706
+ </td>
707
+ </tr>
708
+ <tr>
709
+ <td rowspan="1" colspan="1" data-col-id="1">
710
+ <div data-col-id="1"><p>3</p></div>
711
+ </td>
712
+ </tr>
713
+ </tbody>
714
+ </table>
715
+ </div>
716
+ <p><br></p>
717
+ `;
718
+ expect(quill.root).toEqualHTML(
719
+ afterColHtml,
720
+ { ignoreAttrs: ['class', 'style', 'data-full', 'data-table-id', 'data-row-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
721
+ );
722
+ quill.history.undo();
723
+ await vi.runAllTimersAsync();
724
+ expect(quill.root).toEqualHTML(
725
+ `
726
+ <p><br></p>
727
+ ${createTableHTML(2, 2, { full: false })}
728
+ <p><br></p>
729
+ `,
730
+ { ignoreAttrs: ['class', 'style', 'data-table-id', 'data-row-id', 'data-col-id', 'contenteditable'] },
731
+ );
732
+ quill.history.redo();
733
+ await vi.runAllTimersAsync();
734
+ expect(quill.root).toEqualHTML(
735
+ afterColHtml,
736
+ { ignoreAttrs: ['class', 'style', 'data-full', 'data-table-id', 'data-row-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
737
+ );
738
+ });
688
739
  });
689
740
 
690
741
  describe('undo cell attribute', () => {
@@ -24,15 +24,6 @@ export class ContainerFormat extends Container {
24
24
  return node;
25
25
  }
26
26
 
27
- insertAt(index: number, value: string, def?: any): void {
28
- const [child] = this.children.find(index);
29
- if (!child) {
30
- const defaultChild = this.scroll.create(this.statics.defaultChild.blotName || 'block');
31
- this.appendChild(defaultChild);
32
- }
33
- super.insertAt(index, value, def);
34
- }
35
-
36
27
  public optimize(_context: Record<string, any>) {
37
28
  if (this.children.length === 0) {
38
29
  if (this.statics.defaultChild != null) {
@@ -150,6 +150,16 @@ export class TableCellInnerFormat extends ContainerFormat {
150
150
  }
151
151
  }
152
152
 
153
+ insertAt(index: number, value: string, def?: any): void {
154
+ const [child] = this.children.find(index);
155
+ // always keep TableCellInner not empty
156
+ if (!child && this.statics.defaultChild) {
157
+ const defaultChild = this.scroll.create(this.statics.defaultChild.blotName || 'block');
158
+ this.appendChild(defaultChild);
159
+ }
160
+ super.insertAt(index, value, def);
161
+ }
162
+
153
163
  formats(): Record<string, any> {
154
164
  const value = this.statics.formats(this.domNode);
155
165
  return {
@@ -258,6 +268,20 @@ export class TableCellInnerFormat extends ContainerFormat {
258
268
  return this.parent.insertBefore(cellInnerBlot, this.next);
259
269
  }
260
270
  }
271
+ else if (blot.statics.blotName === blotName.tableCol) {
272
+ try {
273
+ const bodyBlot = findParentBlot(this, blotName.tableBody);
274
+ const index = this.offset(bodyBlot);
275
+ const next = bodyBlot.split(index);
276
+ bodyBlot.parent.insertBefore(blot, next);
277
+ blot.optimize({});
278
+ }
279
+ catch {
280
+ // here should not trigger
281
+ console.warn('TableCellInner not in TableBody');
282
+ }
283
+ return;
284
+ }
261
285
  super.insertBefore(blot, ref);
262
286
  }
263
287
  }
@@ -2,7 +2,7 @@ import type { Parchment as TypeParchment } from 'quill';
2
2
  import type { BlockEmbed as TypeBlockEmbed } from 'quill/blots/block';
3
3
  import type { TableColValue } from '../utils';
4
4
  import Quill from 'quill';
5
- import { blotName, findParentBlot, findParentBlots, tableUpSize } from '../utils';
5
+ import { blotName, findParentBlot, tableUpSize } from '../utils';
6
6
  import { TableCellInnerFormat } from './table-cell-inner-format';
7
7
 
8
8
  const BlockEmbed = Quill.import('blots/block/embed') as typeof TypeBlockEmbed;
@@ -147,45 +147,45 @@ export class TableColFormat extends BlockEmbed {
147
147
  super.insertAt(index, value, def);
148
148
  return;
149
149
  }
150
- const lines = value.split('\n');
151
- const text = lines.pop();
152
- const blocks = lines.map((line) => {
153
- const block = this.scroll.create('block');
154
- block.insertAt(0, line);
155
- return block;
156
- });
157
- const ref = this.split(index);
158
- const [tableColgroupBlot, tableMainBlot] = findParentBlots(this, [blotName.tableColgroup, blotName.tableMain] as const);
159
- const tableBodyBlot = tableColgroupBlot.next;
160
- if (ref) {
161
- const index = ref.offset(tableColgroupBlot);
162
- tableColgroupBlot.split(index);
163
- }
164
- // create tbody
165
- let insertBlot = tableMainBlot.parent.parent;
166
- let nextBlotRef: TypeParchment.Blot | null = tableMainBlot.parent.next;
167
- if (tableBodyBlot) {
168
- const cellInners = tableBodyBlot.descendants(TableCellInnerFormat);
169
- if (cellInners.length > 0) {
170
- const cellInnerBlot = cellInners[0];
171
- const value = TableCellInnerFormat.formats(cellInnerBlot.domNode);
172
- const newBlock = this.scroll.create('block') as TypeParchment.BlockBlot;
173
- const newTableCellInner = newBlock.wrap(blotName.tableCellInner, value);
174
- const newTableCell = newTableCellInner.wrap(blotName.tableCell, value);
175
- const newTableRow = newTableCell.wrap(blotName.tableRow, value);
176
- const newTableBody = newTableRow.wrap(blotName.tableBody, value.tableId);
177
- tableColgroupBlot.parent.insertBefore(newTableBody, tableColgroupBlot.next);
178
-
179
- insertBlot = newBlock;
180
- nextBlotRef = newBlock.next;
150
+ try {
151
+ const lines = value.split('\n');
152
+ const text = lines.pop();
153
+ const tableColgroupBlot = findParentBlot(this, blotName.tableColgroup);
154
+ const tableBodyBlot = tableColgroupBlot.next;
155
+
156
+ // create tbody
157
+ let insertBlot: TypeParchment.Parent = this.scroll;
158
+ // split colgroup
159
+ const nextBlotRef: TypeParchment.Blot | null = tableColgroupBlot.split(this.offset(tableColgroupBlot));
160
+ if (tableBodyBlot) {
161
+ const cellInners = tableBodyBlot.descendants(TableCellInnerFormat);
162
+ if (cellInners.length > 0) {
163
+ const cellInnerBlot = cellInners[0];
164
+ const value = TableCellInnerFormat.formats(cellInnerBlot.domNode);
165
+ const newTableCellInner = this.scroll.create(blotName.tableCellInner, value) as TableCellInnerFormat;
166
+ const newTableCell = newTableCellInner.wrap(blotName.tableCell, value);
167
+ const newTableRow = newTableCell.wrap(blotName.tableRow, value);
168
+ const newTableBody = newTableRow.wrap(blotName.tableBody, value.tableId);
169
+ tableColgroupBlot.parent.insertBefore(newTableBody, nextBlotRef);
170
+
171
+ insertBlot = newTableCellInner;
172
+ }
181
173
  }
182
- }
183
174
 
184
- for (const block of blocks) {
185
- insertBlot.insertBefore(block, nextBlotRef);
175
+ for (const line of lines) {
176
+ const block = this.scroll.create('block');
177
+ block.insertAt(0, line);
178
+ insertBlot.appendChild(block);
179
+ }
180
+ if (text) {
181
+ const lineBlock = this.scroll.create('block') as TypeParchment.ParentBlot;
182
+ lineBlock.appendChild(this.scroll.create('text', text));
183
+ insertBlot.appendChild(lineBlock);
184
+ }
186
185
  }
187
- if (text) {
188
- insertBlot.insertBefore(this.scroll.create('text', text), nextBlotRef);
186
+ catch {
187
+ // here should not trigger
188
+ console.warn('TableCol not in TableColgroup');
189
189
  }
190
190
  }
191
191
  }