quill-table-up 2.2.2 → 2.2.4

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.2",
4
+ "version": "2.2.4",
5
5
  "packageManager": "pnpm@9.9.0",
6
6
  "description": "A table module for quill2.x",
7
7
  "author": "zzxming",
@@ -1,5 +1,5 @@
1
1
  import { expect, test } from '@playwright/test';
2
- import { extendTest } from './utils';
2
+ import { createTableBySelect, extendTest } from './utils';
3
3
 
4
4
  test.beforeEach(async ({ page }) => {
5
5
  await page.goto('http://127.0.0.1:5500/docs/test.html');
@@ -50,3 +50,41 @@ extendTest('test table full width switch redo and undo', async ({ page, editorPa
50
50
  expect(await page.locator('#editor1 .ql-editor .ql-table colgroup[data-full="true"]').count()).toBe(1);
51
51
  expect(await page.locator('#editor1 .ql-editor .ql-table col[data-full="true"]').count()).toBe(4);
52
52
  });
53
+
54
+ extendTest('test table tools should hide after table removed', async ({ page, editorPage }) => {
55
+ editorPage.index = 0;
56
+ await createTableBySelect(page, 'container1', 3, 3);
57
+ await page.locator('#container1 .ql-editor .ql-table td').nth(0).click();
58
+ await page.waitForTimeout(1000);
59
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-selection')).toBeVisible();
60
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-align')).toBeAttached();
61
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-scrollbar__container')).toBeAttached();
62
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-resize-line__col')).toBeAttached();
63
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-resize-line__row')).toBeAttached();
64
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-scale')).toBeAttached();
65
+ await editorPage.setContents([{ insert: 'replace' }]);
66
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-selection')).not.toBeVisible();
67
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-align')).not.toBeAttached();
68
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-scrollbar__container')).not.toBeAttached();
69
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-resize-line__col')).not.toBeAttached();
70
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-resize-line__row')).not.toBeAttached();
71
+ await expect(page.locator('#container1 .table-up-toolbox .table-up-scale')).not.toBeAttached();
72
+
73
+ editorPage.index = 1;
74
+ await createTableBySelect(page, 'container2', 3, 3);
75
+ await page.locator('#container2 .ql-editor .ql-table td').nth(0).click();
76
+ await page.waitForTimeout(1000);
77
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-selection')).toBeVisible();
78
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-align')).toBeAttached();
79
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-menu')).toBeAttached();
80
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-scrollbar__container')).toBeAttached();
81
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-resize-box')).toBeAttached();
82
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-scale')).toBeAttached();
83
+ await editorPage.setContents([{ insert: 'replace' }]);
84
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-selection')).not.toBeVisible();
85
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-menu')).not.toBeVisible();
86
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-align')).not.toBeAttached();
87
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-scrollbar__container')).not.toBeAttached();
88
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-resize-box')).not.toBeAttached();
89
+ await expect(page.locator('#container2 .table-up-toolbox .table-up-scale')).not.toBeAttached();
90
+ });
@@ -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: any, domNode: HTMLElement, _value: any) {
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 (!attrs?.[blotName.tableCellInner] && !hasCol) {
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
- const { colspan } = this.rowspanCount[this.cellCount];
204
- this.cellCount += colspan;
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
 
@@ -159,6 +159,7 @@ export class TableResizeScale {
159
159
  destroy() {
160
160
  this.hide();
161
161
  this.quill.off(Quill.events.TEXT_CHANGE, this.updateWhenTextChange);
162
+ this.resizeobserver.disconnect();
162
163
  if (this.root) {
163
164
  this.root.remove();
164
165
  }
package/src/table-up.ts CHANGED
@@ -328,6 +328,11 @@ export class TableUp {
328
328
  },
329
329
  false,
330
330
  );
331
+ this.quill.on(Quill.events.EDITOR_CHANGE, (type: typeof Quill.events.TEXT_CHANGE | typeof Quill.events.SELECTION_CHANGE) => {
332
+ if (type === Quill.events.TEXT_CHANGE && (!this.table || !this.quill.root.contains(this.table))) {
333
+ this.hideTableTools();
334
+ }
335
+ });
331
336
 
332
337
  this.quillHack();
333
338
  this.listenBalanceCells();
@@ -960,15 +965,23 @@ export class TableUp {
960
965
  Quill.events.SCROLL_OPTIMIZE,
961
966
  (mutations: MutationRecord[]) => {
962
967
  mutations.some((mutation) => {
963
- if (
964
- // TODO: if need add ['COL', 'COLGROUP']
965
- ['TD', 'TR', 'TBODY', 'TABLE'].includes((mutation.target as HTMLElement).tagName)
966
- ) {
968
+ // TODO: if need add ['COL', 'COLGROUP']
969
+ if (['TD', 'TR', 'TBODY', 'TABLE'].includes((mutation.target as HTMLElement).tagName)) {
967
970
  this.fixTableByLisenter();
968
971
  return true;
969
972
  }
970
973
  return false;
971
974
  });
975
+ for (const mutation of mutations) {
976
+ const mutationTarget = mutation.target as HTMLElement;
977
+ if (mutationTarget.tagName === 'TABLE') {
978
+ const tableMain = Quill.find(mutationTarget) as TableMainFormat;
979
+ if (tableMain) {
980
+ tableMain.sortMergeChildren();
981
+ break;
982
+ }
983
+ }
984
+ }
972
985
  },
973
986
  );
974
987
  }