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