quill-table-up 2.3.0 → 2.4.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.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +1001 -937
  4. package/dist/index.js +137 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.umd.js +140 -2
  7. package/dist/index.umd.js.map +1 -1
  8. package/package.json +22 -23
  9. package/src/__tests__/e2e/table-keyboard-handler.test.ts +218 -0
  10. package/src/__tests__/e2e/table-menu.test.ts +9 -8
  11. package/src/__tests__/e2e/table-selection.test.ts +137 -131
  12. package/src/__tests__/unit/table-blots.test.ts +102 -1
  13. package/src/__tests__/unit/table-cell-merge.test.ts +336 -1
  14. package/src/__tests__/unit/table-clipboard.test.ts +62 -44
  15. package/src/__tests__/unit/table-insert.test.ts +39 -2
  16. package/src/__tests__/unit/table-redo-undo.test.ts +69 -0
  17. package/src/__tests__/unit/table-remove.test.ts +4 -2
  18. package/src/__tests__/unit/utils.ts +22 -4
  19. package/src/__tests__/unit/vitest.d.ts +4 -1
  20. package/src/formats/index.ts +6 -0
  21. package/src/formats/overrides/block-embed.ts +54 -0
  22. package/src/formats/overrides/block.ts +10 -4
  23. package/src/formats/overrides/index.ts +1 -0
  24. package/src/formats/table-cell-format.ts +39 -6
  25. package/src/formats/table-cell-inner-format.ts +70 -21
  26. package/src/formats/table-main-format.ts +92 -1
  27. package/src/formats/table-row-format.ts +13 -2
  28. package/src/formats/table-wrapper-format.ts +7 -2
  29. package/src/modules/table-clipboard.ts +30 -35
  30. package/src/modules/table-resize/table-resize-box.ts +2 -2
  31. package/src/modules/table-resize/table-resize-common.ts +1 -1
  32. package/src/modules/table-resize/table-resize-line.ts +1 -1
  33. package/src/modules/table-resize/table-resize-scale.ts +1 -1
  34. package/src/modules/table-scrollbar.ts +5 -5
  35. package/src/modules/table-selection.ts +52 -31
  36. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  37. package/src/style/index.less +1 -0
  38. package/src/style/table-selection.less +1 -0
  39. package/src/table-up.ts +69 -30
  40. package/src/utils/blot-helper.ts +7 -4
  41. package/src/utils/index.ts +1 -1
  42. package/src/utils/{scroll-event-handle.ts → scroll-event-helper.ts} +7 -0
  43. package/src/utils/types.ts +2 -0
