UNPKG

@atlaskit/editor-plugin-collab-edit

Version:

Collab Edit plugin for @atlaskit/editor-core

331 lines (329 loc) 16.8 kB
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; }; } } }; };