UNPKG

@atlaskit/editor-plugin-collab-edit

Version:

Collab Edit plugin for @atlaskit/editor-core

227 lines (222 loc) 13.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.registerAllCustomSteps = exports.handleTelePointer = exports.handlePresence = exports.handleInit = exports.handleConnection = exports.getSendableSelection = exports.applyRemoteSteps = exports.applyRemoteData = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var allAdfSchemaSteps = _interopRequireWildcard(require("@atlaskit/adf-schema/steps")); var allAtlaskitCustomSteps = _interopRequireWildcard(require("@atlaskit/custom-steps")); var _collabAgentEditChrome = require("@atlaskit/editor-common/collab-agent-edit-chrome"); var _collabAgentRemoteEditReview = require("@atlaskit/editor-common/collab-agent-remote-edit-review"); var _state = require("@atlaskit/editor-prosemirror/state"); var _transformOverride = require("@atlaskit/editor-prosemirror/transform-override"); var _expVal = require("@atlaskit/platform-feature-experiments/exp-val"); var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled"); var _fg = require("@atlaskit/platform-feature-flags/fg"); var _prosemirrorCollab = require("@atlaskit/prosemirror-collab"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _analytics = require("./analytics"); var _agentReviewSegments = require("./main/agent-review-segments"); var _agentShimmerDecorations = require("./main/agent-shimmer-decorations"); var _agentEditRequester = require("./main/agent-edit-requester"); var _agentShimmerRanges = require("./main/agent-shimmer-ranges"); var _utils = require("./utils"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } 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) { (0, _defineProperty2.default)(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; } // Ignored via go/ees005 // eslint-disable-next-line import/no-namespace // Ignored via go/ees005 // eslint-disable-next-line import/no-namespace /* * This is a non-op function to force ProseMirror to load and register all custom steps in the same bundle */ var registerAllCustomSteps = exports.registerAllCustomSteps = function registerAllCustomSteps() { Object.entries(allAtlaskitCustomSteps).forEach(function () {}); Object.entries(allAdfSchemaSteps).forEach(function () {}); }; var handleInit = exports.handleInit = function handleInit(initData, view, options, editorAnalyticsApi) { var doc = initData.doc, json = initData.json, version = initData.version, reserveCursor = initData.reserveCursor; if (doc) { var state = view.state; var tr = (0, _utils.replaceDocument)(doc, state, version, options, reserveCursor, editorAnalyticsApi); tr.setMeta('isRemote', true); view.dispatch(tr); } else if (json) { applyRemoteSteps(json, view, undefined, options, editorAnalyticsApi); } }; var handleConnection = exports.handleConnection = function handleConnection(connectionData, view) { var tr = view.state.tr; view.dispatch(tr.setMeta('sessionId', connectionData)); }; var handlePresence = exports.handlePresence = function handlePresence(presenceData, view) { var tr = view.state.tr; view.dispatch(tr.setMeta('presence', presenceData)); }; var applyRemoteData = exports.applyRemoteData = function applyRemoteData(remoteData, view, options, editorAnalyticsApi) { var json = remoteData.json, _remoteData$userIds = remoteData.userIds, userIds = _remoteData$userIds === void 0 ? [] : _remoteData$userIds; if (json) { applyRemoteSteps(json, view, userIds, options, editorAnalyticsApi); } }; var applyRemoteSteps = exports.applyRemoteSteps = function applyRemoteSteps(json, view, userIds, options, editorAnalyticsApi) { if (!json || !json.length) { return; } var state = view.state, schema = view.state.schema; var steps = json.map(function (step) { return _transformOverride.Step.fromJSON(schema, step); }); var tr; if (options && options.useNativePlugin && userIds) { tr = (0, _prosemirrorCollab.receiveTransaction)(state, steps, userIds, { mapSelectionBackward: true }); } else { tr = state.tr; steps.forEach(function (step) { return tr.step(step); }); } if (tr) { var agentEditRequester = (0, _fg.fg)('platform_editor_agent_be_review_undo') ? (0, _agentEditRequester.getAgentEditRequester)(json, view) : null; var shouldRecordAgentEditInHistory = (agentEditRequester === null || agentEditRequester === void 0 ? void 0 : agentEditRequester.isLocalUserRequester) === true; tr.setMeta('addToHistory', shouldRecordAgentEditInHistory); tr.setMeta('isRemote', true); // Agent edit shimmer: mark the ranges agent steps just wrote so the plugin reveals them with the // skeleton loader, then (optionally) a purple "just edited" highlight. Gated as a whole so no // experiment reads run off-experiment; off-path leaves `agentShimmers` empty and everything below // is a no-op. var shimmerDurationMs = 0; var highlightDurationMs = 0; var agentShimmers = []; var isUnifiedPostApplyChromeEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_ai_unified_post_apply_chrome'); if (isUnifiedPostApplyChromeEnabled && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_agent_be_streaming')) { var ranges = (0, _agentShimmerRanges.getAgentEditChromeRanges)(json, steps, tr, view, function (reason, agentType, error) { return editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent((0, _analytics.getAgentEditShimmerNotShownPayload)(reason, agentType, error))(tr); }); var requester = (0, _agentEditRequester.getAgentEditRequester)(json, view); if (ranges.length && requester) { // Collab supplies a machine agent type, not a display name; preserve the legacy // uppercase telepointer label. var telepointerLabel = requester.agentType.trim().toUpperCase(); tr.setMeta(_collabAgentEditChrome.AGENT_EDIT_CHROME_DATA, _objectSpread(_objectSpread(_objectSpread({}, (0, _collabAgentEditChrome.getAgentEditChromeDynamicConfig)()), telepointerLabel ? { telepointerLabel: telepointerLabel } : {}), {}, { ranges: ranges })); } } if (!isUnifiedPostApplyChromeEnabled && (0, _expValEquals.expValEquals)('platform_editor_agent_be_streaming', 'isEnabled', true)) { // The skeleton and purple-highlight phases toggle independently. `shimmerDurationMs` is the // skeleton lifetime; `0` skips the skeleton (an edit can still get the purple highlight). shimmerDurationMs = (0, _expVal.expVal)('platform_editor_agent_be_streaming', 'shimmerDurationMs', _agentShimmerDecorations.AGENT_SHIMMER_DEFAULT_DURATION_MS); // `highlightDurationMs` is the purple "just edited" highlight lifetime; `0` skips the highlight // (the skeleton still shows). `0` on both shows nothing. highlightDurationMs = (0, _expVal.expVal)('platform_editor_agent_be_streaming', 'highlightDurationMs', _agentShimmerDecorations.AGENT_EDIT_HIGHLIGHT_DEFAULT_DURATION_MS); // Telepointer shown by default; `telepointerDisabled` hides it (inverted because `expVal` // only permits `false` as a boolean default). var telepointerEnabled = !(0, _expVal.expVal)('platform_editor_agent_be_streaming', 'telepointerDisabled', false); agentShimmers = (0, _agentShimmerRanges.getAgentShimmerRanges)(json, steps, tr, view, shimmerDurationMs, highlightDurationMs, telepointerEnabled, // An agent edit that applied without the shimmer fires the operational event on the same // transaction, so success stays event-free and only degrades are reported. function (reason, agentType, error) { return editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent((0, _analytics.getAgentEditShimmerNotShownPayload)(reason, agentType, error))(tr); }); if (agentShimmers.length) { tr.setMeta(_agentShimmerDecorations.ADD_AGENT_SHIMMER_META, agentShimmers); } } // Record the agent's edit (original + new slice per change) onto this // transaction; `editor-plugin-ai` consumes the meta to open the BE Review moment. // Gated by the `isBackendReviewMomentEnabled` param on // `platform_editor_ai_new_aifc_editor_experience` (mirrors `isFrontendReviewMomentEnabled`). // `getAgentEditSegments` self-gates (returns null for non-agent edits, never throws), // so this is a no-op for ordinary collaborator edits. if ((0, _expValEquals.expValEquals)('platform_editor_ai_new_aifc_editor_experience', 'isBackendReviewMomentEnabled', true)) { var reviewData = (0, _agentReviewSegments.getAgentEditSegments)(json, steps, tr, view); if (reviewData) { tr.setMeta(_collabAgentRemoteEditReview.AGENT_REMOTE_EDIT_REVIEW_DATA, reviewData); } } /* * Persist marks across transactions. Fixes an issue where * marks are lost if remote transactions are dispatched * between a user creating the mark and typing. */ if (state.tr.storedMarks) { tr.setStoredMarks(state.tr.storedMarks); } view.dispatch(tr); // Schedule each shimmer's phase changes. `dispatchMeta` is guarded so a timer firing after // teardown is a harmless no-op. Only reached when the gated block above produced ranges, so this // whole path is off-experiment-safe. if (agentShimmers.length) { var tornDownReported = false; var dispatchMeta = function dispatchMeta(metaKey, value) { try { view.dispatch(view.state.tr.setMeta(metaKey, value)); } catch (_unused) { // View torn down before the timer fired — nothing to clean up. Report once (not once // per shimmer) so we can see how often shimmers are interrupted by teardown. if (!tornDownReported) { tornDownReported = true; editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.fireAnalyticsEvent((0, _analytics.getAgentEditShimmerNotShownPayload)('tornDownMidAnimation')); } } }; agentShimmers.forEach(function (_ref) { var shimmerId = _ref.shimmerId; if (shimmerDurationMs > 0) { // Skeleton first. When it clears, show the purple highlight (if enabled) then remove; // otherwise remove straight away. setTimeout(function () { if (highlightDurationMs > 0) { dispatchMeta(_agentShimmerDecorations.HIGHLIGHT_AGENT_SHIMMER_META, shimmerId); setTimeout(function () { dispatchMeta(_agentShimmerDecorations.REMOVE_AGENT_SHIMMER_META, shimmerId); }, highlightDurationMs); } else { dispatchMeta(_agentShimmerDecorations.REMOVE_AGENT_SHIMMER_META, shimmerId); } }, shimmerDurationMs); } else { // Skeleton disabled: the range already starts in the highlight phase, so just time its // removal. (Reached only when `highlightDurationMs > 0`, else no ranges were produced.) setTimeout(function () { dispatchMeta(_agentShimmerDecorations.REMOVE_AGENT_SHIMMER_META, shimmerId); }, highlightDurationMs); } }); } } }; var handleTelePointer = exports.handleTelePointer = function handleTelePointer(telepointerData, view) { var tr = view.state.tr; view.dispatch(tr.setMeta('telepointer', telepointerData)); }; function isAllSelection(selection) { return selection instanceof _state.AllSelection; } function isNodeSelection(selection) { return selection instanceof _state.NodeSelection; } var getSendableSelection = exports.getSendableSelection = function getSendableSelection(selection) { /** * <kbd>CMD + A</kbd> triggers a AllSelection * <kbd>escape</kbd> triggers a NodeSelection */ return { type: 'textSelection', anchor: selection.anchor, head: isAllSelection(selection) || isNodeSelection(selection) ? selection.head - 1 : selection.head }; };