UNPKG

@atlaskit/editor-plugin-expand

Version:

Expand plugin for @atlaskit/editor-core

171 lines (166 loc) 8.33 kB
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { createSelectionClickHandler } from '@atlaskit/editor-common/selection'; import { expandClassNames } from '@atlaskit/editor-common/styles'; import { transformSliceExpandToNestedExpand, transformSliceNestedExpandToExpand } from '@atlaskit/editor-common/transforms'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { fg } from '@atlaskit/platform-feature-flags'; import { TOGGLE_EXPAND_RANGE_META_KEY } from '../../editor-commands/toggleExpandRange'; // Ignored via go/ees005 // eslint-disable-next-line import/no-named-as-default import ExpandNodeView from '../node-views'; export var pluginKey = new PluginKey('expandPlugin'); export function containsClass(element, className) { var _element$classList; return Boolean(element === null || element === void 0 || (_element$classList = element.classList) === null || _element$classList === void 0 ? void 0 : _element$classList.contains(className)); } export var createPlugin = function createPlugin(dispatch, getIntl) { var appearance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'full-page'; var useLongPressSelection = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var api = arguments.length > 4 ? arguments[4] : undefined; var nodeViewPortalProviderAPI = arguments.length > 5 ? arguments[5] : undefined; var allowInteractiveExpand = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : true; var __livePage = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false; var isMobile = false; return new SafePlugin({ key: pluginKey, state: { init: function init() { return DecorationSet.empty; }, apply: function apply(tr, decorationSet) { if (!fg('platform_editor_show_diff_scroll_navigation')) { return DecorationSet.empty; } var meta = tr.getMeta(TOGGLE_EXPAND_RANGE_META_KEY); if (meta && meta.positions.length > 0) { // Add node decorations for each expand node that was toggled. // ExpandNodeView.update() uses these decorations to detect it needs to // visually open or close, even when expandedState was set before the // transaction replaced the node objects. // We do NOT map or carry forward existing decorations — we start fresh // each time the meta is present. var decorations = meta.positions.map(function (pos) { var _tr$doc$nodeAt$nodeSi, _tr$doc$nodeAt; return Decoration.node(pos, pos + ((_tr$doc$nodeAt$nodeSi = (_tr$doc$nodeAt = tr.doc.nodeAt(pos)) === null || _tr$doc$nodeAt === void 0 ? void 0 : _tr$doc$nodeAt.nodeSize) !== null && _tr$doc$nodeAt$nodeSi !== void 0 ? _tr$doc$nodeAt$nodeSi : 0), {}, { forceExpandOpen: meta.open }); }); return DecorationSet.create(tr.doc, decorations); } // Map existing decorations through document changes. // They will be naturally cleared when they no longer match any node // (e.g. if the expand is deleted), or on the next toggleExpandRange call. return decorationSet.map(tr.mapping, tr.doc); } }, props: { decorations: function decorations(state) { if (!fg('platform_editor_show_diff_scroll_navigation')) { return undefined; } return pluginKey.getState(state); }, nodeViews: { expand: ExpandNodeView({ getIntl: getIntl, isMobile: isMobile, api: api, nodeViewPortalProviderAPI: nodeViewPortalProviderAPI, allowInteractiveExpand: allowInteractiveExpand, __livePage: __livePage }), nestedExpand: ExpandNodeView({ getIntl: getIntl, isMobile: isMobile, api: api, nodeViewPortalProviderAPI: nodeViewPortalProviderAPI, allowInteractiveExpand: allowInteractiveExpand, __livePage: __livePage }) }, handleKeyDown: function handleKeyDown(_view, event) { return containsClass(event.target, expandClassNames.titleContainer); }, handleKeyPress: function handleKeyPress(_view, event) { return containsClass(event.target, expandClassNames.titleContainer); }, handleScrollToSelection: function handleScrollToSelection() { return containsClass(document.activeElement, expandClassNames.titleInput); }, handleClickOn: createSelectionClickHandler(['expand', 'nestedExpand'], function (target) { return target.classList.contains(expandClassNames.prefix); }, { useLongPressSelection: useLongPressSelection }), handleDrop: function handleDrop(view, event, slice, _moved) { return handleExpandDrag(view, event, slice); } }, // @see ED-8027 to follow up on this work-around filterTransaction: function filterTransaction(tr) { if (containsClass(document.activeElement, expandClassNames.titleInput) && tr.selectionSet && (!tr.steps.length || tr.isGeneric)) { return false; } return true; } }); }; /** * Convert a nested expand to an expand when dropped outside an expand or table. Convert an expand to a nested expand when dropped inside an expand or table. */ export function handleExpandDrag(view, event, slice) { var state = view.state, dispatch = view.dispatch; var tr = state.tr; var selection = state.selection; var from = selection.from, to = selection.to; var sliceContainsExpand = false; var sliceContainsNestedExpand = false; slice.content.forEach(function (node) { if (node.type === state.schema.nodes.expand) { sliceContainsExpand = true; } else if (node.type === state.schema.nodes.nestedExpand) { sliceContainsNestedExpand = true; } }); // Check if the contents of the dragged slice contain a nested expand node or expand node. // Also not handling expands with nested expands for now. if (!sliceContainsExpand && !sliceContainsNestedExpand || sliceContainsExpand && sliceContainsNestedExpand) { return false; } var dropPos = view.posAtCoords({ left: event.clientX, top: event.clientY }); if (!dropPos) { return false; } var resolvedPos = state.doc.resolve(dropPos.pos); var dropLocationNodeType = resolvedPos.node().type; var dropLocationParentNodeType = resolvedPos.depth > 0 ? resolvedPos.node(resolvedPos.depth - 1).type : dropLocationNodeType; var nodesWithNestedExpandSupport = [state.schema.nodes.expand, state.schema.nodes.tableHeader, state.schema.nodes.tableCell]; var isNodeAtDropPosInsideNodesWithNestedExpandSupport = nodesWithNestedExpandSupport.includes(dropLocationNodeType) || nodesWithNestedExpandSupport.includes(dropLocationParentNodeType); var isNodeBeingDroppedInsideNestedExpand = dropLocationNodeType === state.schema.nodes.nestedExpand || dropLocationParentNodeType === state.schema.nodes.nestedExpand; var updatedSlice = slice; if (sliceContainsExpand && isNodeAtDropPosInsideNodesWithNestedExpandSupport) { updatedSlice = transformSliceExpandToNestedExpand(slice); } else if (sliceContainsNestedExpand && !isNodeAtDropPosInsideNodesWithNestedExpandSupport && !isNodeBeingDroppedInsideNestedExpand) { updatedSlice = transformSliceNestedExpandToExpand(slice, state.schema); } if (!updatedSlice || updatedSlice.eq(slice)) { return false; } // The drop position will be affected when the original slice is deleted from the document. var updatedDropPos = dropPos.pos > from ? dropPos.pos - updatedSlice.content.size : dropPos.pos; // Adjust the drop position to place the slice before the node at the position the cursor is pointing at, except when the drop location is the document node. // Otherwise causes weird behaviour with tables & quotes, splits them apart. Only do this for nested expand slice transformed to expand. if (dropLocationNodeType !== state.schema.nodes.doc && !sliceContainsExpand) { updatedDropPos = updatedDropPos - 1; } tr.delete(from, to); tr.insert(updatedDropPos, updatedSlice.content); dispatch(tr); return true; }