UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

272 lines (262 loc) 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getActiveDropTargetDecorations = exports.canMoveNodeOrSliceToPos = void 0; var _selection = require("@atlaskit/editor-common/selection"); var _utils = require("@atlaskit/editor-common/utils"); var _utils2 = require("@atlaskit/editor-prosemirror/utils"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _decorationsCommon = require("./decorations-common"); var _decorationsDropTarget = require("./decorations-drop-target"); var _decorationsFindSurroundingNodes = require("./decorations-find-surrounding-nodes"); var _activeAnchorTracker = require("./utils/active-anchor-tracker"); var _consts = require("./utils/consts"); var _validation = require("./utils/validation"); /** * List of parent node types that can have child nodes */ var PARENT_WITH_END_DROP_TARGET = ['tableCell', 'tableHeader', 'panel', 'layoutColumn', 'expand', 'nestedExpand', 'bodiedExtension']; var PARENT_WITH_END_DROP_TARGET_SYNC_BLOCK = [].concat(PARENT_WITH_END_DROP_TARGET, ['bodiedSyncBlock']); /** * List of node types that does not allow drop targets at before or after the node. */ var NODE_WITH_NO_PARENT_POS = ['tableCell', 'tableHeader', 'layoutColumn']; var UNSUPPORTED_LAYOUT_CONTENT = ['syncBlock', 'bodiedSyncBlock']; var isContainerNode = function isContainerNode(node) { if ((0, _experiments.editorExperiment)('platform_synced_block', true)) { return PARENT_WITH_END_DROP_TARGET_SYNC_BLOCK.includes(node.type.name); } return PARENT_WITH_END_DROP_TARGET.includes(node.type.name); }; var canMoveNodeOrSliceToPos = exports.canMoveNodeOrSliceToPos = function canMoveNodeOrSliceToPos(state, node, parent, index, $toPos, activeNode) { // For deciding to show drop targets or not when multiple nodes are selected var selection = state.selection; var _expandSelectionBound = (0, _selection.expandSelectionBounds)(selection.$anchor, selection.$head), expandedAnchor = _expandSelectionBound.$anchor, expandedHead = _expandSelectionBound.$head; var selectionFrom = Math.min(expandedAnchor.pos, expandedHead.pos); var selectionTo = Math.max(expandedAnchor.pos, expandedHead.pos); var activeNodePos = activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos; var $activeNodePos = typeof activeNodePos === 'number' && state.doc.resolve(activeNodePos); var activePMNode = $activeNodePos && $activeNodePos.nodeAfter; var handleInsideSelection = activeNodePos !== undefined && activeNodePos >= selectionFrom && activeNodePos <= selectionTo; if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) { var selectionSlice = state.doc.slice(selectionFrom, selectionTo, false); var selectionSliceChildCount = selectionSlice.content.childCount; var canDropSingleNode = true; var canDropMultipleNodes = true; // when there is only one node in the slice, use the same logic as when multi select is not on if (selectionSliceChildCount > 1 && handleInsideSelection) { canDropMultipleNodes = (0, _validation.canMoveSliceToIndex)(selectionSlice, selectionFrom, selectionTo, parent, index, $toPos); } else { canDropSingleNode = !!(activePMNode && (0, _validation.canMoveNodeToIndex)(parent, index, activePMNode, $toPos, node)); } if (!canDropMultipleNodes || !canDropSingleNode) { return false; } } else { var canDrop = activePMNode && (0, _validation.canMoveNodeToIndex)(parent, index, activePMNode, $toPos, node); return canDrop; } return true; }; var getActiveDropTargetDecorations = exports.getActiveDropTargetDecorations = function getActiveDropTargetDecorations(activeDropTargetNode, state, api, existingDecs, formatMessage, nodeViewPortalProviderAPI, activeNode, anchorRectCache) { var decsToAdd = []; var decsToRemove = existingDecs.filter(function (dec) { return !!dec; }); var activeNodePos = activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos; var $activeNodePos = typeof activeNodePos === 'number' && state.doc.resolve(activeNodePos); var $toPos = state.doc.resolve(activeDropTargetNode.pos); var existingDecsPos = decsToRemove.map(function (dec) { return dec.from; }); var _findSurroundingNodes = (0, _decorationsFindSurroundingNodes.findSurroundingNodes)(state, $toPos, activeDropTargetNode.nodeTypeName), parent = _findSurroundingNodes.parent, index = _findSurroundingNodes.index, node = _findSurroundingNodes.node, pos = _findSurroundingNodes.pos, before = _findSurroundingNodes.before, after = _findSurroundingNodes.after, depth = _findSurroundingNodes.depth; /** * If the current node is a container node, we show the drop targets * above the first child and below the last child. */ if (isContainerNode(node)) { var isEmptyContainer = node.childCount === 0 || node.childCount === 1 && (0, _utils.isEmptyParagraph)(node.firstChild); // can move to before first child var posBeforeFirstChild = pos + 1; // +1 to get the position of the first child if (node.firstChild && canMoveNodeOrSliceToPos(state, node.firstChild, node, 0, state.doc.resolve(posBeforeFirstChild), activeNode)) { if (existingDecsPos.includes(posBeforeFirstChild)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== posBeforeFirstChild; }); } else { decsToAdd.push((0, _decorationsDropTarget.createDropTargetDecoration)(posBeforeFirstChild, { api: api, prevNode: undefined, nextNode: node.firstChild, parentNode: node, formatMessage: formatMessage, dropTargetStyle: isEmptyContainer ? 'remainingHeight' : 'default' }, nodeViewPortalProviderAPI, 1, anchorRectCache)); } } // can move to after last child // if the node is empty, we don't show the drop target at the end of the node if (!isEmptyContainer) { var posAfterLastChild = pos + node.nodeSize - 1; // -1 to get the position after last child if (node.lastChild && canMoveNodeOrSliceToPos(state, node.lastChild, node, node.childCount - 1, state.doc.resolve(posAfterLastChild), // -1 to get the position after last child activeNode)) { if (existingDecsPos.includes(posAfterLastChild)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== posAfterLastChild; }); } else { decsToAdd.push((0, _decorationsDropTarget.createDropTargetDecoration)(posAfterLastChild, { api: api, prevNode: node.lastChild, nextNode: undefined, parentNode: node, formatMessage: formatMessage, dropTargetStyle: 'remainingHeight' }, nodeViewPortalProviderAPI, -1, anchorRectCache)); } } } } /** * Create drop target before and after the current node */ if (!NODE_WITH_NO_PARENT_POS.includes(node.type.name)) { var isSameLayout = $activeNodePos && (0, _validation.isInSameLayout)($activeNodePos, $toPos); var isInSupportedContainer = ['tableCell', 'tableHeader', 'layoutColumn'].includes((parent === null || parent === void 0 ? void 0 : parent.type.name) || ''); var shouldShowFullHeight = isInSupportedContainer && (parent === null || parent === void 0 ? void 0 : parent.lastChild) === node && (0, _utils.isEmptyParagraph)(node); if (canMoveNodeOrSliceToPos(state, node, parent, index, $toPos, activeNode)) { if (existingDecsPos.includes(pos)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== pos; }); } else { decsToAdd.push((0, _decorationsDropTarget.createDropTargetDecoration)(pos, { api: api, prevNode: before || undefined, nextNode: node, parentNode: parent || undefined, formatMessage: formatMessage, dropTargetStyle: shouldShowFullHeight ? 'remainingHeight' : 'default' }, nodeViewPortalProviderAPI, -1, anchorRectCache, isSameLayout)); } } // if the node is a container node, we show the drop target after the node var posAfterNode = pos + node.nodeSize; if (canMoveNodeOrSliceToPos(state, node, parent, index + 1, state.doc.resolve(posAfterNode), activeNode)) { if (existingDecsPos.includes(posAfterNode)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== posAfterNode; }); } else { decsToAdd.push((0, _decorationsDropTarget.createDropTargetDecoration)(posAfterNode, { api: api, prevNode: node, nextNode: after || undefined, parentNode: parent || undefined, formatMessage: formatMessage, dropTargetStyle: shouldShowFullHeight ? 'remainingHeight' : 'default' }, nodeViewPortalProviderAPI, -1, anchorRectCache, isSameLayout)); } } } var rootNodeWithPos = { node: node, pos: pos }; // if the current node is not a top level node, we create one for advanced layout drop targets if (depth > 1) { var root = (0, _decorationsFindSurroundingNodes.findSurroundingNodes)(state, state.doc.resolve($toPos.before(2))); if (existingDecsPos.includes(root.pos)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== root.pos; }); } else { decsToAdd.push((0, _decorationsDropTarget.createDropTargetDecoration)(root.pos, { api: api, prevNode: root.before || undefined, nextNode: root.node, parentNode: state.doc || undefined, formatMessage: formatMessage, dropTargetStyle: 'default' }, nodeViewPortalProviderAPI, -1, anchorRectCache, false)); } rootNodeWithPos = { node: root.node, pos: root.pos }; } var anchorEmitNodeWithPos = rootNodeWithPos; if ((0, _experiments.editorExperiment)('advanced_layouts', true)) { if ((0, _experiments.editorExperiment)('platform_synced_block', true) && (0, _experiments.editorExperiment)('platform_synced_block_patch_6', true, { exposure: true })) { var schema = rootNodeWithPos.node.type.schema; var layoutSection = schema.nodes.layoutSection; var isLayoutSectionChildOfRoot = (0, _utils2.findChildrenByType)(rootNodeWithPos.node, layoutSection, false).length > 0; if (isLayoutSectionChildOfRoot) { // if node has layoutSection as a child, get the layoutSection node and pos for (var ancestorDepth = $toPos.depth; ancestorDepth >= 1; ancestorDepth--) { if ($toPos.node(ancestorDepth).type.name === 'layoutSection') { anchorEmitNodeWithPos = { node: $toPos.node(ancestorDepth), pos: $toPos.before(ancestorDepth) }; break; } } } else { anchorEmitNodeWithPos = rootNodeWithPos; } } else { anchorEmitNodeWithPos = rootNodeWithPos; } var _isSameLayout = $activeNodePos && (0, _validation.isInSameLayout)($activeNodePos, state.doc.resolve(anchorEmitNodeWithPos.pos)); var hasUnsupportedContent = UNSUPPORTED_LAYOUT_CONTENT.includes((activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeType) || '') && (0, _experiments.editorExperiment)('platform_synced_block', true); if (anchorEmitNodeWithPos.node.type.name === 'layoutSection' && !hasUnsupportedContent) { var layoutSectionNode = anchorEmitNodeWithPos.node; if (layoutSectionNode.childCount < (0, _consts.maxLayoutColumnSupported)() || _isSameLayout) { layoutSectionNode.descendants(function (childNode, childPos, parent, index) { if (childNode.type.name === 'layoutColumn' && (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'layoutSection' && index !== 0 // Not the first node ) { var currentPos = anchorEmitNodeWithPos.pos + childPos + 1; if (existingDecsPos.includes(currentPos)) { // if the decoration already exists, we don't add it again. decsToRemove = decsToRemove.filter(function (dec) { return dec.from !== currentPos; }); } else { decsToAdd.push((0, _decorationsDropTarget.createLayoutDropTargetDecoration)(anchorEmitNodeWithPos.pos + childPos + 1, { api: api, parent: parent, formatMessage: formatMessage }, nodeViewPortalProviderAPI, anchorRectCache)); } } return false; }); } } } _activeAnchorTracker.defaultActiveAnchorTracker.emit((0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? api.core.actions.getAnchorIdForNode(anchorEmitNodeWithPos.node, anchorEmitNodeWithPos.pos) || '' : (0, _decorationsCommon.getNodeAnchor)(anchorEmitNodeWithPos.node)); return { decsToAdd: decsToAdd, decsToRemove: decsToRemove }; };