UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

138 lines (130 loc) 4.93 kB
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { findNodeDecs } from '../pm-plugins/decorations-anchor'; import { getDecorations, key } from '../pm-plugins/main'; import { getNestedNodePosition, getNestedNodeStartingPosition } from '../pm-plugins/utils/getNestedNodePosition'; import { NODE_ANCHOR_ATTR_NAME, NODE_NODE_TYPE_ATTR_NAME } from '../ui/utils/dom-attr-name'; const findParentPosForHandle = state => { var _activeNode$handleOpt; const { selection: { $from } } = state; const { activeNode } = key.getState(state) || {}; // if a node handle is already focused, return the parent pos of that node (with focused handle) if (activeNode && (_activeNode$handleOpt = activeNode.handleOptions) !== null && _activeNode$handleOpt !== void 0 && _activeNode$handleOpt.isFocused) { const $activeNodePos = state.doc.resolve(activeNode.pos); // if the handle is at the top level already, do nothing if ($activeNodePos.depth === 0) { return undefined; } return $activeNodePos.before(); } // if we are in second level of nested node, we should focus the node at level 1 if ($from.depth <= 1) { return $from.before(1); } // if we are inside a table, we should focus the table's handle const parentTableNode = findParentNodeOfType([state.schema.nodes.table])(state.selection); if (parentTableNode) { return parentTableNode.pos; } // else find closest parent node return expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? // With native anchor enabled, all nodes have anchor name attribute despite no drag handle support, e.g. listItem, caption, // as opposed to old approach, node decoration is only added to the node that have drag handle, // hence, we need to return the exact position of the node that can have drag handle getNestedNodeStartingPosition({ selection: state.selection, schema: state.schema, resolve: state.doc.resolve.bind(state.doc) }) : getNestedNodePosition({ selection: state.selection, schema: state.schema, resolve: state.doc.resolve.bind(state.doc) }); }; const findNextAnchorDecoration = state => { const decorations = getDecorations(state); if (!decorations) { return undefined; } const nextHandleNodePos = findParentPosForHandle(state); if (nextHandleNodePos === undefined) { return undefined; } const nextHandleNode = state.doc.nodeAt(nextHandleNodePos); let nodeDecorations = nextHandleNode && findNodeDecs(state, decorations, nextHandleNodePos, nextHandleNodePos + nextHandleNode.nodeSize); if (!nodeDecorations || nodeDecorations.length === 0) { return undefined; } // ensure the decoration covers the position of the look up node nodeDecorations = nodeDecorations.filter(decoration => { return decoration.from <= nextHandleNodePos; }); if (nodeDecorations.length === 0) { return undefined; } // sort the decorations by the position of the node // so we can find the closest decoration to the node nodeDecorations.sort((a, b) => { if (a.from === b.from) { return a.to - b.to; } return b.from - a.from; }); // return the closest decoration to the node return nodeDecorations[0]; }; const findNextAnchorNode = view => { const nextHandleNodePos = findParentPosForHandle(view.state); if (nextHandleNodePos === undefined) { return undefined; } const dom = view.nodeDOM(nextHandleNodePos); if (!(dom instanceof HTMLElement)) { return undefined; } const nodeDOM = dom.closest(`[${NODE_ANCHOR_ATTR_NAME}]`); if (!nodeDOM) { return undefined; } const nodeType = nodeDOM === null || nodeDOM === void 0 ? void 0 : nodeDOM.getAttribute(NODE_NODE_TYPE_ATTR_NAME); const anchorName = nodeDOM === null || nodeDOM === void 0 ? void 0 : nodeDOM.getAttribute(NODE_ANCHOR_ATTR_NAME); if (nodeType && anchorName) { return { pos: nextHandleNodePos, nodeType, anchorName }; } }; export const showDragHandleAtSelection = api => (state, _, view) => { if (view && expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) { const anchorNode = findNextAnchorNode(view); if (api && anchorNode) { const { pos, anchorName, nodeType } = anchorNode; api.core.actions.execute(api.blockControls.commands.showDragHandleAt(pos, anchorName, nodeType, { isFocused: true })); return true; } return false; } else { const decoration = findNextAnchorDecoration(state); if (api && decoration) { api.core.actions.execute(api.blockControls.commands.showDragHandleAt(decoration.from, decoration.spec.anchorName, decoration.spec.nodeTypeWithLevel, { isFocused: true })); return true; } return false; } };