@lofcz/platejs-table 52.0.11
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/LICENSE +24 -0
- package/README.md +11 -0
- package/dist/constants-B6Sm9BNa.js +2536 -0
- package/dist/constants-B6Sm9BNa.js.map +1 -0
- package/dist/index-CbSGuAlP.d.ts +638 -0
- package/dist/index-CbSGuAlP.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +157 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +879 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,2536 @@
|
|
|
1
|
+
import { ElementApi, KEYS, NodeApi, PathApi, PointApi, RangeApi, TextApi, bindFirst, combineTransformMatchOptions, createSlatePlugin, createTSlatePlugin, getEditorPlugin, getPluginTypes, isHotkey } from "platejs";
|
|
2
|
+
import cloneDeep from "lodash/cloneDeep.js";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/api/getEmptyCellNode.ts
|
|
5
|
+
const getEmptyCellNode = (editor, { children, header, row } = {}) => {
|
|
6
|
+
header = header ?? (row ? row.children.every((c) => c.type === editor.getType(KEYS.th)) : false);
|
|
7
|
+
return {
|
|
8
|
+
children: children ?? [editor.api.create.block()],
|
|
9
|
+
type: header ? editor.getType(KEYS.th) : editor.getType(KEYS.td)
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/lib/api/getEmptyRowNode.ts
|
|
15
|
+
const getEmptyRowNode = (editor, { colCount = 1, ...cellOptions } = {}) => {
|
|
16
|
+
const { api } = editor.getPlugin({ key: KEYS.table });
|
|
17
|
+
return {
|
|
18
|
+
children: Array.from({ length: colCount }).fill(colCount).map(() => api.create.tableCell(cellOptions)),
|
|
19
|
+
type: editor.getType(KEYS.tr)
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/lib/api/getEmptyTableNode.ts
|
|
25
|
+
const getEmptyTableNode = (editor, { colCount, header, rowCount = 0, ...cellOptions } = {}) => {
|
|
26
|
+
const { api } = editor.getPlugin({ key: KEYS.table });
|
|
27
|
+
return {
|
|
28
|
+
children: Array.from({ length: rowCount }).fill(rowCount).map((_, index) => api.create.tableRow({
|
|
29
|
+
colCount,
|
|
30
|
+
...cellOptions,
|
|
31
|
+
header: header && index === 0
|
|
32
|
+
})),
|
|
33
|
+
type: editor.getType(KEYS.table)
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/lib/queries/getCellInNextTableRow.ts
|
|
39
|
+
const getCellInNextTableRow = (editor, currentRowPath) => {
|
|
40
|
+
const nextRow = editor.api.node(PathApi.next(currentRowPath));
|
|
41
|
+
if (!nextRow) return;
|
|
42
|
+
const [nextRowNode, nextRowPath] = nextRow;
|
|
43
|
+
const nextCell = nextRowNode?.children?.[0];
|
|
44
|
+
const nextCellPath = nextRowPath.concat(0);
|
|
45
|
+
if (nextCell && nextCellPath) return editor.api.node(nextCellPath);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/lib/queries/getCellInPreviousTableRow.ts
|
|
50
|
+
const getCellInPreviousTableRow = (editor, currentRowPath) => {
|
|
51
|
+
const prevPath = PathApi.previous(currentRowPath);
|
|
52
|
+
if (!prevPath) return;
|
|
53
|
+
const previousRow = editor.api.node(prevPath);
|
|
54
|
+
if (!previousRow) return;
|
|
55
|
+
const [previousRowNode, previousRowPath] = previousRow;
|
|
56
|
+
const previousCell = previousRowNode?.children?.[previousRowNode.children.length - 1];
|
|
57
|
+
const previousCellPath = previousRowPath.concat(previousRowNode.children.length - 1);
|
|
58
|
+
if (previousCell && previousCellPath) return editor.api.node(previousCellPath);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/lib/queries/getColSpan.ts
|
|
63
|
+
/**
|
|
64
|
+
* Returns the colspan attribute of the table cell element.
|
|
65
|
+
*
|
|
66
|
+
* @default 1 if undefined.
|
|
67
|
+
*/
|
|
68
|
+
const getColSpan = (cellElem) => cellElem.colSpan || Number(cellElem.attributes?.colspan) || 1;
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/lib/utils/computeCellIndices.ts
|
|
72
|
+
function computeCellIndices(editor, { all, cellNode, tableNode }) {
|
|
73
|
+
const { api, getOptions, setOption } = getEditorPlugin(editor, { key: KEYS.table });
|
|
74
|
+
if (!tableNode) {
|
|
75
|
+
if (!cellNode) return;
|
|
76
|
+
tableNode = editor.api.above({
|
|
77
|
+
at: cellNode,
|
|
78
|
+
match: { type: editor.getType(KEYS.table) }
|
|
79
|
+
})?.[0];
|
|
80
|
+
if (!tableNode) return;
|
|
81
|
+
}
|
|
82
|
+
const { _cellIndices: prevIndices } = getOptions();
|
|
83
|
+
const cellIndices = { ...prevIndices };
|
|
84
|
+
let hasIndicesChanged = false;
|
|
85
|
+
const skipCells = [];
|
|
86
|
+
let targetIndices;
|
|
87
|
+
for (let rowIndex = 0; rowIndex < tableNode.children.length; rowIndex++) {
|
|
88
|
+
const row = tableNode.children[rowIndex];
|
|
89
|
+
let colIndex = 0;
|
|
90
|
+
for (const cellElement of row.children) {
|
|
91
|
+
while (skipCells[rowIndex]?.[colIndex]) colIndex++;
|
|
92
|
+
const currentIndices = {
|
|
93
|
+
col: colIndex,
|
|
94
|
+
row: rowIndex
|
|
95
|
+
};
|
|
96
|
+
const prevIndicesForCell = prevIndices[cellElement.id];
|
|
97
|
+
if (prevIndicesForCell?.col !== currentIndices.col || prevIndicesForCell?.row !== currentIndices.row) hasIndicesChanged = true;
|
|
98
|
+
cellIndices[cellElement.id] = currentIndices;
|
|
99
|
+
if (cellElement.id === cellNode?.id) {
|
|
100
|
+
targetIndices = currentIndices;
|
|
101
|
+
if (!all) break;
|
|
102
|
+
}
|
|
103
|
+
const colSpan = api.table.getColSpan(cellElement);
|
|
104
|
+
const rowSpan = api.table.getRowSpan(cellElement);
|
|
105
|
+
for (let r = 0; r < rowSpan; r++) {
|
|
106
|
+
skipCells[rowIndex + r] = skipCells[rowIndex + r] || [];
|
|
107
|
+
for (let c = 0; c < colSpan; c++) skipCells[rowIndex + r][colIndex + c] = true;
|
|
108
|
+
}
|
|
109
|
+
colIndex += colSpan;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (hasIndicesChanged) setOption("_cellIndices", cellIndices);
|
|
113
|
+
return targetIndices;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/lib/utils/getCellIndices.ts
|
|
118
|
+
const getCellIndices = (editor, element) => {
|
|
119
|
+
const { getOption } = getEditorPlugin(editor, { key: KEYS.table });
|
|
120
|
+
let indices = getOption("cellIndices", element.id);
|
|
121
|
+
if (!indices) {
|
|
122
|
+
indices = computeCellIndices(editor, { cellNode: element });
|
|
123
|
+
if (!indices) editor.api.debug.warn("No cell indices found for element. Make sure all table cells have an id.", "TABLE_CELL_INDICES");
|
|
124
|
+
}
|
|
125
|
+
return indices ?? {
|
|
126
|
+
col: 0,
|
|
127
|
+
row: 0
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/lib/utils/getCellRowIndexByPath.ts
|
|
133
|
+
const getCellRowIndexByPath = (cellPath) => {
|
|
134
|
+
const index = cellPath.at(-2);
|
|
135
|
+
if (index === void 0) throw new Error(`can not get rowIndex of path ${cellPath}`);
|
|
136
|
+
return index;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/lib/utils/getCellType.ts
|
|
141
|
+
/** Get td and th types */
|
|
142
|
+
const getCellTypes = (editor) => getPluginTypes(editor, [KEYS.td, KEYS.th]);
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/lib/queries/getLeftTableCell.ts
|
|
146
|
+
const getLeftTableCell = (editor, { at: cellPath } = {}) => {
|
|
147
|
+
if (!cellPath) {
|
|
148
|
+
cellPath = editor.api.node({ match: { type: getCellTypes(editor) } })?.[1];
|
|
149
|
+
if (!cellPath) return;
|
|
150
|
+
}
|
|
151
|
+
if (!cellPath.at(-1)) return;
|
|
152
|
+
const prevCellPath = PathApi.previous(cellPath);
|
|
153
|
+
return editor.api.node(prevCellPath);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/queries/getNextTableCell.ts
|
|
158
|
+
const getNextTableCell = (editor, _currentCell, currentPath, currentRow) => {
|
|
159
|
+
const cell = editor.api.node(PathApi.next(currentPath));
|
|
160
|
+
if (cell) return cell;
|
|
161
|
+
const [, currentRowPath] = currentRow;
|
|
162
|
+
return getCellInNextTableRow(editor, currentRowPath);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/lib/queries/getPreviousTableCell.ts
|
|
167
|
+
const getPreviousTableCell = (editor, _currentCell, currentPath, currentRow) => {
|
|
168
|
+
const prevPath = PathApi.previous(currentPath);
|
|
169
|
+
if (!prevPath) {
|
|
170
|
+
const [, currentRowPath] = currentRow;
|
|
171
|
+
return getCellInPreviousTableRow(editor, currentRowPath);
|
|
172
|
+
}
|
|
173
|
+
const cell = editor.api.node(prevPath);
|
|
174
|
+
if (cell) return cell;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/lib/queries/getRowSpan.ts
|
|
179
|
+
/**
|
|
180
|
+
* Returns the rowspan attribute of the table cell element.
|
|
181
|
+
*
|
|
182
|
+
* @default 1 if undefined
|
|
183
|
+
*/
|
|
184
|
+
const getRowSpan = (cellElem) => cellElem.rowSpan || Number(cellElem.attributes?.rowspan) || 1;
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/lib/queries/getSelectedCellsBoundingBox.ts
|
|
188
|
+
/** Return bounding box [minRow..maxRow, minCol..maxCol] of all selected cells. */
|
|
189
|
+
function getSelectedCellsBoundingBox(editor, cells) {
|
|
190
|
+
let minRow = Number.POSITIVE_INFINITY;
|
|
191
|
+
let maxRow = Number.NEGATIVE_INFINITY;
|
|
192
|
+
let minCol = Number.POSITIVE_INFINITY;
|
|
193
|
+
let maxCol = Number.NEGATIVE_INFINITY;
|
|
194
|
+
for (const cell of cells) {
|
|
195
|
+
const { col, row } = getCellIndices(editor, cell);
|
|
196
|
+
const cSpan = getColSpan(cell);
|
|
197
|
+
const endRow = row + getRowSpan(cell) - 1;
|
|
198
|
+
const endCol = col + cSpan - 1;
|
|
199
|
+
if (row < minRow) minRow = row;
|
|
200
|
+
if (endRow > maxRow) maxRow = endRow;
|
|
201
|
+
if (col < minCol) minCol = col;
|
|
202
|
+
if (endCol > maxCol) maxCol = endCol;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
maxCol,
|
|
206
|
+
maxRow,
|
|
207
|
+
minCol,
|
|
208
|
+
minRow
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/lib/queries/getTopTableCell.ts
|
|
214
|
+
const getTopTableCell = (editor, { at: cellPath } = {}) => {
|
|
215
|
+
if (!cellPath) {
|
|
216
|
+
cellPath = editor.api.node({ match: { type: getCellTypes(editor) } })?.[1];
|
|
217
|
+
if (!cellPath) return;
|
|
218
|
+
}
|
|
219
|
+
const cellIndex = cellPath.at(-1);
|
|
220
|
+
const rowIndex = cellPath.at(-2);
|
|
221
|
+
if (rowIndex === 0) return;
|
|
222
|
+
const cellAbovePath = [
|
|
223
|
+
...PathApi.parent(PathApi.parent(cellPath)),
|
|
224
|
+
rowIndex - 1,
|
|
225
|
+
cellIndex
|
|
226
|
+
];
|
|
227
|
+
return editor.api.node(cellAbovePath);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/lib/queries/getSelectedCellsBorders.ts
|
|
232
|
+
/**
|
|
233
|
+
* Get all border states for the selected cells at once. Returns an object with
|
|
234
|
+
* boolean flags for each border state:
|
|
235
|
+
*
|
|
236
|
+
* - Top/bottom/left/right: true if border is visible (size > 0)
|
|
237
|
+
* - Outer: true if all outer borders are visible
|
|
238
|
+
* - None: true if all borders are hidden (size === 0)
|
|
239
|
+
*/
|
|
240
|
+
const getSelectedCellsBorders = (editor, selectedCells, options = {}) => {
|
|
241
|
+
const { select = {
|
|
242
|
+
none: true,
|
|
243
|
+
outer: true,
|
|
244
|
+
side: true
|
|
245
|
+
} } = options;
|
|
246
|
+
let cells = selectedCells;
|
|
247
|
+
if (!cells || cells.length === 0) {
|
|
248
|
+
const cell = editor.api.block({ match: { type: getCellTypes(editor) } });
|
|
249
|
+
if (cell) cells = [cell[0]];
|
|
250
|
+
else return {
|
|
251
|
+
bottom: true,
|
|
252
|
+
left: true,
|
|
253
|
+
none: false,
|
|
254
|
+
outer: true,
|
|
255
|
+
right: true,
|
|
256
|
+
top: true
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const cellElements = cells.map((cell) => cell);
|
|
260
|
+
const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cellElements);
|
|
261
|
+
let hasAnyBorder = false;
|
|
262
|
+
let allOuterBordersSet = true;
|
|
263
|
+
const borderStates = {
|
|
264
|
+
bottom: false,
|
|
265
|
+
left: false,
|
|
266
|
+
right: false,
|
|
267
|
+
top: false
|
|
268
|
+
};
|
|
269
|
+
for (const cell of cellElements) {
|
|
270
|
+
const { col, row } = getCellIndices(editor, cell);
|
|
271
|
+
const cellPath = editor.api.findPath(cell);
|
|
272
|
+
const cSpan = getColSpan(cell);
|
|
273
|
+
const rSpan = getRowSpan(cell);
|
|
274
|
+
const isFirstRow = row === 0;
|
|
275
|
+
const isFirstCell = col === 0;
|
|
276
|
+
if (!cellPath) continue;
|
|
277
|
+
if (select.none && !hasAnyBorder) {
|
|
278
|
+
if (isFirstRow && (cell.borders?.top?.size ?? 1) > 0) hasAnyBorder = true;
|
|
279
|
+
if (isFirstCell && (cell.borders?.left?.size ?? 1) > 0) hasAnyBorder = true;
|
|
280
|
+
if ((cell.borders?.bottom?.size ?? 1) > 0) hasAnyBorder = true;
|
|
281
|
+
if ((cell.borders?.right?.size ?? 1) > 0) hasAnyBorder = true;
|
|
282
|
+
if (!hasAnyBorder) {
|
|
283
|
+
if (!isFirstRow) {
|
|
284
|
+
const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
|
|
285
|
+
if (cellAboveEntry && (cellAboveEntry[0].borders?.bottom?.size ?? 1) > 0) hasAnyBorder = true;
|
|
286
|
+
}
|
|
287
|
+
if (!isFirstCell) {
|
|
288
|
+
const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
|
|
289
|
+
if (prevCellEntry && (prevCellEntry[0].borders?.right?.size ?? 1) > 0) hasAnyBorder = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (select.side || select.outer) for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
|
|
294
|
+
if (rr === minRow) if (isFirstRow) {
|
|
295
|
+
if ((cell.borders?.top?.size ?? 1) < 1) {
|
|
296
|
+
borderStates.top = false;
|
|
297
|
+
if (select.outer) allOuterBordersSet = false;
|
|
298
|
+
} else if (!borderStates.top) borderStates.top = true;
|
|
299
|
+
} else {
|
|
300
|
+
const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
|
|
301
|
+
if (cellAboveEntry) {
|
|
302
|
+
const [cellAbove] = cellAboveEntry;
|
|
303
|
+
if ((cellAbove.borders?.bottom?.size ?? 1) < 1) {
|
|
304
|
+
borderStates.top = false;
|
|
305
|
+
if (select.outer) allOuterBordersSet = false;
|
|
306
|
+
} else if (!borderStates.top) borderStates.top = true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (rr === maxRow) {
|
|
310
|
+
if ((cell.borders?.bottom?.size ?? 1) < 1) {
|
|
311
|
+
borderStates.bottom = false;
|
|
312
|
+
if (select.outer) allOuterBordersSet = false;
|
|
313
|
+
} else if (!borderStates.bottom) borderStates.bottom = true;
|
|
314
|
+
}
|
|
315
|
+
if (cc === minCol) if (isFirstCell) {
|
|
316
|
+
if ((cell.borders?.left?.size ?? 1) < 1) {
|
|
317
|
+
borderStates.left = false;
|
|
318
|
+
if (select.outer) allOuterBordersSet = false;
|
|
319
|
+
} else if (!borderStates.left) borderStates.left = true;
|
|
320
|
+
} else {
|
|
321
|
+
const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
|
|
322
|
+
if (prevCellEntry) {
|
|
323
|
+
const [prevCell] = prevCellEntry;
|
|
324
|
+
if ((prevCell.borders?.right?.size ?? 1) < 1) {
|
|
325
|
+
borderStates.left = false;
|
|
326
|
+
if (select.outer) allOuterBordersSet = false;
|
|
327
|
+
} else if (!borderStates.left) borderStates.left = true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (cc === maxCol) {
|
|
331
|
+
if ((cell.borders?.right?.size ?? 1) < 1) {
|
|
332
|
+
borderStates.right = false;
|
|
333
|
+
if (select.outer) allOuterBordersSet = false;
|
|
334
|
+
} else if (!borderStates.right) borderStates.right = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
...select.side ? borderStates : {
|
|
340
|
+
bottom: true,
|
|
341
|
+
left: true,
|
|
342
|
+
right: true,
|
|
343
|
+
top: true
|
|
344
|
+
},
|
|
345
|
+
none: select.none ? !hasAnyBorder : false,
|
|
346
|
+
outer: select.outer ? allOuterBordersSet : true
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Tells if the entire selection is currently borderless (size=0 on all edges).
|
|
351
|
+
* If **any** edge is > 0, returns false.
|
|
352
|
+
*/
|
|
353
|
+
function isSelectedCellBordersNone(editor, cells) {
|
|
354
|
+
return cells.every((cell) => {
|
|
355
|
+
const { borders } = cell;
|
|
356
|
+
const { col, row } = getCellIndices(editor, cell);
|
|
357
|
+
const cellPath = editor.api.findPath(cell);
|
|
358
|
+
if (!cellPath) return true;
|
|
359
|
+
const isFirstRow = row === 0;
|
|
360
|
+
const isFirstCell = col === 0;
|
|
361
|
+
if (isFirstRow && (borders?.top?.size ?? 1) > 0) return false;
|
|
362
|
+
if (isFirstCell && (borders?.left?.size ?? 1) > 0) return false;
|
|
363
|
+
if ((borders?.bottom?.size ?? 1) > 0) return false;
|
|
364
|
+
if ((borders?.right?.size ?? 1) > 0) return false;
|
|
365
|
+
if (!isFirstRow) {
|
|
366
|
+
const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
|
|
367
|
+
if (cellAboveEntry) {
|
|
368
|
+
const [cellAbove] = cellAboveEntry;
|
|
369
|
+
if ((cellAbove.borders?.bottom?.size ?? 1) > 0) return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (!isFirstCell) {
|
|
373
|
+
const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
|
|
374
|
+
if (prevCellEntry) {
|
|
375
|
+
const [prevCell] = prevCellEntry;
|
|
376
|
+
if ((prevCell.borders?.right?.size ?? 1) > 0) return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Tells if the bounding rectangle for the entire selection is fully set for the
|
|
384
|
+
* **outer** edges, i.e. top/left/bottom/right edges have size=1. We ignore
|
|
385
|
+
* internal edges, only bounding rectangle edges.
|
|
386
|
+
*/
|
|
387
|
+
function isSelectedCellBordersOuter(editor, cells) {
|
|
388
|
+
const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cells);
|
|
389
|
+
for (const cell of cells) {
|
|
390
|
+
const { col, row } = getCellIndices(editor, cell);
|
|
391
|
+
const cSpan = getColSpan(cell);
|
|
392
|
+
const rSpan = getRowSpan(cell);
|
|
393
|
+
for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
|
|
394
|
+
if (rr === minRow && (cell.borders?.top?.size ?? 1) < 1) return false;
|
|
395
|
+
if (rr === maxRow && (cell.borders?.bottom?.size ?? 1) < 1) return false;
|
|
396
|
+
if (cc === minCol && (cell.borders?.left?.size ?? 1) < 1) return false;
|
|
397
|
+
if (cc === maxCol && (cell.borders?.right?.size ?? 1) < 1) return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Tells if the bounding rectangle for the entire selection is fully set for
|
|
404
|
+
* that single side. Example: border='top' => if every cell that sits along the
|
|
405
|
+
* top boundary has top=1.
|
|
406
|
+
*/
|
|
407
|
+
function isSelectedCellBorder(editor, cells, side) {
|
|
408
|
+
const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cells);
|
|
409
|
+
return cells.every((cell) => {
|
|
410
|
+
const { col, row } = getCellIndices(editor, cell);
|
|
411
|
+
const cSpan = getColSpan(cell);
|
|
412
|
+
const rSpan = getRowSpan(cell);
|
|
413
|
+
const cellPath = editor.api.findPath(cell);
|
|
414
|
+
if (!cellPath) return true;
|
|
415
|
+
for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
|
|
416
|
+
if (side === "top" && rr === minRow) {
|
|
417
|
+
if (row === 0) return (cell.borders?.top?.size ?? 1) >= 1;
|
|
418
|
+
const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
|
|
419
|
+
if (!cellAboveEntry) return true;
|
|
420
|
+
const [cellAboveNode] = cellAboveEntry;
|
|
421
|
+
return (cellAboveNode.borders?.bottom?.size ?? 1) >= 1;
|
|
422
|
+
}
|
|
423
|
+
if (side === "bottom" && rr === maxRow) return (cell.borders?.bottom?.size ?? 1) >= 1;
|
|
424
|
+
if (side === "left" && cc === minCol) {
|
|
425
|
+
if (col === 0) return (cell.borders?.left?.size ?? 1) >= 1;
|
|
426
|
+
const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
|
|
427
|
+
if (!prevCellEntry) return true;
|
|
428
|
+
const [prevCellNode] = prevCellEntry;
|
|
429
|
+
return (prevCellNode.borders?.right?.size ?? 1) >= 1;
|
|
430
|
+
}
|
|
431
|
+
if (side === "right" && cc === maxCol) return (cell.borders?.right?.size ?? 1) >= 1;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/lib/queries/getTableAbove.ts
|
|
439
|
+
const getTableAbove = (editor, options) => editor.api.block({
|
|
440
|
+
above: true,
|
|
441
|
+
match: { type: editor.getType(KEYS.table) },
|
|
442
|
+
...options
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/lib/queries/getTableCellBorders.ts
|
|
447
|
+
const getTableCellBorders = (editor, { cellIndices, defaultBorder = { size: 1 }, element }) => {
|
|
448
|
+
const cellPath = editor.api.findPath(element);
|
|
449
|
+
const [rowNode, rowPath] = editor.api.parent(cellPath) ?? [];
|
|
450
|
+
if (!rowNode || !rowPath) return {
|
|
451
|
+
bottom: defaultBorder,
|
|
452
|
+
right: defaultBorder
|
|
453
|
+
};
|
|
454
|
+
const [tableNode] = editor.api.parent(rowPath);
|
|
455
|
+
const { col } = cellIndices ?? getCellIndices(editor, element);
|
|
456
|
+
const isFirstCell = col === 0;
|
|
457
|
+
const isFirstRow = tableNode.children?.[0] === rowNode;
|
|
458
|
+
const getBorder = (dir) => {
|
|
459
|
+
const border = element.borders?.[dir];
|
|
460
|
+
return {
|
|
461
|
+
color: border?.color ?? defaultBorder.color,
|
|
462
|
+
size: border?.size ?? defaultBorder.size,
|
|
463
|
+
style: border?.style ?? defaultBorder.style
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
bottom: getBorder("bottom"),
|
|
468
|
+
left: isFirstCell ? getBorder("left") : void 0,
|
|
469
|
+
right: getBorder("right"),
|
|
470
|
+
top: isFirstRow ? getBorder("top") : void 0
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/lib/queries/getTableCellSize.ts
|
|
476
|
+
/** Get the width of a cell with colSpan support. */
|
|
477
|
+
const getTableCellSize = (editor, { cellIndices, colSizes, element, rowSize }) => {
|
|
478
|
+
const { api } = getEditorPlugin(editor, { key: KEYS.table });
|
|
479
|
+
const path = editor.api.findPath(element);
|
|
480
|
+
if (!rowSize) {
|
|
481
|
+
const [rowElement] = editor.api.parent(path) ?? [];
|
|
482
|
+
if (!rowElement) return {
|
|
483
|
+
minHeight: 0,
|
|
484
|
+
width: 0
|
|
485
|
+
};
|
|
486
|
+
rowSize = rowElement.size;
|
|
487
|
+
}
|
|
488
|
+
if (!colSizes) {
|
|
489
|
+
const [, rowPath] = editor.api.parent(path);
|
|
490
|
+
const [tableNode] = editor.api.parent(rowPath);
|
|
491
|
+
colSizes = getTableOverriddenColSizes(tableNode);
|
|
492
|
+
}
|
|
493
|
+
const colSpan = api.table.getColSpan(element);
|
|
494
|
+
const { col } = cellIndices ?? getCellIndices(editor, element);
|
|
495
|
+
const width = (colSizes ?? []).slice(col, col + colSpan).reduce((total, w) => total + (w || 0), 0);
|
|
496
|
+
return {
|
|
497
|
+
minHeight: rowSize,
|
|
498
|
+
width
|
|
499
|
+
};
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/lib/queries/getTableColumnCount.ts
|
|
504
|
+
const getTableColumnCount = (tableNode) => {
|
|
505
|
+
if (tableNode.children?.[0]?.children) return tableNode.children[0].children.map((element) => element.colSpan || (element?.attributes)?.colspan || 1).reduce((total, num) => Number(total) + Number(num));
|
|
506
|
+
return 0;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/lib/queries/getTableEntries.ts
|
|
511
|
+
/**
|
|
512
|
+
* If at (default = selection) is in table>tr>td|th, return table, row, and cell
|
|
513
|
+
* node entries.
|
|
514
|
+
*/
|
|
515
|
+
const getTableEntries = (editor, { at = editor.selection } = {}) => {
|
|
516
|
+
if (!at) return;
|
|
517
|
+
const cellEntry = editor.api.node({
|
|
518
|
+
at,
|
|
519
|
+
match: { type: getCellTypes(editor) }
|
|
520
|
+
});
|
|
521
|
+
if (!cellEntry) return;
|
|
522
|
+
const [, cellPath] = cellEntry;
|
|
523
|
+
const rowEntry = editor.api.above({
|
|
524
|
+
at: cellPath,
|
|
525
|
+
match: { type: editor.getType(KEYS.tr) }
|
|
526
|
+
});
|
|
527
|
+
if (!rowEntry) return;
|
|
528
|
+
const [, rowPath] = rowEntry;
|
|
529
|
+
const tableEntry = editor.api.above({
|
|
530
|
+
at: rowPath,
|
|
531
|
+
match: { type: editor.getType(KEYS.table) }
|
|
532
|
+
});
|
|
533
|
+
if (!tableEntry) return;
|
|
534
|
+
return {
|
|
535
|
+
cell: cellEntry,
|
|
536
|
+
row: rowEntry,
|
|
537
|
+
table: tableEntry
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/lib/merge/getCellIndicesWithSpans.ts
|
|
543
|
+
const getCellIndicesWithSpans = ({ col, row }, endCell) => ({
|
|
544
|
+
col: col + getColSpan(endCell) - 1,
|
|
545
|
+
row: row + getRowSpan(endCell) - 1
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/lib/merge/findCellByIndexes.ts
|
|
550
|
+
const findCellByIndexes = (editor, table, searchRowIndex, searchColIndex) => {
|
|
551
|
+
return table.children.flatMap((current) => current.children).find((cellNode) => {
|
|
552
|
+
const indices = getCellIndices(editor, cellNode);
|
|
553
|
+
const { col: _startColIndex, row: _startRowIndex } = indices;
|
|
554
|
+
const { col: _endColIndex, row: _endRowIndex } = getCellIndicesWithSpans(indices, cellNode);
|
|
555
|
+
if (searchColIndex >= _startColIndex && searchColIndex <= _endColIndex && searchRowIndex >= _startRowIndex && searchRowIndex <= _endRowIndex) return true;
|
|
556
|
+
return false;
|
|
557
|
+
});
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/lib/merge/getTableGridByRange.ts
|
|
562
|
+
/**
|
|
563
|
+
* Get sub table between 2 cell paths. Ensure that the selection is always a
|
|
564
|
+
* valid table grid.
|
|
565
|
+
*/
|
|
566
|
+
const getTableMergeGridByRange = (editor, { at, format }) => {
|
|
567
|
+
const { api, type } = getEditorPlugin(editor, BaseTablePlugin);
|
|
568
|
+
const startCellEntry = editor.api.node({
|
|
569
|
+
at: at.anchor.path,
|
|
570
|
+
match: { type: getCellTypes(editor) }
|
|
571
|
+
});
|
|
572
|
+
const endCellEntry = editor.api.node({
|
|
573
|
+
at: at.focus.path,
|
|
574
|
+
match: { type: getCellTypes(editor) }
|
|
575
|
+
});
|
|
576
|
+
const startCell = startCellEntry[0];
|
|
577
|
+
const endCell = endCellEntry[0];
|
|
578
|
+
const tablePath = at.anchor.path.slice(0, -2);
|
|
579
|
+
const realTable = editor.api.node({
|
|
580
|
+
at: tablePath,
|
|
581
|
+
match: { type }
|
|
582
|
+
})[0];
|
|
583
|
+
const { col: _startColIndex, row: _startRowIndex } = getCellIndicesWithSpans(getCellIndices(editor, startCell), startCell);
|
|
584
|
+
const { col: _endColIndex, row: _endRowIndex } = getCellIndicesWithSpans(getCellIndices(editor, endCell), endCell);
|
|
585
|
+
let startRowIndex = Math.min(_startRowIndex, _endRowIndex);
|
|
586
|
+
let endRowIndex = Math.max(_startRowIndex, _endRowIndex);
|
|
587
|
+
let startColIndex = Math.min(_startColIndex, _endColIndex);
|
|
588
|
+
let endColIndex = Math.max(_startColIndex, _endColIndex);
|
|
589
|
+
const relativeRowIndex = endRowIndex - startRowIndex;
|
|
590
|
+
const relativeColIndex = endColIndex - startColIndex;
|
|
591
|
+
let table = api.create.table({
|
|
592
|
+
children: [],
|
|
593
|
+
colCount: relativeColIndex + 1,
|
|
594
|
+
rowCount: relativeRowIndex + 1
|
|
595
|
+
});
|
|
596
|
+
let cellEntries = [];
|
|
597
|
+
let cellsSet = /* @__PURE__ */ new WeakSet();
|
|
598
|
+
let rowIndex = startRowIndex;
|
|
599
|
+
let colIndex = startColIndex;
|
|
600
|
+
while (true) {
|
|
601
|
+
const cell = findCellByIndexes(editor, realTable, rowIndex, colIndex);
|
|
602
|
+
if (!cell) break;
|
|
603
|
+
const indicies = getCellIndices(editor, cell);
|
|
604
|
+
const { col: cellColWithSpan, row: cellRowWithSpan } = getCellIndicesWithSpans(indicies, cell);
|
|
605
|
+
const { col: cellCol, row: cellRow } = indicies;
|
|
606
|
+
if (cellRow < startRowIndex || cellRowWithSpan > endRowIndex || cellCol < startColIndex || cellColWithSpan > endColIndex) {
|
|
607
|
+
cellsSet = /* @__PURE__ */ new WeakSet();
|
|
608
|
+
cellEntries = [];
|
|
609
|
+
startRowIndex = Math.min(startRowIndex, cellRow);
|
|
610
|
+
endRowIndex = Math.max(endRowIndex, cellRowWithSpan);
|
|
611
|
+
startColIndex = Math.min(startColIndex, cellCol);
|
|
612
|
+
endColIndex = Math.max(endColIndex, cellColWithSpan);
|
|
613
|
+
rowIndex = startRowIndex;
|
|
614
|
+
colIndex = startColIndex;
|
|
615
|
+
const newRelativeRowIndex = endRowIndex - startRowIndex;
|
|
616
|
+
const newRelativeColIndex = endColIndex - startColIndex;
|
|
617
|
+
table = api.create.table({
|
|
618
|
+
children: [],
|
|
619
|
+
colCount: newRelativeColIndex + 1,
|
|
620
|
+
rowCount: newRelativeRowIndex + 1
|
|
621
|
+
});
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (!cellsSet.has(cell)) {
|
|
625
|
+
cellsSet.add(cell);
|
|
626
|
+
const rows = table.children[rowIndex - startRowIndex].children;
|
|
627
|
+
rows[colIndex - startColIndex] = cell;
|
|
628
|
+
const cellPath = editor.api.findPath(cell);
|
|
629
|
+
cellEntries.push([cell, cellPath]);
|
|
630
|
+
}
|
|
631
|
+
if (colIndex + 1 <= endColIndex) colIndex += 1;
|
|
632
|
+
else if (rowIndex + 1 <= endRowIndex) {
|
|
633
|
+
colIndex = startColIndex;
|
|
634
|
+
rowIndex += 1;
|
|
635
|
+
} else break;
|
|
636
|
+
}
|
|
637
|
+
const formatType = format || "table";
|
|
638
|
+
if (formatType === "cell") return cellEntries;
|
|
639
|
+
table.children?.forEach((rowEl) => {
|
|
640
|
+
const rowElement = rowEl;
|
|
641
|
+
rowElement.children = rowElement.children?.filter((cellEl) => {
|
|
642
|
+
const cellElement = cellEl;
|
|
643
|
+
return api.table.getCellChildren(cellElement).length > 0;
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
if (formatType === "table") return [[table, tablePath]];
|
|
647
|
+
return {
|
|
648
|
+
cellEntries,
|
|
649
|
+
tableEntries: [[table, tablePath]]
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/lib/queries/getTableGridByRange.ts
|
|
655
|
+
/** Get sub table between 2 cell paths. */
|
|
656
|
+
const getTableGridByRange = (editor, { at, format = "table" }) => {
|
|
657
|
+
const { api } = editor.getPlugin({ key: KEYS.table });
|
|
658
|
+
const { disableMerge } = editor.getOptions(BaseTablePlugin);
|
|
659
|
+
if (!disableMerge) return getTableMergeGridByRange(editor, {
|
|
660
|
+
at,
|
|
661
|
+
format
|
|
662
|
+
});
|
|
663
|
+
const startCellPath = at.anchor.path;
|
|
664
|
+
const endCellPath = at.focus.path;
|
|
665
|
+
const _startRowIndex = startCellPath.at(-2);
|
|
666
|
+
const _endRowIndex = endCellPath.at(-2);
|
|
667
|
+
const _startColIndex = startCellPath.at(-1);
|
|
668
|
+
const _endColIndex = endCellPath.at(-1);
|
|
669
|
+
const startRowIndex = Math.min(_startRowIndex, _endRowIndex);
|
|
670
|
+
const endRowIndex = Math.max(_startRowIndex, _endRowIndex);
|
|
671
|
+
const startColIndex = Math.min(_startColIndex, _endColIndex);
|
|
672
|
+
const endColIndex = Math.max(_startColIndex, _endColIndex);
|
|
673
|
+
const tablePath = startCellPath.slice(0, -2);
|
|
674
|
+
const relativeRowIndex = endRowIndex - startRowIndex;
|
|
675
|
+
const relativeColIndex = endColIndex - startColIndex;
|
|
676
|
+
const table = api.create.table({
|
|
677
|
+
children: [],
|
|
678
|
+
colCount: relativeColIndex + 1,
|
|
679
|
+
rowCount: relativeRowIndex + 1
|
|
680
|
+
});
|
|
681
|
+
let rowIndex = startRowIndex;
|
|
682
|
+
let colIndex = startColIndex;
|
|
683
|
+
const cellEntries = [];
|
|
684
|
+
while (true) {
|
|
685
|
+
const cellPath = tablePath.concat([rowIndex, colIndex]);
|
|
686
|
+
const cell = NodeApi.get(editor, cellPath);
|
|
687
|
+
if (!cell) break;
|
|
688
|
+
const rows = table.children[rowIndex - startRowIndex].children;
|
|
689
|
+
rows[colIndex - startColIndex] = cell;
|
|
690
|
+
cellEntries.push([cell, cellPath]);
|
|
691
|
+
if (colIndex + 1 <= endColIndex) colIndex += 1;
|
|
692
|
+
else if (rowIndex + 1 <= endRowIndex) {
|
|
693
|
+
colIndex = startColIndex;
|
|
694
|
+
rowIndex += 1;
|
|
695
|
+
} else break;
|
|
696
|
+
}
|
|
697
|
+
if (format === "cell") return cellEntries;
|
|
698
|
+
return [[table, tablePath]];
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/lib/queries/getTableGridAbove.ts
|
|
703
|
+
/** Get sub table above anchor and focus. Format: tables or cells. */
|
|
704
|
+
const getTableGridAbove = (editor, { format = "table", ...options } = {}) => {
|
|
705
|
+
const { api } = editor.getPlugin({ key: KEYS.table });
|
|
706
|
+
const edges = editor.api.edgeBlocks({
|
|
707
|
+
match: { type: getCellTypes(editor) },
|
|
708
|
+
...options
|
|
709
|
+
});
|
|
710
|
+
if (edges) {
|
|
711
|
+
const [start, end] = edges;
|
|
712
|
+
if (!PathApi.equals(start[1], end[1])) return getTableGridByRange(editor, {
|
|
713
|
+
at: {
|
|
714
|
+
anchor: {
|
|
715
|
+
offset: 0,
|
|
716
|
+
path: start[1]
|
|
717
|
+
},
|
|
718
|
+
focus: {
|
|
719
|
+
offset: 0,
|
|
720
|
+
path: end[1]
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
format
|
|
724
|
+
});
|
|
725
|
+
if (format === "table") {
|
|
726
|
+
const table = api.create.table({ rowCount: 1 });
|
|
727
|
+
table.children[0].children = [start[0]];
|
|
728
|
+
return [[table, start[1].slice(0, -2)]];
|
|
729
|
+
}
|
|
730
|
+
return [start];
|
|
731
|
+
}
|
|
732
|
+
return [];
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
//#endregion
|
|
736
|
+
//#region src/lib/queries/getTableOverriddenColSizes.ts
|
|
737
|
+
/**
|
|
738
|
+
* Returns node.colSizes if it exists, applying overrides, otherwise returns a
|
|
739
|
+
* 0-filled array.
|
|
740
|
+
*/
|
|
741
|
+
const getTableOverriddenColSizes = (tableNode, colSizeOverrides) => {
|
|
742
|
+
const colCount = getTableColumnCount(tableNode);
|
|
743
|
+
return (tableNode.colSizes ? [...tableNode.colSizes] : Array.from({ length: colCount }).fill(0)).map((size, index) => colSizeOverrides?.get?.(index) ?? size);
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
//#endregion
|
|
747
|
+
//#region src/lib/merge/deleteColumnWhenExpanded.ts
|
|
748
|
+
const deleteColumnWhenExpanded = (editor, tableEntry) => {
|
|
749
|
+
const [start, end] = RangeApi.edges(editor.selection);
|
|
750
|
+
const firstRow = NodeApi.child(tableEntry[0], 0);
|
|
751
|
+
const lastRow = NodeApi.child(tableEntry[0], tableEntry[0].children.length - 1);
|
|
752
|
+
const firstSelectionRow = editor.api.above({
|
|
753
|
+
at: start,
|
|
754
|
+
match: (n) => n.type === KEYS.tr
|
|
755
|
+
});
|
|
756
|
+
const lastSelectionRow = editor.api.above({
|
|
757
|
+
at: end,
|
|
758
|
+
match: (n) => n.type === KEYS.tr
|
|
759
|
+
});
|
|
760
|
+
if (!firstSelectionRow || !lastSelectionRow) return;
|
|
761
|
+
if (firstRow.id === firstSelectionRow[0].id && lastSelectionRow[0].id === lastRow.id) deleteSelection(editor);
|
|
762
|
+
};
|
|
763
|
+
const deleteSelection = (editor) => {
|
|
764
|
+
const cells = getTableGridAbove(editor, { format: "cell" });
|
|
765
|
+
const pathRefs = [];
|
|
766
|
+
cells.forEach(([_cell, cellPath]) => {
|
|
767
|
+
pathRefs.push(editor.api.pathRef(cellPath));
|
|
768
|
+
});
|
|
769
|
+
pathRefs.forEach((pathRef) => {
|
|
770
|
+
editor.tf.removeNodes({ at: pathRef.unref() });
|
|
771
|
+
});
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/lib/merge/getCellPath.ts
|
|
776
|
+
const getCellPath = (editor, tableEntry, curRowIndex, curColIndex) => {
|
|
777
|
+
const [tableNode, tablePath] = tableEntry;
|
|
778
|
+
const foundColIndex = tableNode.children[curRowIndex].children.findIndex((c) => {
|
|
779
|
+
const { col: colIndex } = getCellIndices(editor, c);
|
|
780
|
+
return colIndex === curColIndex;
|
|
781
|
+
});
|
|
782
|
+
return tablePath.concat([curRowIndex, foundColIndex]);
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
//#endregion
|
|
786
|
+
//#region src/lib/merge/deleteColumn.ts
|
|
787
|
+
const deleteTableMergeColumn = (editor) => {
|
|
788
|
+
const type = editor.getType(KEYS.table);
|
|
789
|
+
const tableEntry = editor.api.above({ match: { type } });
|
|
790
|
+
if (!tableEntry) return;
|
|
791
|
+
editor.tf.withoutNormalizing(() => {
|
|
792
|
+
const { api } = getEditorPlugin(editor, BaseTablePlugin);
|
|
793
|
+
if (editor.api.isExpanded()) return deleteColumnWhenExpanded(editor, tableEntry);
|
|
794
|
+
const table = tableEntry[0];
|
|
795
|
+
const selectedCellEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
|
|
796
|
+
if (!selectedCellEntry) return;
|
|
797
|
+
const selectedCell = selectedCellEntry[0];
|
|
798
|
+
const { col: deletingColIndex } = getCellIndices(editor, selectedCell);
|
|
799
|
+
const colsDeleteNumber = api.table.getColSpan(selectedCell);
|
|
800
|
+
const endingColIndex = deletingColIndex + colsDeleteNumber - 1;
|
|
801
|
+
const rowNumber = table.children.length;
|
|
802
|
+
const affectedCellsSet = /* @__PURE__ */ new Set();
|
|
803
|
+
for (const rI of Array.from({ length: rowNumber }, (_, i) => i)) for (const cI of Array.from({ length: colsDeleteNumber }, (_, i) => i)) {
|
|
804
|
+
const found = findCellByIndexes(editor, table, rI, deletingColIndex + cI);
|
|
805
|
+
if (found) affectedCellsSet.add(found);
|
|
806
|
+
}
|
|
807
|
+
const affectedCells = Array.from(affectedCellsSet);
|
|
808
|
+
const { squizeColSpanCells } = affectedCells.reduce((acc, cur) => {
|
|
809
|
+
if (!cur) return acc;
|
|
810
|
+
const currentCell = cur;
|
|
811
|
+
const { col: curColIndex } = getCellIndices(editor, currentCell);
|
|
812
|
+
const curColSpan = api.table.getColSpan(currentCell);
|
|
813
|
+
if (curColIndex < deletingColIndex && curColSpan > 1) acc.squizeColSpanCells.push(currentCell);
|
|
814
|
+
else if (curColSpan > 1 && curColIndex + curColSpan - 1 > endingColIndex) acc.squizeColSpanCells.push(currentCell);
|
|
815
|
+
return acc;
|
|
816
|
+
}, { squizeColSpanCells: [] });
|
|
817
|
+
/** Change colSpans */
|
|
818
|
+
squizeColSpanCells.forEach((cur) => {
|
|
819
|
+
const curCell = cur;
|
|
820
|
+
const { col: curColIndex, row: curColRowIndex } = getCellIndices(editor, curCell);
|
|
821
|
+
const curColSpan = api.table.getColSpan(curCell);
|
|
822
|
+
const curCellPath = getCellPath(editor, tableEntry, curColRowIndex, curColIndex);
|
|
823
|
+
const colSpan = curColSpan - (Math.min(curColIndex + curColSpan - 1, endingColIndex) - deletingColIndex + 1);
|
|
824
|
+
const newCell = cloneDeep({
|
|
825
|
+
...curCell,
|
|
826
|
+
colSpan
|
|
827
|
+
});
|
|
828
|
+
if (newCell.attributes?.colspan) newCell.attributes.colspan = colSpan.toString();
|
|
829
|
+
editor.tf.setNodes(newCell, { at: curCellPath });
|
|
830
|
+
});
|
|
831
|
+
const trEntry = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
|
|
832
|
+
/** Remove cells */
|
|
833
|
+
if (selectedCell && trEntry && tableEntry && trEntry[0].children.length > 1) {
|
|
834
|
+
const [tableNode, tablePath] = tableEntry;
|
|
835
|
+
const paths = [];
|
|
836
|
+
affectedCells.forEach((cur) => {
|
|
837
|
+
const curCell = cur;
|
|
838
|
+
const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
|
|
839
|
+
if (!squizeColSpanCells.includes(curCell) && curColIndex >= deletingColIndex && curColIndex <= endingColIndex) {
|
|
840
|
+
const cellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
|
|
841
|
+
if (!paths[curRowIndex]) paths[curRowIndex] = [];
|
|
842
|
+
paths[curRowIndex].push(cellPath);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
paths.forEach((cellPaths) => {
|
|
846
|
+
const pathToDelete = cellPaths[0];
|
|
847
|
+
cellPaths.forEach(() => {
|
|
848
|
+
editor.tf.removeNodes({ at: pathToDelete });
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
const { colSizes } = tableNode;
|
|
852
|
+
if (colSizes) {
|
|
853
|
+
const newColSizes = [...colSizes];
|
|
854
|
+
newColSizes.splice(deletingColIndex, 1);
|
|
855
|
+
editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/lib/merge/deleteRowWhenExpanded.ts
|
|
863
|
+
const deleteRowWhenExpanded = (editor, [table, tablePath]) => {
|
|
864
|
+
const { api } = getEditorPlugin(editor, BaseTablePlugin);
|
|
865
|
+
const columnCount = getTableMergedColumnCount(table);
|
|
866
|
+
const cells = getTableGridAbove(editor, { format: "cell" });
|
|
867
|
+
const firsRowIndex = getCellRowIndexByPath(cells[0][1]);
|
|
868
|
+
if (firsRowIndex === null) return;
|
|
869
|
+
let acrossColumn = 0;
|
|
870
|
+
let lastRowIndex = -1;
|
|
871
|
+
let rowSpanCarry = 0;
|
|
872
|
+
let acrossRow = 0;
|
|
873
|
+
cells.forEach(([cell, cellPath]) => {
|
|
874
|
+
if (cellPath.at(-2) === firsRowIndex) acrossColumn += cell.colSpan ?? 1;
|
|
875
|
+
const currentRowIndex = getCellRowIndexByPath(cellPath);
|
|
876
|
+
if (lastRowIndex !== currentRowIndex) {
|
|
877
|
+
if (rowSpanCarry !== 0) {
|
|
878
|
+
rowSpanCarry--;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const rowSpan = api.table.getRowSpan(cell);
|
|
882
|
+
rowSpanCarry = rowSpan && rowSpan > 1 ? rowSpan - 1 : 0;
|
|
883
|
+
acrossRow += rowSpan ?? 1;
|
|
884
|
+
}
|
|
885
|
+
lastRowIndex = currentRowIndex;
|
|
886
|
+
});
|
|
887
|
+
if (acrossColumn === columnCount) {
|
|
888
|
+
const pathRefs = [];
|
|
889
|
+
for (let i = firsRowIndex; i < firsRowIndex + acrossRow; i++) {
|
|
890
|
+
const removedPath = tablePath.concat(i);
|
|
891
|
+
pathRefs.push(editor.api.pathRef(removedPath));
|
|
892
|
+
}
|
|
893
|
+
pathRefs.forEach((item) => {
|
|
894
|
+
editor.tf.removeNodes({ at: item.unref() });
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region src/lib/merge/deleteRow.ts
|
|
901
|
+
const deleteTableMergeRow = (editor) => {
|
|
902
|
+
const { api, tf, type } = getEditorPlugin(editor, { key: KEYS.table });
|
|
903
|
+
if (editor.api.some({ match: { type } })) {
|
|
904
|
+
const currentTableItem = editor.api.above({ match: { type } });
|
|
905
|
+
if (!currentTableItem) return;
|
|
906
|
+
if (editor.api.isExpanded()) return deleteRowWhenExpanded(editor, currentTableItem);
|
|
907
|
+
const table = currentTableItem[0];
|
|
908
|
+
const selectedCellEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
|
|
909
|
+
if (!selectedCellEntry) return;
|
|
910
|
+
const selectedCell = selectedCellEntry[0];
|
|
911
|
+
const { row: deletingRowIndex } = getCellIndices(editor, selectedCell);
|
|
912
|
+
const rowsDeleteNumber = api.table.getRowSpan(selectedCell);
|
|
913
|
+
const endingRowIndex = deletingRowIndex + rowsDeleteNumber - 1;
|
|
914
|
+
const colNumber = getTableColumnCount(table);
|
|
915
|
+
const affectedCellsSet = /* @__PURE__ */ new Set();
|
|
916
|
+
for (const cI of Array.from({ length: colNumber }, (_, i) => i)) for (const rI of Array.from({ length: rowsDeleteNumber }, (_, i) => i)) {
|
|
917
|
+
const found = findCellByIndexes(editor, table, deletingRowIndex + rI, cI);
|
|
918
|
+
affectedCellsSet.add(found);
|
|
919
|
+
}
|
|
920
|
+
const { moveToNextRowCells, squizeRowSpanCells } = Array.from(affectedCellsSet).reduce((acc, cur) => {
|
|
921
|
+
if (!cur) return acc;
|
|
922
|
+
const currentCell = cur;
|
|
923
|
+
const { row: curRowIndex } = getCellIndices(editor, currentCell);
|
|
924
|
+
const curRowSpan = api.table.getRowSpan(currentCell);
|
|
925
|
+
if (curRowIndex < deletingRowIndex && curRowSpan > 1) acc.squizeRowSpanCells.push(currentCell);
|
|
926
|
+
else if (curRowSpan > 1 && curRowIndex + curRowSpan - 1 > endingRowIndex) acc.moveToNextRowCells.push(currentCell);
|
|
927
|
+
return acc;
|
|
928
|
+
}, {
|
|
929
|
+
moveToNextRowCells: [],
|
|
930
|
+
squizeRowSpanCells: []
|
|
931
|
+
});
|
|
932
|
+
const nextRowIndex = deletingRowIndex + rowsDeleteNumber;
|
|
933
|
+
const nextRow = table.children[nextRowIndex];
|
|
934
|
+
if (nextRow === void 0 && deletingRowIndex === 0) {
|
|
935
|
+
tf.remove.table();
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (nextRow) for (let index = 0; index < moveToNextRowCells.length; index++) {
|
|
939
|
+
const curRowCell = moveToNextRowCells[index];
|
|
940
|
+
const { col: curRowCellColIndex, row: curRowCellRowIndex } = getCellIndices(editor, curRowCell);
|
|
941
|
+
const curRowCellRowSpan = api.table.getRowSpan(curRowCell);
|
|
942
|
+
const startingCellIndex = nextRow.children.findIndex((curC) => {
|
|
943
|
+
const { col: curColIndex } = getCellIndices(editor, curC);
|
|
944
|
+
return curColIndex >= curRowCellColIndex;
|
|
945
|
+
});
|
|
946
|
+
if (startingCellIndex === -1) {
|
|
947
|
+
const startingCell$1 = nextRow.children.at(-1);
|
|
948
|
+
const startingCellPath$1 = editor.api.findPath(startingCell$1);
|
|
949
|
+
const tablePath$1 = startingCellPath$1.slice(0, -2);
|
|
950
|
+
const colPath$1 = startingCellPath$1.at(-1) + index + 1;
|
|
951
|
+
const nextRowStartCellPath$1 = [
|
|
952
|
+
...tablePath$1,
|
|
953
|
+
nextRowIndex,
|
|
954
|
+
colPath$1
|
|
955
|
+
];
|
|
956
|
+
const rowSpan$1 = curRowCellRowSpan - (endingRowIndex - curRowCellRowIndex + 1);
|
|
957
|
+
const newCell$1 = cloneDeep({
|
|
958
|
+
...curRowCell,
|
|
959
|
+
rowSpan: rowSpan$1
|
|
960
|
+
});
|
|
961
|
+
if (newCell$1.attributes?.rowspan) newCell$1.attributes.rowspan = rowSpan$1.toString();
|
|
962
|
+
editor.tf.insertNodes(newCell$1, { at: nextRowStartCellPath$1 });
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const startingCell = nextRow.children[startingCellIndex];
|
|
966
|
+
const { col: startingColIndex } = getCellIndices(editor, startingCell);
|
|
967
|
+
let incrementBy = index;
|
|
968
|
+
if (startingColIndex < curRowCellColIndex) incrementBy += 1;
|
|
969
|
+
const startingCellPath = editor.api.findPath(startingCell);
|
|
970
|
+
const tablePath = startingCellPath.slice(0, -2);
|
|
971
|
+
const colPath = startingCellPath.at(-1);
|
|
972
|
+
const nextRowStartCellPath = [
|
|
973
|
+
...tablePath,
|
|
974
|
+
nextRowIndex,
|
|
975
|
+
colPath + incrementBy
|
|
976
|
+
];
|
|
977
|
+
const rowSpan = curRowCellRowSpan - (endingRowIndex - curRowCellRowIndex + 1);
|
|
978
|
+
const newCell = cloneDeep({
|
|
979
|
+
...curRowCell,
|
|
980
|
+
rowSpan
|
|
981
|
+
});
|
|
982
|
+
if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
|
|
983
|
+
editor.tf.insertNodes(newCell, { at: nextRowStartCellPath });
|
|
984
|
+
}
|
|
985
|
+
squizeRowSpanCells.forEach((cur) => {
|
|
986
|
+
const curRowCell = cur;
|
|
987
|
+
const { row: curRowCellRowIndex } = getCellIndices(editor, curRowCell);
|
|
988
|
+
const curRowCellRowSpan = api.table.getRowSpan(curRowCell);
|
|
989
|
+
const curCellPath = editor.api.findPath(curRowCell);
|
|
990
|
+
const rowSpan = curRowCellRowSpan - (Math.min(curRowCellRowIndex + curRowCellRowSpan - 1, endingRowIndex) - deletingRowIndex + 1);
|
|
991
|
+
const newCell = cloneDeep({
|
|
992
|
+
...curRowCell,
|
|
993
|
+
rowSpan
|
|
994
|
+
});
|
|
995
|
+
if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
|
|
996
|
+
editor.tf.setNodes(newCell, { at: curCellPath });
|
|
997
|
+
});
|
|
998
|
+
const rowToDelete = table.children[deletingRowIndex];
|
|
999
|
+
const rowPath = editor.api.findPath(rowToDelete);
|
|
1000
|
+
Array.from({ length: rowsDeleteNumber }).forEach(() => {
|
|
1001
|
+
editor.tf.removeNodes({ at: rowPath });
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
//#endregion
|
|
1007
|
+
//#region src/lib/merge/getTableMergedColumnCount.ts
|
|
1008
|
+
const getTableMergedColumnCount = (tableNode) => tableNode.children?.[0]?.children?.reduce((prev, cur) => prev + (getColSpan(cur) ?? 1), 0);
|
|
1009
|
+
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/lib/merge/insertTableColumn.ts
|
|
1012
|
+
const insertTableMergeColumn = (editor, { at, before, fromCell, header, select: shouldSelect } = {}) => {
|
|
1013
|
+
const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1014
|
+
const { initialTableWidth, minColumnWidth } = getOptions();
|
|
1015
|
+
if (at && !fromCell) {
|
|
1016
|
+
if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
|
|
1017
|
+
fromCell = NodeApi.lastChild(editor, at.concat([0]))[1];
|
|
1018
|
+
at = void 0;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const cellEntry = fromCell ? editor.api.node({
|
|
1022
|
+
at: fromCell,
|
|
1023
|
+
match: { type: getCellTypes(editor) }
|
|
1024
|
+
}) : editor.api.block({ match: { type: getCellTypes(editor) } });
|
|
1025
|
+
if (!cellEntry) return;
|
|
1026
|
+
const [, cellPath] = cellEntry;
|
|
1027
|
+
const cell = cellEntry[0];
|
|
1028
|
+
const tableEntry = editor.api.block({
|
|
1029
|
+
above: true,
|
|
1030
|
+
at: cellPath,
|
|
1031
|
+
match: { type }
|
|
1032
|
+
});
|
|
1033
|
+
if (!tableEntry) return;
|
|
1034
|
+
const [tableNode, tablePath] = tableEntry;
|
|
1035
|
+
const { col: cellColIndex } = getCellIndices(editor, cell);
|
|
1036
|
+
const cellColSpan = api.table.getColSpan(cell);
|
|
1037
|
+
let nextColIndex;
|
|
1038
|
+
let checkingColIndex;
|
|
1039
|
+
if (PathApi.isPath(at)) {
|
|
1040
|
+
nextColIndex = cellColIndex;
|
|
1041
|
+
checkingColIndex = cellColIndex - 1;
|
|
1042
|
+
} else {
|
|
1043
|
+
nextColIndex = before ? cellColIndex : cellColIndex + cellColSpan;
|
|
1044
|
+
checkingColIndex = before ? cellColIndex : cellColIndex + cellColSpan - 1;
|
|
1045
|
+
}
|
|
1046
|
+
const rowNumber = tableNode.children.length;
|
|
1047
|
+
const firstCol = nextColIndex <= 0;
|
|
1048
|
+
let placementCorrection = before ? 0 : 1;
|
|
1049
|
+
if (firstCol) {
|
|
1050
|
+
checkingColIndex = 0;
|
|
1051
|
+
placementCorrection = 0;
|
|
1052
|
+
}
|
|
1053
|
+
const affectedCellsSet = /* @__PURE__ */ new Set();
|
|
1054
|
+
Array.from({ length: rowNumber }, (_, i) => i).forEach((rI) => {
|
|
1055
|
+
const found = findCellByIndexes(editor, tableNode, rI, checkingColIndex);
|
|
1056
|
+
if (found) affectedCellsSet.add(found);
|
|
1057
|
+
});
|
|
1058
|
+
Array.from(affectedCellsSet).forEach((curCell) => {
|
|
1059
|
+
const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
|
|
1060
|
+
const curRowSpan = api.table.getRowSpan(curCell);
|
|
1061
|
+
const curColSpan = api.table.getColSpan(curCell);
|
|
1062
|
+
const currentCellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
|
|
1063
|
+
if (curColIndex + curColSpan - 1 >= nextColIndex && !firstCol && !before) {
|
|
1064
|
+
const colSpan = curColSpan + 1;
|
|
1065
|
+
const newCell = cloneDeep({
|
|
1066
|
+
...curCell,
|
|
1067
|
+
colSpan
|
|
1068
|
+
});
|
|
1069
|
+
if (newCell.attributes?.colspan) newCell.attributes.colspan = colSpan.toString();
|
|
1070
|
+
editor.tf.setNodes(newCell, { at: currentCellPath });
|
|
1071
|
+
} else {
|
|
1072
|
+
const curRowPath = currentCellPath.slice(0, -1);
|
|
1073
|
+
const curColPath = currentCellPath.at(-1);
|
|
1074
|
+
const placementPath = [...curRowPath, before ? curColPath : curColPath + placementCorrection];
|
|
1075
|
+
const rowElement = editor.api.parent(currentCellPath)[0];
|
|
1076
|
+
const emptyCell = {
|
|
1077
|
+
...api.create.tableCell({
|
|
1078
|
+
header,
|
|
1079
|
+
row: rowElement
|
|
1080
|
+
}),
|
|
1081
|
+
colSpan: 1,
|
|
1082
|
+
rowSpan: curRowSpan
|
|
1083
|
+
};
|
|
1084
|
+
editor.tf.insertNodes(emptyCell, {
|
|
1085
|
+
at: placementPath,
|
|
1086
|
+
select: shouldSelect
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
editor.tf.withoutNormalizing(() => {
|
|
1091
|
+
const { colSizes } = tableNode;
|
|
1092
|
+
if (colSizes) {
|
|
1093
|
+
let newColSizes = [
|
|
1094
|
+
...colSizes.slice(0, nextColIndex),
|
|
1095
|
+
0,
|
|
1096
|
+
...colSizes.slice(nextColIndex)
|
|
1097
|
+
];
|
|
1098
|
+
if (initialTableWidth) {
|
|
1099
|
+
newColSizes[nextColIndex] = colSizes[nextColIndex] ?? colSizes[nextColIndex - 1] ?? initialTableWidth / colSizes.length;
|
|
1100
|
+
const oldTotal = colSizes.reduce((a, b) => a + b, 0);
|
|
1101
|
+
const newTotal = newColSizes.reduce((a, b) => a + b, 0);
|
|
1102
|
+
const maxTotal = Math.max(oldTotal, initialTableWidth);
|
|
1103
|
+
if (newTotal > maxTotal) {
|
|
1104
|
+
const factor = maxTotal / newTotal;
|
|
1105
|
+
newColSizes = newColSizes.map((size) => Math.max(minColumnWidth ?? 0, Math.floor(size * factor)));
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/lib/merge/insertTableRow.ts
|
|
1115
|
+
const insertTableMergeRow = (editor, { at, before, fromRow, header, select: shouldSelect } = {}) => {
|
|
1116
|
+
const { api, type } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1117
|
+
if (at && !fromRow) {
|
|
1118
|
+
if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
|
|
1119
|
+
fromRow = NodeApi.lastChild(editor, at)[1];
|
|
1120
|
+
at = void 0;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const trEntry = editor.api.block({
|
|
1124
|
+
at: fromRow,
|
|
1125
|
+
match: { type: editor.getType(KEYS.tr) }
|
|
1126
|
+
});
|
|
1127
|
+
if (!trEntry) return;
|
|
1128
|
+
const [, trPath] = trEntry;
|
|
1129
|
+
const tableEntry = editor.api.block({
|
|
1130
|
+
above: true,
|
|
1131
|
+
at: trPath,
|
|
1132
|
+
match: { type }
|
|
1133
|
+
});
|
|
1134
|
+
if (!tableEntry) return;
|
|
1135
|
+
const tableNode = tableEntry[0];
|
|
1136
|
+
const cellEntry = editor.api.node({
|
|
1137
|
+
at: fromRow,
|
|
1138
|
+
match: { type: getCellTypes(editor) }
|
|
1139
|
+
});
|
|
1140
|
+
if (!cellEntry) return;
|
|
1141
|
+
const [cellNode, cellPath] = cellEntry;
|
|
1142
|
+
const cellElement = cellNode;
|
|
1143
|
+
const cellRowSpan = api.table.getRowSpan(cellElement);
|
|
1144
|
+
const { row: cellRowIndex } = getCellIndices(editor, cellElement);
|
|
1145
|
+
const rowPath = cellPath.at(-2);
|
|
1146
|
+
const tablePath = cellPath.slice(0, -2);
|
|
1147
|
+
let nextRowIndex;
|
|
1148
|
+
let checkingRowIndex;
|
|
1149
|
+
let nextRowPath;
|
|
1150
|
+
if (PathApi.isPath(at)) {
|
|
1151
|
+
nextRowIndex = at.at(-1);
|
|
1152
|
+
checkingRowIndex = cellRowIndex - 1;
|
|
1153
|
+
nextRowPath = at;
|
|
1154
|
+
} else {
|
|
1155
|
+
nextRowIndex = before ? cellRowIndex : cellRowIndex + cellRowSpan;
|
|
1156
|
+
checkingRowIndex = before ? cellRowIndex - 1 : cellRowIndex + cellRowSpan - 1;
|
|
1157
|
+
nextRowPath = [...tablePath, before ? rowPath : rowPath + cellRowSpan];
|
|
1158
|
+
}
|
|
1159
|
+
const firstRow = nextRowIndex === 0;
|
|
1160
|
+
if (firstRow) checkingRowIndex = 0;
|
|
1161
|
+
const colCount = getTableColumnCount(tableNode);
|
|
1162
|
+
const affectedCellsSet = /* @__PURE__ */ new Set();
|
|
1163
|
+
Array.from({ length: colCount }, (_, i) => i).forEach((cI) => {
|
|
1164
|
+
const found = findCellByIndexes(editor, tableNode, checkingRowIndex, cI);
|
|
1165
|
+
if (found) affectedCellsSet.add(found);
|
|
1166
|
+
});
|
|
1167
|
+
const affectedCells = Array.from(affectedCellsSet);
|
|
1168
|
+
const newRowChildren = [];
|
|
1169
|
+
affectedCells.forEach((cur) => {
|
|
1170
|
+
if (!cur) return;
|
|
1171
|
+
const curCell = cur;
|
|
1172
|
+
const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
|
|
1173
|
+
const curRowSpan = api.table.getRowSpan(curCell);
|
|
1174
|
+
const curColSpan = api.table.getColSpan(curCell);
|
|
1175
|
+
const currentCellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
|
|
1176
|
+
if (curRowIndex + curRowSpan - 1 >= nextRowIndex && !firstRow) {
|
|
1177
|
+
const rowSpan = curRowSpan + 1;
|
|
1178
|
+
const newCell = cloneDeep({
|
|
1179
|
+
...curCell,
|
|
1180
|
+
rowSpan
|
|
1181
|
+
});
|
|
1182
|
+
if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
|
|
1183
|
+
editor.tf.setNodes(newCell, { at: currentCellPath });
|
|
1184
|
+
} else {
|
|
1185
|
+
const rowElement = editor.api.parent(currentCellPath)[0];
|
|
1186
|
+
const emptyCell = api.create.tableCell({
|
|
1187
|
+
header,
|
|
1188
|
+
row: rowElement
|
|
1189
|
+
});
|
|
1190
|
+
newRowChildren.push({
|
|
1191
|
+
...emptyCell,
|
|
1192
|
+
colSpan: curColSpan,
|
|
1193
|
+
rowSpan: 1
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
editor.tf.withoutNormalizing(() => {
|
|
1198
|
+
editor.tf.insertNodes({
|
|
1199
|
+
children: newRowChildren,
|
|
1200
|
+
type: editor.getType(KEYS.tr)
|
|
1201
|
+
}, {
|
|
1202
|
+
at: nextRowPath,
|
|
1203
|
+
select: false
|
|
1204
|
+
});
|
|
1205
|
+
if (shouldSelect) {
|
|
1206
|
+
const cellEntry$1 = editor.api.node({
|
|
1207
|
+
at: nextRowPath,
|
|
1208
|
+
match: { type: getCellTypes(editor) }
|
|
1209
|
+
});
|
|
1210
|
+
if (cellEntry$1) {
|
|
1211
|
+
const [, nextCellPath] = cellEntry$1;
|
|
1212
|
+
editor.tf.select(nextCellPath);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/lib/merge/isTableRectangular.ts
|
|
1220
|
+
const allEqual = (arr) => arr.every((val) => val === arr[0]);
|
|
1221
|
+
/**
|
|
1222
|
+
* Checks if the given table is rectangular, meaning all rows have the same
|
|
1223
|
+
* effective number of cells, considering colspan and rowspan.
|
|
1224
|
+
*/
|
|
1225
|
+
const isTableRectangular = (table) => {
|
|
1226
|
+
const arr = [];
|
|
1227
|
+
table?.children?.forEach((row, rI) => {
|
|
1228
|
+
row.children?.forEach((cell) => {
|
|
1229
|
+
const cellElem = cell;
|
|
1230
|
+
Array.from({ length: getRowSpan(cellElem) || 1 }).forEach((_, i) => {
|
|
1231
|
+
if (!arr[rI + i]) arr[rI + i] = 0;
|
|
1232
|
+
arr[rI + i] += getColSpan(cellElem);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
return allEqual(arr);
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
//#endregion
|
|
1240
|
+
//#region src/lib/merge/mergeTableCells.ts
|
|
1241
|
+
/** Merges multiple selected cells into one. */
|
|
1242
|
+
const mergeTableCells = (editor) => {
|
|
1243
|
+
const { api } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1244
|
+
const cellEntries = getTableGridAbove(editor, { format: "cell" });
|
|
1245
|
+
editor.tf.withoutNormalizing(() => {
|
|
1246
|
+
let colSpan = 0;
|
|
1247
|
+
for (const entry of cellEntries) {
|
|
1248
|
+
const [cell, path] = entry;
|
|
1249
|
+
if (path.at(-2) === cellEntries[0][1].at(-2)) {
|
|
1250
|
+
const cellColSpan = api.table.getColSpan(cell);
|
|
1251
|
+
colSpan += cellColSpan;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
let rowSpan = 0;
|
|
1255
|
+
const { col } = getCellIndices(editor, cellEntries[0][0]);
|
|
1256
|
+
cellEntries.forEach((entry) => {
|
|
1257
|
+
const cell = entry[0];
|
|
1258
|
+
const { col: curCol } = getCellIndices(editor, cell);
|
|
1259
|
+
if (col === curCol) rowSpan += api.table.getRowSpan(cell);
|
|
1260
|
+
});
|
|
1261
|
+
const mergingCellChildren = [];
|
|
1262
|
+
for (const cellEntry of cellEntries) {
|
|
1263
|
+
const [el] = cellEntry;
|
|
1264
|
+
const cellChildren = api.table.getCellChildren(el);
|
|
1265
|
+
if (cellChildren.length !== 1 || !editor.api.isEmpty(cellChildren[0])) mergingCellChildren.push(...cloneDeep(cellChildren));
|
|
1266
|
+
}
|
|
1267
|
+
const cols = {};
|
|
1268
|
+
cellEntries.forEach(([_entry, path]) => {
|
|
1269
|
+
const rowIndex = path.at(-2);
|
|
1270
|
+
if (cols[rowIndex]) cols[rowIndex].push(path);
|
|
1271
|
+
else cols[rowIndex] = [path];
|
|
1272
|
+
});
|
|
1273
|
+
Object.values(cols).forEach((paths) => {
|
|
1274
|
+
paths?.forEach(() => {
|
|
1275
|
+
editor.tf.removeNodes({ at: paths[0] });
|
|
1276
|
+
});
|
|
1277
|
+
});
|
|
1278
|
+
const mergedCell = {
|
|
1279
|
+
...api.create.tableCell({
|
|
1280
|
+
children: mergingCellChildren,
|
|
1281
|
+
header: cellEntries[0][0].type === editor.getType(KEYS.th)
|
|
1282
|
+
}),
|
|
1283
|
+
colSpan,
|
|
1284
|
+
rowSpan
|
|
1285
|
+
};
|
|
1286
|
+
editor.tf.insertNodes(mergedCell, { at: cellEntries[0][1] });
|
|
1287
|
+
});
|
|
1288
|
+
editor.tf.select(editor.api.end(cellEntries[0][1]));
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/lib/merge/splitTableCell.ts
|
|
1293
|
+
const splitTableCell = (editor) => {
|
|
1294
|
+
const { api } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1295
|
+
const tableRowType = editor.getType(KEYS.tr);
|
|
1296
|
+
const [[cellElem, path]] = getTableGridAbove(editor, { format: "cell" });
|
|
1297
|
+
editor.tf.withoutNormalizing(() => {
|
|
1298
|
+
const createEmptyCell = (children) => ({
|
|
1299
|
+
...api.create.tableCell({
|
|
1300
|
+
children,
|
|
1301
|
+
header: cellElem.type === editor.getType(KEYS.th)
|
|
1302
|
+
}),
|
|
1303
|
+
colSpan: 1,
|
|
1304
|
+
rowSpan: 1
|
|
1305
|
+
});
|
|
1306
|
+
const tablePath = path.slice(0, -2);
|
|
1307
|
+
const [rowPath, colPath] = path.slice(-2);
|
|
1308
|
+
const colSpan = api.table.getColSpan(cellElem);
|
|
1309
|
+
const rowSpan = api.table.getRowSpan(cellElem);
|
|
1310
|
+
const colPaths = [];
|
|
1311
|
+
for (let i = 0; i < colSpan; i++) colPaths.push(colPath + i);
|
|
1312
|
+
const { col } = getCellIndices(editor, cellElem);
|
|
1313
|
+
editor.tf.removeNodes({ at: path });
|
|
1314
|
+
const getClosestColPathForRow = (row, targetCol) => {
|
|
1315
|
+
const rowEntry = editor.api.node({
|
|
1316
|
+
at: [...tablePath, row],
|
|
1317
|
+
match: { type: tableRowType }
|
|
1318
|
+
});
|
|
1319
|
+
if (!rowEntry) return 0;
|
|
1320
|
+
const rowEl = rowEntry[0];
|
|
1321
|
+
let closestColPath = [];
|
|
1322
|
+
let smallestDiff = Number.POSITIVE_INFINITY;
|
|
1323
|
+
let isDirectionLeft = false;
|
|
1324
|
+
rowEl.children.forEach((cell) => {
|
|
1325
|
+
const cellElement = cell;
|
|
1326
|
+
const { col: cellCol } = getCellIndices(editor, cellElement);
|
|
1327
|
+
const diff = Math.abs(cellCol - targetCol);
|
|
1328
|
+
if (diff < smallestDiff) {
|
|
1329
|
+
smallestDiff = diff;
|
|
1330
|
+
closestColPath = editor.api.findPath(cellElement);
|
|
1331
|
+
isDirectionLeft = cellCol < targetCol;
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
if (closestColPath.length > 0) {
|
|
1335
|
+
const lastIndex = closestColPath.at(-1);
|
|
1336
|
+
if (isDirectionLeft) return lastIndex + 1;
|
|
1337
|
+
return lastIndex;
|
|
1338
|
+
}
|
|
1339
|
+
return 1;
|
|
1340
|
+
};
|
|
1341
|
+
for (let i = 0; i < rowSpan; i++) {
|
|
1342
|
+
const currentRowPath = rowPath + i;
|
|
1343
|
+
const pathForNextRows = getClosestColPathForRow(currentRowPath, col);
|
|
1344
|
+
const newRowChildren = [];
|
|
1345
|
+
const _rowPath = [...tablePath, currentRowPath];
|
|
1346
|
+
const rowEntry = editor.api.node({
|
|
1347
|
+
at: _rowPath,
|
|
1348
|
+
match: { type: tableRowType }
|
|
1349
|
+
});
|
|
1350
|
+
for (let j = 0; j < colPaths.length; j++) {
|
|
1351
|
+
const cellChildren = api.table.getCellChildren(cellElem);
|
|
1352
|
+
const cellToInsert = i === 0 && j === 0 ? createEmptyCell(cellChildren) : createEmptyCell();
|
|
1353
|
+
if (rowEntry) {
|
|
1354
|
+
const currentColPath = i === 0 ? colPaths[j] : pathForNextRows;
|
|
1355
|
+
const pathForNewCell = [
|
|
1356
|
+
...tablePath,
|
|
1357
|
+
currentRowPath,
|
|
1358
|
+
currentColPath
|
|
1359
|
+
];
|
|
1360
|
+
editor.tf.insertNodes(cellToInsert, { at: pathForNewCell });
|
|
1361
|
+
} else newRowChildren.push(cellToInsert);
|
|
1362
|
+
}
|
|
1363
|
+
if (!rowEntry) editor.tf.insertNodes({
|
|
1364
|
+
children: newRowChildren,
|
|
1365
|
+
type: editor.getType(KEYS.tr)
|
|
1366
|
+
}, { at: _rowPath });
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
editor.tf.select(editor.api.end(path));
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
//#endregion
|
|
1373
|
+
//#region src/lib/normalizeInitialValueTable.ts
|
|
1374
|
+
const normalizeInitialValueTable = ({ editor, type }) => {
|
|
1375
|
+
const tables = editor.api.nodes({
|
|
1376
|
+
at: [],
|
|
1377
|
+
match: { type }
|
|
1378
|
+
});
|
|
1379
|
+
for (const [table] of tables) computeCellIndices(editor, { tableNode: table });
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/lib/transforms/deleteColumn.ts
|
|
1384
|
+
const deleteColumn = (editor) => {
|
|
1385
|
+
const { getOptions, type } = getEditorPlugin(editor, { key: KEYS.table });
|
|
1386
|
+
const { disableMerge } = getOptions();
|
|
1387
|
+
const tableEntry = editor.api.above({ match: { type } });
|
|
1388
|
+
if (!tableEntry) return;
|
|
1389
|
+
editor.tf.withoutNormalizing(() => {
|
|
1390
|
+
if (!disableMerge) {
|
|
1391
|
+
deleteTableMergeColumn(editor);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
if (editor.api.isExpanded()) return deleteColumnWhenExpanded(editor, tableEntry);
|
|
1395
|
+
const tdEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
|
|
1396
|
+
const trEntry = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
|
|
1397
|
+
if (tdEntry && trEntry && tableEntry && trEntry[0].children.length > 1) {
|
|
1398
|
+
const [tableNode, tablePath] = tableEntry;
|
|
1399
|
+
const tdPath = tdEntry[1];
|
|
1400
|
+
const colIndex = tdPath.at(-1);
|
|
1401
|
+
const pathToDelete = tdPath.slice();
|
|
1402
|
+
const replacePathPos = pathToDelete.length - 2;
|
|
1403
|
+
tableNode.children.forEach((row, rowIdx) => {
|
|
1404
|
+
pathToDelete[replacePathPos] = rowIdx;
|
|
1405
|
+
if (row.children.length === 1 || colIndex > row.children.length - 1) return;
|
|
1406
|
+
editor.tf.removeNodes({ at: pathToDelete });
|
|
1407
|
+
});
|
|
1408
|
+
const { colSizes } = tableNode;
|
|
1409
|
+
if (colSizes) {
|
|
1410
|
+
const newColSizes = [...colSizes];
|
|
1411
|
+
newColSizes.splice(colIndex, 1);
|
|
1412
|
+
editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
//#endregion
|
|
1419
|
+
//#region src/lib/transforms/deleteRow.ts
|
|
1420
|
+
const deleteRow = (editor) => {
|
|
1421
|
+
const { getOptions, type } = getEditorPlugin(editor, { key: KEYS.table });
|
|
1422
|
+
const { disableMerge } = getOptions();
|
|
1423
|
+
if (!disableMerge) return deleteTableMergeRow(editor);
|
|
1424
|
+
if (editor.api.some({ match: { type } })) {
|
|
1425
|
+
const currentTableItem = editor.api.above({ match: { type } });
|
|
1426
|
+
if (!currentTableItem) return;
|
|
1427
|
+
if (editor.api.isExpanded()) return deleteRowWhenExpanded(editor, currentTableItem);
|
|
1428
|
+
const currentRowItem = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
|
|
1429
|
+
if (currentRowItem && currentTableItem && currentTableItem[0].children.length > 1) editor.tf.removeNodes({ at: currentRowItem[1] });
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
//#endregion
|
|
1434
|
+
//#region src/lib/transforms/deleteTable.ts
|
|
1435
|
+
const deleteTable = (editor) => {
|
|
1436
|
+
if (editor.api.some({ match: { type: editor.getType(KEYS.table) } })) {
|
|
1437
|
+
const tableItem = editor.api.above({ match: { type: editor.getType(KEYS.table) } });
|
|
1438
|
+
if (tableItem) editor.tf.removeNodes({ at: tableItem[1] });
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
//#endregion
|
|
1443
|
+
//#region src/lib/transforms/insertTable.ts
|
|
1444
|
+
/**
|
|
1445
|
+
* Insert table. If selection in table and no 'at' specified, insert after
|
|
1446
|
+
* current table. Select start of new table.
|
|
1447
|
+
*/
|
|
1448
|
+
const insertTable = (editor, { colCount = 2, header, rowCount = 2 } = {}, { select: shouldSelect, ...options } = {}) => {
|
|
1449
|
+
const { api } = editor.getPlugin({ key: KEYS.table });
|
|
1450
|
+
const type = editor.getType(KEYS.table);
|
|
1451
|
+
editor.tf.withoutNormalizing(() => {
|
|
1452
|
+
const newTable = api.create.table({
|
|
1453
|
+
colCount,
|
|
1454
|
+
header,
|
|
1455
|
+
rowCount
|
|
1456
|
+
});
|
|
1457
|
+
if (!options.at) {
|
|
1458
|
+
const currentTableEntry = editor.api.block({ match: { type } });
|
|
1459
|
+
if (currentTableEntry) {
|
|
1460
|
+
const [, tablePath] = currentTableEntry;
|
|
1461
|
+
const insertPath = PathApi.next(tablePath);
|
|
1462
|
+
editor.tf.insertNodes(newTable, {
|
|
1463
|
+
at: insertPath,
|
|
1464
|
+
...options
|
|
1465
|
+
});
|
|
1466
|
+
if (editor.selection) editor.tf.select(editor.api.start(insertPath));
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
editor.tf.insertNodes(newTable, {
|
|
1471
|
+
nextBlock: !options.at,
|
|
1472
|
+
select: shouldSelect,
|
|
1473
|
+
...options
|
|
1474
|
+
});
|
|
1475
|
+
if (shouldSelect) {
|
|
1476
|
+
const tableEntry = editor.api.node({
|
|
1477
|
+
at: options.at,
|
|
1478
|
+
match: { type }
|
|
1479
|
+
});
|
|
1480
|
+
if (!tableEntry) return;
|
|
1481
|
+
editor.tf.select(editor.api.start(tableEntry[1]));
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
//#endregion
|
|
1487
|
+
//#region src/lib/transforms/insertTableColumn.ts
|
|
1488
|
+
const insertTableColumn = (editor, options = {}) => {
|
|
1489
|
+
const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1490
|
+
const { disableMerge, initialTableWidth, minColumnWidth } = getOptions();
|
|
1491
|
+
if (!disableMerge) return insertTableMergeColumn(editor, options);
|
|
1492
|
+
const { before, header, select: shouldSelect } = options;
|
|
1493
|
+
let { at, fromCell } = options;
|
|
1494
|
+
if (at && !fromCell) {
|
|
1495
|
+
if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
|
|
1496
|
+
fromCell = NodeApi.lastChild(editor, at.concat([0]))[1];
|
|
1497
|
+
at = void 0;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
const cellEntry = editor.api.block({
|
|
1501
|
+
at: fromCell,
|
|
1502
|
+
match: { type: getCellTypes(editor) }
|
|
1503
|
+
});
|
|
1504
|
+
if (!cellEntry) return;
|
|
1505
|
+
const [, cellPath] = cellEntry;
|
|
1506
|
+
const tableEntry = editor.api.block({
|
|
1507
|
+
above: true,
|
|
1508
|
+
at: cellPath,
|
|
1509
|
+
match: { type }
|
|
1510
|
+
});
|
|
1511
|
+
if (!tableEntry) return;
|
|
1512
|
+
const [tableNode, tablePath] = tableEntry;
|
|
1513
|
+
let nextCellPath;
|
|
1514
|
+
let nextColIndex;
|
|
1515
|
+
if (PathApi.isPath(at)) {
|
|
1516
|
+
nextCellPath = at;
|
|
1517
|
+
nextColIndex = at.at(-1);
|
|
1518
|
+
} else {
|
|
1519
|
+
nextCellPath = before ? cellPath : PathApi.next(cellPath);
|
|
1520
|
+
nextColIndex = before ? cellPath.at(-1) : cellPath.at(-1) + 1;
|
|
1521
|
+
}
|
|
1522
|
+
const currentRowIndex = cellPath.at(-2);
|
|
1523
|
+
editor.tf.withoutNormalizing(() => {
|
|
1524
|
+
tableNode.children.forEach((row, rowIndex) => {
|
|
1525
|
+
const insertCellPath = [...nextCellPath];
|
|
1526
|
+
if (PathApi.isPath(at)) insertCellPath[at.length - 2] = rowIndex;
|
|
1527
|
+
else insertCellPath[cellPath.length - 2] = rowIndex;
|
|
1528
|
+
const isHeaderRow = header === void 0 ? row.children.every((c) => c.type === editor.getType(KEYS.th)) : header;
|
|
1529
|
+
editor.tf.insertNodes(api.create.tableCell({ header: isHeaderRow }), {
|
|
1530
|
+
at: insertCellPath,
|
|
1531
|
+
select: shouldSelect && rowIndex === currentRowIndex
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
const { colSizes } = tableNode;
|
|
1535
|
+
if (colSizes) {
|
|
1536
|
+
let newColSizes = [
|
|
1537
|
+
...colSizes.slice(0, nextColIndex),
|
|
1538
|
+
0,
|
|
1539
|
+
...colSizes.slice(nextColIndex)
|
|
1540
|
+
];
|
|
1541
|
+
if (initialTableWidth) {
|
|
1542
|
+
newColSizes[nextColIndex] = colSizes[nextColIndex] ?? colSizes[nextColIndex - 1] ?? initialTableWidth / colSizes.length;
|
|
1543
|
+
const oldTotal = colSizes.reduce((a, b) => a + b, 0);
|
|
1544
|
+
const newTotal = newColSizes.reduce((a, b) => a + b, 0);
|
|
1545
|
+
const maxTotal = Math.max(oldTotal, initialTableWidth);
|
|
1546
|
+
if (newTotal > maxTotal) {
|
|
1547
|
+
const factor = maxTotal / newTotal;
|
|
1548
|
+
newColSizes = newColSizes.map((size) => Math.max(minColumnWidth ?? 0, Math.floor(size * factor)));
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
//#endregion
|
|
1557
|
+
//#region src/lib/transforms/insertTableRow.ts
|
|
1558
|
+
const insertTableRow = (editor, options = {}) => {
|
|
1559
|
+
const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
|
|
1560
|
+
const { disableMerge } = getOptions();
|
|
1561
|
+
if (!disableMerge) return insertTableMergeRow(editor, options);
|
|
1562
|
+
const { before, header, select: shouldSelect } = options;
|
|
1563
|
+
let { at, fromRow } = options;
|
|
1564
|
+
if (at && !fromRow) {
|
|
1565
|
+
if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
|
|
1566
|
+
fromRow = NodeApi.lastChild(editor, at)[1];
|
|
1567
|
+
at = void 0;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const trEntry = editor.api.block({
|
|
1571
|
+
at: fromRow,
|
|
1572
|
+
match: { type: editor.getType(KEYS.tr) }
|
|
1573
|
+
});
|
|
1574
|
+
if (!trEntry) return;
|
|
1575
|
+
const [trNode, trPath] = trEntry;
|
|
1576
|
+
const tableEntry = editor.api.block({
|
|
1577
|
+
above: true,
|
|
1578
|
+
at: trPath,
|
|
1579
|
+
match: { type }
|
|
1580
|
+
});
|
|
1581
|
+
if (!tableEntry) return;
|
|
1582
|
+
const getEmptyRowNode$1 = () => ({
|
|
1583
|
+
children: trNode.children.map((_, i) => {
|
|
1584
|
+
const isHeaderColumn = !(tableEntry[0].children.length === 1) && tableEntry[0].children.every((n) => n.children[i].type === editor.getType(KEYS.th));
|
|
1585
|
+
return api.create.tableCell({ header: header ?? isHeaderColumn });
|
|
1586
|
+
}),
|
|
1587
|
+
type: editor.getType(KEYS.tr)
|
|
1588
|
+
});
|
|
1589
|
+
editor.tf.withoutNormalizing(() => {
|
|
1590
|
+
editor.tf.insertNodes(getEmptyRowNode$1(), { at: PathApi.isPath(at) ? at : before ? trPath : PathApi.next(trPath) });
|
|
1591
|
+
});
|
|
1592
|
+
if (shouldSelect) {
|
|
1593
|
+
const cellEntry = editor.api.block({ match: { type: getCellTypes(editor) } });
|
|
1594
|
+
if (!cellEntry) return;
|
|
1595
|
+
const [, nextCellPath] = cellEntry;
|
|
1596
|
+
if (PathApi.isPath(at)) nextCellPath[nextCellPath.length - 2] = at.at(-2);
|
|
1597
|
+
else nextCellPath[nextCellPath.length - 2] = before ? nextCellPath.at(-2) : nextCellPath.at(-2) + 1;
|
|
1598
|
+
editor.tf.select(nextCellPath);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
//#endregion
|
|
1603
|
+
//#region src/lib/transforms/moveSelectionFromCell.ts
|
|
1604
|
+
/** Move selection by cell unit. */
|
|
1605
|
+
const moveSelectionFromCell = (editor, { at, edge, fromOneCell, reverse } = {}) => {
|
|
1606
|
+
if (edge) {
|
|
1607
|
+
const cellEntries = getTableGridAbove(editor, {
|
|
1608
|
+
at,
|
|
1609
|
+
format: "cell"
|
|
1610
|
+
});
|
|
1611
|
+
const minCell = fromOneCell ? 0 : 1;
|
|
1612
|
+
if (cellEntries.length > minCell) {
|
|
1613
|
+
const [, firstCellPath] = cellEntries[0];
|
|
1614
|
+
const [, lastCellPath] = cellEntries.at(-1);
|
|
1615
|
+
const anchorPath = [...firstCellPath];
|
|
1616
|
+
const focusPath = [...lastCellPath];
|
|
1617
|
+
switch (edge) {
|
|
1618
|
+
case "bottom":
|
|
1619
|
+
focusPath[focusPath.length - 2] += 1;
|
|
1620
|
+
break;
|
|
1621
|
+
case "left":
|
|
1622
|
+
anchorPath[anchorPath.length - 1] -= 1;
|
|
1623
|
+
break;
|
|
1624
|
+
case "right":
|
|
1625
|
+
focusPath[focusPath.length - 1] += 1;
|
|
1626
|
+
break;
|
|
1627
|
+
case "top":
|
|
1628
|
+
anchorPath[anchorPath.length - 2] -= 1;
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
if (NodeApi.has(editor, anchorPath) && NodeApi.has(editor, focusPath)) editor.tf.select({
|
|
1632
|
+
anchor: editor.api.start(anchorPath),
|
|
1633
|
+
focus: editor.api.start(focusPath)
|
|
1634
|
+
});
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
const cellEntry = editor.api.block({
|
|
1640
|
+
at,
|
|
1641
|
+
match: { type: getCellTypes(editor) }
|
|
1642
|
+
});
|
|
1643
|
+
if (cellEntry) {
|
|
1644
|
+
const [, cellPath] = cellEntry;
|
|
1645
|
+
const nextCellPath = [...cellPath];
|
|
1646
|
+
const offset = reverse ? -1 : 1;
|
|
1647
|
+
nextCellPath[nextCellPath.length - 2] += offset;
|
|
1648
|
+
if (NodeApi.has(editor, nextCellPath)) editor.tf.select(editor.api.start(nextCellPath));
|
|
1649
|
+
else {
|
|
1650
|
+
const tablePath = cellPath.slice(0, -2);
|
|
1651
|
+
if (reverse) editor.tf.withoutNormalizing(() => {
|
|
1652
|
+
editor.tf.select(editor.api.start(tablePath));
|
|
1653
|
+
editor.tf.move({ reverse: true });
|
|
1654
|
+
});
|
|
1655
|
+
else editor.tf.withoutNormalizing(() => {
|
|
1656
|
+
editor.tf.select(editor.api.end(tablePath));
|
|
1657
|
+
editor.tf.move();
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
//#endregion
|
|
1665
|
+
//#region src/lib/transforms/overrideSelectionFromCell.ts
|
|
1666
|
+
/**
|
|
1667
|
+
* Override the new selection if the previous selection and the new one are in
|
|
1668
|
+
* different cells.
|
|
1669
|
+
*/
|
|
1670
|
+
const overrideSelectionFromCell = (editor, newSelection) => {
|
|
1671
|
+
let hotkey;
|
|
1672
|
+
if (!editor.dom.currentKeyboardEvent || ![
|
|
1673
|
+
"up",
|
|
1674
|
+
"down",
|
|
1675
|
+
"shift+up",
|
|
1676
|
+
"shift+right",
|
|
1677
|
+
"shift+down",
|
|
1678
|
+
"shift+left"
|
|
1679
|
+
].some((key) => {
|
|
1680
|
+
const valid = isHotkey(key, editor.dom.currentKeyboardEvent);
|
|
1681
|
+
if (valid) hotkey = key;
|
|
1682
|
+
return valid;
|
|
1683
|
+
}) || !editor.selection?.focus || !newSelection?.focus || !editor.api.isAt({
|
|
1684
|
+
at: {
|
|
1685
|
+
anchor: editor.selection.focus,
|
|
1686
|
+
focus: newSelection.focus
|
|
1687
|
+
},
|
|
1688
|
+
blocks: true,
|
|
1689
|
+
match: { type: getCellTypes(editor) }
|
|
1690
|
+
})) return;
|
|
1691
|
+
if (!hotkey) return;
|
|
1692
|
+
const edge = KEY_SHIFT_EDGES[hotkey];
|
|
1693
|
+
if (edge && !editor.api.isAt({
|
|
1694
|
+
block: true,
|
|
1695
|
+
match: { type: getCellTypes(editor) }
|
|
1696
|
+
})) return;
|
|
1697
|
+
const prevSelection = editor.selection;
|
|
1698
|
+
const reverse = ["shift+up", "up"].includes(hotkey);
|
|
1699
|
+
setTimeout(() => {
|
|
1700
|
+
moveSelectionFromCell(editor, {
|
|
1701
|
+
at: prevSelection,
|
|
1702
|
+
edge,
|
|
1703
|
+
fromOneCell: true,
|
|
1704
|
+
reverse
|
|
1705
|
+
});
|
|
1706
|
+
}, 0);
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
//#endregion
|
|
1710
|
+
//#region src/lib/transforms/setBorderSize.ts
|
|
1711
|
+
const setBorderSize = (editor, size, { at, border = "all" } = {}) => {
|
|
1712
|
+
const cellEntry = editor.api.node({
|
|
1713
|
+
at,
|
|
1714
|
+
match: { type: getCellTypes(editor) }
|
|
1715
|
+
});
|
|
1716
|
+
if (!cellEntry) return;
|
|
1717
|
+
const [cellNode, cellPath] = cellEntry;
|
|
1718
|
+
const cellIndex = cellPath.at(-1);
|
|
1719
|
+
const rowIndex = cellPath.at(-2);
|
|
1720
|
+
const borderStyle = { size };
|
|
1721
|
+
const setNodesOptions = { match: (n) => ElementApi.isElement(n) && getCellTypes(editor).includes(n.type) };
|
|
1722
|
+
if (border === "top") {
|
|
1723
|
+
if (rowIndex === 0) {
|
|
1724
|
+
const newBorders$1 = {
|
|
1725
|
+
...cellNode.borders,
|
|
1726
|
+
top: borderStyle
|
|
1727
|
+
};
|
|
1728
|
+
editor.tf.setNodes({ borders: newBorders$1 }, {
|
|
1729
|
+
at: cellPath,
|
|
1730
|
+
...setNodesOptions
|
|
1731
|
+
});
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
|
|
1735
|
+
if (!cellAboveEntry) return;
|
|
1736
|
+
const [cellAboveNode, cellAbovePath] = cellAboveEntry;
|
|
1737
|
+
const newBorders = {
|
|
1738
|
+
...cellAboveNode.borders,
|
|
1739
|
+
bottom: borderStyle
|
|
1740
|
+
};
|
|
1741
|
+
editor.tf.setNodes({ borders: newBorders }, {
|
|
1742
|
+
at: cellAbovePath,
|
|
1743
|
+
...setNodesOptions
|
|
1744
|
+
});
|
|
1745
|
+
} else if (border === "bottom") {
|
|
1746
|
+
const newBorders = {
|
|
1747
|
+
...cellNode.borders,
|
|
1748
|
+
bottom: borderStyle
|
|
1749
|
+
};
|
|
1750
|
+
editor.tf.setNodes({ borders: newBorders }, {
|
|
1751
|
+
at: cellPath,
|
|
1752
|
+
...setNodesOptions
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
if (border === "left") {
|
|
1756
|
+
if (cellIndex === 0) {
|
|
1757
|
+
const newBorders$1 = {
|
|
1758
|
+
...cellNode.borders,
|
|
1759
|
+
left: borderStyle
|
|
1760
|
+
};
|
|
1761
|
+
editor.tf.setNodes({ borders: newBorders$1 }, {
|
|
1762
|
+
at: cellPath,
|
|
1763
|
+
...setNodesOptions
|
|
1764
|
+
});
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
|
|
1768
|
+
if (!prevCellEntry) return;
|
|
1769
|
+
const [prevCellNode, prevCellPath] = prevCellEntry;
|
|
1770
|
+
const newBorders = {
|
|
1771
|
+
...prevCellNode.borders,
|
|
1772
|
+
right: borderStyle
|
|
1773
|
+
};
|
|
1774
|
+
editor.tf.setNodes({ borders: newBorders }, {
|
|
1775
|
+
at: prevCellPath,
|
|
1776
|
+
...setNodesOptions
|
|
1777
|
+
});
|
|
1778
|
+
} else if (border === "right") {
|
|
1779
|
+
const newBorders = {
|
|
1780
|
+
...cellNode.borders,
|
|
1781
|
+
right: borderStyle
|
|
1782
|
+
};
|
|
1783
|
+
editor.tf.setNodes({ borders: newBorders }, {
|
|
1784
|
+
at: cellPath,
|
|
1785
|
+
...setNodesOptions
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
if (border === "all") editor.tf.withoutNormalizing(() => {
|
|
1789
|
+
setBorderSize(editor, size, {
|
|
1790
|
+
at,
|
|
1791
|
+
border: "top"
|
|
1792
|
+
});
|
|
1793
|
+
setBorderSize(editor, size, {
|
|
1794
|
+
at,
|
|
1795
|
+
border: "bottom"
|
|
1796
|
+
});
|
|
1797
|
+
setBorderSize(editor, size, {
|
|
1798
|
+
at,
|
|
1799
|
+
border: "left"
|
|
1800
|
+
});
|
|
1801
|
+
setBorderSize(editor, size, {
|
|
1802
|
+
at,
|
|
1803
|
+
border: "right"
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/lib/transforms/setTableColSize.ts
|
|
1810
|
+
const setTableColSize = (editor, { colIndex, width }, options = {}) => {
|
|
1811
|
+
const table = editor.api.node({
|
|
1812
|
+
match: { type: KEYS.table },
|
|
1813
|
+
...options
|
|
1814
|
+
});
|
|
1815
|
+
if (!table) return;
|
|
1816
|
+
const [tableNode, tablePath] = table;
|
|
1817
|
+
const colSizes = tableNode.colSizes ? [...tableNode.colSizes] : Array.from({ length: getTableColumnCount(tableNode) }).fill(0);
|
|
1818
|
+
colSizes[colIndex] = width;
|
|
1819
|
+
editor.tf.setNodes({ colSizes }, { at: tablePath });
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
//#endregion
|
|
1823
|
+
//#region src/lib/transforms/setTableMarginLeft.ts
|
|
1824
|
+
const setTableMarginLeft = (editor, { marginLeft }, options = {}) => {
|
|
1825
|
+
const table = editor.api.node({
|
|
1826
|
+
match: { type: KEYS.table },
|
|
1827
|
+
...options
|
|
1828
|
+
});
|
|
1829
|
+
if (!table) return;
|
|
1830
|
+
const [, tablePath] = table;
|
|
1831
|
+
editor.tf.setNodes({ marginLeft }, { at: tablePath });
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/lib/transforms/setTableRowSize.ts
|
|
1836
|
+
const setTableRowSize = (editor, { height, rowIndex }, options = {}) => {
|
|
1837
|
+
const table = editor.api.node({
|
|
1838
|
+
match: { type: KEYS.table },
|
|
1839
|
+
...options
|
|
1840
|
+
});
|
|
1841
|
+
if (!table) return;
|
|
1842
|
+
const [, tablePath] = table;
|
|
1843
|
+
const tableRowPath = [...tablePath, rowIndex];
|
|
1844
|
+
editor.tf.setNodes({ size: height }, { at: tableRowPath });
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
//#endregion
|
|
1848
|
+
//#region src/lib/withApplyTable.ts
|
|
1849
|
+
/**
|
|
1850
|
+
* Selection table:
|
|
1851
|
+
*
|
|
1852
|
+
* - If anchor is in table, focus in a block before: set focus to start of table
|
|
1853
|
+
* - If anchor is in table, focus in a block after: set focus to end of table
|
|
1854
|
+
* - If focus is in table, anchor in a block before: set focus to end of table
|
|
1855
|
+
* - If focus is in table, anchor in a block after: set focus to the point before
|
|
1856
|
+
* start of table
|
|
1857
|
+
*/
|
|
1858
|
+
const withApplyTable = ({ api: _api, editor, getOptions, tf: { apply }, type: tableType }) => ({ transforms: { apply(op) {
|
|
1859
|
+
if (op.type === "set_selection" && op.newProperties) {
|
|
1860
|
+
const newSelection = {
|
|
1861
|
+
...editor.selection,
|
|
1862
|
+
...op.newProperties
|
|
1863
|
+
};
|
|
1864
|
+
if (RangeApi.isRange(newSelection) && editor.api.isAt({
|
|
1865
|
+
at: newSelection,
|
|
1866
|
+
blocks: true,
|
|
1867
|
+
match: (n) => n.type === tableType
|
|
1868
|
+
})) {
|
|
1869
|
+
const anchorEntry = editor.api.block({
|
|
1870
|
+
at: newSelection.anchor,
|
|
1871
|
+
match: (n) => n.type === tableType
|
|
1872
|
+
});
|
|
1873
|
+
if (anchorEntry) {
|
|
1874
|
+
const [, anchorPath] = anchorEntry;
|
|
1875
|
+
if (RangeApi.isBackward(newSelection)) op.newProperties.focus = editor.api.start(anchorPath);
|
|
1876
|
+
else if (editor.api.before(anchorPath)) op.newProperties.focus = editor.api.end(anchorPath);
|
|
1877
|
+
} else {
|
|
1878
|
+
const focusEntry = editor.api.block({
|
|
1879
|
+
at: newSelection.focus,
|
|
1880
|
+
match: (n) => n.type === tableType
|
|
1881
|
+
});
|
|
1882
|
+
if (focusEntry) {
|
|
1883
|
+
const [, focusPath] = focusEntry;
|
|
1884
|
+
if (RangeApi.isBackward(newSelection)) {
|
|
1885
|
+
const startPoint = editor.api.start(focusPath);
|
|
1886
|
+
const pointBefore = editor.api.before(startPoint);
|
|
1887
|
+
op.newProperties.focus = pointBefore ?? startPoint;
|
|
1888
|
+
} else op.newProperties.focus = editor.api.end(focusPath);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
overrideSelectionFromCell(editor, newSelection);
|
|
1893
|
+
}
|
|
1894
|
+
const opType = op.type === "remove_node" ? op.node.type : op.type === "move_node" ? editor.api.node(op.path)?.[0].type : void 0;
|
|
1895
|
+
const isTableOperation = (op.type === "remove_node" || op.type === "move_node") && opType && [
|
|
1896
|
+
editor.getType(KEYS.tr),
|
|
1897
|
+
tableType,
|
|
1898
|
+
...getCellTypes(editor)
|
|
1899
|
+
].includes(opType);
|
|
1900
|
+
if (isTableOperation && op.type === "remove_node") {
|
|
1901
|
+
const cells = [...editor.api.nodes({
|
|
1902
|
+
at: op.path,
|
|
1903
|
+
match: { type: getCellTypes(editor) }
|
|
1904
|
+
})];
|
|
1905
|
+
const cellIndices = getOptions()._cellIndices;
|
|
1906
|
+
cells.forEach(([cell]) => {
|
|
1907
|
+
delete cellIndices[cell.id];
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
apply(op);
|
|
1911
|
+
let table;
|
|
1912
|
+
if (isTableOperation && opType !== tableType) {
|
|
1913
|
+
table = editor.api.node({
|
|
1914
|
+
at: op.type === "move_node" ? op.newPath : op.path,
|
|
1915
|
+
match: { type: tableType }
|
|
1916
|
+
})?.[0];
|
|
1917
|
+
if (table) computeCellIndices(editor, { tableNode: table });
|
|
1918
|
+
}
|
|
1919
|
+
} } });
|
|
1920
|
+
|
|
1921
|
+
//#endregion
|
|
1922
|
+
//#region src/lib/withDeleteTable.ts
|
|
1923
|
+
/**
|
|
1924
|
+
* Return true if:
|
|
1925
|
+
*
|
|
1926
|
+
* - At start/end of a cell.
|
|
1927
|
+
* - Next to a table cell. Move selection to the table cell.
|
|
1928
|
+
*/
|
|
1929
|
+
const preventDeleteTableCell = (editor, { reverse, unit }) => {
|
|
1930
|
+
const { selection } = editor;
|
|
1931
|
+
const getNextPoint = reverse ? editor.api.after : editor.api.before;
|
|
1932
|
+
if (editor.api.isCollapsed()) {
|
|
1933
|
+
const cellEntry = editor.api.block({ match: { type: getCellTypes(editor) } });
|
|
1934
|
+
if (cellEntry) {
|
|
1935
|
+
const [, cellPath] = cellEntry;
|
|
1936
|
+
const start = reverse ? editor.api.end(cellPath) : editor.api.start(cellPath);
|
|
1937
|
+
if (selection && PointApi.equals(selection.anchor, start)) return true;
|
|
1938
|
+
} else {
|
|
1939
|
+
const nextPoint = getNextPoint(selection, { unit });
|
|
1940
|
+
if (editor.api.block({
|
|
1941
|
+
at: nextPoint,
|
|
1942
|
+
match: { type: getCellTypes(editor) }
|
|
1943
|
+
})) {
|
|
1944
|
+
editor.tf.move({ reverse: !reverse });
|
|
1945
|
+
return true;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
/** Prevent cell deletion. */
|
|
1951
|
+
const withDeleteTable = ({ editor, tf: { deleteFragment }, type }) => ({ transforms: { deleteFragment(direction) {
|
|
1952
|
+
if (editor.api.isAt({
|
|
1953
|
+
block: true,
|
|
1954
|
+
match: (n) => n.type === type
|
|
1955
|
+
})) {
|
|
1956
|
+
const cellEntries = getTableGridAbove(editor, { format: "cell" });
|
|
1957
|
+
if (cellEntries.length > 1) {
|
|
1958
|
+
editor.tf.withoutNormalizing(() => {
|
|
1959
|
+
cellEntries.forEach(([, cellPath]) => {
|
|
1960
|
+
editor.tf.replaceNodes(editor.api.create.block(), {
|
|
1961
|
+
at: cellPath,
|
|
1962
|
+
children: true
|
|
1963
|
+
});
|
|
1964
|
+
});
|
|
1965
|
+
editor.tf.select({
|
|
1966
|
+
anchor: editor.api.start(cellEntries[0][1]),
|
|
1967
|
+
focus: editor.api.end(cellEntries.at(-1)[1])
|
|
1968
|
+
});
|
|
1969
|
+
});
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
deleteFragment(direction);
|
|
1974
|
+
} } });
|
|
1975
|
+
|
|
1976
|
+
//#endregion
|
|
1977
|
+
//#region src/lib/withGetFragmentTable.ts
|
|
1978
|
+
/** If selection is in a table, get subtable above. */
|
|
1979
|
+
const withGetFragmentTable = ({ api, api: { getFragment }, editor, type }) => ({ api: { getFragment() {
|
|
1980
|
+
const fragment = getFragment();
|
|
1981
|
+
const newFragment = [];
|
|
1982
|
+
fragment.forEach((node) => {
|
|
1983
|
+
if (node.type === type) {
|
|
1984
|
+
const rows = node.children;
|
|
1985
|
+
const rowCount = rows.length;
|
|
1986
|
+
if (!rowCount) return;
|
|
1987
|
+
const colCount = rows[0].children.length;
|
|
1988
|
+
if (rowCount <= 1 && colCount <= 1) {
|
|
1989
|
+
const cell = rows[0];
|
|
1990
|
+
const cellChildren = api.table.getCellChildren(cell);
|
|
1991
|
+
newFragment.push(...cellChildren[0].children);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const subTable = getTableGridAbove(editor);
|
|
1995
|
+
if (subTable.length > 0) {
|
|
1996
|
+
newFragment.push(subTable[0][0]);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
newFragment.push(node);
|
|
2001
|
+
});
|
|
2002
|
+
return newFragment;
|
|
2003
|
+
} } });
|
|
2004
|
+
|
|
2005
|
+
//#endregion
|
|
2006
|
+
//#region src/lib/withInsertFragmentTable.ts
|
|
2007
|
+
/**
|
|
2008
|
+
* If inserting a table, If block above anchor is a table,
|
|
2009
|
+
*
|
|
2010
|
+
* - Replace each cell above by the inserted table until out of bounds.
|
|
2011
|
+
* - Select the inserted cells.
|
|
2012
|
+
*/
|
|
2013
|
+
const withInsertFragmentTable = ({ api, editor, getOptions, tf: { insert, insertFragment }, type }) => ({ transforms: { insertFragment(fragment) {
|
|
2014
|
+
const insertedTable = fragment.find((n) => n.type === type);
|
|
2015
|
+
if (!insertedTable) {
|
|
2016
|
+
if (getTableAbove(editor, { at: editor.selection?.anchor })) {
|
|
2017
|
+
const cellEntries = getTableGridAbove(editor, { format: "cell" });
|
|
2018
|
+
if (cellEntries.length > 1) {
|
|
2019
|
+
cellEntries.forEach((cellEntry) => {
|
|
2020
|
+
if (cellEntry) {
|
|
2021
|
+
const [, cellPath] = cellEntry;
|
|
2022
|
+
editor.tf.replaceNodes(cloneDeep(fragment), {
|
|
2023
|
+
at: cellPath,
|
|
2024
|
+
children: true
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
editor.tf.select({
|
|
2029
|
+
anchor: editor.api.start(cellEntries[0][1]),
|
|
2030
|
+
focus: editor.api.end(cellEntries.at(-1)[1])
|
|
2031
|
+
});
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (insertedTable) {
|
|
2037
|
+
if (getTableAbove(editor, { at: editor.selection?.anchor })) {
|
|
2038
|
+
const [cellEntry] = getTableGridAbove(editor, {
|
|
2039
|
+
at: editor.selection?.anchor,
|
|
2040
|
+
format: "cell"
|
|
2041
|
+
});
|
|
2042
|
+
if (cellEntry) {
|
|
2043
|
+
editor.tf.withoutNormalizing(() => {
|
|
2044
|
+
const [, startCellPath] = cellEntry;
|
|
2045
|
+
const cellPath = [...startCellPath];
|
|
2046
|
+
const startColIndex = cellPath.at(-1);
|
|
2047
|
+
let lastCellPath = null;
|
|
2048
|
+
let initRow = true;
|
|
2049
|
+
insertedTable.children.forEach((row) => {
|
|
2050
|
+
cellPath[cellPath.length - 1] = startColIndex;
|
|
2051
|
+
if (!initRow) {
|
|
2052
|
+
const fromRow = cellPath.slice(0, -1);
|
|
2053
|
+
cellPath[cellPath.length - 2] += 1;
|
|
2054
|
+
if (!NodeApi.has(editor, cellPath)) {
|
|
2055
|
+
if (getOptions().disableExpandOnInsert) return;
|
|
2056
|
+
insert.tableRow({ fromRow });
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
initRow = false;
|
|
2060
|
+
const insertedCells = row.children;
|
|
2061
|
+
let initCell = true;
|
|
2062
|
+
insertedCells.forEach((cell) => {
|
|
2063
|
+
if (!initCell) {
|
|
2064
|
+
const fromCell = [...cellPath];
|
|
2065
|
+
cellPath[cellPath.length - 1] += 1;
|
|
2066
|
+
if (!NodeApi.has(editor, cellPath)) {
|
|
2067
|
+
if (getOptions().disableExpandOnInsert) return;
|
|
2068
|
+
insert.tableColumn({ fromCell });
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
initCell = false;
|
|
2072
|
+
const cellChildren = api.table.getCellChildren(cell);
|
|
2073
|
+
editor.tf.replaceNodes(cloneDeep(cellChildren), {
|
|
2074
|
+
at: cellPath,
|
|
2075
|
+
children: true
|
|
2076
|
+
});
|
|
2077
|
+
lastCellPath = [...cellPath];
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
if (lastCellPath) editor.tf.select({
|
|
2081
|
+
anchor: editor.api.start(startCellPath),
|
|
2082
|
+
focus: editor.api.end(lastCellPath)
|
|
2083
|
+
});
|
|
2084
|
+
});
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
} else if (fragment.length === 1 && fragment[0].type === KEYS.table) {
|
|
2088
|
+
editor.tf.insertNodes(fragment[0]);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
insertFragment(fragment);
|
|
2093
|
+
} } });
|
|
2094
|
+
|
|
2095
|
+
//#endregion
|
|
2096
|
+
//#region src/lib/withInsertTextTable.ts
|
|
2097
|
+
const withInsertTextTable = ({ editor, tf: { insertText } }) => ({ transforms: { insertText(text, options) {
|
|
2098
|
+
if (editor.api.isExpanded()) {
|
|
2099
|
+
if (getTableAbove(editor, { at: editor.selection?.anchor })) {
|
|
2100
|
+
if (getTableGridAbove(editor, { format: "cell" }).length > 1) editor.tf.collapse({ edge: "focus" });
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
insertText(text, options);
|
|
2104
|
+
} } });
|
|
2105
|
+
|
|
2106
|
+
//#endregion
|
|
2107
|
+
//#region src/lib/withNormalizeTable.ts
|
|
2108
|
+
/**
|
|
2109
|
+
* Normalize table:
|
|
2110
|
+
*
|
|
2111
|
+
* - Wrap cell children in a paragraph if they are texts.
|
|
2112
|
+
*/
|
|
2113
|
+
const withNormalizeTable = ({ editor, getOption, getOptions, tf: { normalizeNode }, type }) => ({ transforms: { normalizeNode([n, path]) {
|
|
2114
|
+
const { enableUnsetSingleColSize, initialTableWidth } = getOptions();
|
|
2115
|
+
if (ElementApi.isElement(n)) {
|
|
2116
|
+
if (n.type === type) {
|
|
2117
|
+
const node = n;
|
|
2118
|
+
if (!node.children.some((child) => ElementApi.isElement(child) && child.type === editor.getType(KEYS.tr))) {
|
|
2119
|
+
editor.tf.removeNodes({ at: path });
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
if (node.colSizes && node.colSizes.length > 0 && enableUnsetSingleColSize && getTableColumnCount(node) < 2) {
|
|
2123
|
+
editor.tf.unsetNodes("colSizes", { at: path });
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
if (editor.api.block({
|
|
2127
|
+
above: true,
|
|
2128
|
+
at: path,
|
|
2129
|
+
match: { type }
|
|
2130
|
+
})) {
|
|
2131
|
+
editor.tf.unwrapNodes({ at: path });
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
if (initialTableWidth) {
|
|
2135
|
+
const tableNode = node;
|
|
2136
|
+
const colCount = (tableNode.children[0]?.children)?.length;
|
|
2137
|
+
if (colCount) {
|
|
2138
|
+
const colSizes = [];
|
|
2139
|
+
if (!tableNode.colSizes) for (let i = 0; i < colCount; i++) colSizes.push(initialTableWidth / colCount);
|
|
2140
|
+
else if (tableNode.colSizes.some((size) => !size)) tableNode.colSizes.forEach((colSize) => {
|
|
2141
|
+
colSizes.push(colSize || initialTableWidth / colCount);
|
|
2142
|
+
});
|
|
2143
|
+
if (colSizes.length > 0) {
|
|
2144
|
+
editor.tf.setNodes({ colSizes }, { at: path });
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
if (n.type === editor.getType(KEYS.tr)) {
|
|
2151
|
+
if (editor.api.parent(path)?.[0].type !== type) {
|
|
2152
|
+
editor.tf.unwrapNodes({ at: path });
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
if (getCellTypes(editor).includes(n.type)) {
|
|
2157
|
+
const node = n;
|
|
2158
|
+
const cellIndices = getOption("cellIndices", node.id);
|
|
2159
|
+
if (node.id && !cellIndices) computeCellIndices(editor, {
|
|
2160
|
+
all: true,
|
|
2161
|
+
cellNode: node
|
|
2162
|
+
});
|
|
2163
|
+
const { children } = node;
|
|
2164
|
+
if (editor.api.parent(path)?.[0].type !== editor.getType(KEYS.tr)) {
|
|
2165
|
+
editor.tf.unwrapNodes({ at: path });
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
if (TextApi.isText(children[0])) {
|
|
2169
|
+
editor.tf.wrapNodes(editor.api.create.block({}, path), {
|
|
2170
|
+
at: path,
|
|
2171
|
+
children: true
|
|
2172
|
+
});
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
normalizeNode([n, path]);
|
|
2178
|
+
} } });
|
|
2179
|
+
|
|
2180
|
+
//#endregion
|
|
2181
|
+
//#region src/lib/withSetFragmentDataTable.ts
|
|
2182
|
+
const withSetFragmentDataTable = ({ api, editor, plugin, tf: { setFragmentData } }) => ({ transforms: { setFragmentData(data, originEvent) {
|
|
2183
|
+
const tableEntry = getTableGridAbove(editor, { format: "table" })?.[0];
|
|
2184
|
+
const selectedCellEntries = getTableGridAbove(editor, { format: "cell" });
|
|
2185
|
+
const initialSelection = editor.selection;
|
|
2186
|
+
if (!tableEntry || !initialSelection) {
|
|
2187
|
+
setFragmentData(data, originEvent);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
const [tableNode, tablePath] = tableEntry;
|
|
2191
|
+
const tableRows = tableNode.children;
|
|
2192
|
+
tableNode.children = tableNode.children.filter((v) => v.children.length > 0);
|
|
2193
|
+
let textCsv = "";
|
|
2194
|
+
let textTsv = "";
|
|
2195
|
+
const divElement = document.createElement("div");
|
|
2196
|
+
const tableElement = document.createElement("table");
|
|
2197
|
+
/**
|
|
2198
|
+
* Cover single cell copy | cut operation. In this case, copy cell content
|
|
2199
|
+
* instead of table structure.
|
|
2200
|
+
*/
|
|
2201
|
+
if (tableEntry && initialSelection && selectedCellEntries.length === 1 && (originEvent === "copy" || originEvent === "cut")) {
|
|
2202
|
+
setFragmentData(data);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
editor.tf.withoutNormalizing(() => {
|
|
2206
|
+
tableRows.forEach((row) => {
|
|
2207
|
+
const rowCells = row.children;
|
|
2208
|
+
const cellStrings = [];
|
|
2209
|
+
const rowElement = row.type === editor.getType(KEYS.th) ? document.createElement("th") : document.createElement("tr");
|
|
2210
|
+
rowCells.forEach((cell) => {
|
|
2211
|
+
data.clearData();
|
|
2212
|
+
const cellPath = editor.api.findPath(cell);
|
|
2213
|
+
editor.tf.select({
|
|
2214
|
+
anchor: editor.api.start(cellPath),
|
|
2215
|
+
focus: editor.api.end(cellPath)
|
|
2216
|
+
});
|
|
2217
|
+
setFragmentData(data);
|
|
2218
|
+
cellStrings.push(data.getData("text/plain"));
|
|
2219
|
+
const cellElement = document.createElement("td");
|
|
2220
|
+
cellElement.colSpan = api.table.getColSpan(cell);
|
|
2221
|
+
cellElement.rowSpan = api.table.getRowSpan(cell);
|
|
2222
|
+
cellElement.innerHTML = data.getData("text/html");
|
|
2223
|
+
rowElement.append(cellElement);
|
|
2224
|
+
});
|
|
2225
|
+
tableElement.append(rowElement);
|
|
2226
|
+
textCsv += `${cellStrings.join(",")}\n`;
|
|
2227
|
+
textTsv += `${cellStrings.join(" ")}\n`;
|
|
2228
|
+
});
|
|
2229
|
+
const _tableEntry = editor.api.node({
|
|
2230
|
+
at: tablePath,
|
|
2231
|
+
match: { type: KEYS.table }
|
|
2232
|
+
});
|
|
2233
|
+
if (_tableEntry != null && _tableEntry.length > 0) {
|
|
2234
|
+
const realTable = _tableEntry[0];
|
|
2235
|
+
if (realTable.attributes != null) Object.entries(realTable.attributes).forEach(([key, value]) => {
|
|
2236
|
+
if (value != null && plugin.node.dangerouslyAllowAttributes?.includes(key)) tableElement.setAttribute(key, String(value));
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
editor.tf.select(initialSelection);
|
|
2240
|
+
divElement.append(tableElement);
|
|
2241
|
+
});
|
|
2242
|
+
data.setData("text/csv", textCsv);
|
|
2243
|
+
data.setData("text/tsv", textTsv);
|
|
2244
|
+
data.setData("text/plain", textTsv);
|
|
2245
|
+
data.setData("text/html", divElement.innerHTML);
|
|
2246
|
+
const selectedFragmentStr = JSON.stringify([tableNode]);
|
|
2247
|
+
const encodedFragment = window.btoa(encodeURIComponent(selectedFragmentStr));
|
|
2248
|
+
data.setData("application/x-slate-fragment", encodedFragment);
|
|
2249
|
+
} } });
|
|
2250
|
+
|
|
2251
|
+
//#endregion
|
|
2252
|
+
//#region src/lib/withTableCellSelection.tsx
|
|
2253
|
+
const withTableCellSelection = ({ api: { marks }, editor, tf: { addMark, removeMark, setNodes } }) => {
|
|
2254
|
+
return {
|
|
2255
|
+
api: { marks() {
|
|
2256
|
+
const apply = () => {
|
|
2257
|
+
const { selection } = editor;
|
|
2258
|
+
if (!selection || editor.api.isCollapsed()) return;
|
|
2259
|
+
const matchesCell = getTableGridAbove(editor, { format: "cell" });
|
|
2260
|
+
if (matchesCell.length <= 1) return;
|
|
2261
|
+
const markCounts = {};
|
|
2262
|
+
const totalMarks = {};
|
|
2263
|
+
let totalNodes = 0;
|
|
2264
|
+
matchesCell.forEach(([_cell, cellPath]) => {
|
|
2265
|
+
const textNodeEntry = editor.api.nodes({
|
|
2266
|
+
at: cellPath,
|
|
2267
|
+
match: (n) => TextApi.isText(n)
|
|
2268
|
+
});
|
|
2269
|
+
Array.from(textNodeEntry, (item) => item[0]).forEach((item) => {
|
|
2270
|
+
totalNodes++;
|
|
2271
|
+
const keys = Object.keys(item);
|
|
2272
|
+
if (keys.length === 1) return;
|
|
2273
|
+
keys.splice(keys.indexOf("text"), 1);
|
|
2274
|
+
keys.forEach((k) => {
|
|
2275
|
+
markCounts[k] = (markCounts[k] || 0) + 1;
|
|
2276
|
+
totalMarks[k] = item[k];
|
|
2277
|
+
});
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2280
|
+
Object.keys(markCounts).forEach((mark) => {
|
|
2281
|
+
if (markCounts[mark] !== totalNodes) delete totalMarks[mark];
|
|
2282
|
+
});
|
|
2283
|
+
return totalMarks;
|
|
2284
|
+
};
|
|
2285
|
+
const result = apply();
|
|
2286
|
+
if (result) return result;
|
|
2287
|
+
return marks();
|
|
2288
|
+
} },
|
|
2289
|
+
transforms: {
|
|
2290
|
+
addMark(key, value) {
|
|
2291
|
+
const apply = () => {
|
|
2292
|
+
const { selection } = editor;
|
|
2293
|
+
if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
|
|
2294
|
+
const matchesCell = getTableGridAbove(editor, { format: "cell" });
|
|
2295
|
+
if (matchesCell.length <= 1) return;
|
|
2296
|
+
matchesCell.forEach(([_cell, cellPath]) => {
|
|
2297
|
+
editor.tf.setNodes({ [key]: value }, {
|
|
2298
|
+
at: cellPath,
|
|
2299
|
+
split: true,
|
|
2300
|
+
voids: true,
|
|
2301
|
+
match: (n) => TextApi.isText(n)
|
|
2302
|
+
});
|
|
2303
|
+
});
|
|
2304
|
+
return true;
|
|
2305
|
+
};
|
|
2306
|
+
if (apply()) return;
|
|
2307
|
+
return addMark(key, value);
|
|
2308
|
+
},
|
|
2309
|
+
removeMark(key) {
|
|
2310
|
+
const apply = () => {
|
|
2311
|
+
const { selection } = editor;
|
|
2312
|
+
if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
|
|
2313
|
+
const matchesCell = getTableGridAbove(editor, { format: "cell" });
|
|
2314
|
+
if (matchesCell.length <= 1) return;
|
|
2315
|
+
matchesCell.forEach(([_cell, cellPath]) => {
|
|
2316
|
+
editor.tf.setNodes({ [key]: null }, {
|
|
2317
|
+
at: cellPath,
|
|
2318
|
+
split: true,
|
|
2319
|
+
voids: true,
|
|
2320
|
+
match: (n) => TextApi.isText(n)
|
|
2321
|
+
});
|
|
2322
|
+
});
|
|
2323
|
+
return true;
|
|
2324
|
+
};
|
|
2325
|
+
if (apply()) return;
|
|
2326
|
+
return removeMark(key);
|
|
2327
|
+
},
|
|
2328
|
+
setNodes(props, options) {
|
|
2329
|
+
const apply = () => {
|
|
2330
|
+
const { selection } = editor;
|
|
2331
|
+
if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
|
|
2332
|
+
if (options?.at) {
|
|
2333
|
+
const range = editor.api.range(options.at);
|
|
2334
|
+
if (range && !RangeApi.includes(selection, range)) return;
|
|
2335
|
+
}
|
|
2336
|
+
const matchesCell = getTableGridAbove(editor, { format: "cell" });
|
|
2337
|
+
if (matchesCell.length <= 1) return;
|
|
2338
|
+
setNodes(props, {
|
|
2339
|
+
...options,
|
|
2340
|
+
match: combineTransformMatchOptions(editor, (_, p) => matchesCell.some(([_$1, cellPath]) => PathApi.isCommon(cellPath, p)), options)
|
|
2341
|
+
});
|
|
2342
|
+
return true;
|
|
2343
|
+
};
|
|
2344
|
+
if (apply()) return;
|
|
2345
|
+
return setNodes(props, options);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
//#endregion
|
|
2352
|
+
//#region src/lib/withTable.ts
|
|
2353
|
+
const withTable = (ctx) => {
|
|
2354
|
+
const { editor, tf: { selectAll, tab }, type } = ctx;
|
|
2355
|
+
const cellSelection = withTableCellSelection(ctx);
|
|
2356
|
+
return {
|
|
2357
|
+
api: {
|
|
2358
|
+
...withGetFragmentTable(ctx).api,
|
|
2359
|
+
...cellSelection.api
|
|
2360
|
+
},
|
|
2361
|
+
transforms: {
|
|
2362
|
+
selectAll: () => {
|
|
2363
|
+
const apply = () => {
|
|
2364
|
+
const table = editor.api.above({ match: { type } });
|
|
2365
|
+
if (!table) return;
|
|
2366
|
+
const [, tablePath] = table;
|
|
2367
|
+
editor.tf.select(tablePath);
|
|
2368
|
+
return true;
|
|
2369
|
+
};
|
|
2370
|
+
if (apply()) return true;
|
|
2371
|
+
return selectAll();
|
|
2372
|
+
},
|
|
2373
|
+
tab: (options) => {
|
|
2374
|
+
const apply = () => {
|
|
2375
|
+
if (editor.selection && editor.api.isExpanded()) {
|
|
2376
|
+
if (Array.from(editor.api.nodes({
|
|
2377
|
+
at: editor.selection,
|
|
2378
|
+
match: { type: getCellTypes(editor) }
|
|
2379
|
+
})).length > 1) {
|
|
2380
|
+
editor.tf.collapse({ edge: "end" });
|
|
2381
|
+
return true;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
const entries = getTableEntries(editor);
|
|
2385
|
+
if (!entries) return;
|
|
2386
|
+
const { cell, row } = entries;
|
|
2387
|
+
const [, cellPath] = cell;
|
|
2388
|
+
if (options.reverse) {
|
|
2389
|
+
const previousCell = getPreviousTableCell(editor, cell, cellPath, row);
|
|
2390
|
+
if (previousCell) {
|
|
2391
|
+
const [, previousCellPath] = previousCell;
|
|
2392
|
+
editor.tf.select(previousCellPath);
|
|
2393
|
+
}
|
|
2394
|
+
} else {
|
|
2395
|
+
const nextCell = getNextTableCell(editor, cell, cellPath, row);
|
|
2396
|
+
if (nextCell) {
|
|
2397
|
+
const [, nextCellPath] = nextCell;
|
|
2398
|
+
editor.tf.select(nextCellPath);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
return true;
|
|
2402
|
+
};
|
|
2403
|
+
if (apply()) return true;
|
|
2404
|
+
return tab(options);
|
|
2405
|
+
},
|
|
2406
|
+
...withNormalizeTable(ctx).transforms,
|
|
2407
|
+
...withDeleteTable(ctx).transforms,
|
|
2408
|
+
...withInsertFragmentTable(ctx).transforms,
|
|
2409
|
+
...withInsertTextTable(ctx).transforms,
|
|
2410
|
+
...withApplyTable(ctx).transforms,
|
|
2411
|
+
...withSetFragmentDataTable(ctx).transforms,
|
|
2412
|
+
...cellSelection.transforms
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
};
|
|
2416
|
+
|
|
2417
|
+
//#endregion
|
|
2418
|
+
//#region src/lib/BaseTablePlugin.ts
|
|
2419
|
+
const parse = ({ element, type }) => {
|
|
2420
|
+
const background = element.style.background || element.style.backgroundColor;
|
|
2421
|
+
if (background) return {
|
|
2422
|
+
background,
|
|
2423
|
+
type
|
|
2424
|
+
};
|
|
2425
|
+
return { type };
|
|
2426
|
+
};
|
|
2427
|
+
const BaseTableRowPlugin = createSlatePlugin({
|
|
2428
|
+
key: KEYS.tr,
|
|
2429
|
+
node: {
|
|
2430
|
+
isContainer: true,
|
|
2431
|
+
isElement: true,
|
|
2432
|
+
isStrictSiblings: true
|
|
2433
|
+
},
|
|
2434
|
+
parsers: { html: { deserializer: { rules: [{ validNodeName: "TR" }] } } }
|
|
2435
|
+
});
|
|
2436
|
+
const BaseTableCellPlugin = createSlatePlugin({
|
|
2437
|
+
key: KEYS.td,
|
|
2438
|
+
node: {
|
|
2439
|
+
dangerouslyAllowAttributes: ["colspan", "rowspan"],
|
|
2440
|
+
isContainer: true,
|
|
2441
|
+
isElement: true,
|
|
2442
|
+
isStrictSiblings: true,
|
|
2443
|
+
props: ({ element }) => ({
|
|
2444
|
+
colSpan: (element?.attributes)?.colspan,
|
|
2445
|
+
rowSpan: (element?.attributes)?.rowspan
|
|
2446
|
+
})
|
|
2447
|
+
},
|
|
2448
|
+
parsers: { html: { deserializer: {
|
|
2449
|
+
attributeNames: ["rowspan", "colspan"],
|
|
2450
|
+
parse,
|
|
2451
|
+
rules: [{ validNodeName: "TD" }]
|
|
2452
|
+
} } },
|
|
2453
|
+
rules: { merge: { removeEmpty: false } }
|
|
2454
|
+
});
|
|
2455
|
+
const BaseTableCellHeaderPlugin = createSlatePlugin({
|
|
2456
|
+
key: KEYS.th,
|
|
2457
|
+
node: {
|
|
2458
|
+
dangerouslyAllowAttributes: ["colspan", "rowspan"],
|
|
2459
|
+
isContainer: true,
|
|
2460
|
+
isElement: true,
|
|
2461
|
+
isStrictSiblings: true,
|
|
2462
|
+
props: ({ element }) => ({
|
|
2463
|
+
colSpan: (element?.attributes)?.colspan,
|
|
2464
|
+
rowSpan: (element?.attributes)?.rowspan
|
|
2465
|
+
})
|
|
2466
|
+
},
|
|
2467
|
+
parsers: { html: { deserializer: {
|
|
2468
|
+
attributeNames: ["rowspan", "colspan"],
|
|
2469
|
+
parse,
|
|
2470
|
+
rules: [{ validNodeName: "TH" }]
|
|
2471
|
+
} } },
|
|
2472
|
+
rules: { merge: { removeEmpty: false } }
|
|
2473
|
+
});
|
|
2474
|
+
/** Enables support for tables. */
|
|
2475
|
+
const BaseTablePlugin = createTSlatePlugin({
|
|
2476
|
+
key: KEYS.table,
|
|
2477
|
+
node: {
|
|
2478
|
+
isContainer: true,
|
|
2479
|
+
isElement: true
|
|
2480
|
+
},
|
|
2481
|
+
normalizeInitialValue: normalizeInitialValueTable,
|
|
2482
|
+
options: {
|
|
2483
|
+
_cellIndices: {},
|
|
2484
|
+
disableMerge: false,
|
|
2485
|
+
minColumnWidth: 48,
|
|
2486
|
+
selectedCells: null,
|
|
2487
|
+
selectedTables: null
|
|
2488
|
+
},
|
|
2489
|
+
parsers: { html: { deserializer: { rules: [{ validNodeName: "TABLE" }] } } },
|
|
2490
|
+
plugins: [
|
|
2491
|
+
BaseTableRowPlugin,
|
|
2492
|
+
BaseTableCellPlugin,
|
|
2493
|
+
BaseTableCellHeaderPlugin
|
|
2494
|
+
]
|
|
2495
|
+
}).extendSelectors(({ getOptions }) => ({ cellIndices: (id) => getOptions()._cellIndices[id] })).extendEditorApi(({ editor }) => ({
|
|
2496
|
+
create: {
|
|
2497
|
+
table: bindFirst(getEmptyTableNode, editor),
|
|
2498
|
+
tableCell: bindFirst(getEmptyCellNode, editor),
|
|
2499
|
+
tableRow: bindFirst(getEmptyRowNode, editor)
|
|
2500
|
+
},
|
|
2501
|
+
table: {
|
|
2502
|
+
getCellBorders: bindFirst(getTableCellBorders, editor),
|
|
2503
|
+
getCellSize: bindFirst(getTableCellSize, editor),
|
|
2504
|
+
getColSpan,
|
|
2505
|
+
getRowSpan,
|
|
2506
|
+
getCellChildren: (cell) => cell.children
|
|
2507
|
+
}
|
|
2508
|
+
})).extendEditorTransforms(({ editor }) => ({
|
|
2509
|
+
insert: {
|
|
2510
|
+
table: bindFirst(insertTable, editor),
|
|
2511
|
+
tableColumn: bindFirst(insertTableColumn, editor),
|
|
2512
|
+
tableRow: bindFirst(insertTableRow, editor)
|
|
2513
|
+
},
|
|
2514
|
+
remove: {
|
|
2515
|
+
table: bindFirst(deleteTable, editor),
|
|
2516
|
+
tableColumn: bindFirst(deleteColumn, editor),
|
|
2517
|
+
tableRow: bindFirst(deleteRow, editor)
|
|
2518
|
+
},
|
|
2519
|
+
table: {
|
|
2520
|
+
merge: bindFirst(mergeTableCells, editor),
|
|
2521
|
+
split: bindFirst(splitTableCell, editor)
|
|
2522
|
+
}
|
|
2523
|
+
})).overrideEditor(withTable);
|
|
2524
|
+
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/lib/constants.ts
|
|
2527
|
+
const KEY_SHIFT_EDGES = {
|
|
2528
|
+
"shift+down": "bottom",
|
|
2529
|
+
"shift+left": "left",
|
|
2530
|
+
"shift+right": "right",
|
|
2531
|
+
"shift+up": "top"
|
|
2532
|
+
};
|
|
2533
|
+
|
|
2534
|
+
//#endregion
|
|
2535
|
+
export { isSelectedCellBordersNone as $, mergeTableCells as A, getTableOverriddenColSizes as B, insertTableColumn as C, deleteColumn as D, deleteRow as E, deleteTableMergeRow as F, getCellIndicesWithSpans as G, getTableGridByRange as H, deleteRowWhenExpanded as I, getTableCellSize as J, getTableEntries as K, deleteTableMergeColumn as L, insertTableMergeRow as M, insertTableMergeColumn as N, normalizeInitialValueTable as O, getTableMergedColumnCount as P, isSelectedCellBorder as Q, getCellPath as R, insertTableRow as S, deleteTable as T, getTableMergeGridByRange as U, getTableGridAbove as V, findCellByIndexes as W, getTableAbove as X, getTableCellBorders as Y, getSelectedCellsBorders as Z, setTableMarginLeft as _, BaseTableRowPlugin as a, getNextTableCell as at, overrideSelectionFromCell as b, withSetFragmentDataTable as c, getCellRowIndexByPath as ct, withInsertFragmentTable as d, getColSpan as dt, isSelectedCellBordersOuter as et, withGetFragmentTable as f, getCellInPreviousTableRow as ft, setTableRowSize as g, getEmptyCellNode as gt, withApplyTable as h, getEmptyRowNode as ht, BaseTablePlugin as i, getPreviousTableCell as it, isTableRectangular as j, splitTableCell as k, withNormalizeTable as l, getCellIndices as lt, withDeleteTable as m, getEmptyTableNode as mt, BaseTableCellHeaderPlugin as n, getSelectedCellsBoundingBox as nt, withTable as o, getLeftTableCell as ot, preventDeleteTableCell as p, getCellInNextTableRow as pt, getTableColumnCount as q, BaseTableCellPlugin as r, getRowSpan as rt, withTableCellSelection as s, getCellTypes as st, KEY_SHIFT_EDGES as t, getTopTableCell as tt, withInsertTextTable as u, computeCellIndices as ut, setTableColSize as v, insertTable as w, moveSelectionFromCell as x, setBorderSize as y, deleteColumnWhenExpanded as z };
|
|
2536
|
+
//# sourceMappingURL=constants-B6Sm9BNa.js.map
|