UNPKG

@atlaskit/editor-plugin-expand

Version:

Expand plugin for @atlaskit/editor-core

226 lines (223 loc) 8.98 kB
import { getNextNodeExpandPos, isExpandCollapsed } from '@atlaskit/editor-common/expand'; import { backspace, bindKeymapWithCommand, moveDown, moveLeft, moveRight, moveUp, tab } from '@atlaskit/editor-common/keymaps'; import { GapCursorSelection, RelativeSelectionPos, Side } from '@atlaskit/editor-common/selection'; import { findExpand } from '@atlaskit/editor-common/transforms'; import { isEmptyNode, isPositionNearTableRow } from '@atlaskit/editor-common/utils'; import { keymap } from '@atlaskit/editor-prosemirror/keymap'; import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { isInTable } from '@atlaskit/editor-tables/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { deleteExpand, focusIcon, focusTitle } from '../commands'; const isExpandNode = node => { return (node === null || node === void 0 ? void 0 : node.type.name) === 'expand' || (node === null || node === void 0 ? void 0 : node.type.name) === 'nestedExpand'; }; const isExpandSelected = selection => selection instanceof NodeSelection && isExpandNode(selection.node); export function expandKeymap(api) { const list = {}; bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moveRight.common, (state, dispatch, editorView) => { var _api$selection; if (!editorView) { return false; } const { selection } = state; const selectionSharedState = (api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : _api$selection.sharedState.currentState()) || {}; const { selectionRelativeToNode } = selectionSharedState; if (isExpandSelected(selection) && selectionRelativeToNode === RelativeSelectionPos.Start) { return focusTitle(selection.from + 1)(state, dispatch, editorView); } return false; }, list); bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moveLeft.common, (state, dispatch, editorView) => { var _api$selection2; if (!editorView) { return false; } const { selection } = state; const selectionSharedState = (api === null || api === void 0 ? void 0 : (_api$selection2 = api.selection) === null || _api$selection2 === void 0 ? void 0 : _api$selection2.sharedState.currentState()) || {}; const { selectionRelativeToNode } = selectionSharedState; if (isExpandSelected(selection) && (selectionRelativeToNode === undefined || selectionRelativeToNode === RelativeSelectionPos.End)) { return focusTitle(selection.from + 1)(state, dispatch, editorView); } return false; }, list); bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion tab.common, (state, dispatch, editorView) => { if (editorView && editorView.dom instanceof HTMLElement) { const { from } = state.selection; // if the node selected is an expand if (isExpandSelected(state.selection)) { const expand = editorView.nodeDOM(from); if (!expand) { return false; } return focusIcon(expand)(state, dispatch, editorView); } // if the text selection is inside an expand else if (state.selection instanceof TextSelection && !isInTable(state)) { const expand = findExpand(state); if (expand) { const expandNode = editorView.nodeDOM(expand.pos); if (expandNode) { return focusIcon(expandNode)(state, dispatch, editorView); } } } } return false; }, list); bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moveUp.common, (state, dispatch, editorView) => { if (!editorView) { return false; } const { selection, schema } = state; const { nodeBefore } = selection.$from; if (selection instanceof GapCursorSelection && selection.side === Side.RIGHT && nodeBefore && (nodeBefore.type === schema.nodes.expand || nodeBefore.type === schema.nodes.nestedExpand) && isExpandCollapsed(nodeBefore)) { const { $from } = selection; return focusTitle(Math.max($from.pos - 1, 0))(state, dispatch, editorView); } const { $from } = state.selection; if (editorView.endOfTextblock('up')) { const expand = findExpand(state); // Moving UP in a table should move the cursor to the row above // however when an expand is in a table cell to the left of the // current table cell, arrow UP moves the cursor to the left // see ED-15425 if (isPositionNearTableRow($from, schema, 'before') && !expand) { return false; } const prevCursorPos = Math.max($from.pos - $from.parentOffset - 1, 0); // move cursor from expand's content to its title if (expand && expand.start === prevCursorPos) { return focusTitle(expand.start)(state, dispatch, editorView); } const sel = Selection.findFrom(state.doc.resolve(prevCursorPos), -1); const expandBefore = findExpand(state, sel); if (sel && expandBefore) { // moving cursor from outside of an expand to the title when it is collapsed if (isExpandCollapsed(expandBefore.node)) { return focusTitle(expandBefore.start)(state, dispatch, editorView); } // moving cursor from outside of an expand to the content when it is expanded else if (dispatch) { dispatch(state.tr.setSelection(sel)); } return true; } } return false; }, list); bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion moveDown.common, (state, dispatch, editorView) => { if (!editorView) { return false; } const { expand, nestedExpand } = state.schema.nodes; const { selection } = state; const { nodeAfter } = selection.$from; if (selection instanceof GapCursorSelection && selection.side === Side.LEFT && nodeAfter && (nodeAfter.type === expand || nodeAfter.type === nestedExpand) && isExpandCollapsed(nodeAfter)) { const { $from } = selection; return focusTitle($from.pos + 1)(state, dispatch, editorView); } if (expValEquals('platform_editor_lovability_navigation_fixes', 'isEnabled', true)) { const nextExpandPos = getNextNodeExpandPos(editorView, selection); if (nextExpandPos !== undefined) { return focusTitle(nextExpandPos)(state, dispatch, editorView); } } if (editorView.endOfTextblock('down')) { const { $from } = state.selection; if ($from.depth === 0) { return false; } const $after = state.doc.resolve($from.after()); if ($after.nodeAfter && ($after.nodeAfter.type === expand || $after.nodeAfter.type === nestedExpand)) { return focusTitle($after.pos + 1)(state, dispatch, editorView); } } return false; }, list); bindKeymapWithCommand( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion backspace.common, (state, dispatch, editorView) => { const { selection } = state; const { $from } = selection; if (!editorView || !selection.empty) { return false; } const { expand, nestedExpand } = state.schema.nodes; const expandNode = findExpand(state); if (!expandNode) { // @see ED-7977 const sel = Selection.findFrom(state.doc.resolve(Math.max(selection.$from.pos - 1, 0)), -1); const expandBefore = findExpand(state, sel); if (expandBefore && (expandBefore.node.type === expand || expandBefore.node.type === nestedExpand) && isExpandCollapsed(expandBefore.node)) { return focusTitle(expandBefore.start)(state, dispatch, editorView); } return false; } const parentNode = state.doc.nodeAt($from.before(Math.max($from.depth - 1, 1))); // ED-10012 catch cases where the expand has another node nested within it and // the backspace should be applied only to the inner node instead of the expand if (parentNode && !isExpandNode(parentNode)) { return false; } const textSel = Selection.findFrom(state.doc.resolve(expandNode.pos), 1, true); if (textSel && selection.$from.pos === textSel.$from.pos && isEmptyNode(state.schema)(expandNode.node) && dispatch) { var _api$analytics; return deleteExpand(api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)(state, dispatch); } return false; }, list); return keymap(list); }