quill-table-up 2.0.1 → 2.0.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/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/table-creator.css +1 -1
- package/package.json +9 -14
- package/src/__tests__/e2e/custom-creator.test.ts +44 -0
- package/src/__tests__/e2e/table-align.test.ts +39 -0
- package/src/__tests__/e2e/table-resize.test.ts +152 -0
- package/src/__tests__/e2e/table-scrollbar.test.ts +31 -0
- package/src/__tests__/e2e/table-selection.test.ts +83 -0
- package/src/__tests__/e2e/utils.ts +6 -0
- package/src/__tests__/unit/table-insert-blot.test.ts +464 -0
- package/src/__tests__/unit/table-insert-remove-merge.test.ts +1270 -0
- package/src/__tests__/unit/table-redo-undo.test.ts +909 -0
- package/src/__tests__/unit/utils.test-d.ts +49 -0
- package/src/__tests__/unit/utils.test.ts +715 -0
- package/src/__tests__/unit/utils.ts +216 -0
- package/src/__tests__/unit/vitest.d.ts +12 -0
- package/src/formats/container-format.ts +52 -0
- package/src/formats/index.ts +10 -0
- package/src/formats/overrides/block.ts +93 -0
- package/src/formats/overrides/blockquote.ts +8 -0
- package/src/formats/overrides/code.ts +8 -0
- package/src/formats/overrides/header.ts +8 -0
- package/src/formats/overrides/index.ts +6 -0
- package/src/formats/overrides/list.ts +10 -0
- package/src/formats/overrides/scroll.ts +51 -0
- package/src/formats/table-body-format.ts +92 -0
- package/src/formats/table-cell-format.ts +139 -0
- package/src/formats/table-cell-inner-format.ts +251 -0
- package/src/formats/table-col-format.ts +174 -0
- package/src/formats/table-colgroup-format.ts +133 -0
- package/src/formats/table-main-format.ts +143 -0
- package/src/formats/table-row-format.ts +147 -0
- package/src/formats/table-wrapper-format.ts +55 -0
- package/src/formats/utils.ts +3 -0
- package/src/index.ts +1157 -0
- package/src/modules/index.ts +5 -0
- package/src/modules/table-align.ts +116 -0
- package/src/modules/table-menu/constants.ts +140 -0
- package/src/modules/table-menu/index.ts +3 -0
- package/src/modules/table-menu/table-menu-common.ts +249 -0
- package/src/modules/table-menu/table-menu-contextmenu.ts +94 -0
- package/src/modules/table-menu/table-menu-select.ts +28 -0
- package/src/modules/table-resize/index.ts +5 -0
- package/src/modules/table-resize/table-resize-box.ts +293 -0
- package/src/modules/table-resize/table-resize-common.ts +343 -0
- package/src/modules/table-resize/table-resize-line.ts +163 -0
- package/src/modules/table-resize/table-resize-scale.ts +154 -0
- package/src/modules/table-resize/utils.ts +3 -0
- package/src/modules/table-scrollbar.ts +255 -0
- package/src/modules/table-selection.ts +262 -0
- package/src/style/button.less +45 -0
- package/src/style/color-picker.less +134 -0
- package/src/style/dialog.less +53 -0
- package/src/style/functions.less +9 -0
- package/src/style/index.less +89 -0
- package/src/style/input.less +64 -0
- package/src/style/select-box.less +51 -0
- package/src/style/table-creator.less +68 -0
- package/src/style/table-menu.less +122 -0
- package/src/style/table-resize-scale.less +31 -0
- package/src/style/table-resize.less +183 -0
- package/src/style/table-scrollbar.less +49 -0
- package/src/style/table-selection.less +15 -0
- package/src/style/tooltip.less +19 -0
- package/src/style/variables.less +1 -0
- package/src/svg/background.svg +1 -0
- package/src/svg/border.svg +1 -0
- package/src/svg/color.svg +1 -0
- package/src/svg/insert-bottom.svg +1 -0
- package/src/svg/insert-left.svg +1 -0
- package/src/svg/insert-right.svg +1 -0
- package/src/svg/insert-top.svg +1 -0
- package/src/svg/merge-cell.svg +1 -0
- package/src/svg/remove-column.svg +1 -0
- package/src/svg/remove-row.svg +1 -0
- package/src/svg/remove-table.svg +1 -0
- package/src/svg/split-cell.svg +1 -0
- package/src/types.d.ts +4 -0
- package/src/utils/bem.ts +23 -0
- package/src/utils/color.ts +109 -0
- package/src/utils/components/button.ts +22 -0
- package/src/utils/components/color-picker.ts +236 -0
- package/src/utils/components/dialog.ts +41 -0
- package/src/utils/components/index.ts +6 -0
- package/src/utils/components/input.ts +74 -0
- package/src/utils/components/table/creator.ts +86 -0
- package/src/utils/components/table/index.ts +2 -0
- package/src/utils/components/table/select-box.ts +83 -0
- package/src/utils/components/tooltip.ts +186 -0
- package/src/utils/constants.ts +99 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/is.ts +6 -0
- package/src/utils/position.ts +21 -0
- package/src/utils/types.ts +131 -0
- package/src/utils/utils.ts +139 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { TableColValue } from '../../index';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { expect, vi } from 'vitest';
|
|
4
|
+
import TableUp from '../../index';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line unicorn/prefer-string-replace-all
|
|
7
|
+
export const normalizeHTML = (html: string | { html: string }) => typeof html === 'object' ? html.html : html.replace(/\n\s*/g, '');
|
|
8
|
+
export const sortAttributes = (element: HTMLElement) => {
|
|
9
|
+
const attributes = Array.from(element.attributes);
|
|
10
|
+
const sortedAttributes = attributes.sort((a, b) =>
|
|
11
|
+
a.name.localeCompare(b.name),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
while (element.attributes.length > 0) {
|
|
15
|
+
element.removeAttribute(element.attributes[0].name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const attr of sortedAttributes) {
|
|
19
|
+
element.setAttribute(attr.name, attr.value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
23
|
+
element.childNodes.forEach((child) => {
|
|
24
|
+
if (child instanceof HTMLElement) {
|
|
25
|
+
sortAttributes(child);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export const createQuillWithTableModule = (html: string, options = {}, moduleOptions = {}, register = {}) => {
|
|
30
|
+
Quill.register({
|
|
31
|
+
[`modules/${TableUp.moduleName}`]: TableUp,
|
|
32
|
+
...register,
|
|
33
|
+
}, true);
|
|
34
|
+
const container = document.body.appendChild(document.createElement('div'));
|
|
35
|
+
container.innerHTML = normalizeHTML(html);
|
|
36
|
+
const quill = new Quill(container, {
|
|
37
|
+
modules: {
|
|
38
|
+
[TableUp.moduleName]: {
|
|
39
|
+
full: true,
|
|
40
|
+
...options,
|
|
41
|
+
},
|
|
42
|
+
history: {
|
|
43
|
+
delay: 0,
|
|
44
|
+
},
|
|
45
|
+
...moduleOptions,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return quill;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect.extend({
|
|
52
|
+
toEqualHTML(received, expected, options = {}) {
|
|
53
|
+
const ignoreAttrs = options?.ignoreAttrs ?? [];
|
|
54
|
+
const receivedDOM = document.createElement('div');
|
|
55
|
+
const expectedDOM = document.createElement('div');
|
|
56
|
+
receivedDOM.innerHTML = normalizeHTML(
|
|
57
|
+
typeof received === 'string' ? received : received.innerHTML,
|
|
58
|
+
);
|
|
59
|
+
expectedDOM.innerHTML = normalizeHTML(expected);
|
|
60
|
+
|
|
61
|
+
const doms = [receivedDOM, expectedDOM];
|
|
62
|
+
|
|
63
|
+
for (const dom of doms) {
|
|
64
|
+
for (const node of Array.from(dom.querySelectorAll('.ql-ui'))) {
|
|
65
|
+
node.remove();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const attr of ignoreAttrs) {
|
|
69
|
+
for (const node of Array.from(dom.querySelectorAll(`[${attr}]`))) {
|
|
70
|
+
node.removeAttribute(attr);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sortAttributes(dom);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (this.equals(receivedDOM.innerHTML, expectedDOM.innerHTML)) {
|
|
78
|
+
return { pass: true, message: () => '' };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
pass: false,
|
|
82
|
+
message: () =>
|
|
83
|
+
`HTMLs don't match.\n${this.utils.diff(
|
|
84
|
+
this.utils.stringify(receivedDOM),
|
|
85
|
+
this.utils.stringify(expectedDOM),
|
|
86
|
+
)}`,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
interface TableColDeltaValue extends Omit<TableColValue, 'width' | 'full'> {
|
|
92
|
+
width: number;
|
|
93
|
+
full?: 'true';
|
|
94
|
+
};
|
|
95
|
+
interface TableCreatorOptions {
|
|
96
|
+
isEmpty: boolean;
|
|
97
|
+
tableId: string;
|
|
98
|
+
}
|
|
99
|
+
type ColOptions = Omit<TableColValue, 'width' | 'tableId' | 'colId'> & { width?: number };
|
|
100
|
+
|
|
101
|
+
export const datasetTableId = (id: string) => `data-table-id="${id}"`;
|
|
102
|
+
export const datasetFull = (full: boolean) => full ? ' data-full="true"' : '';
|
|
103
|
+
export const datasetAlign = (align: string) => align === 'left' ? '' : ` data-align="${align}"`;
|
|
104
|
+
export const getColWidthStyle = (options: Required<Omit<ColOptions, 'align' | 'tableId' | 'width'>> & { width?: number; colNum: number }) => {
|
|
105
|
+
const { full, width, colNum } = options;
|
|
106
|
+
let colWidth = `${width}px`;
|
|
107
|
+
if (full) {
|
|
108
|
+
colWidth = `${1 / colNum * 100}%`;
|
|
109
|
+
}
|
|
110
|
+
return `width="${colWidth}"`;
|
|
111
|
+
};
|
|
112
|
+
export const createTableDeltaOps = (row: number, col: number, colOptions?: ColOptions, options: Partial<TableCreatorOptions> = {}) => {
|
|
113
|
+
const { isEmpty = false, tableId = '1' } = options;
|
|
114
|
+
const { full = true, width = 100, align = 'left' } = colOptions || {};
|
|
115
|
+
const table: any[] = [{ insert: '\n' }];
|
|
116
|
+
for (const [i, _] of new Array(col).fill(0).entries()) {
|
|
117
|
+
const value: TableColDeltaValue = { tableId, colId: `${i + 1}`, width: 1 / col * 100 };
|
|
118
|
+
if (full) {
|
|
119
|
+
value.full = 'true';
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
value.width = width;
|
|
123
|
+
}
|
|
124
|
+
if (align !== 'left') {
|
|
125
|
+
value.align = align;
|
|
126
|
+
}
|
|
127
|
+
table.push({ insert: { 'table-up-col': value } });
|
|
128
|
+
}
|
|
129
|
+
for (let i = 0; i < row; i++) {
|
|
130
|
+
for (let j = 0; j < col; j++) {
|
|
131
|
+
if (!isEmpty) {
|
|
132
|
+
table.push({ insert: `${i * col + j + 1}` });
|
|
133
|
+
}
|
|
134
|
+
table.push(
|
|
135
|
+
{
|
|
136
|
+
attributes: { 'table-up-cell-inner': { tableId, rowId: i + 1, colId: j + 1, rowspan: 1, colspan: 1 } },
|
|
137
|
+
insert: '\n',
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
table.push({ insert: '\n' });
|
|
143
|
+
return table;
|
|
144
|
+
};
|
|
145
|
+
export const createTable = async (row: number, col: number, colOptions?: ColOptions, options?: Partial<TableCreatorOptions>) => {
|
|
146
|
+
const quill = createQuillWithTableModule(`<p><br></p>`);
|
|
147
|
+
quill.setContents(createTableDeltaOps(row, col, colOptions, options));
|
|
148
|
+
// set range for undo won't scrollSelectionIntoView
|
|
149
|
+
quill.setSelection({ index: 0, length: 0 });
|
|
150
|
+
await vi.runAllTimersAsync();
|
|
151
|
+
return quill;
|
|
152
|
+
};
|
|
153
|
+
export const createTaleColHTML = (colNum: number, colOptions?: Partial<ColOptions>, options?: Partial<TableCreatorOptions>) => {
|
|
154
|
+
const { full = true, width = 100, align = 'left' } = colOptions || {};
|
|
155
|
+
const { tableId = '1' } = options || {};
|
|
156
|
+
const colWidth = getColWidthStyle({ full, width, colNum });
|
|
157
|
+
return `
|
|
158
|
+
<colgroup ${datasetTableId(tableId)}${datasetFull(full)}${datasetAlign(align)}>
|
|
159
|
+
${new Array(colNum).fill(0).map((_, i) => `<col ${colWidth} ${datasetTableId(tableId)} data-col-id="${i + 1}"${datasetFull(full)}${datasetAlign(align)} />`).join('\n')}
|
|
160
|
+
</colgroup>
|
|
161
|
+
`;
|
|
162
|
+
};
|
|
163
|
+
export const createTableBodyHTML = (row: number, col: number, options?: Partial<TableCreatorOptions>) => {
|
|
164
|
+
const { isEmpty = false, tableId = '1' } = options || {};
|
|
165
|
+
return `
|
|
166
|
+
<tbody ${datasetTableId(tableId)}>
|
|
167
|
+
${
|
|
168
|
+
new Array(row).fill(0).map((_, i) => `
|
|
169
|
+
<tr ${datasetTableId(tableId)} data-row-id="${i + 1}">
|
|
170
|
+
${
|
|
171
|
+
new Array(col).fill(0).map((_, j) => `<td rowspan="1" colspan="1" ${datasetTableId(tableId)} data-row-id="${i + 1}" data-col-id="${j + 1}">
|
|
172
|
+
<div ${datasetTableId(tableId)} data-rowspan="1" data-colspan="1" data-row-id="${i + 1}" data-col-id="${j + 1}">
|
|
173
|
+
<p>
|
|
174
|
+
${isEmpty ? '<br>' : i * row + j + 1}
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
</td>`).join('\n')
|
|
178
|
+
}
|
|
179
|
+
</tr>
|
|
180
|
+
`).join('\n')
|
|
181
|
+
}
|
|
182
|
+
</tbody>
|
|
183
|
+
`;
|
|
184
|
+
};
|
|
185
|
+
export const createTableHTML = (row: number, col: number, colOptions?: ColOptions, options?: Partial<TableCreatorOptions>) => {
|
|
186
|
+
const { full = true, width = 100, align = 'left' } = colOptions || {};
|
|
187
|
+
const { tableId = '1' } = options || {};
|
|
188
|
+
let alignStyle = 'margin-right: auto;';
|
|
189
|
+
switch (align) {
|
|
190
|
+
case 'center': {
|
|
191
|
+
alignStyle = 'margin-left: auto; margin-right: auto;';
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case '':
|
|
195
|
+
case 'left': {
|
|
196
|
+
alignStyle = 'margin-right: auto;';
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'right': {
|
|
200
|
+
alignStyle = 'margin-left: auto;';
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
default: {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `
|
|
209
|
+
<div ${datasetTableId(tableId)}>
|
|
210
|
+
<table cellpadding="0" cellspacing="0" ${datasetTableId(tableId)}${datasetFull(full)}${datasetAlign(align)} style="${alignStyle}${full ? '' : ` width: ${width * col}px;`}">
|
|
211
|
+
${createTaleColHTML(col, colOptions)}
|
|
212
|
+
${createTableBodyHTML(row, col, options)}
|
|
213
|
+
</table>
|
|
214
|
+
</div>
|
|
215
|
+
`;
|
|
216
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* eslint-disable ts/no-empty-object-type */
|
|
2
|
+
/* eslint-disable unused-imports/no-unused-imports */
|
|
3
|
+
import type { Assertion, AsymmetricMatchersContaining } from 'vitest';
|
|
4
|
+
|
|
5
|
+
interface CustomMatchers<R = unknown> {
|
|
6
|
+
toEqualHTML: (html: string, options?: { ignoreAttrs?: string[] }) => R;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module 'vitest' {
|
|
10
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
11
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
12
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { blotName } from '../utils';
|
|
4
|
+
|
|
5
|
+
const Parchment = Quill.import('parchment');
|
|
6
|
+
const Container = Quill.import('blots/container') as typeof TypeParchment.ContainerBlot;
|
|
7
|
+
const Block = Quill.import('blots/block') as TypeParchment.BlotConstructor;
|
|
8
|
+
const BlockEmbed = Quill.import('blots/block/embed') as TypeParchment.BlotConstructor;
|
|
9
|
+
|
|
10
|
+
export class ContainerFormat extends Container {
|
|
11
|
+
static tagName: string;
|
|
12
|
+
static blotName: string = blotName.container;
|
|
13
|
+
static scope = Parchment.Scope.BLOCK_BLOT;
|
|
14
|
+
|
|
15
|
+
static allowedChildren?: TypeParchment.BlotConstructor[] = [Block, BlockEmbed, Container];
|
|
16
|
+
static requiredContainer: TypeParchment.BlotConstructor;
|
|
17
|
+
static defaultChild?: TypeParchment.BlotConstructor;
|
|
18
|
+
|
|
19
|
+
static create(_value?: unknown) {
|
|
20
|
+
const node = document.createElement(this.tagName);
|
|
21
|
+
if (this.className) {
|
|
22
|
+
node.classList.add(this.className);
|
|
23
|
+
}
|
|
24
|
+
return node;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
insertAt(index: number, value: string, def?: any): void {
|
|
28
|
+
const [child] = this.children.find(index);
|
|
29
|
+
if (!child) {
|
|
30
|
+
const defaultChild = this.scroll.create(this.statics.defaultChild.blotName || 'block');
|
|
31
|
+
this.appendChild(defaultChild);
|
|
32
|
+
}
|
|
33
|
+
super.insertAt(index, value, def);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public optimize(_context: Record<string, any>) {
|
|
37
|
+
if (this.children.length === 0) {
|
|
38
|
+
if (this.statics.defaultChild != null) {
|
|
39
|
+
const child = this.scroll.create(this.statics.defaultChild.blotName);
|
|
40
|
+
this.appendChild(child);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.remove();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.children.length > 0 && this.next != null && this.checkMerge()) {
|
|
48
|
+
this.next.moveChildren(this);
|
|
49
|
+
this.next.remove();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './container-format';
|
|
2
|
+
export * from './overrides';
|
|
3
|
+
export * from './table-body-format';
|
|
4
|
+
export * from './table-cell-format';
|
|
5
|
+
export * from './table-cell-inner-format';
|
|
6
|
+
export * from './table-col-format';
|
|
7
|
+
export * from './table-colgroup-format';
|
|
8
|
+
export * from './table-main-format';
|
|
9
|
+
export * from './table-row-format';
|
|
10
|
+
export * from './table-wrapper-format';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
+
import type TypeBlock from 'quill/blots/block';
|
|
3
|
+
import Quill from 'quill';
|
|
4
|
+
import { blotName, findParentBlots } from '../../utils';
|
|
5
|
+
|
|
6
|
+
const Parchment = Quill.import('parchment');
|
|
7
|
+
const Block = Quill.import('blots/block') as typeof TypeBlock;
|
|
8
|
+
|
|
9
|
+
export class BlockOverride extends Block {
|
|
10
|
+
replaceWith(name: string | TypeParchment.Blot, value?: any): TypeParchment.Blot {
|
|
11
|
+
const replacement = typeof name === 'string' ? this.scroll.create(name, value) : name;
|
|
12
|
+
if (replacement instanceof Parchment.ParentBlot) {
|
|
13
|
+
// replace block to TableCellInner length is 0 when setContents
|
|
14
|
+
// that will set text direct in TableCellInner but not in block
|
|
15
|
+
// so we need to set text in block and block in TableCellInner
|
|
16
|
+
// wrap with TableCellInner.formatAt when length is 0 will create a new block
|
|
17
|
+
// that can make sure TableCellInner struct correctly
|
|
18
|
+
if (replacement.statics.blotName === blotName.tableCellInner) {
|
|
19
|
+
const selfParent = this.parent;
|
|
20
|
+
if (selfParent.statics.blotName === blotName.tableCellInner) {
|
|
21
|
+
if (selfParent != null) {
|
|
22
|
+
selfParent.insertBefore(replacement, this.prev ? null : this.next);
|
|
23
|
+
}
|
|
24
|
+
if (this.parent.statics.blotName === blotName.tableCellInner && this.prev) {
|
|
25
|
+
let block: TypeBlock | null = this;
|
|
26
|
+
while (block) {
|
|
27
|
+
const next = block.next as TypeBlock | null;
|
|
28
|
+
replacement.appendChild(block);
|
|
29
|
+
block = next;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
replacement.appendChild(this);
|
|
34
|
+
}
|
|
35
|
+
// remove empty cell. tableCellFormat.optimize need col to compute
|
|
36
|
+
if (selfParent && selfParent.length() === 0) {
|
|
37
|
+
selfParent.parent.remove();
|
|
38
|
+
const selfRow = selfParent.parent.parent;
|
|
39
|
+
if (selfRow.statics.blotName === blotName.tableRow && selfRow.children.length === 0) {
|
|
40
|
+
selfRow.remove();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
if (selfParent != null) {
|
|
46
|
+
selfParent.insertBefore(replacement, this.next);
|
|
47
|
+
}
|
|
48
|
+
replacement.appendChild(this);
|
|
49
|
+
}
|
|
50
|
+
return replacement;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.moveChildren(replacement);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (this.parent != null) {
|
|
57
|
+
this.parent.insertBefore(replacement, this.next || undefined);
|
|
58
|
+
this.remove();
|
|
59
|
+
}
|
|
60
|
+
this.attributes.copy(replacement as TypeParchment.BlockBlot);
|
|
61
|
+
return replacement;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
format(name: string, value: any): void {
|
|
65
|
+
if (name === blotName.tableCellInner && this.parent.statics.blotName === name && !value) {
|
|
66
|
+
// when set tableCellInner null. not only clear current block tableCellInner block and also
|
|
67
|
+
// need move td/tr after current cell out of current table. like code-block, split into two table
|
|
68
|
+
const [tableCell, tableRow, tableWrapper] = findParentBlots(this, [blotName.tableCell, blotName.tableRow, blotName.tableWrapper] as const);
|
|
69
|
+
const tableNext = tableWrapper.next;
|
|
70
|
+
let tableRowNext = tableRow.next;
|
|
71
|
+
let tableCellNext = tableCell.next;
|
|
72
|
+
|
|
73
|
+
// clear cur block
|
|
74
|
+
tableWrapper.parent.insertBefore(this, tableNext);
|
|
75
|
+
// only move out of table. `optimize` will generate new table
|
|
76
|
+
// move table cell
|
|
77
|
+
while (tableCellNext) {
|
|
78
|
+
const next = tableCellNext.next;
|
|
79
|
+
tableWrapper.parent.insertBefore(tableCellNext, tableNext);
|
|
80
|
+
tableCellNext = next as TypeParchment.ContainerBlot;
|
|
81
|
+
}
|
|
82
|
+
// move table row
|
|
83
|
+
while (tableRowNext) {
|
|
84
|
+
const next = tableRowNext.next;
|
|
85
|
+
tableWrapper.parent.insertBefore(tableRowNext, tableNext);
|
|
86
|
+
tableRowNext = next as TypeParchment.ContainerBlot;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
super.format(name, value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type TypeBlockquote from 'quill/formats/blockquote';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { mixinClass } from '../../utils';
|
|
4
|
+
import { BlockOverride } from './block';
|
|
5
|
+
|
|
6
|
+
const Blockquote = Quill.import('formats/blockquote') as typeof TypeBlockquote;
|
|
7
|
+
|
|
8
|
+
export class BlockquoteOverride extends mixinClass(Blockquote, [BlockOverride]) {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type TypeCodeBlock from 'quill/formats/code';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { mixinClass } from '../../utils';
|
|
4
|
+
import { BlockOverride } from './block';
|
|
5
|
+
|
|
6
|
+
const CodeBlock = Quill.import('formats/code-block') as typeof TypeCodeBlock;
|
|
7
|
+
|
|
8
|
+
export class CodeBlockOverride extends mixinClass(CodeBlock, [BlockOverride]) {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type TypeHeader from 'quill/formats/header';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { mixinClass } from '../../utils';
|
|
4
|
+
import { BlockOverride } from './block';
|
|
5
|
+
|
|
6
|
+
const Header = Quill.import('formats/header') as typeof TypeHeader;
|
|
7
|
+
|
|
8
|
+
export class HeaderOverride extends mixinClass(Header, [BlockOverride]) {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type TypeListItem from 'quill/formats/list';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { mixinClass } from '../../utils';
|
|
4
|
+
import { BlockOverride } from './block';
|
|
5
|
+
|
|
6
|
+
const ListItem = Quill.import('formats/list') as typeof TypeListItem;
|
|
7
|
+
|
|
8
|
+
export class ListItemOverride extends mixinClass(ListItem, [BlockOverride]) {
|
|
9
|
+
static register(): void {}
|
|
10
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Parchment as TypeParchment } from 'quill';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { blotName } from '../../utils';
|
|
4
|
+
import { TableCellInnerFormat } from '../table-cell-inner-format';
|
|
5
|
+
|
|
6
|
+
const Parchment = Quill.import('parchment');
|
|
7
|
+
const ScrollBlot = Quill.import('blots/scroll') as any;
|
|
8
|
+
|
|
9
|
+
export class ScrollOverride extends ScrollBlot {
|
|
10
|
+
createBlock(attributes: Record<string, any>, refBlot?: TypeParchment.Blot) {
|
|
11
|
+
let createBlotName: string | undefined;
|
|
12
|
+
let formats: Record<string, any> = {};
|
|
13
|
+
if (attributes[blotName.tableCellInner]) {
|
|
14
|
+
createBlotName = blotName.tableCellInner;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// if attributes have not only one block blot. will save last. that will conflict with list/header in tableCellInner
|
|
18
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
19
|
+
const isBlockBlot = this.query(key, Parchment.Scope.BLOCK & Parchment.Scope.BLOT) != null;
|
|
20
|
+
if (isBlockBlot) {
|
|
21
|
+
createBlotName = key;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
formats[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// only add this judgement to merge block blot at table cell
|
|
29
|
+
if (createBlotName === blotName.tableCellInner) {
|
|
30
|
+
formats = { ...attributes };
|
|
31
|
+
delete formats[createBlotName];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const block = this.create(
|
|
35
|
+
createBlotName || this.statics.defaultChild.blotName,
|
|
36
|
+
createBlotName ? attributes[createBlotName] : undefined,
|
|
37
|
+
) as TypeParchment.ParentBlot;
|
|
38
|
+
|
|
39
|
+
this.insertBefore(block, refBlot || undefined);
|
|
40
|
+
|
|
41
|
+
let length = block.length();
|
|
42
|
+
if (block instanceof TableCellInnerFormat && length === 0) {
|
|
43
|
+
length += 1;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(formats)) {
|
|
46
|
+
block.formatAt(0, length, key, value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return block;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { blotName, findParentBlot, randomId } from '../utils';
|
|
2
|
+
import { ContainerFormat } from './container-format';
|
|
3
|
+
import { TableRowFormat } from './table-row-format';
|
|
4
|
+
|
|
5
|
+
export class TableBodyFormat extends ContainerFormat {
|
|
6
|
+
static blotName = blotName.tableBody;
|
|
7
|
+
static tagName = 'tbody';
|
|
8
|
+
|
|
9
|
+
static create(value: string) {
|
|
10
|
+
const node = super.create() as HTMLElement;
|
|
11
|
+
node.dataset.tableId = value;
|
|
12
|
+
return node;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get tableId() {
|
|
16
|
+
return this.domNode.dataset.tableId!;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// insert row at index
|
|
20
|
+
insertRow(targetIndex: number) {
|
|
21
|
+
const tableBlot = findParentBlot(this, blotName.tableMain);
|
|
22
|
+
if (!tableBlot) return;
|
|
23
|
+
// get all column id. exclude the columns of the target index row with rowspan
|
|
24
|
+
const colIds = tableBlot.getColIds();
|
|
25
|
+
const rows = this.descendants(TableRowFormat);
|
|
26
|
+
const insertColIds = new Set(colIds);
|
|
27
|
+
let index = 0;
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
if (index === targetIndex) break;
|
|
30
|
+
row.foreachCellInner((cell) => {
|
|
31
|
+
if (index + cell.rowspan > targetIndex) {
|
|
32
|
+
cell.rowspan += 1;
|
|
33
|
+
insertColIds.delete(cell.colId);
|
|
34
|
+
// colspan cell need remove all includes colId
|
|
35
|
+
if (cell.colspan !== 1) {
|
|
36
|
+
const colIndex = colIds.indexOf(cell.colId);
|
|
37
|
+
for (let i = 0; i < cell.colspan - 1; i++) {
|
|
38
|
+
insertColIds.delete(colIds[colIndex + i + 1]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
index += 1;
|
|
44
|
+
}
|
|
45
|
+
// append new row
|
|
46
|
+
const tableId = tableBlot.tableId;
|
|
47
|
+
const rowId = randomId();
|
|
48
|
+
const tableRow = this.scroll.create(blotName.tableRow, {
|
|
49
|
+
tableId,
|
|
50
|
+
rowId,
|
|
51
|
+
}) as ContainerFormat;
|
|
52
|
+
for (const colId of insertColIds) {
|
|
53
|
+
const breakBlot = this.scroll.create('break');
|
|
54
|
+
const block = breakBlot.wrap('block');
|
|
55
|
+
const tableCellInner = block.wrap(blotName.tableCellInner, {
|
|
56
|
+
tableId,
|
|
57
|
+
rowId,
|
|
58
|
+
colId,
|
|
59
|
+
rowspan: 1,
|
|
60
|
+
colspan: 1,
|
|
61
|
+
});
|
|
62
|
+
const tableCell = tableCellInner.wrap(blotName.tableCell, {
|
|
63
|
+
tableId,
|
|
64
|
+
rowId,
|
|
65
|
+
colId,
|
|
66
|
+
rowspan: 1,
|
|
67
|
+
colspan: 1,
|
|
68
|
+
});
|
|
69
|
+
tableRow.appendChild(tableCell);
|
|
70
|
+
}
|
|
71
|
+
this.insertBefore(tableRow, rows[targetIndex] || null);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
checkMerge(): boolean {
|
|
75
|
+
const next = this.next as TableBodyFormat;
|
|
76
|
+
return (
|
|
77
|
+
next !== null
|
|
78
|
+
&& next.statics.blotName === this.statics.blotName
|
|
79
|
+
&& next.tableId === this.tableId
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
optimize(context: Record<string, any>) {
|
|
84
|
+
const parent = this.parent;
|
|
85
|
+
if (parent !== null && parent.statics.blotName !== blotName.tableMain) {
|
|
86
|
+
const { tableId } = this;
|
|
87
|
+
this.wrap(blotName.tableMain, { tableId });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
super.optimize(context);
|
|
91
|
+
}
|
|
92
|
+
}
|