quill-table-up 2.0.4 → 2.0.6

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.
@@ -319,146 +319,3 @@ describe('column width calculate', () => {
319
319
  expect(quill.root.querySelectorAll('table')[0].style.width).toBe('300px');
320
320
  });
321
321
  });
322
-
323
- describe('html convert', () => {
324
- it('should convert html code-block correctly', async () => {
325
- const quill = createQuillWithTableModule(`<p><br></p>`);
326
- quill.setContents(
327
- quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="1"><table data-table-id="1" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto; width: 363px;"><colgroup data-table-id="1" contenteditable="false"><col width="121px" data-table-id="1" data-col-id="1"><col width="121px" data-table-id="1" data-col-id="2"><col width="121px" data-table-id="1" data-col-id="3"></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"><p>1</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="1" data-col-id="2" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="1" data-col-id="2" data-rowspan="1" data-colspan="1"><p>2</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="1" data-col-id="3" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="1" data-col-id="3" data-rowspan="1" data-colspan="1"><p>3</p></div></td></tr><tr class="ql-table-row" data-table-id="1" data-row-id="2"><td class="ql-table-cell" data-table-id="1" data-row-id="2" data-col-id="1" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="2" data-col-id="1" data-rowspan="1" data-colspan="1"><p>4</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="2" data-col-id="2" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="2" data-col-id="2" data-rowspan="1" data-colspan="1"><div class="ql-code-block-container" spellcheck="false"><div class="ql-code-block" data-language="plain">5</div><div class="ql-code-block" data-language="plain">5</div><div class="ql-code-block" data-language="plain">5</div></div><p>5</p><p>5</p><p>5</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="2" data-col-id="3" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="2" data-col-id="3" data-rowspan="1" data-colspan="1"><p>6</p></div></td></tr><tr class="ql-table-row" data-table-id="1" data-row-id="3"><td class="ql-table-cell" data-table-id="1" data-row-id="3" data-col-id="1" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="3" data-col-id="1" data-rowspan="1" data-colspan="1"><p>7</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="3" data-col-id="2" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="3" data-col-id="2" data-rowspan="1" data-colspan="1"><p>8</p></div></td><td class="ql-table-cell" data-table-id="1" data-row-id="3" data-col-id="3" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="1" data-row-id="3" data-col-id="3" data-rowspan="1" data-colspan="1"><p>9</p></div></td></tr></tbody></table></div>' }),
328
- );
329
- await vi.runAllTimersAsync();
330
-
331
- const ops = quill.getContents().ops;
332
- const resultOps = [
333
- { insert: '\n' },
334
- { insert: { 'table-up-col': { full: false, width: 121 } } },
335
- { insert: { 'table-up-col': { full: false, width: 121 } } },
336
- { insert: { 'table-up-col': { full: false, width: 121 } } },
337
- { insert: '1' },
338
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
339
- { insert: '2' },
340
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
341
- { insert: '3' },
342
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
343
- { insert: '4' },
344
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
345
- { insert: '5' },
346
- { attributes: { 'code-block': 'plain', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
347
- { insert: '5' },
348
- { attributes: { 'code-block': 'plain', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
349
- { insert: '5' },
350
- { attributes: { 'code-block': 'plain', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
351
- { insert: '5' },
352
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
353
- { insert: '5' },
354
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
355
- { insert: '5' },
356
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
357
- { insert: '6' },
358
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
359
- { insert: '7' },
360
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
361
- { insert: '8' },
362
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
363
- { insert: '9' },
364
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
365
- { insert: '\n' },
366
- ];
367
- for (const [i, op] of ops.entries()) {
368
- expect(op).toMatchObject(resultOps[i]);
369
- }
370
- });
371
-
372
- it('should convert html header correctly', async () => {
373
- const quill = createQuillWithTableModule(`<p><br></p>`);
374
- quill.setContents(
375
- quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="rlabchug06i"><table data-table-id="rlabchug06i" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto; width: 1166px;"><colgroup data-table-id="rlabchug06i" contenteditable="false"><col width="583px" data-table-id="rlabchug06i" data-col-id="ss993p1sqx"><col width="583px" data-table-id="rlabchug06i" data-col-id="2ixexk4mapx"></colgroup><tbody data-table-id="rlabchug06i"><tr class="ql-table-row" data-table-id="rlabchug06i" data-row-id="fvvkgpv86zw"><td class="ql-table-cell" data-table-id="rlabchug06i" data-row-id="fvvkgpv86zw" data-col-id="ss993p1sqx" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="rlabchug06i" data-row-id="fvvkgpv86zw" data-col-id="ss993p1sqx" data-rowspan="1" data-colspan="1"><h1>header1</h1></div></td><td class="ql-table-cell" data-table-id="rlabchug06i" data-row-id="fvvkgpv86zw" data-col-id="2ixexk4mapx" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="rlabchug06i" data-row-id="fvvkgpv86zw" data-col-id="2ixexk4mapx" data-rowspan="1" data-colspan="1"><h3>header3</h3></div></td></tr></tbody></table></div>' }),
376
- );
377
- await vi.runAllTimersAsync();
378
-
379
- const ops = quill.getContents().ops;
380
- const resultOps = [
381
- { insert: '\n' },
382
- { insert: { 'table-up-col': { full: false, width: 583 } } },
383
- { insert: { 'table-up-col': { full: false, width: 583 } } },
384
- { insert: 'header1' },
385
- { attributes: { 'header': 1, 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
386
- { insert: 'header3' },
387
- { attributes: { 'header': 3, 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
388
- { insert: '\n' },
389
- ];
390
- for (const [i, op] of ops.entries()) {
391
- expect(op).toMatchObject(resultOps[i]);
392
- }
393
- });
394
-
395
- it('should convert html image correctly', async () => {
396
- const quill = createQuillWithTableModule(`<p><br></p>`);
397
- quill.setContents(
398
- quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="7oymehdtx0k"><table data-table-id="7oymehdtx0k" data-full="true" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto;"><colgroup data-table-id="7oymehdtx0k" data-full="true" contenteditable="false"><col width="100%" data-full="true" data-table-id="7oymehdtx0k" data-col-id="hr7qo4t2dus"></colgroup><tbody data-table-id="7oymehdtx0k"><tr class="ql-table-row" data-table-id="7oymehdtx0k" data-row-id="69gog08ow04"><td class="ql-table-cell" data-table-id="7oymehdtx0k" data-row-id="69gog08ow04" data-col-id="hr7qo4t2dus" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="7oymehdtx0k" data-row-id="69gog08ow04" data-col-id="hr7qo4t2dus" data-rowspan="1" data-colspan="1"><p><img src="https://upload-bbs.miyoushe.com/upload/2024/06/18/5556092/73b7bae28fded7a72d93a35d5559b24c_3979852353547906724.png"></p></div></td></tr></tbody></table></div>' }),
399
- );
400
- await vi.runAllTimersAsync();
401
-
402
- const ops = quill.getContents().ops;
403
- const resultOps = [
404
- { insert: '\n' },
405
- { insert: { 'table-up-col': { full: true, width: 100 } } },
406
- { insert: { image: 'https://upload-bbs.miyoushe.com/upload/2024/06/18/5556092/73b7bae28fded7a72d93a35d5559b24c_3979852353547906724.png' } },
407
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
408
- { insert: '\n' },
409
- ];
410
- for (const [i, op] of ops.entries()) {
411
- expect(op).toMatchObject(resultOps[i]);
412
- }
413
- });
414
-
415
- it('should convert html video correctly', async () => {
416
- const quill = createQuillWithTableModule(`<p><br></p>`);
417
- quill.setContents(
418
- 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>' }),
419
- );
420
- await vi.runAllTimersAsync();
421
-
422
- const ops = quill.getContents().ops;
423
- const resultOps = [
424
- { insert: '\n' },
425
- { insert: { 'table-up-col': { full: true, width: 100 } } },
426
- { insert: { video: 'http://127.0.0.1:5500/docs/index.html' } },
427
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
428
- { insert: '\n' },
429
- ];
430
- for (const [i, op] of ops.entries()) {
431
- expect(op).toMatchObject(resultOps[i]);
432
- }
433
- });
434
-
435
- it('should convert html list correctly', async () => {
436
- const quill = createQuillWithTableModule(`<p><br></p>`);
437
- quill.setContents(
438
- quill.clipboard.convert({ html: '<div class="ql-table-wrapper" data-table-id="ls2bw9dtr6m"><table data-table-id="ls2bw9dtr6m" class="ql-table" cellpadding="0" cellspacing="0" style="margin-right: auto; width: 1166px;"><colgroup data-table-id="ls2bw9dtr6m" contenteditable="false"><col width="583px" data-table-id="ls2bw9dtr6m" data-col-id="p3imc1n7xlo"><col width="583px" data-table-id="ls2bw9dtr6m" data-col-id="ndvtber87yq"></colgroup><tbody data-table-id="ls2bw9dtr6m"><tr class="ql-table-row" data-table-id="ls2bw9dtr6m" data-row-id="bztm23s128n"><td class="ql-table-cell" data-table-id="ls2bw9dtr6m" data-row-id="bztm23s128n" data-col-id="p3imc1n7xlo" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="ls2bw9dtr6m" data-row-id="bztm23s128n" data-col-id="p3imc1n7xlo" data-rowspan="1" data-colspan="1"><ol><li data-list="ordered"><span class="ql-ui" contenteditable="false"></span>list order</li><li data-list="ordered" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>aaa</li></ol></div></td><td class="ql-table-cell" data-table-id="ls2bw9dtr6m" data-row-id="bztm23s128n" data-col-id="ndvtber87yq" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="ls2bw9dtr6m" data-row-id="bztm23s128n" data-col-id="ndvtber87yq" data-rowspan="1" data-colspan="1"><ol><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span>list bullet</li></ol></div></td></tr><tr class="ql-table-row" data-table-id="ls2bw9dtr6m" data-row-id="3kx2tsgoao5"><td class="ql-table-cell" data-table-id="ls2bw9dtr6m" data-row-id="3kx2tsgoao5" data-col-id="p3imc1n7xlo" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="ls2bw9dtr6m" data-row-id="3kx2tsgoao5" data-col-id="p3imc1n7xlo" data-rowspan="1" data-colspan="1"><ol><li data-list="unchecked"><span class="ql-ui" contenteditable="false"></span>list checkbox</li><li data-list="checked"><span class="ql-ui" contenteditable="false"></span>checkbox checked</li></ol></div></td><td class="ql-table-cell" data-table-id="ls2bw9dtr6m" data-row-id="3kx2tsgoao5" data-col-id="ndvtber87yq" rowspan="1" colspan="1"><div class="ql-table-cell-inner" data-table-id="ls2bw9dtr6m" data-row-id="3kx2tsgoao5" data-col-id="ndvtber87yq" data-rowspan="1" data-colspan="1"><p><br></p></div></td></tr></tbody></table></div>' }),
439
- );
440
- await vi.runAllTimersAsync();
441
-
442
- const ops = quill.getContents().ops;
443
- const resultOps = [
444
- { insert: '\n' },
445
- { insert: { 'table-up-col': { full: false, width: 583 } } },
446
- { insert: { 'table-up-col': { full: false, width: 583 } } },
447
- { insert: 'list order' },
448
- { attributes: { 'list': 'ordered', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
449
- { insert: 'aaa' },
450
- { attributes: { 'indent': 1, 'list': 'ordered', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
451
- { insert: 'list bullet' },
452
- { attributes: { 'list': 'bullet', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
453
- { insert: 'list checkbox' },
454
- { attributes: { 'list': 'unchecked', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
455
- { insert: 'checkbox checked' },
456
- { attributes: { 'list': 'checked', 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
457
- { attributes: { 'table-up-cell-inner': { rowspan: 1, colspan: 1 } }, insert: '\n' },
458
- { insert: '\n' },
459
- ];
460
- for (const [i, op] of ops.entries()) {
461
- expect(op).toMatchObject(resultOps[i]);
462
- }
463
- });
464
- });
@@ -122,6 +122,10 @@ export class TableColFormat extends BlockEmbed {
122
122
  const tableColgroup = findParentBlot(this, blotName.tableColgroup);
123
123
  tableColgroup.align = this.align;
124
124
 
125
+ if (this.next != null && this.checkMerge()) {
126
+ this.next.remove();
127
+ }
128
+
125
129
  super.optimize(context);
126
130
  }
127
131
 
package/src/index.ts CHANGED
@@ -272,7 +272,7 @@ export class TableUp {
272
272
  },
273
273
  false,
274
274
  );
275
- this.quill.on(Quill.events.EDITOR_CHANGE, (event: string, range: Range, oldRange: Range) => {
275
+ this.quill.on(Quill.events.EDITOR_CHANGE, (event: string, range: Range, _oldRange: Range) => {
276
276
  if (event === Quill.events.SELECTION_CHANGE && range) {
277
277
  const [startBlot] = this.quill.getLine(range.index);
278
278
  const [endBlot] = this.quill.getLine(range.index + range.length);
@@ -287,29 +287,30 @@ export class TableUp {
287
287
  }
288
288
  catch {}
289
289
 
290
- // only can select inside table or select all table
291
- if (startBlot instanceof TableColFormat) {
292
- if (!oldRange) {
293
- oldRange = { index: 0, length: 0 };
294
- }
295
- return this.quill.setSelection(
296
- range.index + (oldRange.index > range.index ? -1 : 1),
297
- range.length + (oldRange.length === range.length ? 0 : oldRange.length > range.length ? -1 : 1),
298
- Quill.sources.USER,
299
- );
300
- }
301
- else if (endBlot instanceof TableColFormat) {
302
- return this.quill.setSelection(range.index + 1, range.length + 1, Quill.sources.USER);
303
- }
304
-
305
- if (range.length > 0) {
306
- if (startTableBlot && !endTableBlot) {
307
- this.quill.setSelection(range.index - 1, range.length + 1, Quill.sources.USER);
308
- }
309
- else if (endTableBlot && !startTableBlot) {
310
- this.quill.setSelection(range.index, range.length + 1, Quill.sources.USER);
311
- }
312
- }
290
+ // // TODO: selection some times will be reject mouse drag select
291
+ // // only can select inside table or select all table
292
+ // if (startBlot instanceof TableColFormat) {
293
+ // if (!oldRange) {
294
+ // oldRange = { index: 0, length: 0 };
295
+ // }
296
+ // return this.quill.setSelection(
297
+ // range.index + (oldRange.index > range.index ? -1 : 1),
298
+ // range.length + (oldRange.length === range.length ? 0 : oldRange.length > range.length ? -1 : 1),
299
+ // Quill.sources.USER,
300
+ // );
301
+ // }
302
+ // else if (endBlot instanceof TableColFormat) {
303
+ // return this.quill.setSelection(range.index + 1, range.length + 1, Quill.sources.USER);
304
+ // }
305
+
306
+ // if (range.length > 0) {
307
+ // if (startTableBlot && !endTableBlot) {
308
+ // this.quill.setSelection(range.index - 1, range.length + 1, Quill.sources.USER);
309
+ // }
310
+ // else if (endTableBlot && !startTableBlot) {
311
+ // this.quill.setSelection(range.index, range.length + 1, Quill.sources.USER);
312
+ // }
313
+ // }
313
314
 
314
315
  // if range is not in table. hide table tools
315
316
  if (!startTableBlot || !endTableBlot) {
@@ -375,6 +376,7 @@ export class TableUp {
375
376
  let tableId = randomId();
376
377
  let rowId = randomId();
377
378
  let colIds: string[] = [];
379
+ let rowspanCount: { rowspan: number; colspan: number }[] = [];
378
380
  let cellCount = 0;
379
381
  let colCount = 0;
380
382
 
@@ -396,51 +398,70 @@ export class TableUp {
396
398
 
397
399
  this.quill.clipboard.addMatcher('table', (node, delta) => {
398
400
  if (delta.ops.length === 0) return delta;
399
- // if current in table. prevent paste table
401
+
400
402
  const format = this.quill.getFormat();
401
- if (format[blotName.tableCellInner]) return new Delta();
402
- // remove quill origin table format
403
+ const currentCellFormat = format[blotName.tableCellInner];
403
404
  const ops: Record<string, any>[] = [];
404
405
  const cols: Record<string, any>[] = [];
405
406
  for (let i = 0; i < delta.ops.length; i++) {
406
407
  const { attributes, insert } = delta.ops[i];
408
+ // remove quill origin table format and tableCell format
407
409
  const { table, [blotName.tableCell]: tableCell, ...attrs } = attributes || {};
408
- if (insert && (insert as Record<string, any>)[blotName.tableCol]) {
409
- cols.push({ insert });
410
+ const hasCol = insert && (insert as Record<string, any>)[blotName.tableCol];
411
+ if (currentCellFormat) {
412
+ // if current in cell. no need add col. but need replace paste cell format with current cell format
413
+ if (hasCol) continue;
414
+ const { [blotName.tableCellInner]: tableCellInner, ...keepAtttrs } = attrs;
415
+ ops.push({
416
+ attributes: {
417
+ ...keepAtttrs,
418
+ [blotName.tableCellInner]: currentCellFormat,
419
+ },
420
+ insert,
421
+ });
410
422
  }
411
423
  else {
412
- ops.push({ attributes: attrs, insert });
424
+ if (hasCol) {
425
+ cols.push({ insert });
426
+ }
427
+ else {
428
+ ops.push({ attributes: attrs, insert });
429
+ }
413
430
  }
414
431
  }
415
432
 
416
- const colWidths = calculateCols(node as HTMLElement, colIds.length);
417
- const newCols = colWidths.reduce((colOps, width, i) => {
418
- if (!cols[i]) {
419
- colOps.push({
420
- insert: {
421
- [blotName.tableCol]: {
422
- tableId,
423
- colId: colIds[i],
424
- width,
425
- full: false,
433
+ // if current in cell. no need add col
434
+ if (!currentCellFormat) {
435
+ const colWidths = calculateCols(node as HTMLElement, colIds.length);
436
+ const newCols = colWidths.reduce((colOps, width, i) => {
437
+ if (!cols[i]) {
438
+ colOps.push({
439
+ insert: {
440
+ [blotName.tableCol]: {
441
+ tableId,
442
+ colId: colIds[i],
443
+ width,
444
+ full: false,
445
+ },
426
446
  },
427
- },
428
- });
429
- }
430
- else {
431
- colOps.push(cols[i]);
432
- }
433
- return colOps;
434
- }, [] as Record<string, any>[]);
435
- ops.unshift(...newCols);
447
+ });
448
+ }
449
+ else {
450
+ colOps.push(cols[i]);
451
+ }
452
+ return colOps;
453
+ }, [] as Record<string, any>[]);
454
+ ops.unshift(...newCols);
455
+ // insert break line before table and after table
456
+ ops.unshift({ insert: '\n' });
457
+ ops.push({ insert: '\n' });
458
+ }
436
459
  // reset variable to avoid conflict with other table
437
460
  tableId = randomId();
438
461
  colIds = [];
462
+ rowspanCount = [];
439
463
  cellCount = 0;
440
464
  colCount = 0;
441
- // insert break line before table and after table
442
- ops.unshift({ insert: '\n' });
443
- ops.push({ insert: '\n' });
444
465
  return new Delta(ops);
445
466
  });
446
467
 
@@ -482,17 +503,39 @@ export class TableUp {
482
503
  (op.attributes[blotName.tableCellInner] as Record<string, any>).style = `background:${op.attributes.background};${cellAttrs.style}`;
483
504
  }
484
505
  }
506
+ // minus rowspan
507
+ for (const [i, span] of rowspanCount.entries()) {
508
+ if (span.rowspan > 0) {
509
+ span.rowspan -= 1;
510
+ }
511
+ if (span.rowspan <= 0) {
512
+ rowspanCount[i] = { rowspan: 0, colspan: 0 };
513
+ }
514
+ }
485
515
  return delta;
486
516
  });
487
517
 
488
518
  const matchCell = (node: Node, delta: TypeDelta) => {
489
519
  const cell = node as HTMLElement;
490
520
  const cellFormat = TableCellFormat.formats(cell);
491
- if (!colIds[cellCount]) {
521
+ if (!colIds[cellCount] || !rowspanCount[cellCount]) {
492
522
  for (let i = cellCount; i >= 0; i--) {
493
- if (!colIds[i]) colIds[i] = randomId();
523
+ if (!colIds[i]) {
524
+ colIds[i] = randomId();
525
+ }
526
+ if (!rowspanCount[i]) {
527
+ rowspanCount[i] = { rowspan: 0, colspan: 0 };
528
+ }
494
529
  }
495
530
  }
531
+ // skip the colspan of the cell in the previous row
532
+ const { colspan } = rowspanCount[cellCount];
533
+ cellCount += colspan;
534
+ // add current cell rowspan in `rowspanCount` to calculate next row cell
535
+ if (cellFormat.rowspan > 1) {
536
+ rowspanCount[cellCount] = { rowspan: cellFormat.rowspan, colspan: cellFormat.colspan };
537
+ }
538
+
496
539
  const colId = colIds[cellCount];
497
540
  cellCount += cellFormat.colspan;
498
541
 
@@ -520,7 +563,6 @@ export class TableUp {
520
563
  }
521
564
  return new Delta(ops);
522
565
  };
523
-
524
566
  this.quill.clipboard.addMatcher('td', matchCell);
525
567
  this.quill.clipboard.addMatcher('th', matchCell);
526
568
  }
@@ -1153,5 +1195,15 @@ export function defaultCustomSelect(tableModule: TableUp, picker: QuillThemePick
1153
1195
  export default TableUp;
1154
1196
  export * from './formats';
1155
1197
  export * from './modules';
1156
- export { blotName, findParentBlot, findParentBlots, randomId, tableUpEvent, tableUpSize } from './utils';
1198
+ export {
1199
+ blotName,
1200
+ createColorPicker,
1201
+ createSelectBox,
1202
+ createTooltip,
1203
+ findParentBlot,
1204
+ findParentBlots,
1205
+ randomId,
1206
+ tableUpEvent,
1207
+ tableUpSize,
1208
+ } from './utils';
1157
1209
  export * from './utils/types';
@@ -0,0 +1,110 @@
1
+ import type { Parchment as TypeParchment, Range as TypeRange } from 'quill';
2
+ import type TableUp from '..';
3
+ import type { TableCellInnerFormat } from '../formats';
4
+ import Quill from 'quill';
5
+ import { blotName, findParentBlot } from '../utils';
6
+
7
+ export class UserSelection {
8
+ limitKeyboardSelection: boolean = false;
9
+ selectionDirection: number | null = null;
10
+ lastValidSelection: {
11
+ startContainer: Node;
12
+ startOffset: number;
13
+ endContainer: Node;
14
+ endOffset: number;
15
+ } | null = null;
16
+
17
+ constructor(public quill: Quill, public tableModule: TableUp) {
18
+ document.addEventListener('selectionchange', () => {
19
+ const selection = document.getSelection();
20
+ if (!selection) return;
21
+ const { anchorNode, focusNode } = selection;
22
+ if (!anchorNode || !focusNode) return;
23
+ const anchorBlot = Quill.find(anchorNode);
24
+ const focusBlot = Quill.find(focusNode);
25
+ if (!anchorBlot || !focusBlot) return;
26
+ let anchorCellBlot: TableCellInnerFormat | null = null;
27
+ let focusCellBlot: TableCellInnerFormat | null = null;
28
+ try {
29
+ anchorCellBlot = findParentBlot(anchorBlot as TypeParchment.Blot, blotName.tableCellInner);
30
+ }
31
+ catch {}
32
+ try {
33
+ focusCellBlot = findParentBlot(focusBlot as TypeParchment.Blot, blotName.tableCellInner);
34
+ }
35
+ catch {}
36
+
37
+ if ((anchorCellBlot && !focusCellBlot) || (focusCellBlot && !anchorCellBlot)) {
38
+ // TODO: select whole table
39
+ this.quill.blur();
40
+ }
41
+ else if (anchorCellBlot !== focusCellBlot) {
42
+ this.limitKeyboardSelection = true;
43
+ this.selectionDirection = this.getSelectionDirection();
44
+ // blur and select cell
45
+ if (this.lastValidSelection) {
46
+ const range = document.createRange();
47
+ range.setStart(this.lastValidSelection.startContainer, this.lastValidSelection.startOffset);
48
+ range.setEnd(this.lastValidSelection.endContainer, this.lastValidSelection.endOffset);
49
+ selection.removeAllRanges();
50
+ selection.addRange(range);
51
+ }
52
+ }
53
+ else {
54
+ this.lastValidSelection = {
55
+ startContainer: selection.anchorNode!,
56
+ startOffset: selection.anchorOffset,
57
+ endContainer: selection.focusNode!,
58
+ endOffset: selection.focusOffset,
59
+ };
60
+ this.limitKeyboardSelection = false;
61
+ this.selectionDirection = null;
62
+ }
63
+ });
64
+ document.addEventListener('keydown', (event) => {
65
+ if (event.shiftKey && this.limitKeyboardSelection) {
66
+ // 从上到下选区:阻止向右和向下的选区扩展
67
+ if (this.selectionDirection === Node.DOCUMENT_POSITION_FOLLOWING) {
68
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
69
+ event.preventDefault();
70
+ }
71
+ }
72
+ // 从下到上选区:阻止向左和向上的选区扩展
73
+ else if (
74
+ this.selectionDirection === Node.DOCUMENT_POSITION_PRECEDING
75
+ && (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
76
+ ) {
77
+ event.preventDefault();
78
+ }
79
+ }
80
+ });
81
+ this.quill.on(Quill.events.EDITOR_CHANGE, (event: string, range: TypeRange) => {
82
+ if (event === Quill.events.SELECTION_CHANGE && range) {
83
+ const [startBlot] = this.quill.getLine(range.index);
84
+ const [endBlot] = this.quill.getLine(range.index + range.length);
85
+ let startTableBlot;
86
+ let endTableBlot;
87
+ try {
88
+ startTableBlot = findParentBlot(startBlot!, blotName.tableMain);
89
+ }
90
+ catch {}
91
+ try {
92
+ endTableBlot = findParentBlot(endBlot!, blotName.tableMain);
93
+ }
94
+ catch {}
95
+
96
+ // if range is not in table. hide table tools
97
+ if (!startTableBlot || !endTableBlot) {
98
+ this.tableModule.hideTableTools();
99
+ }
100
+ }
101
+ });
102
+ }
103
+
104
+ getSelectionDirection() {
105
+ const selection = document.getSelection();
106
+ if (!selection || selection.type === 'Caret') return null;
107
+ if (!selection.anchorNode || !selection.focusNode) return null;
108
+ return selection.anchorNode.compareDocumentPosition(selection.focusNode);
109
+ }
110
+ }