@@ -358,12 +358,12 @@ describe('clipboard cell structure', () => {
358
358
  <tbody>
359
359
  <tr>
360
360
  <td rowspan="1" colspan="1" style="border-color: transparent;">
361
- <div data-style="border-color: transparent">
361
+ <div data-style="border-color: transparent;">
362
362
  <p><strong>Col 1</strong></p>
363
363
  </div>
364
364
  </td>
365
365
  <td rowspan="1" colspan="1" style="border-color: transparent;">
366
- <div data-style="border-color: transparent">
366
+ <div data-style="border-color: transparent;">
367
367
  <p class="ql-align-right"><strong>Data 1</strong></p>
368
368
  </div>
369
369
  </td>
@@ -394,12 +394,12 @@ describe('clipboard cell structure', () => {
394
394
  <tbody>
395
395
  <tr>
396
396
  <td rowspan="1" colspan="1" style="border-color: transparent;">
397
- <div data-style="border-color: transparent">
397
+ <div data-style="border-color: transparent;">
398
398
  <p><br></p>
399
399
  </div>
400
400
  </td>
401
401
  <td rowspan="1" colspan="1" style="border-bottom-color: transparent;">
402
- <div data-style="border-bottom-color: transparent">
402
+ <div data-style="border-bottom-color: transparent;">
403
403
  <p><br></p>
404
404
  </div>
405
405
  </td>
@@ -411,12 +411,12 @@ describe('clipboard cell structure', () => {
411
411
  </tr>
412
412
  </tr>
413
413
  <td rowspan="1" colspan="1" style="border-right-color: transparent;">
414
- <div data-style="border-right-color: transparent">
414
+ <div data-style="border-right-color: transparent;">
415
415
  <p><br></p>
416
416
  </div>
417
417
  </td>
418
418
  <td rowspan="1" colspan="1" style="border-color: transparent;">
419
- <div data-style="border-color: transparent">
419
+ <div data-style="border-color: transparent;">
420
420
  <p><br></p>
421
421
  </div>
422
422
  </td>
@@ -492,7 +492,7 @@ describe('clipboard cell structure', () => {
492
492
  </div>
493
493
  </td>
494
494
  <td rowspan="1" colspan="1" style="background-color: transparent;">
495
- <div data-style="background-color: transparent">
495
+ <div data-style="background-color: transparent;">
496
496
  <p><br></p>
497
497
  </div>
498
498
  </td>
@@ -546,12 +546,12 @@ describe('clipboard cell structure', () => {
546
546
  <tbody>
547
547
  <tr>
548
548
  <td rowspan="1" colspan="1" style="height: 73px;">
549
- <div data-style="height: 73px">
549
+ <div data-style="height: 73px;">
550
550
  <p><br></p>
551
551
  </div>
552
552
  </td>
553
553
  <td rowspan="1" colspan="1" style="height: 73px;">
554
- <div data-style="height: 73px">
554
+ <div data-style="height: 73px;">
555
555
  <p><br></p>
556
556
  </div>
557
557
  </td>
@@ -811,7 +811,7 @@ describe('clipboard cell structure', () => {
811
811
  <tbody>
812
812
  <tr>
813
813
  <td colspan="1" rowspan="1" style="background-color: rgb(41, 114, 244);">
814
- <div data-style="background-color: rgb(41, 114, 244)">
814
+ <div data-style="background-color: rgb(41, 114, 244);">
815
815
  <p><span style="background-color: rgb(230, 0, 0);">123</span>456<span style="background-color: rgb(0, 138, 0);">789</span></p>
816
816
  <h1>h<span style="background-color: rgb(0, 0, 0);">ea</span>d</h1>
817
817
  </div>
@@ -837,11 +837,11 @@ describe('clipboard cell structure', () => {
837
837
  { attributes: { background: '#e60000' }, insert: '123' },
838
838
  { insert: '456' },
839
839
  { attributes: { background: '#008a00' }, insert: '789' },
840
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(41, 114, 244)' } }, insert: '\n' },
840
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(41, 114, 244);' } }, insert: '\n' },
841
841
  { insert: 'h' },
842
842
  { attributes: { background: '#000000' }, insert: 'ea' },
843
843
  { insert: 'd' },
844
- { attributes: { 'header': 1, 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(41, 114, 244)' } }, insert: '\n' },
844
+ { attributes: { 'header': 1, 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(41, 114, 244);' } }, insert: '\n' },
845
845
  { insert: '2' },
846
846
  { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
847
847
  { insert: '\n' },
@@ -868,7 +868,7 @@ describe('clipboard cell structure', () => {
868
868
  <tbody>
869
869
  <tr>
870
870
  <td colspan="1" rowspan="1" style="background-color: rgb(0, 102, 204);">
871
- <div data-style="background-color: rgb(0, 102, 204)">
871
+ <div data-style="background-color: rgb(0, 102, 204);">
872
872
  <p>123</p>
873
873
  </div>
874
874
  </td>
@@ -1016,7 +1016,7 @@ describe('clipboard cell structure', () => {
1016
1016
  });
1017
1017
 
1018
1018
  it('clipboard convert col with span attribute', async () => {
1019
- const quill = createQuillWithTableModule(`<p><br></p>`);
1019
+ const quill = createQuillWithTableModule(`<p><br></p>`, { autoMergeCell: false });
1020
1020
  quill.setContents(
1021
1021
  quill.clipboard.convert({
1022
1022
  html: `
@@ -1060,29 +1060,45 @@ describe('clipboard cell structure', () => {
1060
1060
  ${createTaleColHTML(4, { full: false, width: 63 })}
1061
1061
  <tbody>
1062
1062
  <tr>
1063
- <td rowspan="3" colspan="2">
1064
- <div><p>1</p></div>
1063
+ <td rowspan="6" colspan="2" data-empty-row="length:1">
1064
+ <div data-empty-row="length:1"><p>1</p></div>
1065
1065
  </td>
1066
- <td rowspan="1" colspan="2">
1067
- <div><p>2</p></div>
1066
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1067
+ <div data-empty-row="length:1"><p>2</p></div>
1068
1068
  </td>
1069
1069
  </tr>
1070
+ <tr></tr>
1070
1071
  <tr>
1071
- <td rowspan="1" colspan="2">
1072
- <div><p>3</p></div>
1072
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1073
+ <div data-empty-row="length:1"><p>3</p></div>
1073
1074
  </td>
1074
1075
  </tr>
1076
+ <tr></tr>
1075
1077
  <tr>
1076
- <td rowspan="1" colspan="2">
1077
- <div><p>4</p></div>
1078
+ <td rowspan="2" colspan="2" data-empty-row="length:1">
1079
+ <div data-empty-row="length:1"><p>4</p></div>
1078
1080
  </td>
1079
1081
  </tr>
1082
+ <tr></tr>
1080
1083
  </tbody>
1081
1084
  </table>
1082
1085
  </div>
1083
1086
  <p><br></p>
1084
1087
  `,
1085
- { ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'] },
1088
+ {
1089
+ ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'data-style', 'style', 'contenteditable'],
1090
+ replaceAttrs: {
1091
+ 'data-empty-row': function (value: string) {
1092
+ try {
1093
+ const emptyRow = JSON.parse(value);
1094
+ return `length:${emptyRow.length}`;
1095
+ }
1096
+ catch {
1097
+ return value;
1098
+ }
1099
+ },
1100
+ },
1101
+ },
1086
1102
  );
1087
1103
  expectDelta(
1088
1104
  new Delta([
@@ -1092,13 +1108,13 @@ describe('clipboard cell structure', () => {
1092
1108
  { insert: { 'table-up-col': { full: false, width: 63 } } },
1093
1109
  { insert: { 'table-up-col': { full: false, width: 63 } } },
1094
1110
  { insert: '1' },
1095
- { attributes: { 'table-up-cell-inner': { rowspan: 3, colspan: 2 } }, insert: '\n' },
1111
+ { attributes: { 'table-up-cell-inner': { rowspan: 6, colspan: 2 } }, insert: '\n' },
1096
1112
  { insert: '2' },
1097
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1113
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1098
1114
  { insert: '3' },
1099
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1115
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1100
1116
  { insert: '4' },
1101
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 2 } }, insert: '\n' },
1117
+ { attributes: { 'table-up-cell-inner': { rowspan: 2, colspan: 2 } }, insert: '\n' },
1102
1118
  { insert: '\n' },
1103
1119
  ]),
1104
1120
  quill.getContents(),
@@ -1198,7 +1214,7 @@ describe('clipboard content format', () => {
1198
1214
  it('should convert html video correctly', async () => {
1199
1215
  const quill = createQuillWithTableModule(`<p><br></p>`);
1200
1216
  quill.setContents(
1201
- quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="1"><table data-table-id="1" data-full="true" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto;"><colgroup data-table-id="1" data-full="true" contenteditable="false"><col width="100%" data-full="true" data-table-id="1" data-col-id="1"></colgroup><tbody data-table-id="1"><tr class="ql-table-row" data-table-id="1" data-row-id="1"><td class="ql-table-cell" data-table-id="1" data-row-id="1" data-col-id="1" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="1" data-col-id="1" data-rowspan="1" data-colspan="1"><iframe class="ql-video" frameborder="0" allowfullscreen="true" src="http://127.0.0.1:5500/docs/index.html"></iframe><p><br></p></div></td></tr></tbody></table></div>' }),
1217
+ quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="1"><table data-table-id="1" data-full="true" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto;"><colgroup data-table-id="1" data-full="true" contenteditable="false"><col width="100%" data-full="true" data-table-id="1" data-col-id="1"></colgroup><tbody data-table-id="1"><tr class="ql-table-row" data-table-id="1" data-row-id="1"><td class="ql-table-cell" data-table-id="1" data-row-id="1" data-col-id="1" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="1" data-col-id="1" data-rowspan="1" data-colspan="1"><iframe class="ql-video" frameborder="0" allowfullscreen="true" src="http://127.0.0.1:5500/docs/index.html"></iframe></div></td></tr></tbody></table></div>' }),
1202
1218
  );
1203
1219
  await vi.runAllTimersAsync();
1204
1220
 
@@ -1379,14 +1395,14 @@ describe('clipboard content format', () => {
1379
1395
  <tbody>
1380
1396
  <tr>
1381
1397
  <td rowspan="1" colspan="1" style="height: 90px; background-color: rgb(94, 255, 0);">
1382
- <div data-style="height: 90px;background-color: rgb(94, 255, 0)">
1398
+ <div data-style="height: 90px; background-color: rgb(94, 255, 0);">
1383
1399
  <p>
1384
1400
  <span style="background-color: rgb(0, 102, 204); color: rgb(230, 0, 0);">qwf</span>
1385
1401
  </p>
1386
1402
  </div>
1387
1403
  </td>
1388
1404
  <td rowspan="2" colspan="1" style="height: 90px;">
1389
- <div data-style="height: 90px">
1405
+ <div data-style="height: 90px;">
1390
1406
  <blockquote><code style="color: rgb(230, 0, 0); background-color: rgb(0, 102, 204);">qwfqwfw</code></blockquote>
1391
1407
  <ol>
1392
1408
  <li data-list="bullet">
@@ -1397,7 +1413,7 @@ describe('clipboard content format', () => {
1397
1413
  </div>
1398
1414
  </td>
1399
1415
  <td rowspan="1" colspan="1" style="height: 90px;">
1400
- <div data-style="height: 90px">
1416
+ <div data-style="height: 90px;">
1401
1417
  <p>
1402
1418
  <span style="background-color: rgb(0, 102, 204);">qwg</span>
1403
1419
  </p>
@@ -1435,7 +1451,9 @@ describe('clipboard content format', () => {
1435
1451
  </div>
1436
1452
  <p><br></p>
1437
1453
  `,
1438
- { ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
1454
+ {
1455
+ ignoreAttrs: ['class', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'],
1456
+ },
1439
1457
  );
1440
1458
  });
1441
1459
 
@@ -1477,17 +1495,17 @@ describe('clipboard content format', () => {
1477
1495
  <tbody>
1478
1496
  <tr>
1479
1497
  <td style="background-color: rgb(237, 238, 242);">
1480
- <div data-style="background-color: #edeef2;">
1498
+ <div data-style="background-color: rgb(237, 238, 242);">
1481
1499
  <p><span style="background-color: rgb(237, 238, 242);">1</span></p>
1482
1500
  </div>
1483
1501
  </td>
1484
1502
  <td style="background-color: rgb(237, 238, 242);">
1485
- <div data-style="background-color: #edeef2;">
1503
+ <div data-style="background-color: rgb(237, 238, 242);">
1486
1504
  <p><span style="background-color: rgb(237, 238, 242);">2</span></p>
1487
1505
  </div>
1488
1506
  </td>
1489
1507
  <td style="background-color: rgb(237, 238, 242);">
1490
- <div data-style="background-color: #edeef2;">
1508
+ <div data-style="background-color: rgb(237, 238, 242);">
1491
1509
  <p><span style="background-color: rgb(237, 238, 242);">3</span></p>
1492
1510
  </div>
1493
1511
  </td>
@@ -1511,17 +1529,17 @@ describe('clipboard content format', () => {
1511
1529
  </tr>
1512
1530
  <tr>
1513
1531
  <td style="background-color: rgb(237, 238, 242);">
1514
- <div data-style="background-color: #edeef2;">
1532
+ <div data-style="background-color: rgb(237, 238, 242);">
1515
1533
  <p><span style="background-color: rgb(237, 238, 242);">7</span></p>
1516
1534
  </div>
1517
1535
  </td>
1518
1536
  <td style="background-color: rgb(237, 238, 242);">
1519
- <div data-style="background-color: #edeef2;">
1537
+ <div data-style="background-color: rgb(237, 238, 242);">
1520
1538
  <p><span style="background-color: rgb(237, 238, 242);">8</span></p>
1521
1539
  </div>
1522
1540
  </td>
1523
1541
  <td style="background-color: rgb(237, 238, 242);">
1524
- <div data-style="background-color: #edeef2;">
1542
+ <div data-style="background-color: rgb(237, 238, 242);">
1525
1543
  <p><span style="background-color: rgb(237, 238, 242);">9</span></p>
1526
1544
  </div>
1527
1545
  </td>
@@ -1540,11 +1558,11 @@ describe('clipboard content format', () => {
1540
1558
  { insert: { 'table-up-col': { full: false, width: 100 } } },
1541
1559
  { insert: { 'table-up-col': { full: false, width: 100 } } },
1542
1560
  { attributes: { background: '#edeef2' }, insert: '1' },
1543
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1561
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1544
1562
  { attributes: { background: '#edeef2' }, insert: '2' },
1545
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1563
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1546
1564
  { attributes: { background: '#edeef2' }, insert: '3' },
1547
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1565
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1548
1566
  { insert: '4' },
1549
1567
  { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1550
1568
  { insert: '5' },
@@ -1552,11 +1570,11 @@ describe('clipboard content format', () => {
1552
1570
  { insert: '6' },
1553
1571
  { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
1554
1572
  { attributes: { background: '#edeef2' }, insert: '7' },
1555
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1573
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1556
1574
  { attributes: { background: '#edeef2' }, insert: '8' },
1557
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1575
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1558
1576
  { attributes: { background: '#edeef2' }, insert: '9' },
1559
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: #edeef2;' } }, insert: '\n' },
1577
+ { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1, style: 'background-color: rgb(237, 238, 242);' } }, insert: '\n' },
1560
1578
  { insert: '\n' },
1561
1579
  ]),
1562
1580
  quill.getContents(),
@@ -217,14 +217,13 @@ describe('insert block embed blot', () => {
217
217
  <div>
218
218
  <table cellpadding="0" cellspacing="0" data-full="true">
219
219
  <colgroup data-full="true">
220
- <col width="100%" data-full="true" />
220
+ <col width="100%" data-full="true" />
221
221
  </colgroup>
222
222
  <tbody>
223
223
  <tr>
224
224
  <td rowspan="1" colspan="1">
225
225
  <div>
226
226
  <iframe src="https://quilljs.com/" frameborder="0" allowfullscreen="true"></iframe>
227
- <p><br></p>
228
227
  </div>
229
228
  </td>
230
229
  </tr>
@@ -236,6 +235,44 @@ describe('insert block embed blot', () => {
236
235
  { ignoreAttrs: ['class', 'style', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
237
236
  );
238
237
  });
238
+
239
+ it('BlockEmbed should at correct position in cell', async () => {
240
+ const quill = createQuillWithTableModule(`<p><br></p>`);
241
+ quill.setContents([
242
+ { insert: '\n' },
243
+ { insert: { 'table-up-col': { tableId: '2l7117zsa6r', colId: 'd962746f4w8', full: false, width: 100 } } },
244
+ { insert: '1' },
245
+ { attributes: { 'table-up-cell-inner': { tableId: '2l7117zsa6r', rowId: 'a9r52q9z4l', colId: 'd962746f4w8', rowspan: 1, colspan: 1 } }, insert: '\n' },
246
+ { insert: { video: 'http://localhost:5500/docs/index.html' } },
247
+ { insert: 'bbb' },
248
+ { attributes: { 'table-up-cell-inner': { tableId: '2l7117zsa6r', rowId: 'a9r52q9z4l', colId: 'd962746f4w8', rowspan: 1, colspan: 1 } }, insert: '\n' },
249
+ { insert: '\n' },
250
+ ]);
251
+ await vi.runAllTimersAsync();
252
+ expect(quill.root).toEqualHTML(
253
+ `
254
+ <p><br></p>
255
+ <div>
256
+ <table cellpadding="0" cellspacing="0">
257
+ ${createTaleColHTML(1, { full: false, width: 100 })}
258
+ <tbody>
259
+ <tr>
260
+ <td rowspan="1" colspan="1">
261
+ <div>
262
+ <p>1</p>
263
+ <iframe frameborder="0" allowfullscreen="true" src="http://localhost:5500/docs/index.html"></iframe>
264
+ <p>bbb</p>
265
+ </div>
266
+ </td
267
+ </tr>
268
+ </tbody>
269
+ </table>
270
+ </div>
271
+ <p><br></p>
272
+ `,
273
+ { ignoreAttrs: ['class', 'style', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
274
+ );
275
+ });
239
276
  });
240
277
 
241
278
  describe('set contents', () => {
@@ -1188,6 +1188,75 @@ describe('undo cell attribute', () => {
1188
1188
  { ignoreAttrs: ['class', 'style', 'data-table-id', 'data-row-id', 'data-col-id', 'data-rowspan', 'data-colspan', 'contenteditable'] },
1189
1189
  );
1190
1190
  });
1191
+
1192
+ it('undo table style with Container in cell', async () => {
1193
+ const quill = await createTable(3, 3);
1194
+ quill.setContents([
1195
+ { insert: '\n' },
1196
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'vm3doxdsmq', full: false, width: 100 } } },
1197
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'k4ele1u8u4n', full: false, width: 100 } } },
1198
+ { insert: '1' },
1199
+ { attributes: { 'list': 'unchecked', 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n\n' },
1200
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1201
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n' },
1202
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1203
+ { insert: '\n' },
1204
+ ]);
1205
+ await vi.runAllTimersAsync();
1206
+
1207
+ const tableModule = quill.getModule(TableUp.moduleName) as TableUp;
1208
+ const tds = quill.scroll.descendants(TableCellInnerFormat, 0);
1209
+ tableModule.setCellAttrs([tds[0]], 'background-color', 'rgb(0, 163, 245)', true);
1210
+ await vi.runAllTimersAsync();
1211
+ expectDelta(
1212
+ new Delta([
1213
+ { insert: '\n' },
1214
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'vm3doxdsmq', full: false, width: 100 } } },
1215
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'k4ele1u8u4n', full: false, width: 100 } } },
1216
+ { insert: '1' },
1217
+ { attributes: { 'list': 'unchecked', 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1, style: 'background-color: rgb(0, 163, 245);' } }, insert: '\n\n' },
1218
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1219
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n' },
1220
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1221
+ { insert: '\n' },
1222
+ ]),
1223
+ quill.getContents(),
1224
+ );
1225
+
1226
+ quill.history.undo();
1227
+ await vi.runAllTimersAsync();
1228
+ expectDelta(
1229
+ new Delta([
1230
+ { insert: '\n' },
1231
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'vm3doxdsmq', full: false, width: 100 } } },
1232
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'k4ele1u8u4n', full: false, width: 100 } } },
1233
+ { insert: '1' },
1234
+ { attributes: { 'list': 'unchecked', 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n\n' },
1235
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1236
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n' },
1237
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1238
+ { insert: '\n' },
1239
+ ]),
1240
+ quill.getContents(),
1241
+ );
1242
+
1243
+ quill.history.redo();
1244
+ await vi.runAllTimersAsync();
1245
+ expectDelta(
1246
+ new Delta([
1247
+ { insert: '\n' },
1248
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'vm3doxdsmq', full: false, width: 100 } } },
1249
+ { insert: { 'table-up-col': { tableId: 'wm7stgtxmn', colId: 'k4ele1u8u4n', full: false, width: 100 } } },
1250
+ { insert: '1' },
1251
+ { attributes: { 'list': 'unchecked', 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1, style: 'background-color: rgb(0, 163, 245);' } }, insert: '\n\n' },
1252
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'xn7r03opjc', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1253
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'vm3doxdsmq', rowspan: 1, colspan: 1 } }, insert: '\n' },
1254
+ { attributes: { 'table-up-cell-inner': { tableId: 'wm7stgtxmn', rowId: 'nqpe206omjn', colId: 'k4ele1u8u4n', rowspan: 1, colspan: 1 } }, insert: '\n' },
1255
+ { insert: '\n' },
1256
+ ]),
1257
+ quill.getContents(),
1258
+ );
1259
+ });
1191
1260
  });
1192
1261
 
1193
1262
  describe('table caption', () => {
@@ -241,11 +241,13 @@ describe('unusual delete', () => {
241
241
  <div>
242
242
  <table cellpadding="0" cellspacing="0" data-full="true">
243
243
  <colgroup data-full="true">
244
- ${new Array(5).fill(`<col width="20%" data-full="true" />`).join('\n')}
244
+ <col width="60%" data-full="true" />
245
+ <col width="20%" data-full="true" />
246
+ <col width="20%" data-full="true" />
245
247
  </colgroup>
246
248
  <tbody>
247
249
  <tr>
248
- <td rowspan="3" colspan="3">
250
+ <td rowspan="3" colspan="1">
249
251
  <div>
250
252
  <p><br></p>
251
253
  <p><br></p>
@@ -53,16 +53,34 @@ export function createQuillWithTableModule(html: string, tableOptions: Partial<T
53
53
  }
54
54
 
55
55
  expect.extend({
56
- toEqualHTML(received, expected, options = {}) {
57
- const ignoreAttrs = options?.ignoreAttrs ?? [];
56
+ toEqualHTML(
57
+ received,
58
+ expected,
59
+ {
60
+ ignoreAttrs = [],
61
+ replaceAttrs = {},
62
+ }: {
63
+ ignoreAttrs?: string[];
64
+ replaceAttrs?: Record<string, (attrValue: string) => string>;
65
+ } = {},
66
+ ) {
58
67
  const receivedDOM = document.createElement('div');
59
68
  const expectedDOM = document.createElement('div');
60
69
  receivedDOM.innerHTML = normalizeHTML(
61
70
  typeof received === 'string' ? received : received.innerHTML,
62
71
  );
63
72
  expectedDOM.innerHTML = normalizeHTML(expected);
64
- const doms = [receivedDOM, expectedDOM];
65
73
 
74
+ for (const [attr, handler] of Object.entries(replaceAttrs)) {
75
+ for (const node of Array.from(receivedDOM.querySelectorAll(`[${attr}]`))) {
76
+ const attrValue = node.getAttribute(attr);
77
+ if (attrValue) {
78
+ node.setAttribute(attr, handler(attrValue));
79
+ }
80
+ }
81
+ }
82
+
83
+ const doms = [receivedDOM, expectedDOM];
66
84
  for (const dom of doms) {
67
85
  for (const node of Array.from(dom.querySelectorAll('.ql-ui'))) {
68
86
  node.remove();
@@ -86,7 +104,7 @@ expect.extend({
86
104
  `HTMLs don't match.\n${this.utils.diff(
87
105
  this.utils.stringify(receivedDOM),
88
106
  this.utils.stringify(expectedDOM),
89
- )}`,
107
+ )}\n`,
90
108
  };
91
109
  },
92
110
  });
@@ -2,7 +2,10 @@
2
2
  import type { Assertion, AsymmetricMatchersContaining } from 'vitest';
3
3
 
4
4
  interface CustomMatchers<R = unknown> {
5
- toEqualHTML: (html: string, options?: { ignoreAttrs?: string[] }) => R;
5
+ toEqualHTML: (html: string, options?: {
6
+ ignoreAttrs?: string[];
7
+ replaceAttrs?: Record<string, (attrValue: string) => string>;
8
+ }) => R;
6
9
  }
7
10
 
8
11
  declare module 'vitest' {
@@ -16,6 +16,12 @@ export * from './table-wrapper-format';
16
16
 
17
17
  export function getTableMainRect(tableMainBlot: TableMainFormat) {
18
18
  const [tableBodyBlot] = findChildBlot(tableMainBlot, TableBodyFormat);
19
+ if (!tableBodyBlot) {
20
+ return {
21
+ body: null,
22
+ rect: null,
23
+ };
24
+ }
19
25
  return {
20
26
  body: tableBodyBlot,
21
27
  rect: tableBodyBlot.domNode.getBoundingClientRect(),
@@ -0,0 +1,54 @@
1
+ import type { Parchment as TypeParchment } from 'quill';
2
+ import type { BlockEmbed as TypeBlockEmbed } from 'quill/blots/block';
3
+ import Quill from 'quill';
4
+ import { blotName } from '../../utils';
5
+
6
+ const BlockEmbed = Quill.import('blots/block/embed') as typeof TypeBlockEmbed;
7
+
8
+ export class BlockEmbedOverride extends BlockEmbed {
9
+ delta() {
10
+ // if BlockEmbed is the last line of the tableCellInner. need to add value in delta
11
+ const delta = super.delta();
12
+ const formats = bubbleFormats(this);
13
+ if (formats[blotName.tableCellInner]) {
14
+ delta.insert('\n', { [blotName.tableCellInner]: formats[blotName.tableCellInner] });
15
+ }
16
+ return delta;
17
+ }
18
+
19
+ length() {
20
+ // because BlockEmbed is the last line of the tableCellInner. need add value in delta, also need add 1 to length
21
+ const formats = bubbleFormats(this);
22
+ if (formats[blotName.tableCellInner] && (!this.next || this.next.length() <= 1)) {
23
+ return super.length() + 1;
24
+ }
25
+ return super.length();
26
+ }
27
+ }
28
+
29
+ // copy from `quill/blots/block`
30
+ function bubbleFormats(
31
+ blot: TypeParchment.Blot | null,
32
+ formats: Record<string, unknown> = {},
33
+ filter = true,
34
+ ): Record<string, unknown> {
35
+ if (blot == null) return formats;
36
+ if ('formats' in blot && typeof blot.formats === 'function') {
37
+ formats = {
38
+ ...formats,
39
+ ...blot.formats(),
40
+ };
41
+ if (filter) {
42
+ // exclude syntax highlighting from deltas and getFormat()
43
+ delete formats['code-token'];
44
+ }
45
+ }
46
+ if (
47
+ blot.parent == null
48
+ || blot.parent.statics.blotName === 'scroll'
49
+ || blot.parent.statics.scope !== blot.statics.scope
50
+ ) {
51
+ return formats;
52
+ }
53
+ return bubbleFormats(blot.parent, formats, filter);
54
+ }
@@ -1,14 +1,16 @@
1
1
  import type { Parchment as TypeParchment } from 'quill';
2
2
  import type TypeBlock from 'quill/blots/block';
3
+ import type TypeContainer from 'quill/blots/container';
3
4
  import Quill from 'quill';
4
- import { blotName, findParentBlot } from '../../utils';
5
+ import { blotName, findParentBlot, isString } from '../../utils';
5
6
 
6
7
  const Parchment = Quill.import('parchment');
7
8
  const Block = Quill.import('blots/block') as typeof TypeBlock;
9
+ const Container = Quill.import('blots/container') as typeof TypeContainer;
8
10
 
9
11
  export class BlockOverride extends Block {
10
12
  replaceWith(name: string | TypeParchment.Blot, value?: any): TypeParchment.Blot {
11
- const replacement = typeof name === 'string' ? this.scroll.create(name, value) : name;
13
+ const replacement = isString(name) ? this.scroll.create(name, value) : name;
12
14
  if (replacement instanceof Parchment.ParentBlot) {
13
15
  // replace block to TableCellInner length is 0 when setContents
14
16
  // that will set text direct in TableCellInner but not in block
@@ -45,7 +47,11 @@ export class BlockOverride extends Block {
45
47
  }
46
48
  }
47
49
  else {
48
- if (selfParent != null) {
50
+ // `list` will wrap Container. tableCellInner should be insert outside it
51
+ if (selfParent instanceof Container) {
52
+ selfParent.parent.insertBefore(replacement, selfParent);
53
+ }
54
+ else if (selfParent != null) {
49
55
  selfParent.insertBefore(replacement, this.next);
50
56
  }
51
57
  replacement.appendChild(this);
@@ -57,7 +63,7 @@ export class BlockOverride extends Block {
57
63
  }
58
64
  }
59
65
  if (this.parent != null) {
60
- this.parent.insertBefore(replacement, this.next || undefined);
66
+ this.parent.insertBefore(replacement, this.next);
61
67
  this.remove();
62
68
  }
63
69
  this.attributes.copy(replacement as TypeParchment.BlockBlot);
@@ -1,2 +1,3 @@
1
1
  export * from './block';
2
+ export * from './block-embed';
2
3
  export * from './scroll';