UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

181 lines (180 loc) 8.53 kB
import { useEffect } from 'react'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics'; import { startMeasure } from '@atlaskit/editor-common/performance-measures'; import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter'; import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/set-custom-native-drag-preview'; import { defaultActiveAnchorTracker } from '../pm-plugins/utils/active-anchor-tracker'; import { getMultiSelectAnalyticsAttributes } from '../pm-plugins/utils/analytics'; import { isCollapsedHeading, prepareCollapsedHeadingSelection } from '../pm-plugins/utils/collapsed-heading'; import { isHandleCorrelatedToSelection, selectNode } from '../pm-plugins/utils/getSelection'; import { getNodeMarginsForDragPreview, getNodeSpacingForDragPreview } from './block-controls-surface-drag-handle-utils'; import { dragPreview } from './drag-preview'; const EDITOR_BLOCKS_DRAG_INIT = 'Editor Blocks Drag Initialization Time'; /** * Registry surface drag source. This intentionally duplicates the stable widget handle's drag * contract so the legacy implementation remains byte-for-byte unchanged during migration. */ export const useBlockControlsSurfaceDragSource = ({ api, elementRef, getAnchorName, getPos, nodeType, start, view }) => { useEffect(() => { const element = elementRef.current; if (!element || !view) { return; } return draggable({ element, getInitialData: () => ({ type: 'element', start }), onGenerateDragPreview: ({ nativeSetDragImage }) => { var _api$core, _api$blockControls$sh; api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({ tr }) => { const handlePos = getPos(); if (typeof handlePos !== 'number') { return tr; } if (isExperimentEnabled('platform_editor_collapsible_headings') && isCollapsedHeading(api, handlePos)) { prepareCollapsedHeadingSelection({ api, expand: false, headingPos: handlePos, tr }); return tr; } if (!tr.selection.empty && isHandleCorrelatedToSelection(view.state, tr.selection, handlePos)) { var _api$blockControls; api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setMultiSelectPositions()({ tr }); } else { tr = selectNode(tr, handlePos, nodeType, api); } return tr; }); const startPos = getPos(); const { doc, selection } = view.state; let sliceFrom = selection.from; let sliceTo = selection.to; const multiSelect = api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.multiSelectDnD; if (multiSelect) { sliceFrom = Math.min(multiSelect.anchor, multiSelect.head); sliceTo = Math.max(multiSelect.anchor, multiSelect.head); } const expandedSlice = doc.slice(sliceFrom, sliceTo); const isDraggingMultipleNodes = startPos !== undefined && startPos >= sliceFrom && startPos < sliceTo && expandedSlice.content.childCount > 1; setCustomNativeDragPreview({ getOffset: () => { if (!isDraggingMultipleNodes || startPos === undefined) { return { x: 0, y: 0 }; } const domAtPos = view.domAtPos.bind(view); let heightBeforeHandle = 0; let nodeStart = sliceFrom; let activeNodeMarginTop = 0; for (let index = 0; index < expandedSlice.content.childCount; index++) { var _node$nodeSize; const node = expandedSlice.content.maybeChild(index); const nodeEnd = nodeStart + ((_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 0); if (nodeEnd <= startPos) { const nodeElement = findDomRefAtPos(nodeStart, domAtPos); const margins = getNodeMarginsForDragPreview(node); heightBeforeHandle += (nodeElement instanceof HTMLElement ? nodeElement.offsetHeight : 0) + margins.top + margins.bottom; } else { activeNodeMarginTop = getNodeMarginsForDragPreview(node).top; break; } nodeStart = nodeEnd; } return { x: 0, y: heightBeforeHandle + activeNodeMarginTop }; }, render: ({ container }) => { if (!isDraggingMultipleNodes && startPos !== undefined) { const dom = findDomRefAtPos(startPos, view.domAtPos.bind(view)); return dom instanceof HTMLElement ? dragPreview(container, { dom, nodeType }) : undefined; } const previewContent = []; expandedSlice.content.descendants((node, pos) => { const dom = findDomRefAtPos(sliceFrom + pos, view.domAtPos.bind(view)); if (dom instanceof HTMLElement) { previewContent.push({ dom, nodeSpacing: getNodeSpacingForDragPreview(node), nodeType: node.type.name }); } return false; }); return previewContent.length > 0 ? dragPreview(container, previewContent) : undefined; }, nativeSetDragImage }); }, onDragStart: () => { var _api$core2; if (start === undefined) { return; } // This handle is outside ProseMirror, so it owns the side effects normally started by PM. startMeasure(EDITOR_BLOCKS_DRAG_INIT); defaultActiveAnchorTracker.reset(); api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({ tr }) => { var _api$blockControls$sh2, _resolvedMovingNode$n, _api$blockControls2, _api$analytics; const resolvedMovingNode = tr.doc.resolve(start); const multiSelect = api === null || api === void 0 ? void 0 : (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh2 === void 0 ? void 0 : _api$blockControls$sh2.multiSelectDnD; const attributes = multiSelect ? getMultiSelectAnalyticsAttributes(tr, multiSelect.anchor, multiSelect.head) : { hasSelectedMultipleNodes: false, nodeTypes: (_resolvedMovingNode$n = resolvedMovingNode.nodeAfter) === null || _resolvedMovingNode$n === void 0 ? void 0 : _resolvedMovingNode$n.type.name }; api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.setNodeDragged(getPos, getAnchorName(), nodeType)({ tr }); tr.setMeta('scrollIntoView', false); api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({ action: ACTION.DRAGGED, actionSubject: ACTION_SUBJECT.ELEMENT, actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE, attributes: { hasSelectedMultipleNodes: attributes.hasSelectedMultipleNodes, nodeDepth: resolvedMovingNode.depth, nodeTypes: attributes.nodeTypes || '' }, eventType: EVENT_TYPE.UI })(tr); return tr; }); view.focus(); } }); }, [api, elementRef, getAnchorName, getPos, nodeType, start, view]); };