@atlaskit/editor-plugin-collab-edit 13.0.30 → 13.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cjs/pm-plugins/actions.js +39 -0
- package/dist/cjs/pm-plugins/main/agent-shimmer-decorations.js +126 -0
- package/dist/cjs/pm-plugins/main/agent-shimmer-ranges.js +169 -0
- package/dist/cjs/pm-plugins/main/plugin-state.js +54 -3
- package/dist/cjs/pm-plugins/utils.js +6 -2
- package/dist/es2019/pm-plugins/actions.js +40 -0
- package/dist/es2019/pm-plugins/main/agent-shimmer-decorations.js +115 -0
- package/dist/es2019/pm-plugins/main/agent-shimmer-ranges.js +151 -0
- package/dist/es2019/pm-plugins/main/plugin-state.js +52 -4
- package/dist/es2019/pm-plugins/utils.js +7 -1
- package/dist/esm/pm-plugins/actions.js +39 -0
- package/dist/esm/pm-plugins/main/agent-shimmer-decorations.js +120 -0
- package/dist/esm/pm-plugins/main/agent-shimmer-ranges.js +163 -0
- package/dist/esm/pm-plugins/main/plugin-state.js +55 -3
- package/dist/esm/pm-plugins/utils.js +6 -2
- package/dist/types/pm-plugins/main/agent-shimmer-decorations.d.ts +31 -0
- package/dist/types/pm-plugins/main/agent-shimmer-ranges.d.ts +22 -0
- package/dist/types/pm-plugins/main/plugin-state.d.ts +3 -1
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-collab-edit
|
|
2
2
|
|
|
3
|
+
## 13.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`895ac23f1a3be`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/895ac23f1a3be) -
|
|
8
|
+
Agent edit presence: surface agent-authored collaborative edits to other users.
|
|
9
|
+
- `collab-provider` detects agent-authored remote steps (`agentType`/`agentId`) and registers a
|
|
10
|
+
synthetic agent participant in the AI-provider (`agent:`) partition so the agent appears in
|
|
11
|
+
presence, with a 30s sliding inactivity window (CCI-18030). `editor-common` gains optional
|
|
12
|
+
`agentId`/`agentType` on the collab step types.
|
|
13
|
+
- `editor-plugin-collab-edit` + `editor-core` add the in-editor shimmer (CCI-18033): when an
|
|
14
|
+
agent-authored step lands, the top-level block(s) it touched are covered by a skeleton-loader
|
|
15
|
+
shimmer (grey skeleton with a moving highlight) with a Rovo agent telepointer/cursor — labelled
|
|
16
|
+
with the agent's type — at the end of the range, then removed on a timer to reveal the content.
|
|
17
|
+
Gated behind the default-OFF `platform_editor_agent_be_streaming` experiment; the `durationMs`
|
|
18
|
+
dynamic-config param controls how long the shimmer stays (0 disables it) and
|
|
19
|
+
`telepointerDisabled` hides the telepointer. `tmp-editor-statsig` registers the experiment.
|
|
20
|
+
- `ratcheting` excludes the new `agentShimmerStyles.ts` from the "No unsafe typography" rule (the
|
|
21
|
+
agent telepointer label mirrors the existing AI in-editor telepointer's sub-token 10px/9px
|
|
22
|
+
sizing, matching its already-excluded Compiled counterpart).
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies
|
|
27
|
+
|
|
3
28
|
## 13.0.30
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
|
@@ -10,6 +10,10 @@ var allAtlaskitCustomSteps = _interopRequireWildcard(require("@atlaskit/custom-s
|
|
|
10
10
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
11
11
|
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
12
12
|
var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
|
|
13
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
14
|
+
var _expVal = require("@atlaskit/tmp-editor-statsig/expVal");
|
|
15
|
+
var _agentShimmerDecorations = require("./main/agent-shimmer-decorations");
|
|
16
|
+
var _agentShimmerRanges = require("./main/agent-shimmer-ranges");
|
|
13
17
|
var _utils = require("./utils");
|
|
14
18
|
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); }
|
|
15
19
|
// Ignored via go/ees005
|
|
@@ -79,6 +83,22 @@ var applyRemoteSteps = exports.applyRemoteSteps = function applyRemoteSteps(json
|
|
|
79
83
|
tr.setMeta('addToHistory', false);
|
|
80
84
|
tr.setMeta('isRemote', true);
|
|
81
85
|
|
|
86
|
+
// Agent edit shimmer: mark the ranges agent steps just wrote so the plugin reveals them with the
|
|
87
|
+
// gloss-sweep → purple-highlight sequence. Gated as a whole so no experiment reads run
|
|
88
|
+
// off-experiment; off-path leaves `agentShimmers` empty and everything below is a no-op.
|
|
89
|
+
var durationMs = 0;
|
|
90
|
+
var agentShimmers = [];
|
|
91
|
+
if ((0, _expValEquals.expValEquals)('platform_editor_agent_be_streaming', 'isEnabled', true)) {
|
|
92
|
+
durationMs = (0, _expVal.expVal)('platform_editor_agent_be_streaming', 'durationMs', _agentShimmerDecorations.AGENT_SHIMMER_DEFAULT_DURATION_MS);
|
|
93
|
+
// Telepointer shown by default; `telepointerDisabled` hides it (inverted because `expVal`
|
|
94
|
+
// only permits `false` as a boolean default).
|
|
95
|
+
var telepointerEnabled = !(0, _expVal.expVal)('platform_editor_agent_be_streaming', 'telepointerDisabled', false);
|
|
96
|
+
agentShimmers = (0, _agentShimmerRanges.getAgentShimmerRanges)(json, steps, tr, view, durationMs, telepointerEnabled);
|
|
97
|
+
if (agentShimmers.length) {
|
|
98
|
+
tr.setMeta(_agentShimmerDecorations.ADD_AGENT_SHIMMER_META, agentShimmers);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
/*
|
|
83
103
|
* Persist marks across transactions. Fixes an issue where
|
|
84
104
|
* marks are lost if remote transactions are dispatched
|
|
@@ -88,6 +108,25 @@ var applyRemoteSteps = exports.applyRemoteSteps = function applyRemoteSteps(json
|
|
|
88
108
|
tr.setStoredMarks(state.tr.storedMarks);
|
|
89
109
|
}
|
|
90
110
|
view.dispatch(tr);
|
|
111
|
+
|
|
112
|
+
// Remove each shimmer after `durationMs`. `dispatchMeta` is guarded so a timer firing after
|
|
113
|
+
// teardown is a harmless no-op. Only reached when the gated block above produced ranges, so this
|
|
114
|
+
// whole path is off-experiment-safe.
|
|
115
|
+
if (agentShimmers.length) {
|
|
116
|
+
var dispatchMeta = function dispatchMeta(metaKey, value) {
|
|
117
|
+
try {
|
|
118
|
+
view.dispatch(view.state.tr.setMeta(metaKey, value));
|
|
119
|
+
} catch (_unused) {
|
|
120
|
+
// View torn down before the timer fired — nothing to clean up.
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
agentShimmers.forEach(function (_ref) {
|
|
124
|
+
var shimmerId = _ref.shimmerId;
|
|
125
|
+
setTimeout(function () {
|
|
126
|
+
dispatchMeta(_agentShimmerDecorations.REMOVE_AGENT_SHIMMER_META, shimmerId);
|
|
127
|
+
}, durationMs);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
91
130
|
}
|
|
92
131
|
};
|
|
93
132
|
var handleTelePointer = exports.handleTelePointer = function handleTelePointer(telepointerData, view) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.reduceAgentShimmers = exports.buildAgentShimmerDecorations = exports.ROVO_AGENT_TELEPOINTER_LABEL_CLASS = exports.ROVO_AGENT_TELEPOINTER_CLASS = exports.REMOVE_AGENT_SHIMMER_META = exports.AGENT_SHIMMER_DEFAULT_DURATION_MS = exports.AGENT_SHIMMER_CLASS = exports.ADD_AGENT_SHIMMER_META = void 0;
|
|
8
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
10
|
+
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; }
|
|
11
|
+
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; }
|
|
12
|
+
// Kept out of `plugin-state.ts` so all the agent-shimmer plumbing lives together and is easy to
|
|
13
|
+
// remove if the approach changes. `plugin-state` just reduces the active ranges and asks this module
|
|
14
|
+
// to build the decorations.
|
|
15
|
+
|
|
16
|
+
/** Default time the skeleton shimmer stays on the agent-authored content (ms). */
|
|
17
|
+
var AGENT_SHIMMER_DEFAULT_DURATION_MS = exports.AGENT_SHIMMER_DEFAULT_DURATION_MS = 3000;
|
|
18
|
+
|
|
19
|
+
// Skeleton-loader bar over the agent-authored range, plus a Rovo AI telepointer at the end.
|
|
20
|
+
var AGENT_SHIMMER_CLASS = exports.AGENT_SHIMMER_CLASS = 'collab-agent-shimmer';
|
|
21
|
+
var ROVO_AGENT_TELEPOINTER_CLASS = exports.ROVO_AGENT_TELEPOINTER_CLASS = 'ai-in-editor-telepointer';
|
|
22
|
+
var ROVO_AGENT_TELEPOINTER_LABEL_CLASS = exports.ROVO_AGENT_TELEPOINTER_LABEL_CLASS = 'ai-in-editor-telepointer-label';
|
|
23
|
+
var ADD_AGENT_SHIMMER_META = exports.ADD_AGENT_SHIMMER_META = 'addAgentShimmer'; // register the shimmer decorations
|
|
24
|
+
var REMOVE_AGENT_SHIMMER_META = exports.REMOVE_AGENT_SHIMMER_META = 'removeAgentShimmer'; // remove them once the shimmer ends
|
|
25
|
+
|
|
26
|
+
// A range an agent step wrote; the skeleton + telepointer decorations are drawn over `from`..`to`
|
|
27
|
+
// and kept until removal (so positions can be re-mapped). Pure data only. `telepointerLabel` is the
|
|
28
|
+
// label for the trailing agent telepointer (the agent's type); when absent, no telepointer is shown.
|
|
29
|
+
|
|
30
|
+
// Rovo AI in-editor telepointer/cursor shown at the end of an agent-authored range (same DOM/style
|
|
31
|
+
// pattern as editor-plugin-ai's in-editor direct-streaming telepointer).
|
|
32
|
+
var createRovoAgentTelepointer = function createRovoAgentTelepointer(label) {
|
|
33
|
+
var element = document.createElement('span');
|
|
34
|
+
element.setAttribute('data-testid', 'ai-in-editor-telepointer-widget');
|
|
35
|
+
element.className = ROVO_AGENT_TELEPOINTER_CLASS;
|
|
36
|
+
var labelElement = document.createElement('span');
|
|
37
|
+
labelElement.setAttribute('data-testid', 'ai-in-editor-telepointer-widget-label');
|
|
38
|
+
labelElement.className = ROVO_AGENT_TELEPOINTER_LABEL_CLASS;
|
|
39
|
+
labelElement.append(label);
|
|
40
|
+
element.appendChild(labelElement);
|
|
41
|
+
return element;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pure reducer for the active shimmer ranges from a transaction's changes. Maps existing ranges
|
|
46
|
+
* forward as the doc changes, replaces them wholesale when a new agent batch lands (a new batch
|
|
47
|
+
* supersedes any still-in-flight shimmer), and drops a range when its removal timer fires. Returns a
|
|
48
|
+
* fresh array (never mutates in place) plus whether anything changed.
|
|
49
|
+
*/
|
|
50
|
+
var reduceAgentShimmers = exports.reduceAgentShimmers = function reduceAgentShimmers(current, tr, added, removedShimmerId) {
|
|
51
|
+
var next = current;
|
|
52
|
+
var changed = false;
|
|
53
|
+
|
|
54
|
+
// Ranges added in THIS transaction are already in post-change coords, so map the pre-existing
|
|
55
|
+
// ones BEFORE replacing with any new batch.
|
|
56
|
+
if (tr.docChanged && next.length) {
|
|
57
|
+
next = next.map(function (shimmer) {
|
|
58
|
+
return _objectSpread(_objectSpread({}, shimmer), {}, {
|
|
59
|
+
from: tr.mapping.map(shimmer.from, -1),
|
|
60
|
+
to: tr.mapping.map(shimmer.to, 1)
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
if (added !== null && added !== void 0 && added.length) {
|
|
66
|
+
next = added.map(function (shimmer) {
|
|
67
|
+
return _objectSpread({}, shimmer);
|
|
68
|
+
});
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
71
|
+
if (removedShimmerId) {
|
|
72
|
+
next = next.filter(function (shimmer) {
|
|
73
|
+
return shimmer.shimmerId !== removedShimmerId;
|
|
74
|
+
});
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
changed: changed,
|
|
79
|
+
next: next
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds the inline skeleton-bar + trailing telepointer decorations for the active shimmer ranges.
|
|
85
|
+
* `getValidPos` clamps a raw position to a valid decoration position (owned by `plugin-state`).
|
|
86
|
+
* One bad range is isolated via `onError` so it can't kill the others.
|
|
87
|
+
*/
|
|
88
|
+
var buildAgentShimmerDecorations = exports.buildAgentShimmerDecorations = function buildAgentShimmerDecorations(tr, shimmers, getValidPos, onError) {
|
|
89
|
+
var decorations = [];
|
|
90
|
+
var docEnd = tr.doc.nodeSize - 2;
|
|
91
|
+
shimmers.forEach(function (_ref) {
|
|
92
|
+
var shimmerId = _ref.shimmerId,
|
|
93
|
+
from = _ref.from,
|
|
94
|
+
to = _ref.to,
|
|
95
|
+
telepointerLabel = _ref.telepointerLabel;
|
|
96
|
+
try {
|
|
97
|
+
var validFrom = getValidPos(tr, Math.max(from, 1));
|
|
98
|
+
var validTo = getValidPos(tr, Math.min(to, docEnd));
|
|
99
|
+
if (validTo <= validFrom) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Skeleton-loader bar over the whole agent-authored range...
|
|
103
|
+
decorations.push(_view.Decoration.inline(validFrom, validTo, {
|
|
104
|
+
class: AGENT_SHIMMER_CLASS
|
|
105
|
+
}, {
|
|
106
|
+
isAgentShimmer: true,
|
|
107
|
+
shimmerId: shimmerId
|
|
108
|
+
}));
|
|
109
|
+
// ...and, when enabled, a Rovo AI telepointer/cursor (labelled with the agent's type) at the
|
|
110
|
+
// end of the range.
|
|
111
|
+
if (telepointerLabel) {
|
|
112
|
+
decorations.push(_view.Decoration.widget(validTo, createRovoAgentTelepointer(telepointerLabel), {
|
|
113
|
+
isAgentShimmer: true,
|
|
114
|
+
shimmerId: shimmerId,
|
|
115
|
+
class: ROVO_AGENT_TELEPOINTER_CLASS,
|
|
116
|
+
key: "agent-telepointer-".concat(shimmerId),
|
|
117
|
+
side: 1
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
// One bad range must not kill the others.
|
|
122
|
+
onError(err);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return decorations;
|
|
126
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isPositionNeutralStep = exports.getAgentShimmerRanges = void 0;
|
|
7
|
+
var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
|
|
8
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
9
|
+
// When an agent step lands we cover the top-level block(s) it wrote with a skeleton-loader shimmer
|
|
10
|
+
// (plus a Rovo agent telepointer at the end of the range), then remove it on a timer to reveal the
|
|
11
|
+
// content. Gated behind the `platform_editor_agent_be_streaming` experiment; `durationMs` sets how
|
|
12
|
+
// long the shimmer stays and a 0 duration disables it.
|
|
13
|
+
var agentShimmerIdCounter = 0;
|
|
14
|
+
|
|
15
|
+
// A step is position-neutral when its StepMap changes no range's length — i.e. it shifts no
|
|
16
|
+
// positions. Attribute-only steps (e.g. `localId` assignment) produce an empty StepMap, and
|
|
17
|
+
// same-size replacements preserve lengths, so both are position-neutral. Used to decide whether a
|
|
18
|
+
// rebase over local unconfirmed steps could have invalidated our index-based range math.
|
|
19
|
+
var isPositionNeutralStep = exports.isPositionNeutralStep = function isPositionNeutralStep(step) {
|
|
20
|
+
var neutral = true;
|
|
21
|
+
step.getMap().forEach(function (oldStart, oldEnd, newStart, newEnd) {
|
|
22
|
+
if (oldEnd - oldStart !== newEnd - newStart) {
|
|
23
|
+
neutral = false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return neutral;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Derive the shimmer ranges for the agent-authored steps in a received batch, in final-doc
|
|
31
|
+
* coordinates. `agentType` present ⇒ agent-authored (per the NCS↔Editor steps contract). Each
|
|
32
|
+
* emitted range is expanded to the whole top-level block(s) the agent touched, which the plugin
|
|
33
|
+
* covers with the skeleton shimmer. Ranges with no new content (pure deletions) are dropped.
|
|
34
|
+
*
|
|
35
|
+
* Steps whose new content is in the same or directly-adjacent top-level block are coalesced into one
|
|
36
|
+
* range, so an edit that arrives as several steps in a region shimmers as a single unit. Edits
|
|
37
|
+
* separated by an untouched block stay independent.
|
|
38
|
+
*
|
|
39
|
+
* Correctness: the range math assumes `tr` is a linear 1:1 apply of `steps`. Under the native collab
|
|
40
|
+
* plugin, `receiveTransaction` rebases incoming steps over unconfirmed local steps when they exist;
|
|
41
|
+
* that only invalidates our positions if a local step shifted positions, so we skip solely when a
|
|
42
|
+
* rebased local step changed sizes (a rare, safe degrade). Any unexpected error also degrades to no
|
|
43
|
+
* shimmer, so this never throws into the shared remote-step handler.
|
|
44
|
+
*/
|
|
45
|
+
var getAgentShimmerRanges = exports.getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, tr, view, durationMs, telepointerEnabled) {
|
|
46
|
+
var _json$find;
|
|
47
|
+
if (!(0, _expValEquals.expValEquals)('platform_editor_agent_be_streaming', 'isEnabled', true)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
// Nothing to reveal if the shimmer is disabled.
|
|
51
|
+
if (durationMs <= 0) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
// Telepointer label = the agent's type upper-cased (e.g. `mcp` → "MCP"), falling back to a generic
|
|
55
|
+
// "Agent"; `undefined` when the telepointer is disabled, so the plugin skips it. `agentType` is the
|
|
56
|
+
// same on every step of an agent batch, so read it from the first agent-authored step.
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
var agentType = (_json$find = json.find(function (step) {
|
|
59
|
+
return typeof (step === null || step === void 0 ? void 0 : step.agentType) === 'string';
|
|
60
|
+
})) === null || _json$find === void 0 ? void 0 : _json$find.agentType;
|
|
61
|
+
var telepointerLabel = telepointerEnabled ? (agentType === null || agentType === void 0 ? void 0 : agentType.toUpperCase()) || 'Agent' : undefined;
|
|
62
|
+
// When the batch was rebased over local unconfirmed steps, our index-based range math is only
|
|
63
|
+
// valid if those local steps shifted no positions. Attribute-only steps (e.g. `localId`) and
|
|
64
|
+
// same-size replacements are position-neutral, so the shimmer stays correct. Skip only when a
|
|
65
|
+
// local step actually changed sizes (a genuine concurrent content edit).
|
|
66
|
+
if (Number(tr.getMeta('rebased')) > 0) {
|
|
67
|
+
var _getCollabState$uncon, _getCollabState;
|
|
68
|
+
var unconfirmed = (_getCollabState$uncon = (_getCollabState = (0, _prosemirrorCollab.getCollabState)(view.state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.unconfirmed) !== null && _getCollabState$uncon !== void 0 ? _getCollabState$uncon : [];
|
|
69
|
+
if (unconfirmed.some(function (entry) {
|
|
70
|
+
return !isPositionNeutralStep(entry.step);
|
|
71
|
+
})) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
// Map an inserted range in doc_{i+1} forward through the remaining steps to final-doc coords.
|
|
77
|
+
// (A later step's own inserted content starts at its `from`; only subsequent steps shift it.)
|
|
78
|
+
var mapToFinalDoc = function mapToFinalDoc(pos, stepIndex, bias) {
|
|
79
|
+
var p = pos;
|
|
80
|
+
for (var j = stepIndex + 1; j < steps.length; j++) {
|
|
81
|
+
p = steps[j].getMap().map(p, bias);
|
|
82
|
+
}
|
|
83
|
+
return p;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Derive each agent step's changed ranges from its StepMap — the canonical, step-type-agnostic
|
|
87
|
+
// source of what a step wrote. We only need the NEW extent (the content now in the document), in
|
|
88
|
+
// final-doc coords, since the highlight decorates content that is already present.
|
|
89
|
+
|
|
90
|
+
var infos = [];
|
|
91
|
+
json.forEach(function (rawStep, index) {
|
|
92
|
+
if (typeof (rawStep === null || rawStep === void 0 ? void 0 : rawStep.agentType) !== 'string') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
var pmStep = steps[index];
|
|
97
|
+
if (typeof (pmStep === null || pmStep === void 0 ? void 0 : pmStep.getMap) !== 'function') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
pmStep.getMap().forEach(function (_oldStart, _oldEnd, newStart, newEnd) {
|
|
101
|
+
if (newEnd <= newStart) {
|
|
102
|
+
return; // pure deletion / attribute-only — no new content to highlight
|
|
103
|
+
}
|
|
104
|
+
infos.push({
|
|
105
|
+
from: mapToFinalDoc(newStart, index, -1),
|
|
106
|
+
to: mapToFinalDoc(newEnd, index, 1)
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
if (!infos.length) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Group the changed ranges by the TOP-LEVEL block they landed in, then highlight each touched
|
|
115
|
+
// block IN FULL. NCS emits a single agent edit as many small replace fragments and often keeps a
|
|
116
|
+
// common prefix/suffix untouched, so the changed sub-ranges cover only part of a block (e.g. half
|
|
117
|
+
// a rewritten heading). Expanding to the whole block's content makes the entire heading/paragraph
|
|
118
|
+
// shimmer as one unit rather than leaving the unchanged half undecorated. Directly-adjacent
|
|
119
|
+
// touched blocks (index n and n+1) merge into one group so a multi-block rewrite reveals together;
|
|
120
|
+
// a genuinely untouched block in between (index gap > 1) splits the run, so far-apart edits stay
|
|
121
|
+
// independent.
|
|
122
|
+
var docSize = tr.doc.content.size;
|
|
123
|
+
var clampPos = function clampPos(pos) {
|
|
124
|
+
return Math.min(Math.max(pos, 1), docSize);
|
|
125
|
+
};
|
|
126
|
+
var blockIndexAt = function blockIndexAt(pos) {
|
|
127
|
+
return tr.doc.resolve(clampPos(pos)).index(0);
|
|
128
|
+
};
|
|
129
|
+
// Start/end of the content of the top-level block containing `pos`, so the whole block is covered.
|
|
130
|
+
var blockContentStart = function blockContentStart(pos) {
|
|
131
|
+
var $pos = tr.doc.resolve(clampPos(pos));
|
|
132
|
+
return $pos.depth >= 1 ? $pos.start(1) : pos;
|
|
133
|
+
};
|
|
134
|
+
var blockContentEnd = function blockContentEnd(pos) {
|
|
135
|
+
var $pos = tr.doc.resolve(clampPos(pos));
|
|
136
|
+
return $pos.depth >= 1 ? $pos.end(1) : pos;
|
|
137
|
+
};
|
|
138
|
+
var sorted = [].concat(infos).sort(function (a, b) {
|
|
139
|
+
return a.from - b.from || a.to - b.to;
|
|
140
|
+
});
|
|
141
|
+
var groups = [];
|
|
142
|
+
sorted.forEach(function (info) {
|
|
143
|
+
var block = blockIndexAt(info.from);
|
|
144
|
+
var current = groups[groups.length - 1];
|
|
145
|
+
if (current && block <= current.maxBlock + 1) {
|
|
146
|
+
current.to = Math.max(current.to, info.to);
|
|
147
|
+
current.maxBlock = Math.max(current.maxBlock, block);
|
|
148
|
+
} else {
|
|
149
|
+
groups.push({
|
|
150
|
+
from: info.from,
|
|
151
|
+
to: info.to,
|
|
152
|
+
maxBlock: block
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return groups.map(function (group) {
|
|
157
|
+
return {
|
|
158
|
+
shimmerId: "agent-shimmer-".concat(agentShimmerIdCounter++),
|
|
159
|
+
from: blockContentStart(group.from),
|
|
160
|
+
to: blockContentEnd(group.to),
|
|
161
|
+
telepointerLabel: telepointerLabel
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
} catch (_unused) {
|
|
165
|
+
// Never let shimmer range derivation throw into the shared remote-step handler; degrade to no
|
|
166
|
+
// shimmer.
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
};
|
|
@@ -14,6 +14,7 @@ var _state = require("@atlaskit/editor-prosemirror/state");
|
|
|
14
14
|
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
15
15
|
var _participants = require("../participants");
|
|
16
16
|
var _utils = require("../utils");
|
|
17
|
+
var _agentShimmerDecorations = require("./agent-shimmer-decorations");
|
|
17
18
|
/**
|
|
18
19
|
* Returns position where it's possible to place a decoration.
|
|
19
20
|
*/
|
|
@@ -33,7 +34,10 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
33
34
|
var collabInitalised = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
|
34
35
|
var onError = arguments.length > 4 ? arguments[4] : undefined;
|
|
35
36
|
var nudgeAnimations = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : new Map();
|
|
37
|
+
var agentShimmers = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : [];
|
|
36
38
|
(0, _classCallCheck2.default)(this, PluginState);
|
|
39
|
+
// Active agent-edit shimmer ranges (pure data). Replaced with a fresh array of fresh
|
|
40
|
+
// objects whenever it changes — never mutated in place.
|
|
37
41
|
// eslint-disable-next-line no-console
|
|
38
42
|
(0, _defineProperty2.default)(this, "onError", function (error) {
|
|
39
43
|
return console.error(error);
|
|
@@ -44,6 +48,7 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
44
48
|
this.isReady = collabInitalised;
|
|
45
49
|
this.onError = onError || this.onError;
|
|
46
50
|
this.nudgeAnimations = nudgeAnimations;
|
|
51
|
+
this.agentShimmers = agentShimmers;
|
|
47
52
|
}
|
|
48
53
|
return (0, _createClass2.default)(PluginState, [{
|
|
49
54
|
key: "decorations",
|
|
@@ -90,6 +95,8 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
90
95
|
var presenceData = tr.getMeta('presence');
|
|
91
96
|
var telepointerData = tr.getMeta('telepointer');
|
|
92
97
|
var nudgeTelepointerData = tr.getMeta('nudgeTelepointer');
|
|
98
|
+
var agentShimmerData = tr.getMeta(_agentShimmerDecorations.ADD_AGENT_SHIMMER_META);
|
|
99
|
+
var removeAgentShimmerId = tr.getMeta(_agentShimmerDecorations.REMOVE_AGENT_SHIMMER_META);
|
|
93
100
|
var sessionIdData = tr.getMeta('sessionId');
|
|
94
101
|
var collabInitialised = tr.getMeta('collabInitialised');
|
|
95
102
|
if (typeof collabInitialised !== 'boolean') {
|
|
@@ -177,6 +184,11 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
177
184
|
// Ignored via go/ees005
|
|
178
185
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
186
|
this.decorationSet.find().forEach(function (deco) {
|
|
187
|
+
var _deco$spec;
|
|
188
|
+
// Never let telepointer dim/side logic touch agent-shimmer decorations.
|
|
189
|
+
if ((_deco$spec = deco.spec) !== null && _deco$spec !== void 0 && _deco$spec.isAgentShimmer) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
180
192
|
if (deco.type.toDOM) {
|
|
181
193
|
var hasTelepointerDimClass = deco.type.toDOM.classList.contains(_collab.TELEPOINTER_DIM_CLASS);
|
|
182
194
|
var browser = (0, _browser.getBrowserInfo)();
|
|
@@ -205,8 +217,12 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
205
217
|
// Ignored via go/ees005
|
|
206
218
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
207
219
|
this.decorationSet.find().forEach(function (deco) {
|
|
208
|
-
var _deco$
|
|
209
|
-
|
|
220
|
+
var _deco$spec2, _deco$spec3, _deco$spec4;
|
|
221
|
+
// Never let telepointer nudge logic touch agent-shimmer decorations.
|
|
222
|
+
if ((_deco$spec2 = deco.spec) !== null && _deco$spec2 !== void 0 && _deco$spec2.isAgentShimmer) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (deco.type.toDOM && participants.get(nudgeSessionId) && ((_deco$spec3 = deco.spec) === null || _deco$spec3 === void 0 || (_deco$spec3 = _deco$spec3.pointer) === null || _deco$spec3 === void 0 ? void 0 : _deco$spec3.sessionId) === nudgeSessionId && ((_deco$spec4 = deco.spec) === null || _deco$spec4 === void 0 ? void 0 : _deco$spec4.key) === "telepointer-".concat(nudgeSessionId)) {
|
|
210
226
|
// Restart animation by removing and re-adding the class
|
|
211
227
|
deco.type.toDOM.classList.remove(_collab.TELEPOINTER_PULSE_DURING_TR_CLASS);
|
|
212
228
|
deco.type.toDOM.classList.remove(_collab.TELEPOINTER_PULSE_CLASS);
|
|
@@ -216,6 +232,41 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
216
232
|
}
|
|
217
233
|
});
|
|
218
234
|
}
|
|
235
|
+
|
|
236
|
+
// Register / keep-aligned / remove the agent-edit purple highlight
|
|
237
|
+
// decorations. Fully fault-isolated — it works on local copies and only merges into the shared
|
|
238
|
+
// `add`/`remove` (and commits `this.agentShimmers`) after the whole block succeeds, so a fault
|
|
239
|
+
// here can never corrupt telepointer decorations or the shared decoration set. Never mutates
|
|
240
|
+
// shimmer entries in place: each change produces a fresh array of fresh objects.
|
|
241
|
+
try {
|
|
242
|
+
var _reduceAgentShimmers = (0, _agentShimmerDecorations.reduceAgentShimmers)(this.agentShimmers, tr, agentShimmerData, removeAgentShimmerId),
|
|
243
|
+
changed = _reduceAgentShimmers.changed,
|
|
244
|
+
next = _reduceAgentShimmers.next;
|
|
245
|
+
if (changed) {
|
|
246
|
+
// Build into local arrays; only merge into the shared add/remove once the block succeeds.
|
|
247
|
+
var agentRemove = this.decorationSet.find(undefined, undefined, function (spec) {
|
|
248
|
+
return spec.isAgentShimmer;
|
|
249
|
+
});
|
|
250
|
+
var agentAdd = (0, _agentShimmerDecorations.buildAgentShimmerDecorations)(tr, next, getValidPos, this.onError);
|
|
251
|
+
|
|
252
|
+
// Commit only after the whole block succeeded.
|
|
253
|
+
remove = remove.concat(agentRemove);
|
|
254
|
+
add = add.concat(agentAdd);
|
|
255
|
+
this.agentShimmers = next;
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
this.onError(err);
|
|
259
|
+
// Degrade to no shimmer without corrupting telepointer decorations: drop all agent
|
|
260
|
+
// decorations and clear agent state, leaving the telepointer `add`/`remove` untouched.
|
|
261
|
+
this.agentShimmers = [];
|
|
262
|
+
try {
|
|
263
|
+
remove = remove.concat(this.decorationSet.find(undefined, undefined, function (spec) {
|
|
264
|
+
return spec.isAgentShimmer;
|
|
265
|
+
}));
|
|
266
|
+
} catch (_unused) {
|
|
267
|
+
// Teardown must not re-throw.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
219
270
|
if (remove.length) {
|
|
220
271
|
this.decorationSet = this.decorationSet.remove(remove);
|
|
221
272
|
}
|
|
@@ -234,7 +285,7 @@ var PluginState = exports.PluginState = /*#__PURE__*/function () {
|
|
|
234
285
|
}
|
|
235
286
|
}
|
|
236
287
|
}
|
|
237
|
-
var nextState = new PluginState(this.decorationSet, participants, sid, collabInitialised, this.onError, this.nudgeAnimations);
|
|
288
|
+
var nextState = new PluginState(this.decorationSet, participants, sid, collabInitialised, this.onError, this.nudgeAnimations, this.agentShimmers);
|
|
238
289
|
return PluginState.eq(nextState, this) ? this : nextState;
|
|
239
290
|
}
|
|
240
291
|
}], [{
|
|
@@ -19,8 +19,12 @@ var _view = require("@atlaskit/editor-prosemirror/view");
|
|
|
19
19
|
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
|
|
20
20
|
var _preserveNodeIdentity = require("./preserve-node-identity");
|
|
21
21
|
var findPointers = exports.findPointers = function findPointers(id, decorations) {
|
|
22
|
-
return decorations.find().reduce(
|
|
23
|
-
|
|
22
|
+
return decorations.find().reduce(
|
|
23
|
+
// `pointer` is absent on non-telepointer decorations (e.g. the agent-shimmer
|
|
24
|
+
// sweep decorations), so guard against it to avoid crashing this shared helper.
|
|
25
|
+
function (arr, deco) {
|
|
26
|
+
var _deco$spec$pointer;
|
|
27
|
+
return ((_deco$spec$pointer = deco.spec.pointer) === null || _deco$spec$pointer === void 0 ? void 0 : _deco$spec$pointer.presenceId) === id ? arr.concat(deco) : arr;
|
|
24
28
|
}, []);
|
|
25
29
|
};
|
|
26
30
|
function style(options) {
|
|
@@ -7,6 +7,10 @@ import * as allAtlaskitCustomSteps from '@atlaskit/custom-steps';
|
|
|
7
7
|
import { AllSelection, NodeSelection } from '@atlaskit/editor-prosemirror/state';
|
|
8
8
|
import { Step } from '@atlaskit/editor-prosemirror/transform';
|
|
9
9
|
import { receiveTransaction } from '@atlaskit/prosemirror-collab';
|
|
10
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
11
|
+
import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
|
|
12
|
+
import { ADD_AGENT_SHIMMER_META, AGENT_SHIMMER_DEFAULT_DURATION_MS, REMOVE_AGENT_SHIMMER_META } from './main/agent-shimmer-decorations';
|
|
13
|
+
import { getAgentShimmerRanges } from './main/agent-shimmer-ranges';
|
|
10
14
|
import { replaceDocument } from './utils';
|
|
11
15
|
|
|
12
16
|
/*
|
|
@@ -83,6 +87,22 @@ export const applyRemoteSteps = (json, view, userIds, options) => {
|
|
|
83
87
|
tr.setMeta('addToHistory', false);
|
|
84
88
|
tr.setMeta('isRemote', true);
|
|
85
89
|
|
|
90
|
+
// Agent edit shimmer: mark the ranges agent steps just wrote so the plugin reveals them with the
|
|
91
|
+
// gloss-sweep → purple-highlight sequence. Gated as a whole so no experiment reads run
|
|
92
|
+
// off-experiment; off-path leaves `agentShimmers` empty and everything below is a no-op.
|
|
93
|
+
let durationMs = 0;
|
|
94
|
+
let agentShimmers = [];
|
|
95
|
+
if (expValEquals('platform_editor_agent_be_streaming', 'isEnabled', true)) {
|
|
96
|
+
durationMs = expVal('platform_editor_agent_be_streaming', 'durationMs', AGENT_SHIMMER_DEFAULT_DURATION_MS);
|
|
97
|
+
// Telepointer shown by default; `telepointerDisabled` hides it (inverted because `expVal`
|
|
98
|
+
// only permits `false` as a boolean default).
|
|
99
|
+
const telepointerEnabled = !expVal('platform_editor_agent_be_streaming', 'telepointerDisabled', false);
|
|
100
|
+
agentShimmers = getAgentShimmerRanges(json, steps, tr, view, durationMs, telepointerEnabled);
|
|
101
|
+
if (agentShimmers.length) {
|
|
102
|
+
tr.setMeta(ADD_AGENT_SHIMMER_META, agentShimmers);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
/*
|
|
87
107
|
* Persist marks across transactions. Fixes an issue where
|
|
88
108
|
* marks are lost if remote transactions are dispatched
|
|
@@ -92,6 +112,26 @@ export const applyRemoteSteps = (json, view, userIds, options) => {
|
|
|
92
112
|
tr.setStoredMarks(state.tr.storedMarks);
|
|
93
113
|
}
|
|
94
114
|
view.dispatch(tr);
|
|
115
|
+
|
|
116
|
+
// Remove each shimmer after `durationMs`. `dispatchMeta` is guarded so a timer firing after
|
|
117
|
+
// teardown is a harmless no-op. Only reached when the gated block above produced ranges, so this
|
|
118
|
+
// whole path is off-experiment-safe.
|
|
119
|
+
if (agentShimmers.length) {
|
|
120
|
+
const dispatchMeta = (metaKey, value) => {
|
|
121
|
+
try {
|
|
122
|
+
view.dispatch(view.state.tr.setMeta(metaKey, value));
|
|
123
|
+
} catch {
|
|
124
|
+
// View torn down before the timer fired — nothing to clean up.
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
agentShimmers.forEach(({
|
|
128
|
+
shimmerId
|
|
129
|
+
}) => {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
dispatchMeta(REMOVE_AGENT_SHIMMER_META, shimmerId);
|
|
132
|
+
}, durationMs);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
95
135
|
}
|
|
96
136
|
};
|
|
97
137
|
export const handleTelePointer = (telepointerData, view) => {
|