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