@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
259 lines (257 loc) • 12.2 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _regeneratorRuntime from "@babel/runtime/regenerator";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD, TARGET_SELECTION_SOURCE } from '@atlaskit/editor-common/analytics';
import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { findSelectedNodeOfType, replaceParentNodeOfType, replaceSelectedNode } from '@atlaskit/editor-prosemirror/utils';
import { createExtensionAPI, getEditInLegacyMacroBrowser } from '../pm-plugins/extension-api';
import { getPluginState } from '../pm-plugins/plugin-factory';
import { findExtensionWithLocalId } from '../pm-plugins/utils';
export var buildExtensionNode = function buildExtensionNode(type, schema, attrs, content, marks) {
switch (type) {
case 'extension':
return schema.nodes.extension.createChecked(attrs, content, marks);
case 'inlineExtension':
return schema.nodes.inlineExtension.createChecked(attrs, content, marks);
case 'bodiedExtension':
return schema.nodes.bodiedExtension.create(attrs, content, marks);
case 'multiBodiedExtension':
return schema.nodes.multiBodiedExtension.create(attrs, content, marks);
}
};
export var performNodeUpdate = function performNodeUpdate(editorAnalyticsAPI) {
return function (type, newAttrs, content, marks, shouldScrollIntoView) {
return function (_state, _dispatch, view) {
if (!view) {
throw Error('EditorView is required to perform node update!');
}
// NOTE: `state` and `dispatch` are stale at this point so we need to grab
// the latest one from `view` @see HOT-93986
var state = view.state,
dispatch = view.dispatch;
var newNode = buildExtensionNode(type, state.schema, newAttrs, content, marks);
if (!newNode) {
return false;
}
var selection = state.selection,
schema = state.schema;
var _schema$nodes = schema.nodes,
extension = _schema$nodes.extension,
inlineExtension = _schema$nodes.inlineExtension,
bodiedExtension = _schema$nodes.bodiedExtension,
multiBodiedExtension = _schema$nodes.multiBodiedExtension;
var isBodiedExtensionSelected = !!findSelectedNodeOfType([bodiedExtension])(selection);
var isMultiBodiedExtensionSelected = !!findSelectedNodeOfType([multiBodiedExtension])(selection);
var extensionState = getPluginState(state);
var updateSelectionsByNodeType = function updateSelectionsByNodeType(nodeType) {
// Bodied/MultiBodied extensions can trigger an update when the cursor is inside which means that there is no node selected.
// To work around that we replace the parent and create a text selection instead of new node selection
tr = replaceParentNodeOfType(nodeType, newNode)(tr);
// Replacing selected node doesn't update the selection. `selection.node` still returns the old node
tr.setSelection(TextSelection.create(tr.doc, state.selection.anchor));
};
var targetSelectionSource = TARGET_SELECTION_SOURCE.CURRENT_SELECTION;
var action = ACTION.UPDATED;
var tr = state.tr;
// When it's a bodiedExtension but not selected
if (newNode.type === bodiedExtension && !isBodiedExtensionSelected) {
updateSelectionsByNodeType(state.schema.nodes.bodiedExtension);
}
// When it's a multiBodiedExtension but not selected
else if (newNode.type === multiBodiedExtension && !isMultiBodiedExtensionSelected) {
updateSelectionsByNodeType(state.schema.nodes.multiBodiedExtension);
}
// If any extension is currently selected
else if (findSelectedNodeOfType([extension, bodiedExtension, inlineExtension, multiBodiedExtension])(selection)) {
tr = replaceSelectedNode(newNode)(tr);
// Replacing selected node doesn't update the selection. `selection.node` still returns the old node
tr.setSelection(NodeSelection.create(tr.doc, tr.mapping.map(state.selection.anchor)));
}
// When we loose the selection. This usually happens when Synchrony resets or changes
// the selection when user is in the middle of updating an extension.
else if (extensionState.element) {
var pos = view.posAtDOM(extensionState.element, -1);
if (pos > -1) {
tr = tr.replaceWith(pos, pos + (content.size || 0) + 1, newNode);
tr.setSelection(Selection.near(tr.doc.resolve(pos)));
targetSelectionSource = TARGET_SELECTION_SOURCE.HTML_ELEMENT;
} else {
action = ACTION.ERRORED;
}
}
// Only scroll if we have anything to update, best to avoid surprise scroll
if (dispatch && tr.docChanged) {
var _newNode$attrs = newNode.attrs,
extensionType = _newNode$attrs.extensionType,
extensionKey = _newNode$attrs.extensionKey,
layout = _newNode$attrs.layout,
localId = _newNode$attrs.localId;
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
action: action,
actionSubject: ACTION_SUBJECT.EXTENSION,
actionSubjectId: newNode.type.name,
eventType: EVENT_TYPE.TRACK,
attributes: {
// @ts-expect-error - Type is not assignable to parameter of type 'AnalyticsEventPayload'
// This error was introduced after upgrading to TypeScript 5
inputMethod: INPUT_METHOD.CONFIG_PANEL,
extensionType: extensionType,
extensionKey: extensionKey,
layout: layout,
localId: localId,
selection: tr.selection.toJSON(),
targetSelectionSource: targetSelectionSource
}
})(tr);
dispatch(shouldScrollIntoView ? tr.scrollIntoView() : tr);
}
return true;
};
};
};
var updateExtensionParams = function updateExtensionParams(editorAnalyticsAPI) {
return function (updateExtension, node, actions) {
return /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(state, dispatch, view) {
var _node$node, attrs, type, content, marks, parameters, newParameters, newAttrs;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_node$node = node.node, attrs = _node$node.attrs, type = _node$node.type, content = _node$node.content, marks = _node$node.marks;
if (state.schema.nodes[type.name]) {
_context.next = 3;
break;
}
return _context.abrupt("return", false);
case 3:
parameters = attrs.parameters;
_context.prev = 4;
_context.next = 7;
return updateExtension(parameters, actions);
case 7:
newParameters = _context.sent;
if (!newParameters) {
_context.next = 12;
break;
}
newAttrs = _objectSpread(_objectSpread({}, attrs), {}, {
parameters: _objectSpread(_objectSpread({}, parameters), newParameters)
});
if (type.name === 'multiBodiedExtension') {
newAttrs.parameters.macroParams = _objectSpread(_objectSpread({}, parameters.macroParams), newParameters === null || newParameters === void 0 ? void 0 : newParameters.macroParams);
}
return _context.abrupt("return", performNodeUpdate(editorAnalyticsAPI)(type.name, newAttrs, content, marks, true)(state, dispatch, view));
case 12:
_context.next = 16;
break;
case 14:
_context.prev = 14;
_context.t0 = _context["catch"](4);
case 16:
return _context.abrupt("return", true);
case 17:
case "end":
return _context.stop();
}
}, _callee, null, [[4, 14]]);
}));
return function (_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
}();
};
};
export var editExtension = function editExtension(macroProvider, applyChangeToContextPanel, editorAnalyticsAPI, updateExtension) {
return function (state, dispatch, view) {
if (!view) {
return false;
}
var _getPluginState = getPluginState(state),
localId = _getPluginState.localId;
var nodeWithPos = findExtensionWithLocalId(state, localId);
if (!nodeWithPos) {
return false;
}
var editInLegacyMacroBrowser = getEditInLegacyMacroBrowser({
view: view,
macroProvider: macroProvider || undefined,
editorAnalyticsAPI: editorAnalyticsAPI
});
if (updateExtension) {
updateExtension.then(function (updateMethod) {
if (updateMethod && view) {
var actions = createExtensionAPI({
editorView: view,
editInLegacyMacroBrowser: editInLegacyMacroBrowser,
applyChange: applyChangeToContextPanel,
editorAnalyticsAPI: editorAnalyticsAPI
});
updateExtensionParams(editorAnalyticsAPI)(updateMethod, nodeWithPos, actions)(state, dispatch, view);
return;
}
if (!updateMethod && macroProvider) {
editInLegacyMacroBrowser();
return;
}
});
} else {
if (!macroProvider) {
return false;
}
editInLegacyMacroBrowser();
}
return true;
};
};
export var createEditSelectedExtensionAction = function createEditSelectedExtensionAction(_ref2) {
var editorViewRef = _ref2.editorViewRef,
editorAnalyticsAPI = _ref2.editorAnalyticsAPI,
applyChangeToContextPanel = _ref2.applyChangeToContextPanel;
return function () {
var view = editorViewRef.current;
if (!view) {
return false;
}
var _getPluginState2 = getPluginState(view.state),
updateExtension = _getPluginState2.updateExtension;
return editExtension(null, applyChangeToContextPanel, editorAnalyticsAPI, updateExtension)(view.state, view.dispatch, view);
};
};
export var insertOrReplaceExtension = function insertOrReplaceExtension(_ref3) {
var editorView = _ref3.editorView,
action = _ref3.action,
attrs = _ref3.attrs,
content = _ref3.content,
position = _ref3.position,
_ref3$size = _ref3.size,
size = _ref3$size === void 0 ? 0 : _ref3$size,
tr = _ref3.tr;
var newNode = editorView.state.schema.node('extension', attrs, content);
if (action === 'insert') {
tr = editorView.state.tr.insert(position, newNode);
return tr;
} else {
tr.replaceWith(position, position + size, newNode);
return tr;
}
};
export var insertOrReplaceBodiedExtension = function insertOrReplaceBodiedExtension(_ref4) {
var editorView = _ref4.editorView,
action = _ref4.action,
attrs = _ref4.attrs,
content = _ref4.content,
position = _ref4.position,
_ref4$size = _ref4.size,
size = _ref4$size === void 0 ? 0 : _ref4$size,
tr = _ref4.tr;
var newNode = editorView.state.schema.node('bodiedExtension', attrs, content);
if (action === 'insert') {
tr = editorView.state.tr.insert(position, newNode);
return tr;
} else {
tr.replaceWith(position, position + size, newNode);
return tr;
}
};