UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

481 lines (476 loc) 20.9 kB
import { tableMessages as messages } from '@atlaskit/editor-common/messages'; import { GapCursorSelection, isSelectionAtEndOfNode, isSelectionAtStartOfNode, RelativeSelectionPos, Side } from '@atlaskit/editor-common/selection'; import { Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { cellAround, findTable, isColumnSelected, isRowSelected, isTableSelected, selectedRect } from '@atlaskit/editor-tables/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { getClosestSelectionRect } from '../../ui/toolbar'; import { getPluginState } from '../plugin-factory'; import { selectColumn, selectRow } from './misc'; var TableSelectionDirection = /*#__PURE__*/function (TableSelectionDirection) { TableSelectionDirection["TopToBottom"] = "TopToBottom"; TableSelectionDirection["BottomToTop"] = "BottomToTop"; return TableSelectionDirection; }(TableSelectionDirection || {}); export var arrowLeftFromTable = function arrowLeftFromTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var selection = state.selection; if (selection instanceof CellSelection) { return arrowLeftFromCellSelection(editorSelectionAPI)(selection)(state, dispatch); } else if (selection instanceof GapCursorSelection) { return arrowLeftFromGapCursor(editorSelectionAPI)(selection)(state, dispatch); } else if (selection instanceof TextSelection) { return arrowLeftFromText(editorSelectionAPI)(selection)(state, dispatch); } return false; }; }; }; export var arrowRightFromTable = function arrowRightFromTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var selection = state.selection; if (selection instanceof CellSelection) { return arrowRightFromCellSelection(editorSelectionAPI)(selection)(state, dispatch); } else if (selection instanceof GapCursorSelection) { return arrowRightFromGapCursor(editorSelectionAPI)(selection)(state, dispatch); } else if (selection instanceof TextSelection) { return arrowRightFromText(editorSelectionAPI)(selection)(state, dispatch); } return false; }; }; }; var arrowLeftFromCellSelection = function arrowLeftFromCellSelection(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { if (isTableSelected(state.selection) && editorSelectionAPI) { var selectionState = editorSelectionAPI.sharedState.currentState() || {}; if ((selectionState === null || selectionState === void 0 ? void 0 : selectionState.selectionRelativeToNode) === RelativeSelectionPos.Start) { // we have full table cell selection and want to set gap cursor selection before table return setGapCursorBeforeTable(editorSelectionAPI)()(state, dispatch); } else if ((selectionState === null || selectionState === void 0 ? void 0 : selectionState.selectionRelativeToNode) === RelativeSelectionPos.End) { // we have full table cell selection and want to set selection at end of last cell return setSelectionAtEndOfLastCell(editorSelectionAPI)(selection)(state, dispatch); } else if ((selectionState === null || selectionState === void 0 ? void 0 : selectionState.selectionRelativeToNode) === undefined) { // we have full table cell selection and want to set selection at start of first cell return setSelectionAtStartOfFirstCell(editorSelectionAPI)(selection, RelativeSelectionPos.Before)(state, dispatch); } } return false; }; }; }; var arrowRightFromCellSelection = function arrowRightFromCellSelection(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { if (isTableSelected(state.selection) && editorSelectionAPI) { var _ref = editorSelectionAPI.sharedState.currentState() || {}, selectionRelativeToNode = _ref.selectionRelativeToNode; if (selectionRelativeToNode === RelativeSelectionPos.Start) { // we have full table cell selection and want to set selection at start of first cell return setSelectionAtStartOfFirstCell(editorSelectionAPI)(selection)(state, dispatch); } else if (selectionRelativeToNode === RelativeSelectionPos.End || selectionRelativeToNode === undefined) { // we have full table cell selection and want to set gap cursor selection after table return setGapCursorAfterTable(editorSelectionAPI)()(state, dispatch); } } return false; }; }; }; export var selectColumns = function selectColumns(editorSelectionAPI, ariaNotify, getIntl) { return function () { var triggeredByKeyboard = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return function (state, dispatch) { var selection = state.selection; var table = findTable(selection); var rect = selectedRect(state); if (table && isRowSelected(rect.top)(selection)) { return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } if (table && rect) { var selectColumnCommand = selectColumn(rect.left, undefined, triggeredByKeyboard)(state, dispatch); var map = TableMap.get(table.node); if (ariaNotify && getIntl) { ariaNotify(getIntl().formatMessage(messages.columnSelected, { index: rect.left + 1, total: map.width }), { priority: 'important' }); } return selectColumnCommand; } return false; }; }; }; export var selectRows = function selectRows(editorSelectionAPI, ariaNotify, getIntl) { return function () { var triggeredByKeyboard = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return function (state, dispatch) { var selection = state.selection; var table = findTable(selection); var rect = selectedRect(state); if (table && isColumnSelected(rect.left)(selection)) { return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } if (table && rect) { var selectRowCommand = selectRow(rect.top, undefined, triggeredByKeyboard)(state, dispatch); var map = TableMap.get(table.node); if (ariaNotify && getIntl) { ariaNotify(getIntl().formatMessage(messages.rowSelected, { index: rect.top + 1, total: map.height }), { priority: 'important' }); } return selectRowCommand; } return false; }; }; }; var arrowLeftFromGapCursor = function arrowLeftFromGapCursor(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { var doc = state.doc; var $from = selection.$from, from = selection.from, side = selection.side; if (side === Side.RIGHT) { if ($from.nodeBefore && $from.nodeBefore.type.name === 'table') { // we have a gap cursor after a table node and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: $from.nodeBefore, startPos: doc.resolve(from - 1).start($from.depth + 1), dir: TableSelectionDirection.TopToBottom })(state, dispatch); } } else if (side === Side.LEFT) { var table = findTable(selection); if (table && isSelectionAtStartOfTable($from, selection) && editorSelectionAPI) { var selectionState = editorSelectionAPI.sharedState.currentState() || {}; if ((selectionState === null || selectionState === void 0 ? void 0 : selectionState.selectionRelativeToNode) === RelativeSelectionPos.Before) { // we have a gap cursor at start of first table cell and want to set a gap cursor selection before table return setGapCursorBeforeTable(editorSelectionAPI)()(state, dispatch); } else { // we have a gap cursor at start of first table cell and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } } } return false; }; }; }; var arrowRightFromGapCursor = function arrowRightFromGapCursor(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { var $from = selection.$from, from = selection.from, $to = selection.$to, side = selection.side; if (side === Side.LEFT) { if ($from.nodeAfter && $from.nodeAfter.type.name === 'table') { // we have a gap cursor before a table node and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: $from.nodeAfter, startPos: from + 1, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } } else if (side === Side.RIGHT) { var table = findTable(selection); if (table && isSelectionAtEndOfTable($to, selection)) { // we have a gap cursor at end of last table cell and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.TopToBottom })(state, dispatch); } } return false; }; }; }; var arrowLeftFromText = function arrowLeftFromText(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { var table = findTable(selection); if (table) { var $from = selection.$from; var columResizePluginState = getPluginState(state) || {}; var isColumnResizing = Boolean(columResizePluginState === null || columResizePluginState === void 0 ? void 0 : columResizePluginState.isKeyboardResize); if (isSelectionAtStartOfTable($from, selection) && $from.parent.type.name === 'paragraph' && $from.depth === table.depth + 3 && // + 3 for: row, cell & paragraph nodes editorSelectionAPI && !isColumnResizing) { var selectionState = editorSelectionAPI.sharedState.currentState() || {}; if ((selectionState === null || selectionState === void 0 ? void 0 : selectionState.selectionRelativeToNode) === RelativeSelectionPos.Before) { // we have a text selection at start of first table cell, directly inside a top level paragraph, // and want to set gap cursor selection before table return setGapCursorBeforeTable(editorSelectionAPI)()(state, dispatch); } else { // we have a text selection at start of first table cell, directly inside a top level paragraph, // and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } } } return false; }; }; }; var arrowRightFromText = function arrowRightFromText(editorSelectionAPI) { return function (selection) { return function (state, dispatch) { var table = findTable(selection); if (table) { var $to = selection.$to; var columResizePluginState = getPluginState(state) || {}; var isColumnResizing = Boolean(columResizePluginState === null || columResizePluginState === void 0 ? void 0 : columResizePluginState.isKeyboardResize); if (isSelectionAtEndOfTable($to, selection) && $to.parent.type.name === 'paragraph' && $to.depth === table.depth + 3 && // + 3 for: row, cell & paragraph nodes !isColumnResizing) { // we have a text selection at end of last table cell, directly inside a top level paragraph, // and want to set a full table cell selection return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.TopToBottom })(state, dispatch); } } return false; }; }; }; /** * sets a cell selection over the table cell in which the selection is inside of */ var selectTableCell = function selectTableCell(state, dispatch) { var $cell = cellAround(state.selection.$from); if (!$cell) { return false; } if (dispatch) { dispatch(state.tr.setSelection(new CellSelection($cell))); return true; } return false; }; /** * Sets a cell selection over all the cells in the table node * We use this instead of selectTable from prosemirror-utils so we can control which * cell is the anchor and which is the head, and also so we can set the relative selection * pos in the selection plugin */ var selectFullTable = function selectFullTable(editorSelectionAPI) { return function (_ref2) { var node = _ref2.node, startPos = _ref2.startPos, dir = _ref2.dir; return function (state, dispatch) { var doc = state.doc; var _TableMap$get = TableMap.get(node), map = _TableMap$get.map; var $firstCell = doc.resolve(startPos + map[0]); var $lastCell = doc.resolve(startPos + map[map.length - 1]); var fullTableSelection; var selectionRelativeToNode; if (dir === TableSelectionDirection.TopToBottom) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any fullTableSelection = new CellSelection($firstCell, $lastCell); selectionRelativeToNode = RelativeSelectionPos.End; } else { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any fullTableSelection = new CellSelection($lastCell, $firstCell); selectionRelativeToNode = RelativeSelectionPos.Start; } if (editorSelectionAPI) { var tr = editorSelectionAPI.actions.selectNearNode({ selectionRelativeToNode: selectionRelativeToNode, selection: fullTableSelection })(state); if (dispatch) { dispatch(tr); return true; } } return false; }; }; }; var setSelectionAtStartOfFirstCell = function setSelectionAtStartOfFirstCell(editorSelectionAPI) { return function (selection, selectionRelativeToNode) { return function (state, dispatch) { var $anchorCell = selection.$anchorCell, $headCell = selection.$headCell; var $firstCell = $anchorCell.pos < $headCell.pos ? $anchorCell : $headCell; var $firstPosInsideCell = state.doc.resolve($firstCell.pos + 1); // check if first pos should have a gap cursor, otherwise find closest text selection var selectionAtStartOfCell = GapCursorSelection.valid($firstPosInsideCell) ? new GapCursorSelection($firstPosInsideCell, Side.LEFT) : Selection.findFrom($firstPosInsideCell, 1); if (editorSelectionAPI) { var tr = editorSelectionAPI.actions.selectNearNode({ selectionRelativeToNode: selectionRelativeToNode, selection: selectionAtStartOfCell })(state); if (dispatch) { dispatch(tr); return true; } } return false; }; }; }; var setSelectionAtEndOfLastCell = function setSelectionAtEndOfLastCell(editorSelectionAPI) { return function (selection, selectionRelativeToNode) { return function (state, dispatch) { var $anchorCell = selection.$anchorCell, $headCell = selection.$headCell; var $lastCell = $anchorCell.pos > $headCell.pos ? $anchorCell : $headCell; var lastPosInsideCell = $lastCell.pos + ($lastCell.nodeAfter ? $lastCell.nodeAfter.content.size : 0) + 1; var $lastPosInsideCell = state.doc.resolve(lastPosInsideCell); // check if last pos should have a gap cursor, otherwise find closest text selection var selectionAtEndOfCell = GapCursorSelection.valid($lastPosInsideCell) ? new GapCursorSelection($lastPosInsideCell, Side.RIGHT) : Selection.findFrom($lastPosInsideCell, -1); if (editorSelectionAPI) { var tr = editorSelectionAPI.actions.selectNearNode({ selectionRelativeToNode: selectionRelativeToNode, selection: selectionAtEndOfCell })(state); if (dispatch) { dispatch(tr); return true; } } return false; }; }; }; var setGapCursorBeforeTable = function setGapCursorBeforeTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var table = findTable(state.selection); if (table) { var $beforeTablePos = state.doc.resolve(table.pos); if (GapCursorSelection.valid($beforeTablePos)) { var selectionBeforeTable = new GapCursorSelection($beforeTablePos, Side.LEFT); if (editorSelectionAPI) { var tr = editorSelectionAPI.actions.selectNearNode({ selectionRelativeToNode: undefined, selection: selectionBeforeTable })(state); if (dispatch) { dispatch(tr); return true; } } } } return false; }; }; }; var setGapCursorAfterTable = function setGapCursorAfterTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var table = findTable(state.selection); if (table) { var $afterTablePos = state.doc.resolve(table.pos + table.node.nodeSize); if (GapCursorSelection.valid($afterTablePos)) { var selectionAfterTable = new GapCursorSelection($afterTablePos, Side.RIGHT); if (editorSelectionAPI) { var tr = editorSelectionAPI.actions.selectNearNode({ selectionRelativeToNode: undefined, selection: selectionAfterTable })(state); if (dispatch) { dispatch(tr); return true; } } return false; } } return false; }; }; }; var isSelectionAtStartOfTable = function isSelectionAtStartOfTable($pos, selection) { var _findTable; return isSelectionAtStartOfNode($pos, (_findTable = findTable(selection)) === null || _findTable === void 0 ? void 0 : _findTable.node); }; var isSelectionAtEndOfTable = function isSelectionAtEndOfTable($pos, selection) { var _findTable2; return isSelectionAtEndOfNode($pos, (_findTable2 = findTable(selection)) === null || _findTable2 === void 0 ? void 0 : _findTable2.node); }; export var shiftArrowUpFromTable = function shiftArrowUpFromTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var selection = state.selection; var table = findTable(selection); var selectionRect = getClosestSelectionRect(state); var index = selectionRect === null || selectionRect === void 0 ? void 0 : selectionRect.top; if (table && index === 0) { return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } return false; }; }; }; export var modASelectTable = function modASelectTable(editorSelectionAPI) { return function () { return function (state, dispatch) { var selection = state.selection; var table = findTable(selection); if (!table) { return false; } var $from = selection.$from, $to = selection.$to; var tableSelected = isTableSelected(selection); var isCellSelection = selection instanceof CellSelection; // if no cells are selected if (!isCellSelection && expValEquals('platform_editor_lovability_select_all_shortcut', 'isEnabled', true)) { return selectTableCell(state, dispatch); } // else if any number of cells are selected but not the full table else if (!tableSelected && $from.pos > table.start + 1 && $to.pos < table.start + table.node.nodeSize) { return selectFullTable(editorSelectionAPI)({ node: table.node, startPos: table.start, dir: TableSelectionDirection.BottomToTop })(state, dispatch); } return false; }; }; };