UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

296 lines (289 loc) 15.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.findDropTargetDecs = exports.dropTargetDecorations = exports.createLayoutDropTargetDecoration = exports.createDropTargetDecoration = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = require("react"); var _uuid = _interopRequireDefault(require("uuid")); var _selection = require("@atlaskit/editor-common/selection"); var _utils = require("@atlaskit/editor-common/utils"); var _view = require("@atlaskit/editor-prosemirror/view"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _consts = require("../ui/consts"); var _dropTarget = require("../ui/drop-target"); var _dropTargetLayout = require("../ui/drop-target-layout"); var _decorationsCommon = require("./decorations-common"); var _consts2 = require("./utils/consts"); var _validation = require("./utils/validation"); 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) { (0, _defineProperty2.default)(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; } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead 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 _consts.nodeMargins['default']; } var nodeTypeName = node.type.name; if (nodeTypeName === 'heading') { return _consts.nodeMargins["heading".concat(node.attrs.level)] || _consts.nodeMargins['default']; } return _consts.nodeMargins[nodeTypeName] || _consts.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 = (0, _experiments.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 */ var findDropTargetDecs = exports.findDropTargetDecs = function findDropTargetDecs(decorations, from, to) { return decorations.find(from, to, function (spec) { return spec.type === _decorationsCommon.TYPE_DROP_TARGET_DEC; }); }; var createDropTargetDecoration = exports.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 = (0, _uuid.default)(); return _view.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(_dropTarget.EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_OFFSET, "".concat(offset, "px")); element.style.setProperty(_dropTarget.EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, "".concat(gap, "px")); element.style.setProperty('display', 'block'); nodeViewPortalProviderAPI.render(function () { return /*#__PURE__*/(0, _react.createElement)(_dropTarget.DropTarget, _objectSpread(_objectSpread({}, props), {}, { getPos: getPos, anchorRectCache: anchorRectCache, isSameLayout: isSameLayout })); }, element, key, undefined, // @portal-render-immediately true); return element; }, { type: _decorationsCommon.TYPE_DROP_TARGET_DEC, side: side, destroy: function destroy() { nodeViewPortalProviderAPI.remove(key); } }); }; var createLayoutDropTargetDecoration = exports.createLayoutDropTargetDecoration = function createLayoutDropTargetDecoration(pos, props, nodeViewPortalProviderAPI, anchorRectCache) { // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead var key = (0, _uuid.default)(); return _view.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 = (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? _dropTargetLayout.DropTargetLayoutNativeAnchorSupport : _dropTargetLayout.DropTargetLayout; nodeViewPortalProviderAPI.render(function () { return /*#__PURE__*/(0, _react.createElement)(DropTargetLayoutComponent, _objectSpread(_objectSpread({}, props), {}, { getPos: getPos, anchorRectCache: anchorRectCache })); }, element, key, undefined, // @portal-render-immediately true); return element; }, { type: _decorationsCommon.TYPE_DROP_TARGET_DEC, destroy: function destroy() { nodeViewPortalProviderAPI.remove(key); } }); }; var dropTargetDecorations = exports.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 = (0, _experiments.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 = (0, _experiments.editorExperiment)('advanced_layouts', true); // For deciding to show drop targets or not when multiple nodes are selected var selection = newState.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 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 && (0, _validation.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) < (0, _consts2.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 = (0, _validation.canMoveSliceToIndex)(selectionSlice, selectionFrom, selectionTo, parent, index, $pos); } else { canDropSingleNode = !!(activePMNode && (0, _validation.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 && (0, _validation.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 = (0, _experiments.editorExperiment)('platform_synced_block', true) ? PARENT_WITH_END_DROP_TARGET_NEXT : PARENT_WITH_END_DROP_TARGET; if (parent.lastChild === node && !(0, _utils.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 && (0, _utils.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 < _decorationsCommon.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; };