UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

407 lines (393 loc) 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.moveToLayout = void 0; var _analytics = require("@atlaskit/editor-common/analytics"); var _monitoring = require("@atlaskit/editor-common/monitoring"); var _model = require("@atlaskit/editor-prosemirror/model"); var _state = require("@atlaskit/editor-prosemirror/state"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _analytics2 = require("../pm-plugins/utils/analytics"); var _checkFragment = require("../pm-plugins/utils/check-fragment"); var _consts = require("../pm-plugins/utils/consts"); var _removeFromSource = require("../pm-plugins/utils/remove-from-source"); var _selection = require("../pm-plugins/utils/selection"); var _validation = require("../pm-plugins/utils/validation"); var _consts2 = require("../ui/consts"); var createNewLayout = function createNewLayout(schema, layoutContents) { if (layoutContents.length === 0 || layoutContents.length > (0, _consts.maxLayoutColumnSupported)()) { return null; } var width = _consts2.DEFAULT_COLUMN_DISTRIBUTIONS[layoutContents.length]; if (!width) { return null; } var _ref = schema.nodes || {}, layoutSection = _ref.layoutSection, layoutColumn = _ref.layoutColumn; try { var layoutContent = _model.Fragment.fromArray(layoutContents.map(function (layoutContent) { return layoutColumn.createChecked({ width: width }, layoutContent); })); var layoutSectionNode = layoutSection.createChecked(undefined, layoutContent); return layoutSectionNode; } catch (error) { (0, _monitoring.logException)(error, { location: 'editor-plugin-block-controls/move-to-layout' }); } return null; }; var moveToExistingLayout = function moveToExistingLayout(toLayout, toLayoutPos, sourceContent, from, to, tr, $originalFrom, $originalTo, api, selectMovedNode) { var isSameLayout = (0, _validation.isInSameLayout)($originalFrom, $originalTo); var sourceContentEndPos = -1; var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true); var sourceNodeTypes, hasSelectedMultipleNodes; if (isMultiSelect) { if (sourceContent instanceof _model.Fragment) { sourceContentEndPos = from + sourceContent.size; var attributes = (0, _analytics2.getMultiSelectAnalyticsAttributes)(tr, from, sourceContentEndPos); hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes; sourceNodeTypes = attributes.nodeTypes; } } else { if (sourceContent instanceof _model.Node) { sourceContentEndPos = from + sourceContent.nodeSize; } } if (sourceContentEndPos === -1) { return tr; } if (isSameLayout) { var _$originalFrom$nodeAf; // reorder columns tr.delete(from, sourceContentEndPos); var mappedTo = tr.mapping.map(to); tr.insert(mappedTo, sourceContent); if (selectMovedNode) { tr.setSelection(new _state.NodeSelection(tr.doc.resolve(mappedTo))).scrollIntoView(); } (0, _analytics2.attachMoveNodeAnalytics)(tr, _analytics.INPUT_METHOD.DRAG_AND_DROP, $originalFrom.depth, ((_$originalFrom$nodeAf = $originalFrom.nodeAfter) === null || _$originalFrom$nodeAf === void 0 ? void 0 : _$originalFrom$nodeAf.type.name) || '', 1, 'layoutSection', true, api, sourceNodeTypes, hasSelectedMultipleNodes); } else if (toLayout.childCount < (0, _consts.maxLayoutColumnSupported)()) { var _$originalFrom$nodeAf2; (0, _removeFromSource.removeFromSource)(tr, tr.doc.resolve(from), sourceContentEndPos); insertToDestinationNoWidthUpdate(tr, tr.mapping.map(to), sourceContent); (0, _analytics2.attachMoveNodeAnalytics)(tr, _analytics.INPUT_METHOD.DRAG_AND_DROP, $originalFrom.depth, ((_$originalFrom$nodeAf2 = $originalFrom.nodeAfter) === null || _$originalFrom$nodeAf2 === void 0 ? void 0 : _$originalFrom$nodeAf2.type.name) || '', 1, 'layoutSection', false, api, sourceNodeTypes, hasSelectedMultipleNodes); } return tr; }; /** * This function is similar to insertToDestination * But without update width step, mainly rely on the append transaction from layout. * @param tr * @param to * @param sourceNode * @returns */ var insertToDestinationNoWidthUpdate = function insertToDestinationNoWidthUpdate(tr, to, sourceContent) { var _ref2 = tr.doc.type.schema.nodes || {}, layoutColumn = _ref2.layoutColumn; var content = null; try { if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) { if (sourceContent instanceof _model.Fragment) { var _sourceFragment$first; var sourceFragment = sourceContent; content = layoutColumn.createChecked({ width: 0 }, (0, _checkFragment.isFragmentOfType)(sourceFragment, 'layoutColumn') ? (_sourceFragment$first = sourceFragment.firstChild) === null || _sourceFragment$first === void 0 ? void 0 : _sourceFragment$first.content : sourceFragment); } } else { if (sourceContent instanceof _model.Node) { var sourceNode = sourceContent; content = layoutColumn.createChecked({ width: 0 }, sourceNode.type.name === 'layoutColumn' ? sourceNode.content : sourceNode); } } } catch (error) { (0, _monitoring.logException)(error, { location: 'editor-plugin-block-controls/move-to-layout' }); } if (content) { tr.insert(to, content); } return tr; }; /** * Check if the node at `from` can be moved to node at `to` to create/expand a layout. * Returns the source and destination nodes and positions if it's a valid move, otherwise, undefined */ var canMoveToLayout = function canMoveToLayout(api, from, to, tr, moveNodeAtCursorPos) { if (from === to) { return; } var _ref3 = tr.doc.type.schema.nodes || {}, layoutSection = _ref3.layoutSection, layoutColumn = _ref3.layoutColumn, doc = _ref3.doc, bodiedSyncBlock = _ref3.bodiedSyncBlock; // layout plugin does not exist if (!layoutSection || !layoutColumn) { return; } var $to = tr.doc.resolve(to); var allowedParentTypes = [doc, layoutSection]; if (bodiedSyncBlock && (0, _experiments.editorExperiment)('platform_synced_block', true) && (0, _experiments.editorExperiment)('platform_synced_block_patch_6', true, { exposure: true })) { allowedParentTypes.push(bodiedSyncBlock); } // drop at invalid position, not top level, or not a layout column if (!$to.nodeAfter || !allowedParentTypes.includes($to.parent.type)) { return; } var $from = tr.doc.resolve(from); var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true); // invalid from position or dragging a layout if (!$from.nodeAfter || $from.nodeAfter.type === layoutSection) { return; } var sourceContent = $from.nodeAfter; var sourceFrom = from; var sourceTo = from + sourceContent.nodeSize; if (isMultiSelect && !moveNodeAtCursorPos) { var _getMultiSelectionIfP = (0, _selection.getMultiSelectionIfPosInside)(api, from), anchor = _getMultiSelectionIfP.anchor, head = _getMultiSelectionIfP.head; if (anchor !== undefined && head !== undefined) { sourceFrom = Math.min(anchor, head); sourceTo = Math.max(anchor, head); sourceContent = tr.doc.slice(sourceFrom, sourceTo).content; // TODO: ED-26959 - this might become expensive for large content, consider removing it if check has been done beforehand if ((0, _checkFragment.containsNodeOfType)(sourceContent, 'layoutSection')) { return; } } else { sourceContent = _model.Fragment.from($from.nodeAfter); } } var toNode = $to.nodeAfter; return { toNode: toNode, $to: $to, sourceContent: sourceContent, $sourceFrom: tr.doc.resolve(sourceFrom), sourceTo: sourceTo }; }; var removeBreakoutMarks = function removeBreakoutMarks(tr, $from, to) { var fromContentWithoutBreakout = $from.nodeAfter; var _ref4 = tr.doc.type.schema.marks || {}, breakout = _ref4.breakout; if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) { tr.doc.nodesBetween($from.pos, to, function (node, pos, parent) { // should never remove breakout from previous layoutSection if ((0, _expValEquals.expValEquals)('platform_editor_breakout_resizing', 'isEnabled', true)) { if (node.type.name === 'layoutSection') { return false; } } // breakout doesn't exist on nested nodes if ((parent === null || parent === void 0 ? void 0 : parent.type.name) === 'doc' && node.marks.some(function (m) { return m.type === breakout; })) { tr.removeNodeMark(pos, breakout); } // descending is not needed as breakout doesn't exist on nested nodes return false; }); // resolve again the source content after node updated (remove breakout marks) fromContentWithoutBreakout = tr.doc.slice($from.pos, to).content; } else { if (breakout && $from.nodeAfter && $from.nodeAfter.marks.some(function (m) { return m.type === breakout; })) { tr.removeNodeMark($from.pos, breakout); // resolve again the source node after node updated (remove breakout marks) fromContentWithoutBreakout = tr.doc.resolve($from.pos).nodeAfter; } } return fromContentWithoutBreakout; }; var getBreakoutMode = function getBreakoutMode(content, breakout) { if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) { if (content instanceof _model.Node) { var _content$marks$find; return (_content$marks$find = content.marks.find(function (m) { return m.type === breakout; })) === null || _content$marks$find === void 0 ? void 0 : _content$marks$find.attrs.mode; } else if (content instanceof _model.Fragment) { // Find the first breakout mode in the fragment var firstBreakoutMode; for (var i = 0; i < content.childCount; i++) { var child = content.child(i); var breakoutMark = child.marks.find(function (m) { return m.type === breakout; }); if (breakoutMark) { firstBreakoutMode = breakoutMark.attrs.mode; break; } } return firstBreakoutMode; } } else { // Without multi-select support, we can assume source content is of type PMNode if (content instanceof _model.Node) { var _content$marks$find2; return (_content$marks$find2 = content.marks.find(function (m) { return m.type === breakout; })) === null || _content$marks$find2 === void 0 ? void 0 : _content$marks$find2.attrs.mode; } } }; var getBreakoutModeAndWidth = function getBreakoutModeAndWidth(content, breakout) { var findBreakoutMark = function findBreakoutMark(node) { return node.marks.find(function (m) { return m.type === breakout; }); }; var extractBreakoutAttributes = function extractBreakoutAttributes(mark) { return mark ? { breakoutMode: mark.attrs.mode, breakoutWidth: mark.attrs.width } : null; }; if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) { if (content instanceof _model.Node) { return extractBreakoutAttributes(findBreakoutMark(content)); } else if (content instanceof _model.Fragment) { // Find the first breakout mode in the fragment for (var i = 0; i < content.childCount; i++) { var child = content.child(i); var breakoutMark = findBreakoutMark(child); if (breakoutMark) { return extractBreakoutAttributes(breakoutMark); } } } } else { // Without multi-select support, we can assume source content is of type PMNode if (content instanceof _model.Node) { return extractBreakoutAttributes(findBreakoutMark(content)); } } return null; }; // TODO: ED-26959 - As part of platform_editor_element_drag_and_drop_multiselect clean up, // source content variable that has type of `PMNode | Fragment` should be updated to `Fragment` only var moveToLayout = exports.moveToLayout = function moveToLayout(api) { return function (from, to, options) { return function (_ref5) { var tr = _ref5.tr; if (!api) { return tr; } var canMove = canMoveToLayout(api, from, to, tr, options === null || options === void 0 ? void 0 : options.moveNodeAtCursorPos); if (!canMove) { return tr; } var toNode = canMove.toNode, $to = canMove.$to, sourceContent = canMove.sourceContent, $sourceFrom = canMove.$sourceFrom, sourceTo = canMove.sourceTo; var _ref6 = tr.doc.type.schema.nodes || {}, layoutSection = _ref6.layoutSection, layoutColumn = _ref6.layoutColumn; var _ref7 = tr.doc.type.schema.marks || {}, breakout = _ref7.breakout; // get breakout mode from destination node, // if not found, get from source node, var breakoutMode; var breakoutWidth; if ((0, _expValEquals.expValEquals)('platform_editor_breakout_resizing', 'isEnabled', true)) { var _ref8 = getBreakoutModeAndWidth(toNode, breakout) || getBreakoutModeAndWidth(sourceContent, breakout) || {}; breakoutMode = _ref8.breakoutMode; breakoutWidth = _ref8.breakoutWidth; } else { breakoutMode = getBreakoutMode(toNode, breakout) || getBreakoutMode(sourceContent, breakout); } // we don't want to remove marks when moving/re-ordering layoutSection var shouldRemoveMarks = !($sourceFrom.node().type === layoutSection && (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)); var fromContentBeforeBreakoutMarksRemoved = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true) ? tr.doc.slice($sourceFrom.pos, sourceTo).content : $sourceFrom.nodeAfter; // remove breakout from source content var fromContentWithoutBreakout = shouldRemoveMarks ? removeBreakoutMarks(tr, $sourceFrom, sourceTo) : fromContentBeforeBreakoutMarksRemoved; if (!fromContentWithoutBreakout) { return tr; } if ((0, _platformFeatureFlags.fg)('platform_editor_ease_of_use_metrics')) { var _api$metrics; api === null || api === void 0 || (_api$metrics = api.metrics) === null || _api$metrics === void 0 || _api$metrics.commands.setContentMoved()({ tr: tr }); } var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true); if (toNode.type === layoutSection) { var toPos = options !== null && options !== void 0 && options.moveToEnd ? to + toNode.nodeSize - 1 : to + 1; return moveToExistingLayout(toNode, to, fromContentWithoutBreakout, $sourceFrom.pos, toPos, tr, $sourceFrom, $to, api, options === null || options === void 0 ? void 0 : options.selectMovedNode); } else if (toNode.type === layoutColumn) { var toLayout = $to.parent; var toLayoutPos = to - $to.parentOffset - 1; var _toPos = options !== null && options !== void 0 && options.moveToEnd ? to + toNode.nodeSize : to; return moveToExistingLayout(toLayout, toLayoutPos, fromContentWithoutBreakout, $sourceFrom.pos, _toPos, tr, $sourceFrom, $to, api, options === null || options === void 0 ? void 0 : options.selectMovedNode); } else { var toNodeWithoutBreakout = toNode; // remove breakout from node; if (breakout && $to.nodeAfter && $to.nodeAfter.marks.some(function (m) { return m.type === breakout; })) { tr.removeNodeMark(to, breakout); // resolve again the source node after node updated (remove breakout marks) toNodeWithoutBreakout = tr.doc.resolve(to).nodeAfter || toNode; } if (isMultiSelect) { if ((0, _checkFragment.isFragmentOfType)(fromContentWithoutBreakout, 'layoutColumn') && fromContentWithoutBreakout.firstChild) { fromContentWithoutBreakout = fromContentWithoutBreakout.firstChild.content; } } else { if (fromContentWithoutBreakout instanceof _model.Node && fromContentWithoutBreakout.type.name === 'layoutColumn') { fromContentWithoutBreakout = fromContentWithoutBreakout.content; } } var layoutContents = options !== null && options !== void 0 && options.moveToEnd ? [toNodeWithoutBreakout, fromContentWithoutBreakout] : [fromContentWithoutBreakout, toNodeWithoutBreakout]; var newLayout = createNewLayout(tr.doc.type.schema, layoutContents); if (newLayout) { var sourceNodeTypes, hasSelectedMultipleNodes; if (isMultiSelect) { var attributes = (0, _analytics2.getMultiSelectAnalyticsAttributes)(tr, $sourceFrom.pos, sourceTo); hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes; sourceNodeTypes = attributes.nodeTypes; } tr = (0, _removeFromSource.removeFromSource)(tr, $sourceFrom, sourceTo); var mappedTo = tr.mapping.map(to); tr.delete(mappedTo, mappedTo + toNodeWithoutBreakout.nodeSize).insert(mappedTo, newLayout); if ((0, _expValEquals.expValEquals)('platform_editor_breakout_resizing', 'isEnabled', true)) { breakoutMode && tr.setNodeMarkup(mappedTo, newLayout.type, newLayout.attrs, [breakout.create({ mode: breakoutMode, width: breakoutWidth })]); } else { breakoutMode && tr.setNodeMarkup(mappedTo, newLayout.type, newLayout.attrs, [breakout.create({ mode: breakoutMode })]); } if ((0, _platformFeatureFlags.fg)('platform_editor_column_count_analytics')) { // layout created via drag and drop will always be 2 columns (0, _analytics2.fireInsertLayoutAnalytics)(tr, api, sourceNodeTypes, hasSelectedMultipleNodes, 2); } else { (0, _analytics2.fireInsertLayoutAnalytics)(tr, api, sourceNodeTypes, hasSelectedMultipleNodes); } } return tr; } }; }; };