UNPKG

@atlaskit/editor-plugin-extension

Version:

editor-plugin-extension plugin for @atlaskit/editor-core

321 lines (315 loc) 17.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.handleUpdate = exports.createPlugin = exports.createExtensionProviderHandler = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _selection2 = require("@atlaskit/editor-common/selection"); var _state = require("@atlaskit/editor-prosemirror/state"); var _utils = require("@atlaskit/editor-prosemirror/utils"); var _commands = require("../editor-commands/commands"); var _lazyExtension = require("../nodeviews/lazyExtension"); var _pluginFactory = require("./plugin-factory"); var _pluginKey = require("./plugin-key"); var _updateEditButton = require("./update-edit-button"); var _utils2 = require("./utils"); var shouldShowEditButton = function shouldShowEditButton(extensionHandler, extensionProvider) { var usesLegacyMacroBrowser = !extensionHandler && !extensionProvider || typeof extensionHandler === 'function'; var usesModernUpdateMethod = (0, _typeof2.default)(extensionHandler) === 'object' && typeof extensionHandler.update === 'function'; if (usesLegacyMacroBrowser || usesModernUpdateMethod) { return true; } return false; }; var getUpdateExtensionPromise = /*#__PURE__*/function () { var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(view, extensionHandler, extensionProvider) { var updateMethod; return _regenerator.default.wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: if (!(extensionHandler && (0, _typeof2.default)(extensionHandler) === 'object')) { _context.next = 4; break; } return _context.abrupt("return", extensionHandler.update); case 4: if (!extensionProvider) { _context.next = 10; break; } _context.next = 7; return (0, _updateEditButton.updateEditButton)(view, extensionProvider); case 7: updateMethod = _context.sent; if (!updateMethod) { _context.next = 10; break; } return _context.abrupt("return", updateMethod); case 10: throw new Error('No update method available'); case 11: case "end": return _context.stop(); } }, _callee); })); return function getUpdateExtensionPromise(_x, _x2, _x3) { return _ref.apply(this, arguments); }; }(); var createExtensionProviderHandler = exports.createExtensionProviderHandler = function createExtensionProviderHandler(view) { return /*#__PURE__*/function () { var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(name, provider) { var extensionProvider; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: if (!(name === 'extensionProvider' && provider)) { _context2.next = 13; break; } _context2.prev = 1; _context2.next = 4; return provider; case 4: extensionProvider = _context2.sent; (0, _commands.updateState)({ extensionProvider: extensionProvider })(view.state, view.dispatch); _context2.next = 8; return (0, _updateEditButton.updateEditButton)(view, extensionProvider); case 8: _context2.next = 13; break; case 10: _context2.prev = 10; _context2.t0 = _context2["catch"](1); (0, _commands.updateState)({ extensionProvider: undefined })(view.state, view.dispatch); case 13: case "end": return _context2.stop(); } }, _callee2, null, [[1, 10]]); })); return function (_x4, _x5) { return _ref2.apply(this, arguments); }; }(); }; var handleUpdate = exports.handleUpdate = function handleUpdate(_ref3) { var view = _ref3.view, prevState = _ref3.prevState, domAtPos = _ref3.domAtPos, extensionHandlers = _ref3.extensionHandlers, applyChange = _ref3.applyChange; var state = view.state, dispatch = view.dispatch; var _getPluginState = (0, _pluginFactory.getPluginState)(state), element = _getPluginState.element, localId = _getPluginState.localId, extensionProvider = _getPluginState.extensionProvider, showContextPanel = _getPluginState.showContextPanel, showEditButton = _getPluginState.showEditButton; // This fetches the selected extension node, either by keyboard selection or click for all types of extensions var selectedExtension = (0, _utils2.getSelectedExtension)(state, true); if (!selectedExtension) { if (showContextPanel) { (0, _commands.clearEditingContext)(applyChange)(state, dispatch); } return; } var node = selectedExtension.node; var newElement = (0, _utils2.getSelectedDomElement)(state.schema, domAtPos, selectedExtension); // In some cases, showEditButton can be stale and the edit button doesn't show - @see ED-15285 // To be safe, we update the showEditButton state here var shouldUpdateEditButton = !showEditButton && extensionProvider && element === newElement && !(0, _utils2.getSelectedExtension)(prevState, true); var isNewNodeSelected = node.attrs.localId ? localId !== node.attrs.localId : // This is the current assumption and it's wrong but we are keeping it // as fallback in case we need to turn off `allowLocalIdGeneration` element !== newElement; if (isNewNodeSelected || shouldUpdateEditButton) { if (showContextPanel) { (0, _commands.clearEditingContext)(applyChange)(state, dispatch); return; } var extensionType = node.attrs.extensionType; var extensionHandler = extensionHandlers[extensionType]; // showEditButton might change async based on results from extension providers var _showEditButton = shouldShowEditButton(extensionHandler, extensionProvider); var updateExtension = getUpdateExtensionPromise(view, extensionHandler, extensionProvider).catch(function () { // do nothing; }); (0, _commands.updateState)({ localId: node.attrs.localId, showContextPanel: false, element: newElement, showEditButton: _showEditButton, updateExtension: updateExtension })(state, dispatch); } // New DOM element doesn't necessarily mean it's a new Node else if (element !== newElement) { (0, _commands.updateState)({ element: newElement })(state, dispatch); } return true; }; var createPlugin = exports.createPlugin = function createPlugin(dispatch, providerFactory, extensionHandlers, portalProviderAPI, eventDispatcher, pluginInjectionApi) { var _featureFlags$macroIn; var useLongPressSelection = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false; var options = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : {}; var featureFlags = arguments.length > 8 ? arguments[8] : undefined; var __rendererExtensionOptions = arguments.length > 9 ? arguments[9] : undefined; var state = (0, _pluginFactory.createPluginState)(dispatch, { showEditButton: false, showContextPanel: false }); var extensionNodeViewOptions = { appearance: options.appearance, getExtensionHeight: options.getExtensionHeight }; var macroInteractionDesignFeatureFlags = { showMacroInteractionDesignUpdates: (_featureFlags$macroIn = featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.macroInteractionUpdates) !== null && _featureFlags$macroIn !== void 0 ? _featureFlags$macroIn : false }; var showLivePagesBodiedMacrosRendererView = __rendererExtensionOptions === null || __rendererExtensionOptions === void 0 ? void 0 : __rendererExtensionOptions.isAllowedToUseRendererView; return new _safePlugin.SafePlugin({ state: state, view: function view(editorView) { var domAtPos = editorView.domAtPos.bind(editorView); var extensionProviderHandler = createExtensionProviderHandler(editorView); providerFactory.subscribe('extensionProvider', extensionProviderHandler); return { update: function update(view, prevState) { var _pluginInjectionApi$c; handleUpdate({ view: view, prevState: prevState, domAtPos: domAtPos, extensionHandlers: extensionHandlers, applyChange: pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.contextPanel) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.actions.applyChange }); }, destroy: function destroy() { providerFactory.unsubscribe('extensionProvider', extensionProviderHandler); } }; }, key: _pluginKey.pluginKey, props: { handleDOMEvents: { /** * ED-18072 - Cannot shift + arrow past bodied extension if it is not empty. * This code is to handle the case where the selection starts inside or on the node and the user is trying to shift + arrow. * For other part of the solution see code in: packages/editor/editor-core/src/plugins/selection/pm-plugins/events/keydown.ts */ keydown: function keydown(view, event) { if (event instanceof KeyboardEvent && event.shiftKey && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) { var _view$state = view.state, schema = _view$state.schema, selection = _view$state.selection, $head = _view$state.selection.$head, doc = _view$state.doc, tr = _view$state.tr; var bodiedExtension = schema.nodes.bodiedExtension; if (selection instanceof _state.TextSelection || selection instanceof _state.NodeSelection) { var maybeBodiedExtension = selection instanceof _state.TextSelection ? (0, _utils.findParentNodeOfTypeClosestToPos)($head, bodiedExtension) : (0, _utils.findSelectedNodeOfType)(bodiedExtension)(selection); if (maybeBodiedExtension) { var end = maybeBodiedExtension.pos + maybeBodiedExtension.node.nodeSize; if (event.key === 'ArrowUp' || event.key === 'ArrowLeft' && (0, _selection2.isSelectionAtStartOfNode)($head, maybeBodiedExtension === null || maybeBodiedExtension === void 0 ? void 0 : maybeBodiedExtension.node)) { var anchor = end + 1; // an offset is used here so that left arrow selects the first character before the node (consistent with arrow right) var headOffset = event.key === 'ArrowLeft' ? -1 : 0; var head = maybeBodiedExtension.pos + headOffset; var newSelection = _state.TextSelection.create(doc, Math.max(anchor, selection.anchor), head); view.dispatch(tr.setSelection(newSelection)); return true; } if (event.key === 'ArrowDown' || event.key === 'ArrowRight' && (0, _selection2.isSelectionAtEndOfNode)($head, maybeBodiedExtension === null || maybeBodiedExtension === void 0 ? void 0 : maybeBodiedExtension.node)) { var _anchor = maybeBodiedExtension.pos - 1; var _head = end + 1; var _newSelection = _state.TextSelection.create(doc, Math.min(_anchor, selection.anchor), _head); view.dispatch(tr.setSelection(_newSelection)); return true; } } } } // Handle non shift key case for MBE if (event instanceof KeyboardEvent && !event.shiftKey && event.key === 'ArrowLeft') { var _view$state2 = view.state, _schema = _view$state2.schema, _selection = _view$state2.selection, _$head = _view$state2.selection.$head, _doc = _view$state2.doc, _tr = _view$state2.tr; var _schema$nodes = _schema.nodes, multiBodiedExtension = _schema$nodes.multiBodiedExtension, extensionFrame = _schema$nodes.extensionFrame, paragraph = _schema$nodes.paragraph; if (_selection instanceof _selection2.GapCursorSelection || _selection instanceof _state.TextSelection && _$head.parent.type === paragraph) { var maybeMultiBodiedExtension = (0, _utils.findParentNodeOfTypeClosestToPos)(_$head, multiBodiedExtension); if (maybeMultiBodiedExtension) { var _tr$doc$nodeAt; /* In case of gap cursor, we need to decrement the position by 1 as we need to check the node at previous position * In case of text selection, we need to decrement the position by 2 as we need to jump back twice, once from text node and then its parent paragraph node */ var previousPositionDecrement = _selection instanceof _selection2.GapCursorSelection ? 1 : 2; if (((_tr$doc$nodeAt = _tr.doc.nodeAt(_$head.pos - previousPositionDecrement)) === null || _tr$doc$nodeAt === void 0 ? void 0 : _tr$doc$nodeAt.type) === extensionFrame) { var _newSelection2 = _state.TextSelection.create(_doc, _tr.doc.resolve(_$head.pos - previousPositionDecrement).start(_$head.depth - previousPositionDecrement)); view.dispatch(_tr.setSelection(_newSelection2)); } } } } return false; } }, nodeViews: { // WARNING: referentiality-plugin also creates these nodeviews extension: (0, _lazyExtension.lazyExtensionNodeView)('extension', portalProviderAPI, eventDispatcher, providerFactory, extensionHandlers, extensionNodeViewOptions, pluginInjectionApi, macroInteractionDesignFeatureFlags), // WARNING: referentiality-plugin also creates these nodeviews bodiedExtension: (0, _lazyExtension.lazyExtensionNodeView)('bodiedExtension', portalProviderAPI, eventDispatcher, providerFactory, extensionHandlers, extensionNodeViewOptions, pluginInjectionApi, macroInteractionDesignFeatureFlags, showLivePagesBodiedMacrosRendererView, __rendererExtensionOptions === null || __rendererExtensionOptions === void 0 ? void 0 : __rendererExtensionOptions.showUpdated1PBodiedExtensionUI, __rendererExtensionOptions === null || __rendererExtensionOptions === void 0 ? void 0 : __rendererExtensionOptions.rendererExtensionHandlers), // WARNING: referentiality-plugin also creates these nodeviews inlineExtension: (0, _lazyExtension.lazyExtensionNodeView)('inlineExtension', portalProviderAPI, eventDispatcher, providerFactory, extensionHandlers, extensionNodeViewOptions, pluginInjectionApi, macroInteractionDesignFeatureFlags), multiBodiedExtension: (0, _lazyExtension.lazyExtensionNodeView)('multiBodiedExtension', portalProviderAPI, eventDispatcher, providerFactory, extensionHandlers, extensionNodeViewOptions, pluginInjectionApi, macroInteractionDesignFeatureFlags) }, createSelectionBetween: function createSelectionBetween(view, anchor, head) { var _view$state3 = view.state, schema = _view$state3.schema, doc = _view$state3.doc; var multiBodiedExtension = schema.nodes.multiBodiedExtension; var isAnchorInMBE = (0, _utils.findParentNodeOfTypeClosestToPos)(anchor, multiBodiedExtension); var isHeadInMBE = (0, _utils.findParentNodeOfTypeClosestToPos)(head, multiBodiedExtension); if (isAnchorInMBE !== undefined && isHeadInMBE === undefined) { // Anchor is in MBE, where user started selecting within MBE and then moved outside var newSelection = _state.TextSelection.create(doc, isAnchorInMBE.pos < head.pos ? isAnchorInMBE.pos : isAnchorInMBE.pos + isAnchorInMBE.node.nodeSize, // isAnchorInMBE.pos < head.pos represents downwards selection head.pos); return newSelection; } if (isAnchorInMBE === undefined && isHeadInMBE !== undefined) { // Head is in MBE, where user started selecting outside MBE and then moved inside var _newSelection3 = _state.TextSelection.create(doc, anchor.pos, isHeadInMBE.pos < anchor.pos ? isHeadInMBE.pos : isHeadInMBE.pos + isHeadInMBE.node.nodeSize // isHeadInMBE.pos < anchor.pos represents upwards selection ); return _newSelection3; } return null; }, handleClickOn: (0, _selection2.createSelectionClickHandler)(['extension', 'bodiedExtension', 'multiBodiedExtension'], function (target) { return !target.closest('.extension-non-editable-area') && (!target.closest('.extension-content') || !!target.closest('.extension-container')); }, // It's to enable nested extensions selection { useLongPressSelection: useLongPressSelection }) } }); };