UNPKG

@atlaskit/editor-plugin-extension

Version:

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

299 lines (289 loc) 14.1 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _typeof from "@babel/runtime/helpers/typeof"; 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 { validator } from '@atlaskit/adf-utils/validator'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { nodeToJSON } from '@atlaskit/editor-common/utils'; import { JSONTransformer } from '@atlaskit/editor-json-transformer'; import { Fragment, Mark } from '@atlaskit/editor-prosemirror/model'; import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { setTextSelection } from '@atlaskit/editor-prosemirror/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { setEditingContextToContextPanel } from '../editor-commands/commands'; import { insertMacroFromMacroBrowser } from './macro/actions'; import { pluginKey as macroPluginKey } from './macro/plugin-key'; import { findNodePosWithLocalId, getDataConsumerMark, getNodeTypesReferenced, getSelectedExtension } from './utils'; export var getEditInLegacyMacroBrowser = function getEditInLegacyMacroBrowser(_ref) { var view = _ref.view, macroProvider = _ref.macroProvider, editorAnalyticsAPI = _ref.editorAnalyticsAPI; return function () { if (!view) { throw new Error("Missing view. Can't update without EditorView"); } if (!macroProvider) { throw new Error("Missing macroProvider. Can't use the macro browser for updates"); } var nodeWithPos = getSelectedExtension(view.state, true); if (!nodeWithPos) { throw new Error("Missing nodeWithPos. Can't determine position of node"); } insertMacroFromMacroBrowser(editorAnalyticsAPI)(macroProvider, nodeWithPos.node, true)(view); }; }; var extensionAPICallPayload = function extensionAPICallPayload(functionName) { return { action: ACTION.INVOKED, actionSubject: ACTION_SUBJECT.EXTENSION, actionSubjectId: ACTION_SUBJECT_ID.EXTENSION_API, attributes: { functionName: functionName }, eventType: EVENT_TYPE.TRACK }; }; export var createExtensionAPI = function createExtensionAPI(options) { var schema = options.editorView.state.schema, editorAnalyticsAPI = options.editorAnalyticsAPI; var nodes = Object.keys(schema.nodes); var marks = Object.keys(schema.marks); var validate = validator(nodes, marks, { allowPrivateAttributes: true }); /** * Finds the node and its position by `localId`. Throws if the node could not be found. * * @returns {NodeWithPos} */ var ensureNodePosByLocalId = function ensureNodePosByLocalId(localId, _ref2) { var opName = _ref2.opName; // Be extra cautious since 3rd party devs can use regular JS without type safety if (typeof localId !== 'string' || localId === '') { throw new Error("".concat(opName, "(): Invalid localId '").concat(localId, "'.")); } // Find the node + position matching the given ID var state = options.editorView.state; var nodePos = findNodePosWithLocalId(state, localId); if (!nodePos) { throw new Error("".concat(opName, "(): Could not find node with ID '").concat(localId, "'.")); } return nodePos; }; var doc = { insertAfter: function insertAfter(localId, adf, opt) { try { validate(adf); } catch (e) { throw new Error("insertAfter(): Invalid ADF given."); } var nodePos = ensureNodePosByLocalId(localId, { opName: 'insertAfter' }); var editorView = options.editorView; var dispatch = editorView.dispatch, state = editorView.state; // Validate the given ADF var tr = state.tr, schema = state.schema; var nodeType = schema.nodes[adf.type]; if (!nodeType) { throw new Error("insertAfter(): Invalid ADF type '".concat(adf.type, "'.")); } var fragment = Fragment.fromJSON(schema, adf.content); var marks = (adf.marks || []).map(function (markEntity) { return Mark.fromJSON(schema, markEntity); }); var newNode = nodeType === null || nodeType === void 0 ? void 0 : nodeType.createChecked(adf.attrs, fragment, marks); if (!newNode) { throw new Error('insertAfter(): Could not create a node for given ADFEntity.'); } var insertPosition = nodePos.pos + nodePos.node.nodeSize; tr.insert(insertPosition, newNode); // Validate if the document is valid at this point try { tr.doc.check(); } catch (err) { throw new Error("insertAfter(): The given ADFEntity cannot be inserted in the current position.\n".concat(err)); } // Analytics - tracking the api call var apiCallPayload = extensionAPICallPayload('insertAfter'); editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(apiCallPayload)(tr); // Analytics - tracking node types added var nodesAdded = [newNode]; newNode.descendants(function (node) { nodesAdded.push(node); return true; }); nodesAdded.forEach(function (node) { var _node$attrs = node.attrs, extensionKey = _node$attrs.extensionKey, extensionType = _node$attrs.extensionType; var dataConsumerMark = getDataConsumerMark(node); var stringIds = (dataConsumerMark === null || dataConsumerMark === void 0 ? void 0 : dataConsumerMark.attrs.sources.map(function (sourceId) { return sourceId; })) || []; var hasReferentiality = !!dataConsumerMark; var nodeTypesReferenced = hasReferentiality ? getNodeTypesReferenced(stringIds, state) : undefined; // fire off analytics for this ADF var payload = { action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.DOCUMENT, attributes: { nodeType: node.type.name, inputMethod: INPUT_METHOD.EXTENSION_API, hasReferentiality: hasReferentiality, nodeTypesReferenced: nodeTypesReferenced, layout: node.attrs.layout, extensionType: extensionType, extensionKey: extensionKey }, eventType: EVENT_TYPE.TRACK }; editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(payload)(tr); }); if (opt) { if (opt.allowSelectionToNewNode) { tr.setSelection(new NodeSelection(tr.doc.resolve(insertPosition))); } else if (opt.allowSelectionNearNewNode) { tr.setSelection(TextSelection.near(tr.doc.resolve(insertPosition))); } } dispatch(tr); }, scrollTo: function scrollTo(localId) { var nodePos = ensureNodePosByLocalId(localId, { opName: 'scrollTo' }); // Analytics - tracking the api call var apiCallPayload = extensionAPICallPayload('scrollTo'); var _options$editorView = options.editorView, dispatch = _options$editorView.dispatch, state = _options$editorView.state; var tr = state.tr; editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(apiCallPayload)(tr); tr = setTextSelection(nodePos.pos)(tr); var useScrollIntoView = true; if (nodePos.node.type.name === 'table' && expValEquals('platform_editor_table_sticky_header_improvements', 'cohort', 'test_with_overflow') && expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) { var tableDOM = options.editorView.nodeDOM(nodePos.pos); if (tableDOM instanceof HTMLElement) { tableDOM.scrollIntoView({ block: 'start', behavior: 'smooth' }); useScrollIntoView = false; } } if (useScrollIntoView) { tr.scrollIntoView(); } dispatch(tr); }, update: function update(localId, mutationCallback, opts) { var _changedValues$marks; var _ensureNodePosByLocal = ensureNodePosByLocalId(localId, { opName: 'update' }), node = _ensureNodePosByLocal.node, pos = _ensureNodePosByLocal.pos; var _options$editorView2 = options.editorView, dispatch = _options$editorView2.dispatch, state = _options$editorView2.state; var tr = state.tr, schema = state.schema; var changedValues = mutationCallback({ content: nodeToJSON(node).content, attrs: node.attrs, marks: node.marks.map(function (pmMark) { return { type: pmMark.type.name, attrs: pmMark.attrs }; }) }); var ensureValidMark = function ensureValidMark(mark) { if (_typeof(mark) !== 'object' || Array.isArray(mark)) { throw new Error("update(): Invalid mark given."); } var _state$doc$resolve = state.doc.resolve(pos), parent = _state$doc$resolve.parent; // Ensure that the given mark is present in the schema var markType = schema.marks[mark.type]; if (!markType) { throw new Error("update(): Invalid ADF mark type '".concat(mark.type, "'.")); } if (!parent.type.allowsMarkType(markType)) { throw new Error("update(): Parent of type '".concat(parent.type.name, "' does not allow marks of type '").concat(mark.type, "'.")); } return { mark: markType, attrs: mark.attrs }; }; var newMarks = changedValues.hasOwnProperty('marks') ? (_changedValues$marks = changedValues.marks) === null || _changedValues$marks === void 0 ? void 0 : _changedValues$marks.map(ensureValidMark).map(function (_ref3) { var mark = _ref3.mark, attrs = _ref3.attrs; return mark.create(attrs); }) : node.marks; var newContent = changedValues.hasOwnProperty('content') ? Fragment.fromJSON(schema, changedValues.content) : node.content; var newAttrs = changedValues.hasOwnProperty('attrs') ? changedValues.attrs : node.attrs; if (node.type.name === 'multiBodiedExtension') { var _changedValues$attrs, _node$attrs$parameter, _changedValues$attrs2; newAttrs = _objectSpread(_objectSpread(_objectSpread({}, node.attrs), changedValues.attrs), {}, { parameters: _objectSpread(_objectSpread(_objectSpread({}, node.attrs.parameters), (_changedValues$attrs = changedValues.attrs) === null || _changedValues$attrs === void 0 ? void 0 : _changedValues$attrs.parameters), {}, { macroParams: _objectSpread(_objectSpread({}, (_node$attrs$parameter = node.attrs.parameters) === null || _node$attrs$parameter === void 0 ? void 0 : _node$attrs$parameter.macroParams), (_changedValues$attrs2 = changedValues.attrs) === null || _changedValues$attrs2 === void 0 || (_changedValues$attrs2 = _changedValues$attrs2.parameters) === null || _changedValues$attrs2 === void 0 ? void 0 : _changedValues$attrs2.macroParams) }) }); // console.log('newAttrs', newAttrs); } // Validate if the new attributes, content and marks result in a valid node and adf. try { var newNode = node.type.createChecked(newAttrs, newContent, newMarks); var newNodeAdf = new JSONTransformer().encodeNode(newNode); validate(newNodeAdf); tr.replaceWith(pos, pos + node.nodeSize, newNode); // Keep selection if content does not change if (newContent === node.content) { tr.setSelection(Selection.fromJSON(tr.doc, state.selection.toJSON())); } } catch (err) { throw new Error("update(): The given ADFEntity cannot be inserted in the current position.\n".concat(err)); } // Analytics - tracking the api call var apiCallPayload = extensionAPICallPayload('update'); editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(apiCallPayload)(tr); if (typeof (opts === null || opts === void 0 ? void 0 : opts.addToHistory) === 'boolean') { tr.setMeta('addToHistory', opts.addToHistory); } if (typeof (opts === null || opts === void 0 ? void 0 : opts.scrollIntoView) === 'boolean') { tr.setMeta('scrollIntoView', opts.scrollIntoView); } dispatch(tr); } }; return { editInContextPanel: function editInContextPanel(transformBefore, transformAfter) { var editorView = options.editorView; setEditingContextToContextPanel(transformBefore, transformAfter, options.applyChange)(editorView.state, editorView.dispatch, editorView); }, _editInLegacyMacroBrowser: function _editInLegacyMacroBrowser() { var editorView = options.editorView; var editInLegacy = options.editInLegacyMacroBrowser; if (!editInLegacy) { var macroState = macroPluginKey.getState(editorView.state); editInLegacy = getEditInLegacyMacroBrowser({ view: options.editorView, macroProvider: (macroState === null || macroState === void 0 ? void 0 : macroState.macroProvider) || undefined, editorAnalyticsAPI: editorAnalyticsAPI }); } editInLegacy(); }, getNodeWithPosByLocalId: function getNodeWithPosByLocalId(localId) { return ensureNodePosByLocalId(localId, { opName: 'getNodeWithPosByLocalId' }); }, doc: doc }; };