@atlaskit/editor-plugin-collab-edit
Version:
Collab Edit plugin for @atlaskit/editor-core
331 lines (329 loc) • 16.8 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
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 _regeneratorRuntime from "@babel/runtime/regenerator";
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { isEmptyDocument } from '@atlaskit/editor-common/utils';
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
import { AddMarkStep, AddNodeMarkStep } from '@atlaskit/editor-prosemirror/transform';
import { fg } from '@atlaskit/platform-feature-flags';
import { collab, getCollabState, sendableSteps } from '@atlaskit/prosemirror-collab';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { addSynchronyErrorAnalytics } from './pm-plugins/analytics';
import { sendTransaction } from './pm-plugins/events/send-transaction';
import { filterAnalyticsSteps } from './pm-plugins/filterAnalytics';
import { createPlugin } from './pm-plugins/main';
import { pluginKey as mainPluginKey } from './pm-plugins/main/plugin-key';
import { mergeUnconfirmedSteps } from './pm-plugins/mergeUnconfirmed';
import { monitorOrganic } from './pm-plugins/monitor-organic-changes';
import { nativeCollabProviderPlugin } from './pm-plugins/native-collab-provider-plugin';
import { sanitizeFilteredStep, createPlugin as trackSpammingStepsPlugin } from './pm-plugins/track-and-filter-spamming-steps';
import { createPlugin as createLastOrganicChangePlugin, trackLastOrganicChangePluginKey } from './pm-plugins/track-last-organic-change';
import { createPlugin as createTrackNCSInitializationPlugin, trackNCSInitializationPluginKey } from './pm-plugins/track-ncs-initialization';
import { createPlugin as createTrackReconnectionConflictPlugin, trackLastRemoteConflictPluginKey } from './pm-plugins/track-reconnection-conflict';
import { track } from './pm-plugins/track-steps';
import { getAvatarColor } from './pm-plugins/utils';
var providerBuilder = function providerBuilder(collabEditProviderPromise) {
return /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(codeToExecute, onError) {
var provider;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return collabEditProviderPromise;
case 3:
provider = _context.sent;
if (!provider) {
_context.next = 6;
break;
}
return _context.abrupt("return", codeToExecute(provider));
case 6:
_context.next = 11;
break;
case 8:
_context.prev = 8;
_context.t0 = _context["catch"](0);
if (onError) {
onError(_context.t0);
} else {
// eslint-disable-next-line no-console
console.error(_context.t0);
}
case 11:
case "end":
return _context.stop();
}
}, _callee, null, [[0, 8]]);
}));
return function (_x, _x2) {
return _ref.apply(this, arguments);
};
}();
};
var createAddInlineCommentMark = function createAddInlineCommentMark(providerPromise) {
return function (_ref2) {
var from = _ref2.from,
to = _ref2.to,
mark = _ref2.mark;
providerPromise.then(function (provider) {
var _provider$api;
var commentMark = new AddMarkStep(Math.min(from, to), Math.max(from, to), mark);
// @ts-expect-error 2339: Property 'api' does not exist on type 'CollabEditProvider<CollabEvents>'.
(_provider$api = provider.api) === null || _provider$api === void 0 || _provider$api.addComment([commentMark]);
});
return false;
};
};
var createAddInlineCommentNodeMark = function createAddInlineCommentNodeMark(providerPromise) {
return function (_ref3) {
var pos = _ref3.pos,
mark = _ref3.mark;
providerPromise.then(function (provider) {
var _provider$api2;
var commentMark = new AddNodeMarkStep(pos, mark);
// @ts-expect-error 2339: Property 'api' does not exist on type 'CollabEditProvider<CollabEvents>'.
(_provider$api2 = provider.api) === null || _provider$api2 === void 0 || _provider$api2.addComment([commentMark]);
});
return false;
};
};
export var collabEditPlugin = function collabEditPlugin(_ref4) {
var _api$featureFlags;
var options = _ref4.config,
api = _ref4.api;
var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
var providerResolver = function providerResolver() {};
var editorViewRef = {
current: null
};
var collabEditProviderPromise = new Promise(function (_providerResolver) {
providerResolver = _providerResolver;
});
var executeProviderCode = providerBuilder(collabEditProviderPromise);
return {
name: 'collabEdit',
getSharedState: function getSharedState(state) {
if (!state) {
return {
initialised: {
collabInitialisedAt: null,
firstChangeAfterInitAt: null,
firstContentBodyChangeAfterInitAt: null,
lastLocalOrganicChangeAt: null,
lastRemoteOrganicChangeAt: null,
lastLocalOrganicBodyChangeAt: null,
lastRemoteOrganicBodyChangeAt: null
},
activeParticipants: undefined,
sessionId: undefined,
lastReconnectionConflictMetadata: undefined
};
}
var collabPluginState = mainPluginKey.getState(state);
var metadata = trackNCSInitializationPluginKey.getState(state);
var lastOrganicChangeState = trackLastOrganicChangePluginKey.getState(state);
var lastRemoteConflict = trackLastRemoteConflictPluginKey.getState(state);
return {
activeParticipants: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.activeParticipants,
sessionId: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.sessionId,
initialised: {
collabInitialisedAt: (metadata === null || metadata === void 0 ? void 0 : metadata.collabInitialisedAt) || null,
firstChangeAfterInitAt: (metadata === null || metadata === void 0 ? void 0 : metadata.firstChangeAfterInitAt) || null,
firstContentBodyChangeAfterInitAt: (metadata === null || metadata === void 0 ? void 0 : metadata.firstContentBodyChangeAfterInitAt) || null,
lastLocalOrganicChangeAt: (lastOrganicChangeState === null || lastOrganicChangeState === void 0 ? void 0 : lastOrganicChangeState.lastLocalOrganicChangeAt) || null,
lastRemoteOrganicChangeAt: (lastOrganicChangeState === null || lastOrganicChangeState === void 0 ? void 0 : lastOrganicChangeState.lastRemoteOrganicChangeAt) || null,
lastLocalOrganicBodyChangeAt: (lastOrganicChangeState === null || lastOrganicChangeState === void 0 ? void 0 : lastOrganicChangeState.lastLocalOrganicBodyChangeAt) || null,
lastRemoteOrganicBodyChangeAt: (lastOrganicChangeState === null || lastOrganicChangeState === void 0 ? void 0 : lastOrganicChangeState.lastRemoteOrganicBodyChangeAt) || null
},
lastReconnectionConflictMetadata: lastRemoteConflict
};
},
actions: {
getAvatarColor: getAvatarColor,
addInlineCommentMark: createAddInlineCommentMark(collabEditProviderPromise),
addInlineCommentNodeMark: createAddInlineCommentNodeMark(collabEditProviderPromise),
isRemoteReplaceDocumentTransaction: function isRemoteReplaceDocumentTransaction(tr) {
return tr.getMeta('isRemote') && tr.getMeta('replaceDocument');
},
getCurrentCollabState: function getCurrentCollabState() {
var _getCollabState;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var adfDocument = new JSONTransformer().encode(editorViewRef.current.state.doc);
return {
content: adfDocument,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
version: ((_getCollabState = getCollabState(editorViewRef.current.state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.version) || 0,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sendableSteps: sendableSteps(editorViewRef.current.state)
};
},
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validatePMJSONDocument: function validatePMJSONDocument(doc) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var content = (doc.content || []).map(function (child) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
editorViewRef.current.state.schema.nodeFromJSON(child)
);
});
return content.every(function (node) {
try {
node.check(); // this will throw an error if the node is invalid
} catch (_unused) {
return false;
}
return true;
});
}
},
pmPlugins: function pmPlugins() {
var _ref5 = options || {},
_ref5$useNativePlugin = _ref5.useNativePlugin,
useNativePlugin = _ref5$useNativePlugin === void 0 ? false : _ref5$useNativePlugin,
_ref5$userId = _ref5.userId,
userId = _ref5$userId === void 0 ? null : _ref5$userId;
var transformUnconfirmed = function transformUnconfirmed(steps) {
var transformed = steps;
if (editorExperiment('platform_editor_reduce_noisy_steps_ncs', true, {
exposure: true
})) {
transformed = filterAnalyticsSteps(transformed);
}
if (editorExperiment('platform_editor_offline_editing_web', true) || expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
transformed = mergeUnconfirmedSteps(transformed, api);
}
return transformed;
};
var plugins = [].concat(_toConsumableArray(useNativePlugin ? [{
name: 'pmCollab',
plugin: function plugin() {
return collab({
clientID: userId,
transformUnconfirmed: transformUnconfirmed
});
}
}, {
name: 'nativeCollabProviderPlugin',
plugin: function plugin() {
return nativeCollabProviderPlugin({
providerPromise: collabEditProviderPromise
});
}
}] : []), [{
name: 'collab',
plugin: function plugin(_ref6) {
var dispatch = _ref6.dispatch,
providerFactory = _ref6.providerFactory;
return createPlugin(dispatch, providerFactory, providerResolver, executeProviderCode, options, featureFlags, api);
}
}, {
name: 'collabTrackNCSInitializationPlugin',
plugin: createTrackNCSInitializationPlugin
}]);
plugins.push({
name: 'trackAndFilterSpammingSteps',
plugin: function plugin() {
return trackSpammingStepsPlugin(function (tr) {
var _api$analytics;
var sanitizedSteps = tr.steps.map(function (step) {
return sanitizeFilteredStep(step);
});
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.fireAnalyticsEvent({
action: ACTION.STEPS_FILTERED,
actionSubject: ACTION_SUBJECT.COLLAB,
attributes: {
steps: sanitizedSteps
},
eventType: EVENT_TYPE.OPERATIONAL
});
});
}
});
plugins.push({
name: 'collabTrackLastOrganicChangePlugin',
plugin: createLastOrganicChangePlugin
});
if (editorExperiment('platform_editor_offline_editing_web', true)) {
plugins.push({
name: 'trackLastRemoteConflictPlugin',
plugin: createTrackReconnectionConflictPlugin
});
}
return plugins;
},
onEditorViewStateUpdated: function onEditorViewStateUpdated(props) {
var _api$analytics2, _api$editorViewMode, _options$useNativePlu, _options$hideTelecurs;
var addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
var isEmptyDoc = isEmptyDocument(props.newEditorState.doc);
var viewMode = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode;
executeProviderCode(sendTransaction({
originalTransaction: props.originalTransaction,
transactions: props.transactions,
oldEditorState: props.oldEditorState,
newEditorState: props.newEditorState,
useNativePlugin: (_options$useNativePlu = options.useNativePlugin) !== null && _options$useNativePlu !== void 0 ? _options$useNativePlu : false,
hideTelecursorOnLoad: !isEmptyDoc && ((_options$hideTelecurs = options.hideTelecursorOnLoad) !== null && _options$hideTelecurs !== void 0 ? _options$hideTelecurs : false),
viewMode: viewMode
}), addErrorAnalytics);
if (!expValEquals('platform_editor_remove_collab_step_metrics', 'isEnabled', true)) {
track(_objectSpread(_objectSpread({
api: api
}, props), {}, {
onTrackDataProcessed: function onTrackDataProcessed(steps) {
var _api$analytics3;
api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.fireAnalyticsEvent({
action: ACTION.STEPS_TRACKED,
actionSubject: ACTION_SUBJECT.COLLAB,
attributes: {
steps: steps
},
eventType: EVENT_TYPE.OPERATIONAL
});
}
}));
}
if (fg('platform_editor_collab_organic_change_reporting')) {
monitorOrganic(_objectSpread(_objectSpread({
api: api
}, props), {}, {
onDataProcessed: function onDataProcessed(data) {
var _api$analytics4;
api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 || (_api$analytics4 = _api$analytics4.actions) === null || _api$analytics4 === void 0 || _api$analytics4.fireAnalyticsEvent({
action: ACTION.ORGANIC_CHANGES_TRACKED,
actionSubject: ACTION_SUBJECT.COLLAB,
attributes: {
organicChanges: data
},
eventType: EVENT_TYPE.OPERATIONAL
});
}
}));
}
},
commands: {
nudgeTelepointer: function nudgeTelepointer(sessionId) {
return function (_ref7) {
var tr = _ref7.tr;
tr.setMeta('nudgeTelepointer', {
sessionId: sessionId
});
return tr;
};
}
}
};
};