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