UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

290 lines (284 loc) 14.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { createElement } from 'react'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import uuid from 'uuid'; import { expandSelectionBounds } from '@atlaskit/editor-common/selection'; import { isEmptyParagraph } from '@atlaskit/editor-common/utils'; import { Decoration } from '@atlaskit/editor-prosemirror/view'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { nodeMargins } from '../ui/consts'; import { DropTarget, EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_OFFSET } from '../ui/drop-target'; import { DropTargetLayout, DropTargetLayoutNativeAnchorSupport } from '../ui/drop-target-layout'; import { NESTED_DEPTH, TYPE_DROP_TARGET_DEC } from './decorations-common'; import { maxLayoutColumnSupported } from './utils/consts'; import { canMoveNodeToIndex, canMoveSliceToIndex, isInSameLayout } from './utils/validation'; var IGNORE_NODES = ['tableCell', 'tableHeader', 'tableRow', 'layoutColumn', 'listItem', 'caption']; var PARENT_WITH_END_DROP_TARGET = ['tableCell', 'tableHeader', 'panel', 'layoutColumn', 'expand', 'nestedExpand', 'bodiedExtension']; var PARENT_WITH_END_DROP_TARGET_NEXT = ['tableCell', 'tableHeader', 'panel', 'layoutColumn', 'expand', 'nestedExpand', 'bodiedExtension', 'bodiedSyncBlock']; var DISABLE_CHILD_DROP_TARGET = ['orderedList', 'bulletList']; var shouldDescend = function shouldDescend(node) { return !['mediaSingle', 'paragraph', 'heading'].includes(node.type.name); }; var getNodeMargins = function getNodeMargins(node) { if (!node) { return nodeMargins['default']; } var nodeTypeName = node.type.name; if (nodeTypeName === 'heading') { return nodeMargins["heading".concat(node.attrs.level)] || nodeMargins['default']; } return nodeMargins[nodeTypeName] || nodeMargins['default']; }; var shouldCollapseMargin = function shouldCollapseMargin(prevNode, nextNode) { if (((prevNode === null || prevNode === void 0 ? void 0 : prevNode.type.name) === 'mediaSingle' || (nextNode === null || nextNode === void 0 ? void 0 : nextNode.type.name) === 'mediaSingle') && (prevNode === null || prevNode === void 0 ? void 0 : prevNode.type.name) !== (nextNode === null || nextNode === void 0 ? void 0 : nextNode.type.name)) { return false; } return true; }; var getGapAndOffset = function getGapAndOffset(prevNode, nextNode, parentNode) { var isSyncBlockOffsetPatchEnabled = editorExperiment('platform_synced_block', true); if (!prevNode && nextNode) { // first node - adjust for bodied containers var _offset = 0; if (isSyncBlockOffsetPatchEnabled && parentNode !== null && parentNode !== void 0 && parentNode.type.name && parentNode.type.name === 'bodiedSyncBlock') { _offset += 4; } return { gap: 0, offset: _offset }; } else if (prevNode && !nextNode) { // last node - adjust for bodied containers var _offset2 = 0; if (isSyncBlockOffsetPatchEnabled && parentNode !== null && parentNode !== void 0 && parentNode.type.name && parentNode.type.name === 'bodiedSyncBlock') { _offset2 -= 4; } return { gap: 0, offset: _offset2 }; } var top = getNodeMargins(nextNode).top || 4; var bottom = getNodeMargins(prevNode).bottom || 4; var gap = shouldCollapseMargin(prevNode, nextNode) ? Math.max(top, bottom) : top + bottom; var offset = top - gap / 2; if ((prevNode === null || prevNode === void 0 ? void 0 : prevNode.type.name) === 'mediaSingle' && (nextNode === null || nextNode === void 0 ? void 0 : nextNode.type.name) === 'mediaSingle') { offset = -offset; } else if (prevNode !== null && prevNode !== void 0 && prevNode.type.name && ['tableCell', 'tableHeader'].includes(prevNode === null || prevNode === void 0 ? void 0 : prevNode.type.name)) { offset = 0; } return { gap: gap, offset: offset }; }; /** * Find drop target decorations in the pos range between from and to * @param decorations * @param from * @param to * @returns */ export var findDropTargetDecs = function findDropTargetDecs(decorations, from, to) { return decorations.find(from, to, function (spec) { return spec.type === TYPE_DROP_TARGET_DEC; }); }; export var createDropTargetDecoration = function createDropTargetDecoration(pos, props, nodeViewPortalProviderAPI, side, anchorRectCache, isSameLayout) { // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead var key = uuid(); return Decoration.widget(pos, function (_, getPosUnsafe) { var getPos = function getPos() { try { return getPosUnsafe(); } catch (e) { return undefined; } }; var element = document.createElement('div'); element.setAttribute('data-blocks-drop-target-container', 'true'); element.setAttribute('data-blocks-drop-target-key', key); element.style.clear = 'unset'; var _getGapAndOffset = getGapAndOffset(props.prevNode, props.nextNode, props.parentNode), gap = _getGapAndOffset.gap, offset = _getGapAndOffset.offset; element.style.setProperty(EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_OFFSET, "".concat(offset, "px")); element.style.setProperty(EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, "".concat(gap, "px")); element.style.setProperty('display', 'block'); nodeViewPortalProviderAPI.render(function () { return /*#__PURE__*/createElement(DropTarget, _objectSpread(_objectSpread({}, props), {}, { getPos: getPos, anchorRectCache: anchorRectCache, isSameLayout: isSameLayout })); }, element, key, undefined, // @portal-render-immediately true); return element; }, { type: TYPE_DROP_TARGET_DEC, side: side, destroy: function destroy() { nodeViewPortalProviderAPI.remove(key); } }); }; export var createLayoutDropTargetDecoration = function createLayoutDropTargetDecoration(pos, props, nodeViewPortalProviderAPI, anchorRectCache) { // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead var key = uuid(); return Decoration.widget(pos, function (_, getPosUnsafe) { var getPos = function getPos() { try { return getPosUnsafe(); } catch (e) { return undefined; } }; var element = document.createElement('div'); element.setAttribute('data-blocks-drop-target-container', 'true'); element.setAttribute('data-blocks-drop-target-key', key); element.style.clear = 'unset'; var DropTargetLayoutComponent = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? DropTargetLayoutNativeAnchorSupport : DropTargetLayout; nodeViewPortalProviderAPI.render(function () { return /*#__PURE__*/createElement(DropTargetLayoutComponent, _objectSpread(_objectSpread({}, props), {}, { getPos: getPos, anchorRectCache: anchorRectCache })); }, element, key, undefined, // @portal-render-immediately true); return element; }, { type: TYPE_DROP_TARGET_DEC, destroy: function destroy() { nodeViewPortalProviderAPI.remove(key); } }); }; export var dropTargetDecorations = function dropTargetDecorations(newState, api, formatMessage, nodeViewPortalProviderAPI, activeNode, anchorRectCache, from, to) { var decs = []; var POS_END_OF_DOC = newState.doc.nodeSize - 2; var docFrom = from === undefined || from < 0 ? 0 : from; var docTo = to === undefined || to > POS_END_OF_DOC ? POS_END_OF_DOC : to; var activeNodePos = activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos; var $activeNodePos = typeof activeNodePos === 'number' && newState.doc.resolve(activeNodePos); var activePMNode = $activeNodePos && $activeNodePos.nodeAfter; var isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true); anchorRectCache === null || anchorRectCache === void 0 || anchorRectCache.clear(); var prevNodeStack = []; var popNodeStack = function popNodeStack(depth) { var result; var toDepth = Math.max(depth, 0); while (prevNodeStack.length > toDepth) { result = prevNodeStack.pop(); } return result; }; var pushNodeStack = function pushNodeStack(node, depth) { popNodeStack(depth); prevNodeStack.push(node); }; var isAdvancedLayoutsPreRelease2 = editorExperiment('advanced_layouts', true); // For deciding to show drop targets or not when multiple nodes are selected var selection = newState.selection; var _expandSelectionBound = 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 handleInsideSelection = activeNodePos !== undefined && activeNodePos >= selectionFrom && activeNodePos <= selectionTo; newState.doc.nodesBetween(docFrom, docTo, function (node, pos, parent, index) { var depth = 0; // drop target deco at the end position var endPos; var $pos = newState.doc.resolve(pos); var isSameLayout = $activeNodePos && isInSameLayout($activeNodePos, $pos); depth = $pos.depth; if (isAdvancedLayoutsPreRelease2) { if ((activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos) === pos && activeNode.nodeType !== 'layoutColumn') { return false; } if (node.type.name === 'layoutColumn' && (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'layoutSection' && index !== 0 && ( // Not the first node (parent === null || parent === void 0 ? void 0 : parent.childCount) < maxLayoutColumnSupported() || isSameLayout)) { // Add drop target for layout columns decs.push(createLayoutDropTargetDecoration(pos, { api: api, parent: parent, formatMessage: formatMessage }, nodeViewPortalProviderAPI, anchorRectCache)); } } if (node.isInline || !parent || DISABLE_CHILD_DROP_TARGET.includes(parent.type.name)) { pushNodeStack(node, depth); return false; } if (IGNORE_NODES.includes(node.type.name)) { pushNodeStack(node, depth); return shouldDescend(node); //skip over, don't consider it a valid depth } // When multi select is on, validate all the nodes in the selection instead of just the handle node if (isMultiSelect) { var selectionSlice = newState.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 = canMoveSliceToIndex(selectionSlice, selectionFrom, selectionTo, parent, index, $pos); } else { canDropSingleNode = !!(activePMNode && canMoveNodeToIndex(parent, index, activePMNode, $pos, node)); } if (!canDropMultipleNodes || !canDropSingleNode) { pushNodeStack(node, depth); return false; //not valid pos, so nested not valid either } } else { var canDrop = activePMNode && canMoveNodeToIndex(parent, index, activePMNode, $pos, node); //NOTE: This will block drop targets showing for nodes that are valid after transformation (i.e. expand -> nestedExpand) if (!canDrop) { pushNodeStack(node, depth); return false; //not valid pos, so nested not valid either } } var parentTypesWithEndDropTarget = editorExperiment('platform_synced_block', true) ? PARENT_WITH_END_DROP_TARGET_NEXT : PARENT_WITH_END_DROP_TARGET; if (parent.lastChild === node && !isEmptyParagraph(node) && parentTypesWithEndDropTarget.includes(parent.type.name)) { endPos = pos + node.nodeSize; } var previousNode = popNodeStack(depth); // created scoped variable // only table and layout need to render full height drop target 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 && isEmptyParagraph(node); decs.push(createDropTargetDecoration(pos, { api: api, prevNode: previousNode, nextNode: node, parentNode: parent || undefined, formatMessage: formatMessage, dropTargetStyle: shouldShowFullHeight ? 'remainingHeight' : 'default' }, nodeViewPortalProviderAPI, -1, anchorRectCache, isSameLayout)); if (endPos !== undefined) { decs.push(createDropTargetDecoration(endPos, { api: api, prevNode: node, parentNode: parent || undefined, formatMessage: formatMessage, dropTargetStyle: 'remainingHeight' }, nodeViewPortalProviderAPI, -1, anchorRectCache)); } pushNodeStack(node, depth); return depth < NESTED_DEPTH && shouldDescend(node); }); if (docTo === POS_END_OF_DOC) { decs.push(createDropTargetDecoration(POS_END_OF_DOC, { api: api, formatMessage: formatMessage, prevNode: newState.doc.lastChild || undefined, parentNode: newState.doc }, nodeViewPortalProviderAPI, undefined, anchorRectCache)); } return decs; };