@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
321 lines (315 loc) • 17.5 kB
JavaScript
"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
})
}
});
};