quill-table-up 2.1.9 → 2.2.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 (79) hide show
  1. package/README.md +41 -34
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +53 -17
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.umd.js +1 -1
  7. package/dist/index.umd.js.map +1 -1
  8. package/package.json +16 -16
  9. package/src/__tests__/e2e/editor-page.ts +6 -0
  10. package/src/__tests__/e2e/table-blots.test.ts +52 -0
  11. package/src/__tests__/e2e/table-caption.test.ts +93 -0
  12. package/src/__tests__/e2e/table-hack.test.ts +43 -0
  13. package/src/__tests__/e2e/table-resize.test.ts +136 -5
  14. package/src/__tests__/e2e/table-selection.test.ts +7 -10
  15. package/src/__tests__/e2e/utils.ts +2 -2
  16. package/src/__tests__/unit/table-blots.test.ts +223 -0
  17. package/src/__tests__/unit/table-caption.test.ts +237 -0
  18. package/src/__tests__/unit/table-clipboard.test.ts +6 -6
  19. package/src/__tests__/unit/table-hack.test.ts +137 -63
  20. package/src/__tests__/unit/table-insert.test.ts +4 -4
  21. package/src/__tests__/unit/table-redo-undo.test.ts +252 -3
  22. package/src/__tests__/unit/utils.test.ts +2 -2
  23. package/src/__tests__/unit/utils.ts +44 -25
  24. package/src/__tests__/unit/vitest.d.ts +0 -1
  25. package/src/formats/index.ts +1 -0
  26. package/src/formats/overrides/scroll.ts +6 -0
  27. package/src/formats/table-caption-format.ts +115 -0
  28. package/src/formats/table-cell-format.ts +63 -33
  29. package/src/formats/table-cell-inner-format.ts +3 -3
  30. package/src/formats/table-col-format.ts +16 -12
  31. package/src/formats/table-colgroup-format.ts +2 -2
  32. package/src/formats/table-main-format.ts +0 -9
  33. package/src/formats/table-wrapper-format.ts +2 -2
  34. package/src/modules/table-clipboard.ts +21 -3
  35. package/src/modules/table-menu/constants.ts +18 -2
  36. package/src/modules/table-menu/table-menu-common.ts +0 -5
  37. package/src/modules/table-resize/table-resize-box.ts +74 -31
  38. package/src/modules/table-resize/table-resize-common.ts +8 -7
  39. package/src/modules/table-resize/table-resize-scale.ts +12 -5
  40. package/src/modules/table-scrollbar.ts +12 -7
  41. package/src/modules/table-selection.ts +16 -19
  42. package/src/style/color-picker.less +4 -2
  43. package/src/style/index.less +19 -0
  44. package/src/style/table-resize.less +16 -1
  45. package/src/svg/arrow-up-down.svg +11 -0
  46. package/src/svg/auto-full.svg +11 -1
  47. package/src/svg/background.svg +10 -1
  48. package/src/svg/border.svg +10 -1
  49. package/src/svg/color.svg +10 -1
  50. package/src/svg/copy.svg +8 -1
  51. package/src/svg/cut.svg +7 -1
  52. package/src/svg/insert-bottom.svg +6 -1
  53. package/src/svg/insert-left.svg +6 -1
  54. package/src/svg/insert-right.svg +6 -1
  55. package/src/svg/insert-top.svg +6 -1
  56. package/src/svg/merge-cell.svg +6 -1
  57. package/src/svg/remove-column.svg +6 -1
  58. package/src/svg/remove-row.svg +6 -1
  59. package/src/svg/remove-table.svg +6 -1
  60. package/src/svg/split-cell.svg +6 -1
  61. package/src/svg/table-head.svg +4 -0
  62. package/src/table-up.ts +149 -112
  63. package/src/utils/bem.ts +2 -2
  64. package/src/utils/blot-helper.ts +12 -0
  65. package/src/utils/color.ts +12 -12
  66. package/src/utils/components/button.ts +2 -2
  67. package/src/utils/components/color-picker.ts +2 -2
  68. package/src/utils/components/dialog.ts +2 -2
  69. package/src/utils/components/input.ts +2 -2
  70. package/src/utils/components/table/creator.ts +2 -2
  71. package/src/utils/components/table/select-box.ts +2 -2
  72. package/src/utils/components/tooltip.ts +2 -2
  73. package/src/utils/constants.ts +5 -3
  74. package/src/utils/position.ts +2 -2
  75. package/src/utils/resize-observer-helper.ts +2 -2
  76. package/src/utils/transformer.ts +5 -0
  77. package/src/utils/transition-event-helper.ts +2 -2
  78. package/src/utils/types.ts +5 -1
  79. package/src/utils/utils.ts +2 -2
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm11.94 5.5h2v-4h2v4h2l-3 3z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm11.94 5.5h2v-4h2v4h2l-3 3z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm14.44 2v2h4v2h-4v2l-3-3z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm14.44 2v2h4v2h-4v2l-3-3z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm15.44 8v-2h-4v-2h4v-2l3 3z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm15.44 8v-2h-4v-2h4v-2l3 3z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm17.94 4.5h-2v4h-2v-4h-2l3-3z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4zm17.94 4.5h-2v4h-2v-4h-2l3-3z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M5 10H3V4h8v2H5zm14 8h-6v2h8v-6h-2zM5 18v-4H3v6h8v-2zM21 4h-8v2h6v4h2zM8 13v2l3-3l-3-3v2H3v2zm8-2V9l-3 3l3 3v-2h5v-2z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M5 10H3V4h8v2H5zm14 8h-6v2h8v-6h-2zM5 18v-4H3v6h8v-2zM21 4h-8v2h6v4h2zM8 13v2l3-3l-3-3v2H3v2zm8-2V9l-3 3l3 3v-2h5v-2z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2h7a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m0 8v4h7v-4zm0 6v4h7v-4zM4 4v4h7V4zm13.59 8L15 9.41L16.41 8L19 10.59L21.59 8L23 9.41L20.41 12L23 14.59L21.59 16L19 13.41L16.41 16L15 14.59z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M4 2h7a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m0 8v4h7v-4zm0 6v4h7v-4zM4 4v4h7V4zm13.59 8L15 9.41L16.41 8L19 10.59L21.59 8L23 9.41L20.41 12L23 14.59L21.59 16L19 13.41L16.41 16L15 14.59z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M9.41 13L12 15.59L14.59 13L16 14.41L13.41 17L16 19.59L14.59 21L12 18.41L9.41 21L8 19.59L10.59 17L8 14.41zM22 9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2zM4 9h4V6H4zm6 0h4V6h-4zm6 0h4V6h-4z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M9.41 13L12 15.59L14.59 13L16 14.41L13.41 17L16 19.59L14.59 21L12 18.41L9.41 21L8 19.59L10.59 17L8 14.41zM22 9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2zM4 9h4V6H4zm6 0h4V6h-4zm6 0h4V6h-4z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="m15.46 15.88l1.42-1.42L19 16.59l2.12-2.13l1.42 1.42L20.41 18l2.13 2.12l-1.42 1.42L19 19.41l-2.12 2.13l-1.42-1.42L17.59 18zM4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="m15.46 15.88l1.42-1.42L19 16.59l2.12-2.13l1.42 1.42L20.41 18l2.13 2.12l-1.42 1.42L19 19.41l-2.12 2.13l-1.42-1.42L17.59 18zM4 3h14a2 2 0 0 1 2 2v7.08a6 6 0 0 0-4.32.92H12v4h1.08c-.11.68-.11 1.35 0 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m0 4v4h6V7zm8 0v4h6V7zm-8 6v4h6v-4z"
5
+ />
6
+ </svg>
@@ -1 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M19 14h2v6H3v-6h2v4h14zM3 4v6h2V6h14v4h2V4zm8 7v2H8v2l-3-3l3-3v2zm5 0V9l3 3l-3 3v-2h-3v-2z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <path
3
+ fill="currentColor"
4
+ d="M19 14h2v6H3v-6h2v4h14zM3 4v6h2V6h14v4h2V4zm8 7v2H8v2l-3-3l3-3v2zm5 0V9l3 3l-3 3v-2h-3v-2z"
5
+ />
6
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
2
+ <!-- Icon from TDesign Icons by TDesign - https://github.com/Tencent/tdesign-icons/blob/main/LICENSE -->
3
+ <path fill="currentColor" d="M21 10v12h-2V12H5v10H3V10zm0-8v6H3V2zm-2 2H5v2h14z" />
4
+ </svg>
package/src/table-up.ts CHANGED
@@ -4,9 +4,9 @@ import type TypeKeyboard from 'quill/modules/keyboard';
4
4
  import type TypeToolbar from 'quill/modules/toolbar';
