@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
299 lines (289 loc) • 14.1 kB
JavaScript
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
};
};