BookStackApp/BookStack

View on GitHub
resources/js/wysiwyg/ui/defaults/buttons/tables.ts

Summary

Maintainability
F
1 wk
Test Coverage
import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
import tableIcon from "@icons/editor/table.svg";
import deleteIcon from "@icons/editor/table-delete.svg";
import deleteColumnIcon from "@icons/editor/table-delete-column.svg";
import deleteRowIcon from "@icons/editor/table-delete-row.svg";
import insertColumnAfterIcon from "@icons/editor/table-insert-column-after.svg";
import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg";
import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
import {EditorUiContext} from "../../framework/core";
import {$getSelection, BaseSelection} from "lexical";
import {$isCustomTableNode} from "../../../nodes/custom-table";
import {
    $deleteTableColumn__EXPERIMENTAL,
    $deleteTableRow__EXPERIMENTAL,
    $insertTableColumn__EXPERIMENTAL,
    $insertTableRow__EXPERIMENTAL,
    $isTableNode, $isTableSelection, $unmergeCell, TableCellNode,
} from "@lexical/table";
import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
import {$getParentOfType} from "../../../utils/nodes";
import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell";
import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables";
import {
    $clearTableFormatting,
    $clearTableSizes, $getTableFromSelection,
    $getTableRowsFromSelection,
    $mergeTableCellsInSelection
} from "../../../utils/tables";
import {$isCustomTableRowNode} from "../../../nodes/custom-table-row";
import {
    $copySelectedColumnsToClipboard,
    $copySelectedRowsToClipboard,
    $cutSelectedColumnsToClipboard,
    $cutSelectedRowsToClipboard,
    $pasteClipboardRowsBefore,
    $pasteClipboardRowsAfter,
    isColumnClipboardEmpty,
    isRowClipboardEmpty,
    $pasteClipboardColumnsBefore, $pasteClipboardColumnsAfter
} from "../../../utils/table-copy-paste";

const neverActive = (): boolean => false;
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode);

export const table: EditorBasicButtonDefinition = {
    label: 'Table',
    icon: tableIcon,
};

export const tableProperties: EditorButtonDefinition = {
    label: 'Table properties',
    icon: tableIcon,
    action(context: EditorUiContext) {
        context.editor.getEditorState().read(() => {
            const table = $getTableFromSelection($getSelection());
            if ($isCustomTableNode(table)) {
                $showTablePropertiesForm(table, context);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const clearTableFormatting: EditorButtonDefinition = {
    label: 'Clear table formatting',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
            if (!$isCustomTableCellNode(cell)) {
                return;
            }

            const table = $getParentOfType(cell, $isTableNode);
            if ($isCustomTableNode(table)) {
                $clearTableFormatting(table);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const resizeTableToContents: EditorButtonDefinition = {
    label: 'Resize to contents',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
            if (!$isCustomTableCellNode(cell)) {
                return;
            }

            const table = $getParentOfType(cell, $isCustomTableNode);
            if ($isCustomTableNode(table)) {
                $clearTableSizes(table);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const deleteTable: EditorButtonDefinition = {
    label: 'Delete table',
    icon: deleteIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            const table = $getNodeFromSelection($getSelection(), $isCustomTableNode);
            if (table) {
                table.remove();
            }
        });
    },
    isActive() {
        return false;
    }
};

export const deleteTableMenuAction: EditorButtonDefinition = {
    ...deleteTable,
    format: 'long',
    isDisabled(selection) {
        return !$selectionContainsNodeType(selection, $isTableNode);
    },
};

export const insertRowAbove: EditorButtonDefinition = {
    label: 'Insert row before',
    icon: insertRowAboveIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $insertTableRow__EXPERIMENTAL(false);
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const insertRowBelow: EditorButtonDefinition = {
    label: 'Insert row after',
    icon: insertRowBelowIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $insertTableRow__EXPERIMENTAL(true);
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const deleteRow: EditorButtonDefinition = {
    label: 'Delete row',
    icon: deleteRowIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $deleteTableRow__EXPERIMENTAL();
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const rowProperties: EditorButtonDefinition = {
    label: 'Row properties',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.getEditorState().read(() => {
            const rows = $getTableRowsFromSelection($getSelection());
            if ($isCustomTableRowNode(rows[0])) {
                $showRowPropertiesForm(rows[0], context);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const cutRow: EditorButtonDefinition = {
    label: 'Cut row',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $cutSelectedRowsToClipboard();
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const copyRow: EditorButtonDefinition = {
    label: 'Copy row',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.getEditorState().read(() => {
            try {
                $copySelectedRowsToClipboard();
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const pasteRowBefore: EditorButtonDefinition = {
    label: 'Paste row before',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $pasteClipboardRowsBefore(context.editor);
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
};

export const pasteRowAfter: EditorButtonDefinition = {
    label: 'Paste row after',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $pasteClipboardRowsAfter(context.editor);
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
};

export const cutColumn: EditorButtonDefinition = {
    label: 'Cut column',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $cutSelectedColumnsToClipboard();
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const copyColumn: EditorButtonDefinition = {
    label: 'Copy column',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.getEditorState().read(() => {
            try {
                $copySelectedColumnsToClipboard();
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const pasteColumnBefore: EditorButtonDefinition = {
    label: 'Paste column before',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $pasteClipboardColumnsBefore(context.editor);
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: (selection) => cellNotSelected(selection) || isColumnClipboardEmpty(),
};

export const pasteColumnAfter: EditorButtonDefinition = {
    label: 'Paste column after',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            try {
                $pasteClipboardColumnsAfter(context.editor);
            } catch (e: any) {
                context.error(e);
            }
        });
    },
    isActive: neverActive,
    isDisabled: (selection) => cellNotSelected(selection) || isColumnClipboardEmpty(),
};

export const insertColumnBefore: EditorButtonDefinition = {
    label: 'Insert column before',
    icon: insertColumnBeforeIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $insertTableColumn__EXPERIMENTAL(false);
        });
    },
    isActive() {
        return false;
    }
};

export const insertColumnAfter: EditorButtonDefinition = {
    label: 'Insert column after',
    icon: insertColumnAfterIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $insertTableColumn__EXPERIMENTAL(true);
        });
    },
    isActive() {
        return false;
    }
};

export const deleteColumn: EditorButtonDefinition = {
    label: 'Delete column',
    icon: deleteColumnIcon,
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $deleteTableColumn__EXPERIMENTAL();
        });
    },
    isActive() {
        return false;
    }
};

export const cellProperties: EditorButtonDefinition = {
    label: 'Cell properties',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.getEditorState().read(() => {
            const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
            if ($isCustomTableCellNode(cell)) {
                $showCellPropertiesForm(cell, context);
            }
        });
    },
    isActive: neverActive,
    isDisabled: cellNotSelected,
};

export const mergeCells: EditorButtonDefinition = {
    label: 'Merge cells',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            const selection = $getSelection();
            if ($isTableSelection(selection)) {
                $mergeTableCellsInSelection(selection);
            }
        });
    },
    isActive: neverActive,
    isDisabled(selection) {
        return !$isTableSelection(selection);
    }
};

export const splitCell: EditorButtonDefinition = {
    label: 'Split cell',
    format: 'long',
    action(context: EditorUiContext) {
        context.editor.update(() => {
            $unmergeCell();
        });
    },
    isActive: neverActive,
    isDisabled(selection) {
        const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null;
        if (cell) {
            const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;
            return !merged;
        }

        return true;
    }
};