@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
177 lines (176 loc) • 8.88 kB
JavaScript
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';
var 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 var useBlockControlsSurfaceDragSource = function useBlockControlsSurfaceDragSource(_ref) {
var api = _ref.api,
elementRef = _ref.elementRef,
getAnchorName = _ref.getAnchorName,
getPos = _ref.getPos,
nodeType = _ref.nodeType,
start = _ref.start,
view = _ref.view;
useEffect(function () {
var element = elementRef.current;
if (!element || !view) {
return;
}
return draggable({
element: element,
getInitialData: function getInitialData() {
return {
type: 'element',
start: start
};
},
onGenerateDragPreview: function onGenerateDragPreview(_ref2) {
var _api$core, _api$blockControls$sh;
var nativeSetDragImage = _ref2.nativeSetDragImage;
api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
var tr = _ref3.tr;
var handlePos = getPos();
if (typeof handlePos !== 'number') {
return tr;
}
if (isExperimentEnabled('platform_editor_collapsible_headings') && isCollapsedHeading(api, handlePos)) {
prepareCollapsedHeadingSelection({
api: api,
expand: false,
headingPos: handlePos,
tr: tr
});
return tr;
}
if (!tr.selection.empty && isHandleCorrelatedToSelection(view.state, tr.selection, handlePos)) {
var _api$blockControls;
api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.setMultiSelectPositions()({
tr: tr
});
} else {
tr = selectNode(tr, handlePos, nodeType, api);
}
return tr;
});
var startPos = getPos();
var _view$state = view.state,
doc = _view$state.doc,
selection = _view$state.selection;
var sliceFrom = selection.from;
var sliceTo = selection.to;
var multiSelect = api === null || api === 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);
}
var expandedSlice = doc.slice(sliceFrom, sliceTo);
var isDraggingMultipleNodes = startPos !== undefined && startPos >= sliceFrom && startPos < sliceTo && expandedSlice.content.childCount > 1;
setCustomNativeDragPreview({
getOffset: function getOffset() {
if (!isDraggingMultipleNodes || startPos === undefined) {
return {
x: 0,
y: 0
};
}
var domAtPos = view.domAtPos.bind(view);
var heightBeforeHandle = 0;
var nodeStart = sliceFrom;
var activeNodeMarginTop = 0;
for (var index = 0; index < expandedSlice.content.childCount; index++) {
var _node$nodeSize;
var node = expandedSlice.content.maybeChild(index);
var nodeEnd = nodeStart + ((_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 0);
if (nodeEnd <= startPos) {
var nodeElement = findDomRefAtPos(nodeStart, domAtPos);
var 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: function render(_ref4) {
var container = _ref4.container;
if (!isDraggingMultipleNodes && startPos !== undefined) {
var dom = findDomRefAtPos(startPos, view.domAtPos.bind(view));
return dom instanceof HTMLElement ? dragPreview(container, {
dom: dom,
nodeType: nodeType
}) : undefined;
}
var previewContent = [];
expandedSlice.content.descendants(function (node, pos) {
var dom = findDomRefAtPos(sliceFrom + pos, view.domAtPos.bind(view));
if (dom instanceof HTMLElement) {
previewContent.push({
dom: dom,
nodeSpacing: getNodeSpacingForDragPreview(node),
nodeType: node.type.name
});
}
return false;
});
return previewContent.length > 0 ? dragPreview(container, previewContent) : undefined;
},
nativeSetDragImage: nativeSetDragImage
});
},
onDragStart: function 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 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref5) {
var _api$blockControls$sh2, _resolvedMovingNode$n, _api$blockControls2, _api$analytics;
var tr = _ref5.tr;
var resolvedMovingNode = tr.doc.resolve(start);
var multiSelect = api === null || api === void 0 || (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh2 === void 0 ? void 0 : _api$blockControls$sh2.multiSelectDnD;
var 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 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.setNodeDragged(getPos, getAnchorName(), nodeType)({
tr: tr
});
tr.setMeta('scrollIntoView', false);
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === 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]);
};