@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
481 lines (476 loc) • 20.9 kB
JavaScript
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;
};
};
};