]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/defaults/buttons/tables.ts
Lexical: Merged custom table node code
[bookstack] / resources / js / wysiwyg / ui / defaults / buttons / tables.ts
1 import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
2 import tableIcon from "@icons/editor/table.svg";
3 import deleteIcon from "@icons/editor/table-delete.svg";
4 import deleteColumnIcon from "@icons/editor/table-delete-column.svg";
5 import deleteRowIcon from "@icons/editor/table-delete-row.svg";
6 import insertColumnAfterIcon from "@icons/editor/table-insert-column-after.svg";
7 import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg";
8 import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
9 import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
10 import {EditorUiContext} from "../../framework/core";
11 import {$getSelection, BaseSelection} from "lexical";
12 import {
13     $deleteTableColumn__EXPERIMENTAL,
14     $deleteTableRow__EXPERIMENTAL,
15     $insertTableColumn__EXPERIMENTAL,
16     $insertTableRow__EXPERIMENTAL, $isTableCellNode,
17     $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode,
18 } from "@lexical/table";
19 import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
20 import {$getParentOfType} from "../../../utils/nodes";
21 import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables";
22 import {
23     $clearTableFormatting,
24     $clearTableSizes, $getTableFromSelection,
25     $getTableRowsFromSelection,
26     $mergeTableCellsInSelection
27 } from "../../../utils/tables";
28 import {
29     $copySelectedColumnsToClipboard,
30     $copySelectedRowsToClipboard,
31     $cutSelectedColumnsToClipboard,
32     $cutSelectedRowsToClipboard,
33     $pasteClipboardRowsBefore,
34     $pasteClipboardRowsAfter,
35     isColumnClipboardEmpty,
36     isRowClipboardEmpty,
37     $pasteClipboardColumnsBefore, $pasteClipboardColumnsAfter
38 } from "../../../utils/table-copy-paste";
39
40 const neverActive = (): boolean => false;
41 const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode);
42
43 export const table: EditorBasicButtonDefinition = {
44     label: 'Table',
45     icon: tableIcon,
46 };
47
48 export const tableProperties: EditorButtonDefinition = {
49     label: 'Table properties',
50     icon: tableIcon,
51     action(context: EditorUiContext) {
52         context.editor.getEditorState().read(() => {
53             const table = $getTableFromSelection($getSelection());
54             if ($isTableNode(table)) {
55                 $showTablePropertiesForm(table, context);
56             }
57         });
58     },
59     isActive: neverActive,
60     isDisabled: cellNotSelected,
61 };
62
63 export const clearTableFormatting: EditorButtonDefinition = {
64     label: 'Clear table formatting',
65     format: 'long',
66     action(context: EditorUiContext) {
67         context.editor.update(() => {
68             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
69             if (!$isTableCellNode(cell)) {
70                 return;
71             }
72
73             const table = $getParentOfType(cell, $isTableNode);
74             if ($isTableNode(table)) {
75                 $clearTableFormatting(table);
76             }
77         });
78     },
79     isActive: neverActive,
80     isDisabled: cellNotSelected,
81 };
82
83 export const resizeTableToContents: EditorButtonDefinition = {
84     label: 'Resize to contents',
85     format: 'long',
86     action(context: EditorUiContext) {
87         context.editor.update(() => {
88             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
89             if (!$isTableCellNode(cell)) {
90                 return;
91             }
92
93             const table = $getParentOfType(cell, $isTableNode);
94             if ($isTableNode(table)) {
95                 $clearTableSizes(table);
96             }
97         });
98     },
99     isActive: neverActive,
100     isDisabled: cellNotSelected,
101 };
102
103 export const deleteTable: EditorButtonDefinition = {
104     label: 'Delete table',
105     icon: deleteIcon,
106     action(context: EditorUiContext) {
107         context.editor.update(() => {
108             const table = $getNodeFromSelection($getSelection(), $isTableNode);
109             if (table) {
110                 table.remove();
111             }
112         });
113     },
114     isActive() {
115         return false;
116     }
117 };
118
119 export const deleteTableMenuAction: EditorButtonDefinition = {
120     ...deleteTable,
121     format: 'long',
122     isDisabled(selection) {
123         return !$selectionContainsNodeType(selection, $isTableNode);
124     },
125 };
126
127 export const insertRowAbove: EditorButtonDefinition = {
128     label: 'Insert row before',
129     icon: insertRowAboveIcon,
130     action(context: EditorUiContext) {
131         context.editor.update(() => {
132             $insertTableRow__EXPERIMENTAL(false);
133         });
134     },
135     isActive: neverActive,
136     isDisabled: cellNotSelected,
137 };
138
139 export const insertRowBelow: EditorButtonDefinition = {
140     label: 'Insert row after',
141     icon: insertRowBelowIcon,
142     action(context: EditorUiContext) {
143         context.editor.update(() => {
144             $insertTableRow__EXPERIMENTAL(true);
145         });
146     },
147     isActive: neverActive,
148     isDisabled: cellNotSelected,
149 };
150
151 export const deleteRow: EditorButtonDefinition = {
152     label: 'Delete row',
153     icon: deleteRowIcon,
154     action(context: EditorUiContext) {
155         context.editor.update(() => {
156             $deleteTableRow__EXPERIMENTAL();
157         });
158     },
159     isActive: neverActive,
160     isDisabled: cellNotSelected,
161 };
162
163 export const rowProperties: EditorButtonDefinition = {
164     label: 'Row properties',
165     format: 'long',
166     action(context: EditorUiContext) {
167         context.editor.getEditorState().read(() => {
168             const rows = $getTableRowsFromSelection($getSelection());
169             if ($isTableRowNode(rows[0])) {
170                 $showRowPropertiesForm(rows[0], context);
171             }
172         });
173     },
174     isActive: neverActive,
175     isDisabled: cellNotSelected,
176 };
177
178 export const cutRow: EditorButtonDefinition = {
179     label: 'Cut row',
180     format: 'long',
181     action(context: EditorUiContext) {
182         context.editor.update(() => {
183             try {
184                 $cutSelectedRowsToClipboard();
185             } catch (e: any) {
186                 context.error(e);
187             }
188         });
189     },
190     isActive: neverActive,
191     isDisabled: cellNotSelected,
192 };
193
194 export const copyRow: EditorButtonDefinition = {
195     label: 'Copy row',
196     format: 'long',
197     action(context: EditorUiContext) {
198         context.editor.getEditorState().read(() => {
199             try {
200                 $copySelectedRowsToClipboard();
201             } catch (e: any) {
202                 context.error(e);
203             }
204         });
205     },
206     isActive: neverActive,
207     isDisabled: cellNotSelected,
208 };
209
210 export const pasteRowBefore: EditorButtonDefinition = {
211     label: 'Paste row before',
212     format: 'long',
213     action(context: EditorUiContext) {
214         context.editor.update(() => {
215             try {
216                 $pasteClipboardRowsBefore(context.editor);
217             } catch (e: any) {
218                 context.error(e);
219             }
220         });
221     },
222     isActive: neverActive,
223     isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
224 };
225
226 export const pasteRowAfter: EditorButtonDefinition = {
227     label: 'Paste row after',
228     format: 'long',
229     action(context: EditorUiContext) {
230         context.editor.update(() => {
231             try {
232                 $pasteClipboardRowsAfter(context.editor);
233             } catch (e: any) {
234                 context.error(e);
235             }
236         });
237     },
238     isActive: neverActive,
239     isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
240 };
241
242 export const cutColumn: EditorButtonDefinition = {
243     label: 'Cut column',
244     format: 'long',
245     action(context: EditorUiContext) {
246         context.editor.update(() => {
247             try {
248                 $cutSelectedColumnsToClipboard();
249             } catch (e: any) {
250                 context.error(e);
251             }
252         });
253     },
254     isActive: neverActive,
255     isDisabled: cellNotSelected,
256 };
257
258 export const copyColumn: EditorButtonDefinition = {
259     label: 'Copy column',
260     format: 'long',
261     action(context: EditorUiContext) {
262         context.editor.getEditorState().read(() => {
263             try {
264                 $copySelectedColumnsToClipboard();
265             } catch (e: any) {
266                 context.error(e);
267             }
268         });
269     },
270     isActive: neverActive,
271     isDisabled: cellNotSelected,
272 };
273
274 export const pasteColumnBefore: EditorButtonDefinition = {
275     label: 'Paste column before',
276     format: 'long',
277     action(context: EditorUiContext) {
278         context.editor.update(() => {
279             try {
280                 $pasteClipboardColumnsBefore(context.editor);
281             } catch (e: any) {
282                 context.error(e);
283             }
284         });
285     },
286     isActive: neverActive,
287     isDisabled: (selection) => cellNotSelected(selection) || isColumnClipboardEmpty(),
288 };
289
290 export const pasteColumnAfter: EditorButtonDefinition = {
291     label: 'Paste column after',
292     format: 'long',
293     action(context: EditorUiContext) {
294         context.editor.update(() => {
295             try {
296                 $pasteClipboardColumnsAfter(context.editor);
297             } catch (e: any) {
298                 context.error(e);
299             }
300         });
301     },
302     isActive: neverActive,
303     isDisabled: (selection) => cellNotSelected(selection) || isColumnClipboardEmpty(),
304 };
305
306 export const insertColumnBefore: EditorButtonDefinition = {
307     label: 'Insert column before',
308     icon: insertColumnBeforeIcon,
309     action(context: EditorUiContext) {
310         context.editor.update(() => {
311             $insertTableColumn__EXPERIMENTAL(false);
312         });
313     },
314     isActive() {
315         return false;
316     }
317 };
318
319 export const insertColumnAfter: EditorButtonDefinition = {
320     label: 'Insert column after',
321     icon: insertColumnAfterIcon,
322     action(context: EditorUiContext) {
323         context.editor.update(() => {
324             $insertTableColumn__EXPERIMENTAL(true);
325         });
326     },
327     isActive() {
328         return false;
329     }
330 };
331
332 export const deleteColumn: EditorButtonDefinition = {
333     label: 'Delete column',
334     icon: deleteColumnIcon,
335     action(context: EditorUiContext) {
336         context.editor.update(() => {
337             $deleteTableColumn__EXPERIMENTAL();
338         });
339     },
340     isActive() {
341         return false;
342     }
343 };
344
345 export const cellProperties: EditorButtonDefinition = {
346     label: 'Cell properties',
347     format: 'long',
348     action(context: EditorUiContext) {
349         context.editor.getEditorState().read(() => {
350             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
351             if ($isTableCellNode(cell)) {
352                 $showCellPropertiesForm(cell, context);
353             }
354         });
355     },
356     isActive: neverActive,
357     isDisabled: cellNotSelected,
358 };
359
360 export const mergeCells: EditorButtonDefinition = {
361     label: 'Merge cells',
362     format: 'long',
363     action(context: EditorUiContext) {
364         context.editor.update(() => {
365             const selection = $getSelection();
366             if ($isTableSelection(selection)) {
367                 $mergeTableCellsInSelection(selection);
368             }
369         });
370     },
371     isActive: neverActive,
372     isDisabled(selection) {
373         return !$isTableSelection(selection);
374     }
375 };
376
377 export const splitCell: EditorButtonDefinition = {
378     label: 'Split cell',
379     format: 'long',
380     action(context: EditorUiContext) {
381         context.editor.update(() => {
382             $unmergeCell();
383         });
384     },
385     isActive: neverActive,
386     isDisabled(selection) {
387         const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null;
388         if (cell) {
389             const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;
390             return !merged;
391         }
392
393         return true;
394     }
395 };