]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/defaults/forms/tables.ts
Lexical: Linked table form to have caption toggle option
[bookstack] / resources / js / wysiwyg / ui / defaults / forms / tables.ts
1 import {
2     EditorFormDefinition,
3     EditorFormFieldDefinition, EditorFormFields,
4     EditorFormTabs,
5     EditorSelectFormFieldDefinition
6 } from "../../framework/forms";
7 import {EditorUiContext} from "../../framework/core";
8 import {EditorFormModal} from "../../framework/modals";
9 import {$getSelection} from "lexical";
10 import {
11     $forEachTableCell, $getCellPaddingForTable,
12     $getTableCellColumnWidth,
13     $getTableCellsFromSelection, $getTableFromSelection,
14     $getTableRowsFromSelection,
15     $setTableCellColumnWidth
16 } from "../../../utils/tables";
17 import {formatSizeValue} from "../../../utils/dom";
18 import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
19 import {CommonBlockAlignment} from "lexical/nodes/common";
20 import {colorFieldBuilder} from "../../framework/blocks/color-field";
21 import {$addCaptionToTable, $isCaptionNode, $tableHasCaption} from "@lexical/table/LexicalCaptionNode";
22
23 const borderStyleInput: EditorSelectFormFieldDefinition = {
24     label: 'Border style',
25     name: 'border_style',
26     type: 'select',
27     valuesByLabel: {
28         'Select...': '',
29         "Solid": 'solid',
30         "Dotted": 'dotted',
31         "Dashed": 'dashed',
32         "Double": 'double',
33         "Groove": 'groove',
34         "Ridge": 'ridge',
35         "Inset": 'inset',
36         "Outset": 'outset',
37         "None": 'none',
38         "Hidden": 'hidden',
39     }
40 };
41
42 const borderColorInput: EditorFormFieldDefinition = {
43     label: 'Border color',
44     name: 'border_color',
45     type: 'text',
46 };
47
48 const backgroundColorInput: EditorFormFieldDefinition = {
49     label: 'Background color',
50     name: 'background_color',
51     type: 'text',
52 };
53
54 const alignmentInput: EditorSelectFormFieldDefinition = {
55     label: 'Alignment',
56     name: 'align',
57     type: 'select',
58     valuesByLabel: {
59         'None': '',
60         'Left': 'left',
61         'Center': 'center',
62         'Right': 'right',
63     }
64 };
65
66 export function $showCellPropertiesForm(cell: TableCellNode, context: EditorUiContext): EditorFormModal {
67     const styles = cell.getStyles();
68     const modalForm = context.manager.createModal('cell_properties');
69     modalForm.show({
70         width: $getTableCellColumnWidth(context.editor, cell),
71         height: styles.get('height') || '',
72         type: cell.getTag(),
73         h_align: cell.getAlignment(),
74         v_align: styles.get('vertical-align') || '',
75         border_width: styles.get('border-width') || '',
76         border_style: styles.get('border-style') || '',
77         border_color: styles.get('border-color') || '',
78         background_color: styles.get('background-color') || '',
79     });
80     return modalForm;
81 }
82
83 export const cellProperties: EditorFormDefinition = {
84     submitText: 'Save',
85     async action(formData, context: EditorUiContext) {
86         context.editor.update(() => {
87             const cells = $getTableCellsFromSelection($getSelection());
88             for (const cell of cells) {
89                 const width = formData.get('width')?.toString() || '';
90
91                 $setTableCellColumnWidth(cell, width);
92                 cell.updateTag(formData.get('type')?.toString() || '');
93                 cell.setAlignment((formData.get('h_align')?.toString() || '') as CommonBlockAlignment);
94
95                 const styles = cell.getStyles();
96                 styles.set('height', formatSizeValue(formData.get('height')?.toString() || ''));
97                 styles.set('vertical-align', formData.get('v_align')?.toString() || '');
98                 styles.set('border-width', formatSizeValue(formData.get('border_width')?.toString() || ''));
99                 styles.set('border-style', formData.get('border_style')?.toString() || '');
100                 styles.set('border-color', formData.get('border_color')?.toString() || '');
101                 styles.set('background-color', formData.get('background_color')?.toString() || '');
102
103                 cell.setStyles(styles);
104             }
105         });
106
107         return true;
108     },
109     fields: [
110         {
111             build() {
112                 const generalFields: EditorFormFieldDefinition[] = [
113                     {
114                         label: 'Width', // Colgroup width
115                         name: 'width',
116                         type: 'text',
117                     },
118                     {
119                         label: 'Height', // inline-style: height
120                         name: 'height',
121                         type: 'text',
122                     },
123                     {
124                         label: 'Cell type', // element
125                         name: 'type',
126                         type: 'select',
127                         valuesByLabel: {
128                             'Cell': 'td',
129                             'Header cell': 'th',
130                         }
131                     } as EditorSelectFormFieldDefinition,
132                     {
133                         ...alignmentInput, // class: 'align-right/left/center'
134                         label: 'Horizontal align',
135                         name: 'h_align',
136                     },
137                     {
138                         label: 'Vertical align', // inline-style: vertical-align
139                         name: 'v_align',
140                         type: 'select',
141                         valuesByLabel: {
142                             'None': '',
143                             'Top': 'top',
144                             'Middle': 'middle',
145                             'Bottom': 'bottom',
146                         }
147                     } as EditorSelectFormFieldDefinition,
148                 ];
149
150                 const advancedFields: EditorFormFields = [
151                     {
152                         label: 'Border width', // inline-style: border-width
153                         name: 'border_width',
154                         type: 'text',
155                     },
156                     borderStyleInput, // inline-style: border-style
157                     colorFieldBuilder(borderColorInput),
158                     colorFieldBuilder(backgroundColorInput),
159                 ];
160
161                 return new EditorFormTabs([
162                     {
163                         label: 'General',
164                         contents: generalFields,
165                     },
166                     {
167                         label: 'Advanced',
168                         contents: advancedFields,
169                     }
170                 ])
171             }
172         },
173     ],
174 };
175
176 export function $showRowPropertiesForm(row: TableRowNode, context: EditorUiContext): EditorFormModal {
177     const styles = row.getStyles();
178     const modalForm = context.manager.createModal('row_properties');
179     modalForm.show({
180         height: styles.get('height') || '',
181         border_style: styles.get('border-style') || '',
182         border_color: styles.get('border-color') || '',
183         background_color: styles.get('background-color') || '',
184     });
185     return modalForm;
186 }
187
188 export const rowProperties: EditorFormDefinition = {
189     submitText: 'Save',
190     async action(formData, context: EditorUiContext) {
191         context.editor.update(() => {
192             const rows = $getTableRowsFromSelection($getSelection());
193             for (const row of rows) {
194                 const styles = row.getStyles();
195                 styles.set('height', formatSizeValue(formData.get('height')?.toString() || ''));
196                 styles.set('border-style', formData.get('border_style')?.toString() || '');
197                 styles.set('border-color', formData.get('border_color')?.toString() || '');
198                 styles.set('background-color', formData.get('background_color')?.toString() || '');
199                 row.setStyles(styles);
200             }
201         });
202         return true;
203     },
204     fields: [
205         // Removed fields:
206         // Removed 'Row Type' as we don't currently support thead/tfoot elements
207         //  TinyMCE would move rows up/down into these parents when set
208         // Removed 'Alignment' since this was broken in our editor (applied alignment class to whole parent table)
209         {
210             label: 'Height', // style on tr: height
211             name: 'height',
212             type: 'text',
213         },
214         borderStyleInput, // style on tr: height
215         colorFieldBuilder(borderColorInput),
216         colorFieldBuilder(backgroundColorInput),
217     ],
218 };
219
220 export function $showTablePropertiesForm(table: TableNode, context: EditorUiContext): EditorFormModal {
221     const styles = table.getStyles();
222     const modalForm = context.manager.createModal('table_properties');
223
224     modalForm.show({
225         width: styles.get('width') || '',
226         height: styles.get('height') || '',
227         cell_spacing: styles.get('cell-spacing') || '',
228         cell_padding: $getCellPaddingForTable(table),
229         border_width: styles.get('border-width') || '',
230         border_style: styles.get('border-style') || '',
231         border_color: styles.get('border-color') || '',
232         background_color: styles.get('background-color') || '',
233         caption: $tableHasCaption(table) ? 'true' : '',
234         align: table.getAlignment(),
235     });
236     return modalForm;
237 }
238
239 export const tableProperties: EditorFormDefinition = {
240     submitText: 'Save',
241     async action(formData, context: EditorUiContext) {
242         context.editor.update(() => {
243             const table = $getTableFromSelection($getSelection());
244             if (!table) {
245                 return;
246             }
247
248             const styles = table.getStyles();
249             styles.set('width', formatSizeValue(formData.get('width')?.toString() || ''));
250             styles.set('height', formatSizeValue(formData.get('height')?.toString() || ''));
251             styles.set('cell-spacing', formatSizeValue(formData.get('cell_spacing')?.toString() || ''));
252             styles.set('border-width', formatSizeValue(formData.get('border_width')?.toString() || ''));
253             styles.set('border-style', formData.get('border_style')?.toString() || '');
254             styles.set('border-color', formData.get('border_color')?.toString() || '');
255             styles.set('background-color', formData.get('background_color')?.toString() || '');
256             table.setStyles(styles);
257
258             table.setAlignment(formData.get('align') as CommonBlockAlignment);
259
260             const cellPadding = (formData.get('cell_padding')?.toString() || '');
261             if (cellPadding) {
262                 const cellPaddingFormatted = formatSizeValue(cellPadding);
263                 $forEachTableCell(table, (cell: TableCellNode) => {
264                     const styles = cell.getStyles();
265                     styles.set('padding', cellPaddingFormatted);
266                     cell.setStyles(styles);
267                 });
268             }
269
270             const showCaption = Boolean(formData.get('caption')?.toString() || '');
271             const hasCaption = $tableHasCaption(table);
272             if (showCaption && !hasCaption) {
273                 $addCaptionToTable(table, context.translate('Caption'));
274             } else if (!showCaption && hasCaption) {
275                 for (const child of table.getChildren()) {
276                     if ($isCaptionNode(child)) {
277                         child.remove();
278                     }
279                 }
280             }
281         });
282         return true;
283     },
284     fields: [
285         {
286             build() {
287                 const generalFields: EditorFormFieldDefinition[] = [
288                     {
289                         label: 'Width', // Style - width
290                         name: 'width',
291                         type: 'text',
292                     },
293                     {
294                         label: 'Height', // Style - height
295                         name: 'height',
296                         type: 'text',
297                     },
298                     {
299                         label: 'Cell spacing', // Style - border-spacing
300                         name: 'cell_spacing',
301                         type: 'text',
302                     },
303                     {
304                         label: 'Cell padding', // Style - padding on child cells?
305                         name: 'cell_padding',
306                         type: 'text',
307                     },
308                     {
309                         label: 'Border width', // Style - border-width
310                         name: 'border_width',
311                         type: 'text',
312                     },
313                     {
314                         label: 'Show caption', // Caption element
315                         name: 'caption',
316                         type: 'checkbox',
317                     },
318                     alignmentInput, // alignment class
319                 ];
320
321                 const advancedFields: EditorFormFields = [
322                     borderStyleInput,
323                     colorFieldBuilder(borderColorInput),
324                     colorFieldBuilder(backgroundColorInput),
325                 ];
326
327                 return new EditorFormTabs([
328                     {
329                         label: 'General',
330                         contents: generalFields,
331                     },
332                     {
333                         label: 'Advanced',
334                         contents: advancedFields,
335                     }
336                 ])
337             }
338         },
339     ],
340 };