5
5
  import type { InternalModule, InternalTableSelectionModule, QuillTheme, QuillThemePicker, TableCellValue, TableConstantsData, TableTextOptions, TableUpOptions } from './utils';
6
6
  import Quill from 'quill';
7
- import { BlockOverride, ContainerFormat, ScrollOverride, TableBodyFormat, TableCellFormat, TableCellInnerFormat, TableColFormat, TableColgroupFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from './formats';
7
+ import { BlockOverride, ContainerFormat, ScrollOverride, TableBodyFormat, TableCaptionFormat, TableCellFormat, TableCellInnerFormat, TableColFormat, TableColgroupFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from './formats';
8
8
  import { TableClipboard } from './modules';
9
- import { blotName, createBEM, createSelectBox, debounce, findParentBlot, findParentBlots, isForbidInTable, isFunction, isNumber, isString, limitDomInViewPort, mixinClass, randomId, tableCantInsert, tableUpEvent, tableUpInternal, tableUpSize } from './utils';
9
+ import { blotName, createBEM, createSelectBox, cssTextToObject, debounce, findParentBlot, findParentBlots, isForbidInTable, isFunction, isNumber, isString, limitDomInViewPort, mixinClass, objectToCssText, randomId, tableCantInsert, tableUpEvent, tableUpInternal, tableUpSize } from './utils';
10
10
 
11
11
  const Parchment = Quill.import('parchment');
12
12
  const Delta = Quill.import('delta');
@@ -67,10 +67,10 @@ export function defaultCustomSelect(tableModule: TableUp, picker: QuillThemePick
67
67
 
68
68
  function generateTableArrowHandler(up: boolean) {
69
69
  return {
70
+ bindInHead: false,
70
71
  key: up ? 'ArrowUp' : 'ArrowDown',
71
72
  collapsed: true,
72
73
  format: [blotName.tableCellInner],
73
- bindInHead: false,
74
74
  handler(this: { quill: Quill }, range: TypeRange, context: Context) {
75
75
  let tableBlot: TableWrapperFormat;
76
76
  let tableMain: TableMainFormat;
@@ -86,7 +86,25 @@ function generateTableArrowHandler(up: boolean) {
86
86
  const colIds = tableMain.getColIds();
87
87
  const direction = up ? 'prev' : 'next';
88
88
  const childDirection = up ? 'tail' : 'head';
89
- const aroundLine = tableBlot[direction];
89
+ const tableCaption = tableBlot.descendants(TableCaptionFormat, 0)[0];
90
+
91
+ let aroundLine;
92
+ if (tableCaption) {
93
+ const captionSide = window.getComputedStyle(tableCaption.domNode);
94
+ if (direction === 'next' && captionSide.captionSide === 'bottom') {
95
+ aroundLine = tableCaption;
96
+ }
97
+ else if (direction === 'next') {
98
+ aroundLine = tableBlot.next;
99
+ }
100
+ else {
101
+ aroundLine = tableCaption;
102
+ }
103
+ }
104
+ else {
105
+ aroundLine = tableBlot[direction];
106
+ }
107
+
90
108
  if (context.line[direction] || !aroundLine) {
91
109
  return true;
92
110
  }
@@ -114,6 +132,7 @@ function generateTableArrowHandler(up: boolean) {
114
132
  export class TableUp {
115
133
  static moduleName: string = tableUpInternal.moduleName;
116
134
  static toolName: string = blotName.tableWrapper;
135
+ // TODO: add custom property `bindInHead`, but Quill doesn't export `BindingObject`
117
136
  static keyboradHandler = {
118
137
  'forbid remove table by backspace': {
119
138
  bindInHead: true,
@@ -159,14 +178,25 @@ export class TableUp {
159
178
  },
160
179
  'table up': generateTableArrowHandler(true),
161
180
  'table down': generateTableArrowHandler(false),
181
+ 'table caption break': {
182
+ bindInHead: true,
183
+ key: 'Enter',
184
+ shiftKey: null,
185
+ format: [blotName.tableCaption],
186
+ handler(this: { quill: Quill }, _range: TypeRange, _context: Context) {
187
+ return false;
188
+ },
189
+ },
162
190
  };
163
191
 
164
192
  static register() {
165
193
  TableWrapperFormat.allowedChildren = [TableMainFormat];
166
194
 
167
- TableMainFormat.allowedChildren = [TableBodyFormat, TableColgroupFormat];
195
+ TableMainFormat.allowedChildren = [TableBodyFormat, TableColgroupFormat, TableCaptionFormat];
168
196
  TableMainFormat.requiredContainer = TableWrapperFormat;
169
197
 
198
+ TableCaptionFormat.requiredContainer = TableMainFormat;
199
+
170
200
  TableColgroupFormat.allowedChildren = [TableColFormat];
171
201
  TableColgroupFormat.requiredContainer = TableMainFormat;
172
202
 
@@ -201,6 +231,7 @@ export class TableUp {
201
231
  [`formats/${blotName.tableBody}`]: TableBodyFormat,
202
232
  [`formats/${blotName.tableCol}`]: TableColFormat,
203
233
  [`formats/${blotName.tableColgroup}`]: TableColgroupFormat,
234
+ [`formats/${blotName.tableCaption}`]: TableCaptionFormat,
204
235
  [`formats/${blotName.tableMain}`]: TableMainFormat,
205
236
  [`formats/${blotName.tableWrapper}`]: TableWrapperFormat,
206
237
  'modules/clipboard': TableClipboard,
@@ -364,17 +395,16 @@ export class TableUp {
364
395
  quillHack() {
365
396
  const originGetSemanticHTML = this.quill.getSemanticHTML;
366
397
  this.quill.getSemanticHTML = ((index: number = 0, length?: number) => {
367
- const tableCellInnerFormat = Quill.import(`formats/${blotName.tableCellInner}`) as typeof TableCellInnerFormat;
368
- const inners = this.quill.scroll.domNode.querySelectorAll(`.${tableCellInnerFormat.className}`);
369
- for (const inner of Array.from(inners)) {
370
- inner.setAttribute('contenteditable', String(false));
371
- }
372
398
  const html = originGetSemanticHTML.call(this.quill, index, length);
373
- const isEnabled = this.quill.isEnabled();
374
- for (const inner of Array.from(inners)) {
375
- inner.setAttribute('contenteditable', String(isEnabled));
399
+
400
+ const tableWrapperFormat = Quill.import(`formats/${blotName.tableWrapper}`) as typeof TableWrapperFormat;
401
+ const parser = new DOMParser();
402
+ const doc = parser.parseFromString(html, 'text/html');
403
+ for (const node of Array.from(doc.querySelectorAll(`.${tableWrapperFormat.className} caption[contenteditable], .${tableWrapperFormat.className} td > [contenteditable]`))) {
404
+ node.removeAttribute('contenteditable');
376
405
  }
377
- return html;
406
+
407
+ return doc.body.innerHTML;
378
408
  }) as typeof originGetSemanticHTML;
379
409
 
380
410
  // make sure toolbar item can format selected cells
@@ -430,7 +460,7 @@ export class TableUp {
430
460
  if (toolbar) {
431
461
  const cleanHandler = toolbar.handlers?.clean;
432
462
  if (cleanHandler) {
433
- const cleanFormatExcludeTable = (index: number, length: number, cleanCellStyle: boolean = true) => {
463
+ const cleanFormatExcludeTable = (index: number, length: number, changeCellStyle: false | ((styleStr: string | undefined) => string) = () => '') => {
434
464
  // base on `removeFormat`. but not remove tableCellInner
435
465
  const text = this.quill.getText(index, length);
436
466
  const [line, offset] = this.quill.getLine(index + length);
@@ -458,11 +488,15 @@ export class TableUp {
458
488
 
459
489
  if (attributes) {
460
490
  const { [blotName.tableCellInner]: nullValue, ...attrs } = attributes;
461
- if (cleanCellStyle) {
491
+ if (changeCellStyle) {
462
492
  const tableCellInner = contents.slice(deltaIndex - 1, deltaIndex).ops[0];
463
493
  if (tableCellInner && tableCellInner.attributes && tableCellInner.attributes[blotName.tableCellInner]) {
464
494
  const tableCellInnerValue = tableCellInner.attributes[blotName.tableCellInner] as TableCellValue;
465
495
  const { style, ...value } = tableCellInnerValue;
496
+ const newStyle = changeCellStyle(style);
497
+ if (newStyle) {
498
+ return { ...other, attributes: { ...attrs, [blotName.tableCellInner]: { style: newStyle, ...value } } };
499
+ }
466
500
  return { ...other, attributes: { ...attrs, [blotName.tableCellInner]: value } };
467
501
  }
468
502
  }
@@ -486,16 +520,60 @@ export class TableUp {
486
520
  }
487
521
  // if selection range is not in table, but use the TableSelection selected cells
488
522
  // clean all other formats in cell
489
- if (tableUpModule && tableUpModule.tableSelection && tableUpModule.tableSelection.selectedTds.length > 0) {
523
+ if (tableUpModule && tableUpModule.tableSelection && tableUpModule.tableSelection.selectedTds.length > 0 && tableUpModule.tableSelection.table) {
524
+ const tableMain = Quill.find(tableUpModule.tableSelection.table) as TableMainFormat;
525
+ if (!tableMain) {
526
+ console.warn('TableMainFormat not found');
527
+ return;
528
+ }
490
529
  const selectedTds = tableUpModule.tableSelection.selectedTds;
491
530
 
531
+ // get all need clean style cells. include border-right/border-bottom effect cells
532
+ const editTds = new Set<TableCellFormat>();
533
+ const tds: { td: TableCellFormat; cleanBorder: 'bottom' | 'right' | true }[] = [];
534
+ for (const innerTd of selectedTds) {
535
+ if (innerTd.parent instanceof TableCellFormat) {
536
+ for (const td of innerTd.parent.getNearByCell('top')) {
537
+ if (editTds.has(td)) continue;
538
+ editTds.add(td);
539
+ tds.push({ td, cleanBorder: 'bottom' });
540
+ }
541
+ for (const td of innerTd.parent.getNearByCell('left')) {
542
+ if (editTds.has(td)) continue;
543
+ editTds.add(td);
544
+ tds.push({ td, cleanBorder: 'right' });
545
+ }
546
+
547
+ editTds.add(innerTd.parent);
548
+ tds.push({ td: innerTd.parent, cleanBorder: true });
549
+ }
550
+ }
551
+ // sort cells makesure index correct
552
+ const allCells = tableMain.descendants(TableCellFormat);
553
+ const cellIndexMap = new Map(allCells.map((cell, index) => [cell, index]));
554
+ tds.sort((a, b) => cellIndexMap.get(a.td)! - cellIndexMap.get(b.td)!);
555
+
556
+ // compute delta
492
557
  let delta = new Delta();
493
558
  let lastIndex = 0;
494
- for (const innerTd of selectedTds) {
495
- const index = innerTd.offset(this.quill.scroll);
496
- const length = innerTd.length();
559
+ for (const { td, cleanBorder } of tds) {
560
+ const index = td.getCellInner().offset(this.quill.scroll);
561
+ const length = td.getCellInner().length();
497
562
  // `line` length will include a break(\n) at the end. minus 1 to remove break
498
- const diff = cleanFormatExcludeTable(index, length - 1);
563
+ const diff = cleanFormatExcludeTable(
564
+ index,
565
+ length - 1,
566
+ (styleStr: string | undefined) => {
567
+ if (!styleStr || cleanBorder === true) return '';
568
+ // only clean border-right/border-bottom style
569
+ const css = cssTextToObject(styleStr);
570
+ const filterStyle = Object.keys(css).filter(key => !key.startsWith(`border-${cleanBorder}`)).reduce((acc: Record<string, string>, key: string) => {
571
+ acc[key] = css[key];
572
+ return acc;
573
+ }, {});
574
+ return objectToCssText(filterStyle);
575
+ },
576
+ );
499
577
  const cellDiff = new Delta().retain(index - lastIndex).concat(diff);
500
578
  delta = delta.concat(cellDiff);
501
579
  lastIndex = index + length;
@@ -617,110 +695,69 @@ export class TableUp {
617
695
  }
618
696
 
619
697
  if (!tableMain) return '';
620
-
621
- const colgroupBlot = tableMain.children.head;
622
- const tbodyBlot = tableMain.children.tail;
623
- if (!tbodyBlot || !colgroupBlot) {
624
- console.error('tableMain has no tbody or colgroup');
625
- return '';
626
- }
627
-
628
- function getElementTags(element: HTMLElement) {
629
- const tagName = element.tagName.toLowerCase();
630
- const attributes = Array.from(element.attributes)
631
- .map(attr => `${attr.name}="${attr.value}"`)
632
- .join(' ');
633
- const startTag = `<${tagName}${attributes ? ` ${attributes}` : ''}>`;
634
- const selfClosingTags = [
635
- 'area',
636
- 'base',
637
- 'br',
638
- 'col',
639
- 'embed',
640
- 'hr',
641
- 'img',
642
- 'input',
643
- 'link',
644
- 'meta',
645
- 'param',
646
- 'source',
647
- 'track',
648
- 'wbr',
649
- ];
650
-
651
- return {
652
- startTag,
653
- endTag: selfClosingTags.includes(tagName) ? '' : `</${tagName}>`,
654
- };
655
- }
656
-
657
- let html = '';
658
- const colIds: Set<string> = new Set();
659
- let tdRows = 0;
660
- let lastTrId: string | null = null;
698
+ const tableIndex = this.quill.getIndex(tableMain);
699
+ const tableLength = tableMain.length();
700
+ const tableHTML = this.quill.getSemanticHTML(tableIndex, tableLength);
701
+ const parser = new DOMParser();
702
+ const doc = parser.parseFromString(tableHTML, 'text/html');
703
+
704
+ const cellColWidth: string[] = [];
705
+ const cellColIds = new Set<string>();
706
+ const cellIds = new Set<string>();
661
707
  for (const td of tds) {
662
- const i = this.quill.getIndex(td);
663
- const len = td.length();
664
- const htmlStr = this.quill.getSemanticHTML(i, len);
665
- const parser = new DOMParser();
666
- const doc = parser.parseFromString(htmlStr, 'text/html');
667
- // remove contenteditable on tableCellInner
668
- for (const td of Array.from(doc.querySelectorAll('td > div[contenteditable]'))) {
669
- td.removeAttribute('contenteditable');
708
+ cellColIds.add(td.colId);
709
+ cellIds.add(`${td.rowId}-${td.colId}`);
710
+ }
711
+ // filter col
712
+ for (const col of Array.from(doc.querySelectorAll('col'))) {
713
+ if (!cellColIds.has(col.dataset.colId!)) {
714
+ col.remove();
670
715
  }
671
- const tr = doc.querySelector('tr')!;
672
- if (td.rowId !== lastTrId) {
673
- tdRows += 1;
674
- const { startTag, endTag } = getElementTags(tr);
675
- html += `${html ? endTag : ''}${startTag}${tr.innerHTML}`;
716
+ else {
717
+ cellColWidth.push(col.getAttribute('width')!);
718
+ }
719
+ }
720
+ // filter td
721
+ let rowCount = 0;
722
+ let lastRowId: string | null = null;
723
+ for (const td of Array.from(doc.querySelectorAll('td'))) {
724
+ if (!cellIds.has(`${td.dataset.rowId}-${td.dataset.colId}`)) {
725
+ const parent = td.parentElement;
726
+ td.remove();
727
+ if (parent && parent.children.length <= 0) {
728
+ parent.remove();
729
+ }
676
730
  }
677
731
  else {
678
- html += tr.innerHTML;
732
+ if (lastRowId !== td.dataset.rowId) {
733
+ rowCount += 1;
734
+ lastRowId = td.dataset.rowId!;
735
+ }
679
736
  }
680
- lastTrId = td.rowId;
681
- colIds.add(td.colId);
682
737
  }
683
- html += '</tr>';
684
-
685
- const { startTag, endTag } = getElementTags(tbodyBlot.domNode as HTMLElement);
686
- html = `${startTag}${html}${endTag}`;
687
-
688
- const cols = tableMain.getCols();
689
- const { startTag: colgroupStartTag, endTag: colgroupEndTag } = getElementTags(colgroupBlot.domNode as HTMLElement);
690
- let colStr = '';
691
- let width = 0;
692
- const convertCols = cols.filter(col => colIds.has(col.colId)).map(col => col.clone() as TableColFormat);
738
+ // calculate width
739
+ const cols = Array.from(doc.querySelectorAll('col'));
740
+ const colsValue = cols.map(col => TableColFormat.value(col));
693
741
  if (tableMain.full) {
694
- // Complete the remaining width
695
- const totalWidth = convertCols.reduce((total, col) => total + col.width, 0);
696
- const totalRemainingWidth = 100 - totalWidth;
697
- const part = totalRemainingWidth / totalWidth;
742
+ const totalWidth = colsValue.reduce((total, col) => col.width + total, 0);
698
743
 
699
- for (const col of convertCols) {
700
- col.width += Math.round(col.width * part);
744
+ for (const [i, col] of colsValue.entries()) {
745
+ col.width = Math.round((col.width / totalWidth) * 100);
746
+ cols[i].setAttribute('width', `${col.width}%`);
701
747
  }
702
748
  }
703
- for (const col of convertCols) {
704
- const { startTag } = getElementTags(col.domNode as HTMLElement);
705
- colStr += startTag;
706
- width += col.width;
707
- }
708
- html = colgroupStartTag + colStr + colgroupEndTag + html;
709
-
710
- const tableMainDom = tableMain.domNode.cloneNode() as HTMLElement;
711
- if (!tableMain.full) {
749
+ else {
750
+ let width = 0;
751
+ for (const col of colsValue) {
752
+ width += col.width;
753
+ }
754
+ const tableMainDom = doc.querySelector('table')!;
712
755
  tableMainDom.style.width = `${width}px`;
713
756
  }
714
757
 
715
- const { startTag: mainStartTag, endTag: mainEndTag } = getElementTags(tableMainDom);
716
- html = mainStartTag + html + mainEndTag;
717
-
718
- const { startTag: wrapperStartTag, endTag: wrapperEndTag } = getElementTags(tableMain.parent.domNode as HTMLElement);
719
- html = wrapperStartTag + html + wrapperEndTag;
720
-
721
758
  if (isCut) {
722
759
  const trs = tableMain.getRows();
723
- if (tdRows === trs.length) {
760
+ if (rowCount === trs.length) {
724
761
  this.removeCol(tds);
725
762
  }
726
763
  else {
@@ -729,7 +766,7 @@ export class TableUp {
729
766
  }
730
767
  }
731
768
  }
732
- return html;
769
+ return doc.body.innerHTML;
733
770
  }
734
771
 
735
772
  insertTable(rows: number, columns: number) {
@@ -962,7 +999,7 @@ export class TableUp {
962
999
  colgroup.insertColByIndex(columnIndex, {
963
1000
  tableId,
964
1001
  colId: newColId,
965
- width: tableBlot.full ? '6%' : '160px',
1002
+ width: tableBlot.full ? 6 : 160,
966
1003
  full: tableBlot.full,
967
1004
  });
968
1005
  }
package/src/utils/bem.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cssNamespace } from './constants';
2
2
 
3
- export const createBEM = (b: string, n: string = cssNamespace) => {
3
+ export function createBEM(b: string, n: string = cssNamespace) {
4
4
  const prefix = n ? `${n}-` : '';
5
5
  return {
6
6
  /** n-b */
@@ -20,4 +20,4 @@ export const createBEM = (b: string, n: string = cssNamespace) => {
20
20
  /** is-n */
21
21
  is: (n: string) => `is-${n}`,
22
22
  };
23
- };
23
+ }
@@ -63,6 +63,18 @@ export function findAllParentBlot(Blot: TypeParchment.Blot) {
63
63
  return blots;
64
64
  }
65
65
 
66
+ export function findChildBlot<T extends TypeParchment.BlotConstructor>(parent: TypeParchment.Parent, blot: T) {
67
+ const descendants: InstanceType<T>[] = [];
68
+ const next = parent.children.iterator();
69
+ let cur: TypeParchment.Blot | null = null;
70
+ while ((cur = next())) {
71
+ if (cur instanceof blot) {
72
+ descendants.push(cur as InstanceType<T>);
73
+ }
74
+ }
75
+ return descendants;
76
+ }
77
+
66
78
  function mixinProps<T = any, U = any>(target: T, source: U) {
67
79
  for (const prop of Object.getOwnPropertyNames(source)) {
68
80
  if (/^constructor$/.test(prop)) continue;
@@ -10,7 +10,7 @@ export interface RGB {
10
10
  b: number;
11
11
  a: number;
12
12
  }
13
- const normalizeValue = function (value: number | string, max: number | string) {
13
+ function normalizeValue(value: number | string, max: number | string) {
14
14
  value = Math.min(max as number, Math.max(0, Number.parseFloat(`${value}`)));
15
15
 
16
16
  // Handle floating point rounding errors
@@ -20,24 +20,24 @@ const normalizeValue = function (value: number | string, max: number | string) {
20
20
 
21
21
  // Convert into [0, 1] range if it isn't already
22
22
  return (value % (max as number)) / Number.parseFloat(max as string);
23
- };
24
- export const validateHSB = (hsb: HSB): HSB => {
23
+ }
24
+ export function validateHSB(hsb: HSB): HSB {
25
25
  return {
26
26
  h: Math.min(360, Math.max(0, hsb.h)),
27
27
  s: Math.min(100, Math.max(0, hsb.s)),
28
28
  b: Math.min(100, Math.max(0, hsb.b)),
29
29
  a: Math.min(1, Math.max(0, hsb.a)),
30
30
  };
31
- };
32
- export const HEXtoRGB = (hex: string): RGB => {
31
+ }
32
+ export function HEXtoRGB(hex: string): RGB {
33
33
  hex = hex.startsWith('#') ? hex.slice(1) : hex;
34
34
  const r = Number.parseInt(hex.slice(0, 2), 16);
35
35
  const g = Number.parseInt(hex.slice(2, 4), 16);
36
36
  const b = Number.parseInt(hex.slice(4, 6), 16);
37
37
  const a = Number((Number.parseInt(hex.slice(6, 8) || 'ff', 16) / 255).toFixed(2));
38
38
  return { r, g, b, a };
39
- };
40
- export const RGBtoHSB = (rgb: RGB): HSB => {
39
+ }
40
+ export function RGBtoHSB(rgb: RGB): HSB {
41
41
  let { r, g, b, a } = rgb;
42
42
  r = normalizeValue(r, 255);
43
43
  g = normalizeValue(g, 255);
@@ -73,8 +73,8 @@ export const RGBtoHSB = (rgb: RGB): HSB => {
73
73
  }
74
74
 
75
75
  return { h: h! * 360, s: s * 100, b: v * 100, a };
76
- };
77
- export const HSBtoRGB = (hsb: HSB): RGB => {
76
+ }
77
+ export function HSBtoRGB(hsb: HSB): RGB {
78
78
  let { h, s, b, a } = hsb;
79
79
  h = normalizeValue(h, 360) * 6;
80
80
  s = normalizeValue(s, 100);
@@ -96,8 +96,8 @@ export const HSBtoRGB = (hsb: HSB): RGB => {
96
96
  b: Math.round(v * 255),
97
97
  a,
98
98
  };
99
- };
100
- export const RGBtoHEX = (rgb: RGB): string => {
99
+ }
100
+ export function RGBtoHEX(rgb: RGB): string {
101
101
  const hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16), Math.round(rgb.a * 255).toString(16)];
102
102
  for (const key in hex) {
103
103
  if (hex[key].length === 1) {
@@ -105,5 +105,5 @@ export const RGBtoHEX = (rgb: RGB): string => {
105
105
  }
106
106
  }
107
107
  return hex.join('');
108
- };
108
+ }
109
109
  export const HSBtoHEX = (hsb: HSB): string => RGBtoHEX(HSBtoRGB(hsb));
@@ -5,7 +5,7 @@ interface ButtonOptions {
5
5
  type: 'confirm' | 'default';
6
6
  content: HTMLElement | string;
7
7
  }
8
- export const createButton = (options?: Partial<ButtonOptions>) => {
8
+ export function createButton(options?: Partial<ButtonOptions>) {
9
9
  const { type = 'default', content } = options || {};
10
10
  const bem = createBEM('button');
11
11
  const btn = document.createElement('button');
@@ -19,4 +19,4 @@ export const createButton = (options?: Partial<ButtonOptions>) => {
19
19
  }
20
20
  }
21
21
  return btn;
22
- };
22
+ }
@@ -6,7 +6,7 @@ interface ColorPickerOptions {
6
6
  color: string;
7
7
  onChange: (color: string) => void;
8
8
  }
9
- export const createColorPicker = (options: Partial<ColorPickerOptions> = {}) => {
9
+ export function createColorPicker(options: Partial<ColorPickerOptions> = {}) {
10
10
  const contentWidth = 230;
11
11
  const contentHeight = 150;
12
12
  const handleSizeSec = 10;
@@ -233,4 +233,4 @@ export const createColorPicker = (options: Partial<ColorPickerOptions> = {}) =>
233
233
 
234
234
  updateUI();
235
235
  return root;
236
- };
236
+ }
@@ -6,7 +6,7 @@ interface DialogOptions {
6
6
  beforeClose?: () => void;
7
7
  }
8
8
  let zindex = 8000;
9
- export const createDialog = ({ child, target = document.body, beforeClose = () => {} }: DialogOptions = {}) => {
9
+ export function createDialog({ child, target = document.body, beforeClose = () => {} }: DialogOptions = {}) {
10
10
  const bem = createBEM('dialog');
11
11
  const appendTo = target;
12
12
  const dialog = document.createElement('div');
@@ -38,4 +38,4 @@ export const createDialog = ({ child, target = document.body, beforeClose = () =
38
38
  zindex += 1;
39
39
 
40
40
  return { dialog, close };
41
- };
41
+ }
@@ -7,7 +7,7 @@ interface InputOptions {
7
7
  min?: number;
8
8
  [key: string]: any;
9
9
  }
10
- export const createInputItem = (label: string, options: InputOptions) => {
10
+ export function createInputItem(label: string, options: InputOptions) {
11
11
  const bem = createBEM('input');
12
12
  options.type || (options.type = 'text');
13
13
  options.value || (options.value = '');
@@ -71,4 +71,4 @@ export const createInputItem = (label: string, options: InputOptions) => {
71
71
  };
72
72
 
73
73
  return { item: inputItem, input, errorTip };
74
- };
74
+ }