@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
290 lines (284 loc) • 14.4 kB
JavaScript
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;
};