UNPKG

@atlaskit/editor-plugin-card

Version:

Card plugin for @atlaskit/editor-core

405 lines (391 loc) 14.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.eventsFromTransaction = eventsFromTransaction; exports.findChanged = void 0; var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _steps = require("@atlaskit/adf-schema/steps"); var _customSteps = require("@atlaskit/custom-steps"); var _analytics = require("@atlaskit/editor-common/analytics"); var _card = require("@atlaskit/editor-common/card"); var _utils = require("@atlaskit/editor-common/utils"); var _transform = require("@atlaskit/editor-prosemirror/transform"); var _pluginKey = require("../../pm-plugins/plugin-key"); var _state = require("../../pm-plugins/util/state"); var _types = require("./types"); var _utils2 = require("./utils"); /** * Find the links, smartLinks, datasources that were changed in a transaction */ var findChanged = exports.findChanged = function findChanged(tr, state) { var schema = tr.doc.type.schema; var removed = []; var inserted = []; /** * Ideally we have the "before" and "after" states of the "entity" * being updated, but if the update is via a "queue for upgrade" * then we no longer have access to the "before" state, because the "before" * state was replaced with a blue link to be upgraded */ var updated = []; var queuedForUpgrade = isTransactionQueuedForUpgrade(tr); var isResolveReplace = isTransactionResolveReplace(tr); var isAutoConvert = isAutoConvertTr(tr); // History var historyMeta = tr.getMeta(_utils.pmHistoryPluginKey); var isUndo = isHistoryMeta(historyMeta) && historyMeta.redo === false; var isRedo = isHistoryMeta(historyMeta) && historyMeta.redo === true; var isUpdate = isUpdateTr(tr, isUndo || isRedo); var _loop = function _loop(i) { var _tr$docs$i, _tr$docs; var step = tr.steps[i]; var stepMap = step.getMap(); var removedInStep = []; var insertedInStep = []; var before = (_tr$docs$i = tr.docs[i]) !== null && _tr$docs$i !== void 0 ? _tr$docs$i : tr.before; var after = (_tr$docs = tr.docs[i + 1]) !== null && _tr$docs !== void 0 ? _tr$docs : tr.doc; /** * AddMarkStep and RemoveMarkSteps don't produce stepMap ranges * because there are no "changed tokens" only marks added/removed * So have to check these manually */ if (step instanceof _transform.AddMarkStep) { var addMarkStep = step; if ((0, _utils.isLinkMark)(addMarkStep.mark, schema)) { var node = after.nodeAt(addMarkStep.from); if (node) { /** * For url text pasted on plain text */ insertedInStep.push({ pos: addMarkStep.from, node: node, nodeContext: (0, _utils2.getNodeContext)(after, addMarkStep.from) }); } } } if (step instanceof _transform.RemoveMarkStep) { var removeMarkStep = step; if ((0, _utils.isLinkMark)(removeMarkStep.mark, schema)) { var _node = before.nodeAt(removeMarkStep.from); if (_node) { removedInStep.push({ pos: removeMarkStep.from, node: _node, nodeContext: (0, _utils2.getNodeContext)(before, removeMarkStep.from) }); } } } stepMap.forEach(function (oldStart, oldEnd, newStart, newEnd) { var _tr$docs2; var before = tr.docs[i]; var after = (_tr$docs2 = tr.docs[i + 1]) !== null && _tr$docs2 !== void 0 ? _tr$docs2 : tr.doc; var removedInRange = []; var insertedInRange = []; // Removed removedInRange.push.apply(removedInRange, (0, _toConsumableArray2.default)((0, _utils2.findInNodeRange)(before, oldStart, oldEnd, function (node) { return !!(0, _utils2.getNodeSubject)(node); }))); // Inserted insertedInRange.push.apply(insertedInRange, (0, _toConsumableArray2.default)((0, _utils2.findInNodeRange)(after, newStart, newEnd, function (node) { return !!(0, _utils2.getNodeSubject)(node); }))); removedInStep.push.apply(removedInStep, removedInRange); insertedInStep.push.apply(insertedInStep, insertedInRange); }); var omitRequestsForUpgrade = function omitRequestsForUpgrade(links) { if (!queuedForUpgrade) { return links; } /** * Skip/filter out links that have been queued, they will be tracked later */ var queuedPositions = getQueuedPositions(tr); return links.filter(function (link) { return !queuedPositions.includes(link.pos); }); }; /** * Skip "deletions" when the transaction is relating to * replacing links queued for upgrade to cards, * because the "deleted" link has not actually been * tracked as "created" yet. * Also skip when the transaction is an auto-convert * (e.g. a pasted link being converted to a native embed extension), * because the link is being converted, not deleted by the user. */ if (!isResolveReplace && !isAutoConvert) { removed.push.apply(removed, removedInStep); } inserted.push.apply(inserted, (0, _toConsumableArray2.default)(omitRequestsForUpgrade(insertedInStep))); }; for (var i = 0; i < tr.steps.length; i++) { _loop(i); } /** * If there are no links changed but the transaction is a "resolve" action * Then this means we have resolved a link but it has failed to upgrade * We should track all resolved links as now being created */ if (inserted.length === 0 && isResolveReplace) { var positions = getResolvePositions(tr, state); inserted.push.apply(inserted, (0, _toConsumableArray2.default)((0, _utils2.findAtPositions)(tr, positions))); } if (!isUpdate) { var _getLinkMetadataFromT = (0, _card.getLinkMetadataFromTransaction)(tr), inputMethod = _getLinkMetadataFromT.inputMethod; /** * If there is no identifiable input method, and the links inserted and removed appear to be the same, * then this transaction likely is not intended to be considered to be the insertion and removal of links */ if (!inputMethod && (0, _utils2.areSameNodes)(removed, inserted)) { return { removed: [], inserted: [], updated: updated }; } return { removed: removed, inserted: inserted, updated: updated }; } var updateInserted = []; var updateRemoved = []; for (var _i = 0; _i < inserted.length; _i++) { if (isResolveReplace) { var newLink = inserted[_i]; // what is the 2nd argument 'assoc = -1' doing here exactly? var mappedPos = tr.mapping.map(newLink.pos, -1); var previousDisplay = getResolveLinkPrevDisplay(state, mappedPos); updated.push({ inserted: inserted[_i], previous: { display: previousDisplay } }); continue; } if (inserted.length === removed.length) { var previousSubject = (0, _utils2.getNodeSubject)(removed[_i].node); var currentSubject = (0, _utils2.getNodeSubject)(inserted[_i].node); if (isDatasourceUpgrade(previousSubject, currentSubject) || isDatasourceDowngrade(previousSubject, currentSubject)) { updateInserted.push(inserted[_i]); updateRemoved.push(removed[_i]); } else { updated.push({ removed: removed[_i], inserted: inserted[_i] }); } } } return { inserted: updateInserted, removed: updateRemoved, updated: updated }; }; /** * List of actions to be considered link "updates" */ var UPDATE_ACTIONS = [_analytics.ACTION.CHANGED_TYPE, _analytics.ACTION.UPDATED]; /** * Returns true if the transaction has LinkMetaSteps that indicate the transaction is * intended to be perceived as an update to links, rather than insertion+deletion */ var isUpdateTr = function isUpdateTr(tr, isUndoOrRedo) { return !!tr.steps.find(function (step) { if (!(step instanceof _steps.LinkMetaStep)) { return false; } var _step$getMetadata = step.getMetadata(), action = _step$getMetadata.action, cardAction = _step$getMetadata.cardAction; /** * Undo of a resolve step should be considered an update * because the user is choosing to update the url back to the un-upgraded display */ if (cardAction === 'RESOLVE' && isUndoOrRedo) { return true; } if (!action) { return false; } return UPDATE_ACTIONS.includes(action); }); }; var hasType = function hasType(pluginMeta) { return (0, _typeof2.default)(pluginMeta) === 'object' && pluginMeta !== null && 'type' in pluginMeta; }; var isTransactionQueuedForUpgrade = function isTransactionQueuedForUpgrade(tr) { var pluginMeta = tr.getMeta(_pluginKey.pluginKey); return isMetadataQueue(pluginMeta); }; var isMetadataQueue = function isMetadataQueue(metaData) { return hasType(metaData) && metaData.type === 'QUEUE'; }; var isTransactionResolveReplace = function isTransactionResolveReplace(tr) { var pluginMeta = tr.getMeta(_pluginKey.pluginKey); return isMetadataResolve(pluginMeta); }; /** * Checks if the transaction is an auto-convert action * (e.g. a pasted link being converted to a native embed extension node). * In this case the link removal should not be tracked as a deletion. */ var isAutoConvertTr = function isAutoConvertTr(tr) { return !!tr.steps.find(function (step) { if (!(step instanceof _steps.LinkMetaStep)) { return false; } return step.getMetadata().cardAction === 'AUTO_CONVERT'; }); }; var isMetadataResolve = function isMetadataResolve(metaData) { return hasType(metaData) && metaData.type === 'RESOLVE'; }; var isHistoryMeta = function isHistoryMeta(meta) { return (0, _typeof2.default)(meta) === 'object' && meta !== null && 'redo' in meta; }; var getQueuedPositions = function getQueuedPositions(tr) { var pluginMeta = tr.getMeta(_pluginKey.pluginKey); if (!isMetadataQueue(pluginMeta)) { return []; } return pluginMeta.requests.map(function (_ref) { var pos = _ref.pos; return pos; }); }; var getResolvePositions = function getResolvePositions(tr, state) { var cardState = (0, _state.getPluginState)(state); if (!cardState) { return []; } var pluginMeta = tr.getMeta(_pluginKey.pluginKey); if (!isMetadataResolve(pluginMeta)) { return []; } return cardState.requests.filter(function (request) { return request.url === pluginMeta.url; }).map(function (request) { return request.pos; }); }; var getResolveLinkPrevDisplay = function getResolveLinkPrevDisplay(state, pos) { var _cardState$requests$f; var cardState = (0, _state.getPluginState)(state); if (!cardState) { return undefined; } return (_cardState$requests$f = cardState.requests.find(function (request) { return request.pos === pos; })) === null || _cardState$requests$f === void 0 ? void 0 : _cardState$requests$f.previousAppearance; }; var isDatasourceDowngrade = function isDatasourceDowngrade(previousSubject, currentSubject) { return previousSubject === _types.EVENT_SUBJECT.DATASOURCE && currentSubject === _types.EVENT_SUBJECT.LINK; }; var isDatasourceUpgrade = function isDatasourceUpgrade(previousSubject, currentSubject) { return previousSubject === _types.EVENT_SUBJECT.LINK && currentSubject === _types.EVENT_SUBJECT.DATASOURCE; }; function eventsFromTransaction(tr, state) { var events = []; try { /** * Skip transactions sent by collab (identified by 'isRemote' key) * Skip entire document replace steps * We are only concerned with transactions performed on the document directly by the user */ var isRemote = tr.getMeta('isRemote'); var isReplaceDocument = tr.getMeta('replaceDocument'); var isTableSort = tr.steps.find(function (step) { return step instanceof _customSteps.TableSortStep; }); if (isRemote || isReplaceDocument || isTableSort) { return events; } var historyMeta = tr.getMeta(_utils.pmHistoryPluginKey); var isUndo = isHistoryMeta(historyMeta) && historyMeta.redo === false; var isRedo = isHistoryMeta(historyMeta) && historyMeta.redo === true; /** * Retrieve metadata from the LinkMetaStep(s) in the transaction */ var _getLinkMetadataFromT2 = (0, _card.getLinkMetadataFromTransaction)(tr), action = _getLinkMetadataFromT2.action, inputMethod = _getLinkMetadataFromT2.inputMethod, sourceEvent = _getLinkMetadataFromT2.sourceEvent; var _findChanged = findChanged(tr, state), removed = _findChanged.removed, inserted = _findChanged.inserted, updated = _findChanged.updated; var MAX_LINK_EVENTS = 50; if ([removed, inserted, updated].some(function (arr) { return arr.length > MAX_LINK_EVENTS; })) { return []; } for (var i = 0; i < updated.length; i++) { var _update$previous$disp; var update = updated[i]; var _inserted = update.inserted; var node = _inserted.node, nodeContext = _inserted.nodeContext; var subject = (0, _utils2.getNodeSubject)(node); /** * Not great, wish we had the previous node but we never stored it */ var previousDisplay = 'removed' in update ? (0, _utils2.appearanceForLink)(update.removed.node) : (_update$previous$disp = update.previous.display) !== null && _update$previous$disp !== void 0 ? _update$previous$disp : 'unknown'; if (subject) { events.push({ event: _types.EVENT.UPDATED, subject: subject, data: { node: node, nodeContext: nodeContext, action: action, inputMethod: inputMethod, sourceEvent: sourceEvent, isUndo: isUndo, isRedo: isRedo, previousDisplay: previousDisplay } }); } } var pushEvents = function pushEvents(entities, event) { for (var _i2 = 0; _i2 < entities.length; _i2++) { var _entities$_i = entities[_i2], _node2 = _entities$_i.node, _nodeContext = _entities$_i.nodeContext; var _subject = (0, _utils2.getNodeSubject)(_node2); if (_subject) { events.push({ event: event, subject: _subject, data: { node: _node2, nodeContext: _nodeContext, action: action, inputMethod: inputMethod, sourceEvent: sourceEvent, isUndo: isUndo, isRedo: isRedo } }); } } }; pushEvents(removed, _types.EVENT.DELETED); pushEvents(inserted, _types.EVENT.CREATED); return events; } catch (err) { return events; } }