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/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.3",
5
5
  "packageManager": "pnpm@9.9.0",
6
6
  "description": "A table module for quill2.x",
7
7
  "author": "zzxming",
@@ -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
 
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[]) => {