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 {$createNodeSelection, $createRangeSelection, $getSelection, BaseSelection} from "lexical";
12 import {$isCustomTableNode} from "../../../nodes/custom-table";
14 $deleteTableColumn__EXPERIMENTAL,
15 $deleteTableRow__EXPERIMENTAL,
16 $insertTableColumn__EXPERIMENTAL,
17 $insertTableRow__EXPERIMENTAL,
18 $isTableNode, $isTableRowNode, $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} from "../forms/tables";
24 import {$getTableRowsFromSelection, $mergeTableCellsInSelection} from "../../../utils/tables";
25 import {$isCustomTableRowNode, CustomTableRowNode} from "../../../nodes/custom-table-row";
26 import {NodeClipboard} from "../../../services/node-clipboard";
27 import {r} from "@codemirror/legacy-modes/mode/r";
28 import {$generateHtmlFromNodes} from "@lexical/html";
30 const neverActive = (): boolean => false;
31 const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode);
33 export const table: EditorBasicButtonDefinition = {
38 export const tableProperties: EditorButtonDefinition = {
39 label: 'Table properties',
41 action(context: EditorUiContext) {
42 context.editor.getEditorState().read(() => {
43 const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
44 if (!$isCustomTableCellNode(cell)) {
48 const table = $getParentOfType(cell, $isTableNode);
49 const modalForm = context.manager.createModal('table_properties');
54 isActive: neverActive,
55 isDisabled: cellNotSelected,
58 export const clearTableFormatting: EditorButtonDefinition = {
59 label: 'Clear table formatting',
61 action(context: EditorUiContext) {
62 context.editor.getEditorState().read(() => {
63 const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
64 if (!$isCustomTableCellNode(cell)) {
68 const table = $getParentOfType(cell, $isTableNode);
72 isActive: neverActive,
73 isDisabled: cellNotSelected,
76 export const resizeTableToContents: EditorButtonDefinition = {
77 label: 'Resize to contents',
79 action(context: EditorUiContext) {
80 context.editor.getEditorState().read(() => {
81 const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
82 if (!$isCustomTableCellNode(cell)) {
86 const table = $getParentOfType(cell, $isCustomTableNode);
87 if (!$isCustomTableNode(table)) {
91 for (const row of table.getChildren()) {
92 if ($isTableRowNode(row)) {
93 // TODO - Come back later as this may depend on if we
94 // are using a custom table row
99 isActive: neverActive,
100 isDisabled: cellNotSelected,
103 export const deleteTable: EditorButtonDefinition = {
104 label: 'Delete table',
106 action(context: EditorUiContext) {
107 context.editor.update(() => {
108 const table = $getNodeFromSelection($getSelection(), $isCustomTableNode);
119 export const deleteTableMenuAction: EditorButtonDefinition = {
122 isDisabled(selection) {
123 return !$selectionContainsNodeType(selection, $isTableNode);
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);
135 isActive: neverActive,
136 isDisabled: cellNotSelected,
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);
147 isActive: neverActive,
148 isDisabled: cellNotSelected,
151 export const deleteRow: EditorButtonDefinition = {
154 action(context: EditorUiContext) {
155 context.editor.update(() => {
156 $deleteTableRow__EXPERIMENTAL();
159 isActive: neverActive,
160 isDisabled: cellNotSelected,
163 export const rowProperties: EditorButtonDefinition = {
164 label: 'Row properties',
166 action(context: EditorUiContext) {
167 context.editor.getEditorState().read(() => {
168 const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
169 if (!$isCustomTableCellNode(cell)) {
173 const row = $getParentOfType(cell, $isCustomTableRowNode);
174 if ($isCustomTableRowNode(row)) {
175 $showRowPropertiesForm(row, context);
179 isActive: neverActive,
180 isDisabled: cellNotSelected,
183 const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(CustomTableRowNode);
185 export const cutRow: EditorButtonDefinition = {
188 action(context: EditorUiContext) {
189 context.editor.update(() => {
190 const rows = $getTableRowsFromSelection($getSelection());
191 rowClipboard.set(...rows);
192 for (const row of rows) {
197 isActive: neverActive,
198 isDisabled: cellNotSelected,
201 export const copyRow: EditorButtonDefinition = {
204 action(context: EditorUiContext) {
205 context.editor.getEditorState().read(() => {
206 const rows = $getTableRowsFromSelection($getSelection());
207 rowClipboard.set(...rows);
210 isActive: neverActive,
211 isDisabled: cellNotSelected,
214 export const pasteRowBefore: EditorButtonDefinition = {
215 label: 'Paste row before',
217 action(context: EditorUiContext) {
218 context.editor.update(() => {
219 const rows = $getTableRowsFromSelection($getSelection());
220 const lastRow = rows[rows.length - 1];
222 for (const row of rowClipboard.get(context.editor)) {
223 lastRow.insertBefore(row);
228 isActive: neverActive,
229 isDisabled: (selection) => cellNotSelected(selection) || rowClipboard.size() === 0,
232 export const pasteRowAfter: EditorButtonDefinition = {
233 label: 'Paste row after',
235 action(context: EditorUiContext) {
236 context.editor.update(() => {
237 const rows = $getTableRowsFromSelection($getSelection());
238 const lastRow = rows[rows.length - 1];
240 for (const row of rowClipboard.get(context.editor).reverse()) {
241 lastRow.insertAfter(row);
246 isActive: neverActive,
247 isDisabled: (selection) => cellNotSelected(selection) || rowClipboard.size() === 0,
250 export const cutColumn: EditorButtonDefinition = {
253 action(context: EditorUiContext) {
254 context.editor.getEditorState().read(() => {
258 isActive: neverActive,
259 isDisabled: cellNotSelected,
262 export const copyColumn: EditorButtonDefinition = {
263 label: 'Copy column',
265 action(context: EditorUiContext) {
266 context.editor.getEditorState().read(() => {
270 isActive: neverActive,
271 isDisabled: cellNotSelected,
274 export const pasteColumnBefore: EditorButtonDefinition = {
275 label: 'Paste column before',
277 action(context: EditorUiContext) {
278 context.editor.getEditorState().read(() => {
282 isActive: neverActive,
283 isDisabled: cellNotSelected,
286 export const pasteColumnAfter: EditorButtonDefinition = {
287 label: 'Paste column after',
289 action(context: EditorUiContext) {
290 context.editor.getEditorState().read(() => {
294 isActive: neverActive,
295 isDisabled: cellNotSelected,
298 export const insertColumnBefore: EditorButtonDefinition = {
299 label: 'Insert column before',
300 icon: insertColumnBeforeIcon,
301 action(context: EditorUiContext) {
302 context.editor.update(() => {
303 $insertTableColumn__EXPERIMENTAL(false);
311 export const insertColumnAfter: EditorButtonDefinition = {
312 label: 'Insert column after',
313 icon: insertColumnAfterIcon,
314 action(context: EditorUiContext) {
315 context.editor.update(() => {
316 $insertTableColumn__EXPERIMENTAL(true);
324 export const deleteColumn: EditorButtonDefinition = {
325 label: 'Delete column',
326 icon: deleteColumnIcon,
327 action(context: EditorUiContext) {
328 context.editor.update(() => {
329 $deleteTableColumn__EXPERIMENTAL();
337 export const cellProperties: EditorButtonDefinition = {
338 label: 'Cell properties',
339 action(context: EditorUiContext) {
340 context.editor.getEditorState().read(() => {
341 const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
342 if ($isCustomTableCellNode(cell)) {
343 $showCellPropertiesForm(cell, context);
347 isActive: neverActive,
348 isDisabled: cellNotSelected,
351 export const mergeCells: EditorButtonDefinition = {
352 label: 'Merge cells',
353 action(context: EditorUiContext) {
354 context.editor.update(() => {
355 const selection = $getSelection();
356 if ($isTableSelection(selection)) {
357 $mergeTableCellsInSelection(selection);
361 isActive: neverActive,
362 isDisabled(selection) {
363 return !$isTableSelection(selection);
367 export const splitCell: EditorButtonDefinition = {
369 action(context: EditorUiContext) {
370 context.editor.update(() => {
374 isActive: neverActive,
375 isDisabled(selection) {
376 const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null;
378 const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;