@atlaskit/editor-plugin-show-diff 14.3.2 → 14.4.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/afm-cc/tsconfig.json +18 -0
  3. package/afm-products/tsconfig.json +18 -0
  4. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +47 -26
  5. package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +61 -9
  6. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +77 -20
  7. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +10 -0
  8. package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +2 -0
  9. package/dist/cjs/pm-plugins/decorations/createAnchorDecorationWidgets.js +52 -7
  10. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +172 -0
  11. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
  12. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
  13. package/dist/cjs/pm-plugins/decorations/decorationKeys.js +31 -5
  14. package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +128 -0
  15. package/dist/cjs/pm-plugins/main.js +57 -6
  16. package/dist/cjs/pm-plugins/resolveDiffContributors.js +143 -0
  17. package/dist/cjs/showDiffPlugin.js +21 -6
  18. package/dist/cjs/ui/ContributorTag/ContributorTag.compiled.css +29 -0
  19. package/dist/cjs/ui/ContributorTag/ContributorTag.js +383 -0
  20. package/dist/cjs/ui/ContributorTag/ContributorTagWidget.js +50 -0
  21. package/dist/cjs/ui/ContributorTag/messages.js +24 -0
  22. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +48 -27
  23. package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +39 -0
  24. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +53 -11
  25. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +9 -0
  26. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +2 -0
  27. package/dist/es2019/pm-plugins/decorations/createAnchorDecorationWidgets.js +43 -1
  28. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +163 -0
  29. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +38 -5
  30. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +38 -6
  31. package/dist/es2019/pm-plugins/decorations/decorationKeys.js +30 -2
  32. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +101 -0
  33. package/dist/es2019/pm-plugins/main.js +52 -3
  34. package/dist/es2019/pm-plugins/resolveDiffContributors.js +128 -0
  35. package/dist/es2019/showDiffPlugin.js +20 -5
  36. package/dist/es2019/ui/ContributorTag/ContributorTag.compiled.css +29 -0
  37. package/dist/es2019/ui/ContributorTag/ContributorTag.js +312 -0
  38. package/dist/es2019/ui/ContributorTag/ContributorTagWidget.js +43 -0
  39. package/dist/es2019/ui/ContributorTag/messages.js +18 -0
  40. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +49 -28
  41. package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +60 -8
  42. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +77 -19
  43. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +9 -0
  44. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +2 -0
  45. package/dist/esm/pm-plugins/decorations/createAnchorDecorationWidgets.js +51 -6
  46. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +166 -0
  47. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
  48. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
  49. package/dist/esm/pm-plugins/decorations/decorationKeys.js +30 -5
  50. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +122 -0
  51. package/dist/esm/pm-plugins/main.js +57 -6
  52. package/dist/esm/pm-plugins/resolveDiffContributors.js +137 -0
  53. package/dist/esm/showDiffPlugin.js +21 -6
  54. package/dist/esm/ui/ContributorTag/ContributorTag.compiled.css +29 -0
  55. package/dist/esm/ui/ContributorTag/ContributorTag.js +375 -0
  56. package/dist/esm/ui/ContributorTag/ContributorTagWidget.js +43 -0
  57. package/dist/esm/ui/ContributorTag/messages.js +18 -0
  58. package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
  59. package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +7 -1
  60. package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +10 -0
  61. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +24 -2
  62. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +7 -1
  63. package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +1 -2
  64. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +2 -1
  65. package/dist/types/pm-plugins/decorations/createAnchorDecorationWidgets.d.ts +14 -0
  66. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +55 -0
  67. package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +5 -1
  68. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +5 -1
  69. package/dist/types/pm-plugins/decorations/decorationKeys.d.ts +28 -3
  70. package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +9 -0
  71. package/dist/types/pm-plugins/main.d.ts +8 -2
  72. package/dist/types/pm-plugins/resolveDiffContributors.d.ts +7 -0
  73. package/dist/types/showDiffPluginType.d.ts +65 -2
  74. package/dist/types/ui/ContributorTag/ContributorTag.d.ts +37 -0
  75. package/dist/types/ui/ContributorTag/ContributorTagWidget.d.ts +22 -0
  76. package/dist/types/ui/ContributorTag/messages.d.ts +6 -0
  77. package/package.json +16 -7
@@ -0,0 +1,375 @@
1
+ /* ContributorTag.tsx generated by @compiled/babel-plugin v2.0.0 */
2
+ import _extends from "@babel/runtime/helpers/extends";
3
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
4
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
+ import "./ContributorTag.compiled.css";
6
+ import * as React from 'react';
7
+ import { ax, ix } from "@compiled/react/runtime";
8
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
9
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
10
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
11
+ import { useCallback, useEffect, useRef, useState } from 'react';
12
+ import { bindAll } from 'bind-event-listener';
13
+ import { useIntl } from 'react-intl';
14
+ import Avatar from '@atlaskit/avatar/avatar';
15
+ import { AvatarContent } from '@atlaskit/avatar/avatar-content';
16
+ import AiBotIcon from '@atlaskit/icon-lab/core/ai-bot';
17
+ import { RovoHexIcon } from '@atlaskit/logo/rovo-hex/icon';
18
+ import Tooltip from '@atlaskit/tooltip/Tooltip';
19
+ import VisuallyHidden from '@atlaskit/visually-hidden/visually-hidden';
20
+ import { getAccentTokens } from '../../pm-plugins/decorations/colorSchemes/factory';
21
+ import { colorSchemeRegistry } from '../../pm-plugins/decorations/colorSchemes/schemes';
22
+ import { contributorTagMessages } from './messages';
23
+
24
+ // Floor for the tag width (~one 16px avatar plus padding), so the highlight-width clamp below
25
+ // cannot collapse the box on a very short highlight.
26
+ var MIN_TAG_WIDTH = '28px';
27
+ var styles = {
28
+ constraint: "_1e0c1ule _kqswstnw _94n51osq _1e02idpf _1ul9idpf _1pbykb7n _uiztglyw",
29
+ tag: "_zulp1b66 _1reo15vq _18m915vq _2rko12b0 _11c8wadc _18zr1b66 _1rjcv77o _1e0c116y _4cvr1h6o _p12f1osq _1ul9idpf _1il9nqa1 _1doc1dm9 _16qs130s _o5721q9c _vchhusvi",
30
+ hidden: "_tzy4idpf _lcxvglyw",
31
+ visible: "_tzy4kb7n _lcxv1wug",
32
+ avatars: "_1e0c116y _4cvr1h6o _1o9zidpf",
33
+ leadingAvatar: "_1e0c116y _12l2r5cr",
34
+ name: "_1reo15vq _18m915vq _1e0c1ule _1ul9idpf _1bto1l2s _o5721q9c"
35
+ };
36
+ var getContributorName = function getContributorName(contributor, formatMessage) {
37
+ var _contributor$name, _contributor$agentKin;
38
+ var name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
39
+ if (name) {
40
+ return name;
41
+ }
42
+
43
+ // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
44
+ // agent falls back to a generic label rather than rendering a nameless tag.
45
+ if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
46
+ return formatMessage(contributorTagMessages.externalAgentName);
47
+ }
48
+ return '';
49
+ };
50
+ var ContributorAvatar = function ContributorAvatar(_ref) {
51
+ var contributor = _ref.contributor,
52
+ name = _ref.name,
53
+ ringColor = _ref.ringColor,
54
+ stackIndex = _ref.stackIndex;
55
+ if (contributor.kind === 'user') {
56
+ return /*#__PURE__*/React.createElement(Avatar, {
57
+ appearance: "circle",
58
+ size: "xxsmall",
59
+ src: contributor.avatarUrl,
60
+ name: name,
61
+ stackIndex: stackIndex,
62
+ borderColor: ringColor
63
+ });
64
+ }
65
+ return /*#__PURE__*/React.createElement(Avatar, {
66
+ appearance: "hexagon",
67
+ size: "xxsmall",
68
+ src: contributor.avatarUrl,
69
+ name: name,
70
+ stackIndex: stackIndex,
71
+ borderColor: ringColor
72
+ }, contributor.avatarUrl ? undefined : /*#__PURE__*/React.createElement(AvatarContent, null, contributor.agentKind === 'rovo' ? /*#__PURE__*/React.createElement(RovoHexIcon, {
73
+ size: "xxsmall",
74
+ label: ""
75
+ }) :
76
+ /*#__PURE__*/
77
+ // `small` (12px) fits inside the 16px hexagon.
78
+ React.createElement(AiBotIcon, {
79
+ label: "",
80
+ size: "small"
81
+ })));
82
+ };
83
+ /**
84
+ * Resolves the tag's accent from the same scheme value and token maps the highlight used, so the two
85
+ * can never drift.
86
+ */
87
+ var getTagAccent = function getTagAccent(_ref2) {
88
+ var colorScheme = _ref2.colorScheme,
89
+ isActive = _ref2.isActive,
90
+ isInserted = _ref2.isInserted;
91
+ var colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
92
+ var accent = isInserted ? isActive ? colors.insertActiveColor : colors.insertColor : isActive ? colors.deleteActiveColor : colors.deleteColor;
93
+ return getAccentTokens(accent);
94
+ };
95
+
96
+ /** The line the change starts on: topmost of its fragments, leftmost among those. */
97
+ var findChangeStartLine = function findChangeStartLine(highlights) {
98
+ var start;
99
+ var _iterator = _createForOfIteratorHelper(highlights),
100
+ _step;
101
+ try {
102
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
103
+ var highlight = _step.value;
104
+ var _iterator2 = _createForOfIteratorHelper(highlight.getClientRects()),
105
+ _step2;
106
+ try {
107
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
108
+ var rect = _step2.value;
109
+ if (!start || rect.top < start.top - 1 || Math.abs(rect.top - start.top) <= 1 && rect.left < start.left) {
110
+ start = rect;
111
+ }
112
+ }
113
+ } catch (err) {
114
+ _iterator2.e(err);
115
+ } finally {
116
+ _iterator2.f();
117
+ }
118
+ }
119
+ } catch (err) {
120
+ _iterator.e(err);
121
+ } finally {
122
+ _iterator.f();
123
+ }
124
+ return start;
125
+ };
126
+
127
+ /**
128
+ * Observes the highlight this tag describes, for what CSS here cannot see: its hover state (the
129
+ * highlight is a ProseMirror decoration in a different DOM subtree, so listeners are bound
130
+ * imperatively via `data-diff-id`) and the width the tag is clamped to.
131
+ *
132
+ * Deliberately not routed through plugin state: a transaction per pointer move would re-run the
133
+ * memoised decoration calculation.
134
+ */
135
+ var useHighlightState = function useHighlightState(diffId, highlightSelector, getEditorRoot) {
136
+ var _useState = useState(false),
137
+ _useState2 = _slicedToArray(_useState, 2),
138
+ isHovered = _useState2[0],
139
+ setIsHovered = _useState2[1];
140
+ var _useState3 = useState(undefined),
141
+ _useState4 = _slicedToArray(_useState3, 2),
142
+ maxWidthPx = _useState4[0],
143
+ setMaxWidthPx = _useState4[1];
144
+ useEffect(function () {
145
+ // Both queries are scoped to this editor's content root, so a tag can only ever bind to the
146
+ // decorations its own plugin instance rendered — another editor on the page, or one nested
147
+ // inside this one, cannot supply the geometry.
148
+ var editorRoot = getEditorRoot();
149
+ var highlights = editorRoot === null || editorRoot === void 0 ? void 0 : editorRoot.querySelectorAll(highlightSelector);
150
+ if (!editorRoot || !(highlights !== null && highlights !== void 0 && highlights.length)) {
151
+ return;
152
+ }
153
+ var elements = Array.from(highlights);
154
+ // The width comes from this tag's OWN decoration; sibling halves of the change are here for
155
+ // hover only.
156
+ var ownElements = Array.from(editorRoot.querySelectorAll("[data-diff-id=\"".concat(diffId, "\"]")));
157
+ var measure = function measure() {
158
+ var _findChangeStartLine;
159
+ // Counted by distinct line box, not by client rect: an inline node inside the change (a
160
+ // mention, emoji, date) splits the highlight into several rects on the same line, and
161
+ // treating that as a wrap would drop the width clamp on changes that never wrapped.
162
+ var lineTops = new Set();
163
+ for (var _i = 0, _ownElements = ownElements; _i < _ownElements.length; _i++) {
164
+ var highlight = _ownElements[_i];
165
+ var _iterator3 = _createForOfIteratorHelper(highlight.getClientRects()),
166
+ _step3;
167
+ try {
168
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
169
+ var rect = _step3.value;
170
+ lineTops.add(Math.round(rect.top));
171
+ }
172
+ } catch (err) {
173
+ _iterator3.e(err);
174
+ } finally {
175
+ _iterator3.f();
176
+ }
177
+ }
178
+
179
+ // A wrapped change's first line says nothing useful about how wide the tag may be.
180
+ var width = lineTops.size > 1 ? undefined : (_findChangeStartLine = findChangeStartLine(ownElements)) === null || _findChangeStartLine === void 0 ? void 0 : _findChangeStartLine.width;
181
+ setMaxWidthPx(width);
182
+ };
183
+ measure();
184
+
185
+ // An inline element cannot be observed itself (ResizeObserver skips boxes with no principal
186
+ // box), so the block it sits in is observed instead.
187
+ var resizeObserver = new ResizeObserver(measure);
188
+ var _iterator4 = _createForOfIteratorHelper(new Set(ownElements.map(function (_ref3) {
189
+ var parentElement = _ref3.parentElement;
190
+ return parentElement;
191
+ }))),
192
+ _step4;
193
+ try {
194
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
195
+ var parent = _step4.value;
196
+ if (parent) {
197
+ resizeObserver.observe(parent);
198
+ }
199
+ }
200
+
201
+ // `bindAll` takes a single target, and a change can render more than one highlight.
202
+ } catch (err) {
203
+ _iterator4.e(err);
204
+ } finally {
205
+ _iterator4.f();
206
+ }
207
+ var unbinds = elements.map(function (highlight) {
208
+ return bindAll(highlight, [{
209
+ type: 'mouseenter',
210
+ listener: function listener() {
211
+ return setIsHovered(true);
212
+ }
213
+ }, {
214
+ type: 'mouseleave',
215
+ listener: function listener() {
216
+ return setIsHovered(false);
217
+ }
218
+ }]);
219
+ });
220
+ return function () {
221
+ resizeObserver.disconnect();
222
+ unbinds.forEach(function (unbind) {
223
+ return unbind();
224
+ });
225
+ };
226
+ }, [diffId, getEditorRoot, highlightSelector]);
227
+ return {
228
+ isHovered: isHovered,
229
+ maxWidthPx: maxWidthPx
230
+ };
231
+ };
232
+
233
+ /**
234
+ * A small tag naming who made one diff, rendered into the host widget its decoration places on the
235
+ * change's first character and positioned above it.
236
+ */
237
+ export function ContributorTag(_ref4) {
238
+ var colorScheme = _ref4.colorScheme,
239
+ connectedContributor = _ref4.connectedContributor,
240
+ contributor = _ref4.contributor,
241
+ diffId = _ref4.diffId,
242
+ getEditorRoot = _ref4.getEditorRoot,
243
+ isActive = _ref4.isActive,
244
+ _ref4$isInserted = _ref4.isInserted,
245
+ isInserted = _ref4$isInserted === void 0 ? true : _ref4$isInserted,
246
+ linkedDiffIds = _ref4.linkedDiffIds;
247
+ var _useIntl = useIntl(),
248
+ formatMessage = _useIntl.formatMessage;
249
+ // A plain string, so the hook's effect is not re-run by a fresh array on every render.
250
+ var highlightSelector = [diffId].concat(_toConsumableArray(linkedDiffIds !== null && linkedDiffIds !== void 0 ? linkedDiffIds : [])).map(function (id) {
251
+ return "[data-diff-id=\"".concat(id, "\"]");
252
+ }).join(', ');
253
+ var _useHighlightState = useHighlightState(diffId, highlightSelector, getEditorRoot),
254
+ isHighlightHovered = _useHighlightState.isHovered,
255
+ maxWidthPx = _useHighlightState.maxWidthPx;
256
+ var accent = getTagAccent({
257
+ colorScheme: colorScheme,
258
+ isActive: Boolean(isActive),
259
+ isInserted: isInserted
260
+ });
261
+ var nameRef = useRef(null);
262
+ var _useState5 = useState(false),
263
+ _useState6 = _slicedToArray(_useState5, 2),
264
+ isTruncated = _useState6[0],
265
+ setIsTruncated = _useState6[1];
266
+ var _useState7 = useState(false),
267
+ _useState8 = _slicedToArray(_useState7, 2),
268
+ isHoveredOrFocused = _useState8[0],
269
+ setIsHoveredOrFocused = _useState8[1];
270
+ var isConnectedPair = Boolean(connectedContributor);
271
+ var isPrimaryAgent = contributor.kind === 'agent';
272
+ var primaryName = getContributorName(contributor, formatMessage);
273
+ var secondaryName = connectedContributor ? getContributorName(connectedContributor, formatMessage) : '';
274
+
275
+ // Resolved by `kind` so a reverse `connectedTo` (an attributed user pointing at its agent)
276
+ // renders identically.
277
+ var agentName = isPrimaryAgent ? primaryName : secondaryName;
278
+ var userName = isPrimaryAgent ? secondaryName : primaryName;
279
+ var userContributor = isPrimaryAgent ? connectedContributor : contributor;
280
+ var agentContributor = isPrimaryAgent ? contributor : connectedContributor;
281
+ var displayName = isConnectedPair ? agentName || primaryName : primaryName;
282
+ var fullLabel = isConnectedPair ? formatMessage(contributorTagMessages.changedByConnected, {
283
+ agentName: agentName,
284
+ userName: userName
285
+ }) : formatMessage(contributorTagMessages.changedBy, {
286
+ name: primaryName
287
+ });
288
+ var measureTruncation = useCallback(function () {
289
+ var element = nameRef.current;
290
+ if (element) {
291
+ setIsTruncated(element.scrollWidth > element.clientWidth);
292
+ }
293
+ }, []);
294
+ var isVisible = Boolean(isActive) || isHighlightHovered || isHoveredOrFocused;
295
+
296
+ // A connected pair only ever renders the agent name, so it always gets a tooltip; a single
297
+ // contributor only when its name is clipped.
298
+ var tooltipContent = isTruncated || isConnectedPair ? fullLabel : null;
299
+ return /*#__PURE__*/React.createElement("span", {
300
+ style: {
301
+ // Clamped to the highlight's own width, floored at `MIN_TAG_WIDTH` so a one-character
302
+ // change still shows an avatar.
303
+ maxWidth: maxWidthPx === undefined ? undefined : "max(".concat(Math.round(maxWidthPx), "px, ").concat(MIN_TAG_WIDTH, ")")
304
+ },
305
+ className: ax([styles.constraint])
306
+ }, /*#__PURE__*/React.createElement(Tooltip, {
307
+ content: tooltipContent
308
+ // A tooltip below would cover the highlighted text the tag describes.
309
+ ,
310
+ position: "top"
311
+ // The label is already exposed by the visually hidden text below.
312
+ ,
313
+ isScreenReaderAnnouncementDisabled: true
314
+ }, function (tooltipProps) {
315
+ return /*#__PURE__*/React.createElement("span", _extends({}, tooltipProps, {
316
+ "data-testid": "diff-contributor-tag"
317
+ // Deliberately no `aria-label`: it would be announced ahead of the element's
318
+ // contents, but design requires the contributor *after* the change — hence the
319
+ // visually hidden last child.
320
+ ,
321
+ role: "note"
322
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex, @atlassian/a11y/no-noninteractive-tabindex
323
+ ,
324
+ tabIndex: 0,
325
+ style: {
326
+ backgroundColor: accent.background,
327
+ borderBottomColor: accent.border
328
+ },
329
+ onMouseOver: function onMouseOver(event) {
330
+ tooltipProps.onMouseOver(event);
331
+ measureTruncation();
332
+ setIsHoveredOrFocused(true);
333
+ },
334
+ onMouseOut: function onMouseOut(event) {
335
+ tooltipProps.onMouseOut(event);
336
+ setIsHoveredOrFocused(false);
337
+ },
338
+ onFocus: function onFocus(event) {
339
+ tooltipProps.onFocus(event);
340
+ measureTruncation();
341
+ setIsHoveredOrFocused(true);
342
+ },
343
+ onBlur: function onBlur(event) {
344
+ tooltipProps.onBlur(event);
345
+ setIsHoveredOrFocused(false);
346
+ },
347
+ className: ax([styles.tag, isVisible ? styles.visible : styles.hidden])
348
+ }), /*#__PURE__*/React.createElement("span", {
349
+ className: ax([styles.avatars])
350
+ }, isConnectedPair && userContributor ? /*#__PURE__*/React.createElement("span", {
351
+ className: ax([styles.leadingAvatar])
352
+ }, /*#__PURE__*/React.createElement(ContributorAvatar, {
353
+ contributor: userContributor,
354
+ name: userName,
355
+ ringColor: accent.background,
356
+ stackIndex: 1
357
+ })) : null, isConnectedPair && agentContributor ? /*#__PURE__*/React.createElement(ContributorAvatar, {
358
+ contributor: agentContributor,
359
+ name: agentName,
360
+ ringColor: accent.background,
361
+ stackIndex: 0
362
+ }) : null, !isConnectedPair ? /*#__PURE__*/React.createElement(ContributorAvatar, {
363
+ contributor: contributor,
364
+ name: primaryName,
365
+ ringColor: accent.background
366
+ }) : null), /*#__PURE__*/React.createElement("span", {
367
+ "data-testid": "diff-contributor-tag-name",
368
+ ref: nameRef,
369
+ style: {
370
+ color: accent.text
371
+ },
372
+ className: ax([styles.name])
373
+ }, displayName), /*#__PURE__*/React.createElement(VisuallyHidden, null, fullLabel));
374
+ }));
375
+ }
@@ -0,0 +1,43 @@
1
+ /* ContributorTagWidget.tsx generated by @compiled/babel-plugin v2.0.0 */
2
+ import * as React from 'react';
3
+ import { ax, ix } from "@compiled/react/runtime";
4
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
5
+ import { ContributorTag } from './ContributorTag';
6
+
7
+ /**
8
+ * One contributor tag, mounted by its diff decoration into the host element that decoration draws
9
+ * (see `mountContributorTag`).
10
+ *
11
+ * The model is read from plugin state here rather than passed in, because ProseMirror skips `toDOM`
12
+ * for a widget it reuses: stepping to the next change republishes the tags without redrawing their
13
+ * hosts, so anything the tag needs to react to — `isActive` above all — has to arrive through a
14
+ * subscription of its own. Renders nothing when this diff has no tag, which is the case for the
15
+ * deleted half of a replacement: that half is folded into the inline tag.
16
+ */
17
+ export var ContributorTagWidget = function ContributorTagWidget(_ref) {
18
+ var api = _ref.api,
19
+ diffId = _ref.diffId,
20
+ getEditorRoot = _ref.getEditorRoot;
21
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['showDiff'], function (state) {
22
+ var _state$showDiffState;
23
+ return {
24
+ model: (_state$showDiffState = state.showDiffState) === null || _state$showDiffState === void 0 || (_state$showDiffState = _state$showDiffState.contributorTags) === null || _state$showDiffState === void 0 ? void 0 : _state$showDiffState.find(function (tag) {
25
+ return tag.diffId === diffId;
26
+ })
27
+ };
28
+ }),
29
+ model = _useSharedPluginState.model;
30
+ if (!model) {
31
+ return null;
32
+ }
33
+ return /*#__PURE__*/React.createElement(ContributorTag, {
34
+ colorScheme: model.colorScheme,
35
+ connectedContributor: model.connectedContributor,
36
+ contributor: model.contributor,
37
+ diffId: model.diffId,
38
+ getEditorRoot: getEditorRoot,
39
+ isActive: model.isActive,
40
+ isInserted: model.isInserted,
41
+ linkedDiffIds: model.linkedDiffIds
42
+ });
43
+ };
@@ -0,0 +1,18 @@
1
+ import { defineMessages } from 'react-intl';
2
+ export var contributorTagMessages = defineMessages({
3
+ changedBy: {
4
+ id: 'editor-plugin-show-diff.ContributorTag.changedBy',
5
+ defaultMessage: 'Changed by {name}',
6
+ description: 'Screen-reader label and tooltip for the tag pinned to a highlighted change in the version-history diff, naming the single person or agent that made the change. The name placeholder is the contributor display name.'
7
+ },
8
+ changedByConnected: {
9
+ id: 'editor-plugin-show-diff.ContributorTag.changedByConnected',
10
+ defaultMessage: 'Changed by {agentName} with {userName}',
11
+ description: 'Screen-reader label and tooltip for the tag pinned to a highlighted change in the version-history diff when an AI agent made the change on behalf of a user. The agentName placeholder is the agent display name. The userName placeholder is the display name of the user that invoked the agent.'
12
+ },
13
+ externalAgentName: {
14
+ id: 'editor-plugin-show-diff.ContributorTag.externalAgentName',
15
+ defaultMessage: 'External agent',
16
+ description: 'Fallback display name shown in the contributor tag of the version-history diff for an unidentified third-party agent (for example an MCP or CLI client) that has no name of its own.'
17
+ }
18
+ });
@@ -1 +1 @@
1
- export type { DeletedDiffPlacement, DeletedDiffWidget, DiffParams, DiffStepAttribution, DiffType, PMDiffParams, ShowDiffParams, ShowDiffPlugin, SmartDiffThresholds, StepWithAttribution, } from '../showDiffPluginType';
1
+ export type { DeletedDiffPlacement, DeletedDiffWidget, DiffContributorProfile, DiffParams, DiffStepAttribution, DiffType, PMDiffParams, ShowDiffParams, ShowDiffPlugin, SmartDiffThresholds, StepWithAttribution, } from '../showDiffPluginType';
@@ -4,10 +4,13 @@ import type { IntlShape } from 'react-intl';
4
4
  import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
5
5
  import type { EditorState } from '@atlaskit/editor-prosemirror/state';
6
6
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
- import type { ColorScheme, DeletedDiffPlacement, DiffDescriptor, InlineDeletedDiffPlacement, ShowDiffPlugin, SmartDiffThresholds } from '../../showDiffPluginType';
7
+ import type { ColorScheme, ContributorTagModel, DeletedDiffPlacement, DiffDescriptor, DiffType, InlineDeletedDiffPlacement, ShowDiffPlugin, SmartDiffThresholds } from '../../showDiffPluginType';
8
+ import type { ContributorTagMountContext } from '../decorations/createContributorTagWidget';
8
9
  import type { ShowDiffPluginState } from '../main';
9
10
  import type { NodeViewSerializer } from '../NodeViewSerializer';
10
11
  type CalculatedDiffs = {
12
+ /** Resolved here, not in React, so the attribution keys and contributor lookup stay internal. */
13
+ contributorTags: ContributorTagModel[];
11
14
  decorations: DecorationSet;
12
15
  diffDescriptors: DiffDescriptor[];
13
16
  };
@@ -35,14 +38,17 @@ export declare const calculateDiffDecorations: MemoizedFn<({ state, pluginState,
35
38
  api: ExtractInjectionAPI<ShowDiffPlugin> | undefined;
36
39
  colorScheme?: ColorScheme;
37
40
  deletedDiffPlacement?: DeletedDiffPlacement;
41
+ diffType?: DiffType;
38
42
  hideAddedDiffsUnderline?: boolean;
39
43
  hideDeletedDiffs?: boolean;
40
44
  inlineDeletedDiffPlacement?: InlineDeletedDiffPlacement;
41
45
  intl: IntlShape;
46
+ isInverted?: boolean;
42
47
  nodeViewSerializer: NodeViewSerializer;
43
48
  pluginState: Omit<ShowDiffPluginState, 'decorations'>;
44
49
  showIndicators?: boolean;
45
50
  smartThresholds?: Partial<SmartDiffThresholds>;
46
51
  state: EditorState;
52
+ tagMountContext?: ContributorTagMountContext;
47
53
  }) => CalculatedDiffs>;
48
54
  export {};
@@ -1,5 +1,15 @@
1
1
  import { type Change } from 'prosemirror-changeset';
2
2
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
5
+ * independently, so runs either side of an unattributed step can come back covering the same
6
+ * characters — which stacks two inline decorations and renders one contributor tag per copy.
7
+ *
8
+ * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
9
+ * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
10
+ * is not credited to it.
11
+ */
12
+ export declare const collapseOverlappingChanges: (changes: Change[]) => Change[];
3
13
  /**
4
14
  * Simplifies and optimizes consecutive changes without crossing an attribution boundary.
5
15
  * A change which already contains more than one identity is deliberately kept isolated.
@@ -1,12 +1,34 @@
1
1
  import type { Change } from 'prosemirror-changeset';
2
- import type { ColorScheme as PublicColorScheme, DiffStepAttribution } from '../../../showDiffPluginType';
3
- import { type ColorScheme, type DiffParticipantColorScheme } from './types';
2
+ import type { DiffContributors, DiffStepAttribution, TagContributor } from '../../../showDiffPluginType';
3
+ import { type ColorScheme, type DiffParticipantColorScheme, type PublicColorScheme } from './types';
4
4
  export type DiffAttributionSpanData = DiffStepAttribution & {
5
5
  stepIndex: number;
6
6
  };
7
+ /** A contributor with both of its identities collapsed to internal attribution keys. */
8
+ export type ResolvedDiffContributor = TagContributor & {
9
+ connectedToKey?: string;
10
+ };
11
+ /** Contributors addressed by attribution key. The key format is internal. */
12
+ export type ResolvedDiffContributors = Record<string, ResolvedDiffContributor>;
7
13
  /** `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared users. */
8
14
  export declare const getAttributionKey: (attribution: DiffStepAttribution | undefined) => string | undefined;
15
+ /**
16
+ * Public contributor list to internal key-addressed record. A contributor whose attribution carries
17
+ * no identity is dropped, since it could never match a step. Later entries win.
18
+ */
19
+ export declare const resolveContributors: (contributors: DiffContributors | undefined) => ResolvedDiffContributors | undefined;
9
20
  export declare const areAttributionColorGatesEnabled: () => boolean;
10
21
  export declare const isAttributionColoringEnabled: (stepAttributions: Array<DiffStepAttribution | undefined> | undefined) => boolean;
22
+ /**
23
+ * Shares the colours' gates, but with a lower bar: colouring needs two actors to tell apart, whereas
24
+ * a tag only has to name one.
25
+ */
26
+ export declare const isContributorTagsEnabled: (stepAttributions: Array<DiffStepAttribution | undefined> | undefined, contributors: ResolvedDiffContributors | undefined) => boolean;
11
27
  export declare const createAttributionColorMap: (stepAttributions: Array<DiffStepAttribution | undefined>) => Map<string, DiffParticipantColorScheme>;
28
+ /**
29
+ * The attribution key owning a change. Inline and step changes retain direct attribution spans, so
30
+ * resolve those first. Grouped and smart-classified changes can lose that data; for those, fall
31
+ * back to the latest overlapping attributed step.
32
+ */
33
+ export declare const getAttributionKeyForChange: (change: Change, attributedChanges: Change[]) => string | undefined;
12
34
  export declare const getColorSchemeForChange: (change: Change, attributedChanges: Change[], attributionColors: Map<string, DiffParticipantColorScheme>, fallback: PublicColorScheme | undefined) => ColorScheme | undefined;
@@ -1,4 +1,10 @@
1
- import { type DiffColorScheme } from './types';
1
+ import { type DiffColorScheme, type DiffSchemeColor } from './types';
2
+ /** Presentation tokens for one diff colour, shared by highlights and contributor tags. */
3
+ export declare function getAccentTokens(color: DiffSchemeColor): {
4
+ background: string;
5
+ border: string;
6
+ text: string;
7
+ };
2
8
  /** Inline inserted content — default state. */
3
9
  export declare function buildInsertStyle(colors: DiffColorScheme): string;
4
10
  /** Inline insert style for content within a block node (no background, underline only). */
@@ -1,5 +1,4 @@
1
- import type { ColorScheme as PublicColorScheme } from '../../../showDiffPluginType';
2
- import type { ColorScheme, DiffColorScheme } from './types';
1
+ import type { ColorScheme, DiffColorScheme, PublicColorScheme } from './types';
3
2
  /** Green insertions, red deletions. */
4
3
  export declare const traditionalScheme: DiffColorScheme;
5
4
  /** Purple insertions, gray deletions (red on active). */
@@ -1,4 +1,5 @@
1
- import type { ColorScheme as PublicColorScheme } from '../../../showDiffPluginType';
1
+ /** Schemes consumers may explicitly request through the show-diff API. */
2
+ export type PublicColorScheme = 'standard' | 'traditional';
2
3
  /** Attribution-only participant slots selected by the plugin, never by consumers. */
3
4
  export declare const PARTICIPANT_COLOR_SCHEMES: readonly ['red-bolder', 'blue-bolder', 'green-bolder', 'yellow-bolder', 'purple-bolder', 'magenta-bolder', 'teal-bolder', 'orange-bolder', 'lime-bolder', 'gray-bolder', 'blue-subtle', 'red-subtle', 'orange-subtle', 'yellow-subtle', 'green-subtle', 'teal-subtle', 'purple-subtle', 'magenta-subtle'];
4
5
  export type DiffParticipantColorScheme = (typeof PARTICIPANT_COLOR_SCHEMES)[number];
@@ -38,6 +38,20 @@ export declare const createBlockIndicatorAnchorWidgets: ({ doc, from, to, diffId
38
38
  from: number;
39
39
  to: number;
40
40
  }) => Decoration[];
41
+ /**
42
+ * A `from`/`to` on a `tableRow` boundary makes the anchor a direct `<tr>` (CSS grid)
43
+ * child, adding a phantom column that collapses the cells (EDITOR-8442). Clamp it
44
+ * inward into the neighbouring cell (`direction: 1` forward, `-1` back). Positions
45
+ * not on a row boundary are returned as-is.
46
+ */
47
+ export declare const clampAnchorPosIntoCell: (doc: PMNode, pos: number, direction: 1 | -1) => number;
48
+ /**
49
+ * A zero-size inline span carrying nothing but the `anchor-name` an overlay aligns against. A
50
+ * fragmented inline element reports the union of its line fragments, so aligning against one
51
+ * horizontally gives the enclosing block's content edge; a zero-size span is always a single
52
+ * fragment.
53
+ */
54
+ export declare const createAnchorNameSpan: (anchorKey: string) => HTMLSpanElement;
41
55
  /**
42
56
  * Invisible `from`/`to` (and optional `left`) anchor widgets for one inline diff
43
57
  * range, so the `IndicatorBar` can align itself via CSS anchor positioning.
@@ -0,0 +1,55 @@
1
+ import type { PortalProviderAPI } from '@atlaskit/editor-common/portal';
2
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
3
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
+ import type { ShowDiffPlugin } from '../../showDiffPluginType';
6
+ /** Marks the host element of one tag, so a tag can be found in the document by its own diff. */
7
+ export declare const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = "data-contributor-tag-host";
8
+ /**
9
+ * Everything a tag needs to mount itself into the document, plumbed down from the plugin. Absent in
10
+ * unit tests that build decorations directly: the hosts are still emitted, they just stay empty.
11
+ */
12
+ export type ContributorTagMountContext = {
13
+ api?: ExtractInjectionAPI<ShowDiffPlugin>;
14
+ /** This editor's own content root — see `ContributorTagProps.getEditorRoot`. */
15
+ getEditorRoot: () => HTMLElement | null;
16
+ portalProviderAPI: PortalProviderAPI;
17
+ };
18
+ /** A mounted tag, so whoever emitted the host can take it down again. */
19
+ export type ContributorTagMount = {
20
+ key: string;
21
+ portalProviderAPI: PortalProviderAPI;
22
+ };
23
+ /**
24
+ * Renders the tag into `host` through the editor's portal provider, the way the rest of the editor
25
+ * mounts React into decorations. The tag resolves its own model from plugin state, so a redraw of
26
+ * the host re-parents the tag instead of stranding it, and `isActive` changes arrive as a re-render.
27
+ */
28
+ export declare const mountContributorTag: ({ diffId, host, mountContext, }: {
29
+ diffId: string;
30
+ host: HTMLElement;
31
+ mountContext: ContributorTagMountContext | undefined;
32
+ }) => ContributorTagMount | undefined;
33
+ export declare const unmountContributorTag: (mount: ContributorTagMount | undefined) => void;
34
+ /**
35
+ * The host for a tag on deleted content, which renders inside its own widget decoration rather than
36
+ * needing one of its own. Unmount with `unmountContributorTag` from that widget's `destroy`.
37
+ */
38
+ export declare const createContributorTagHost: (diffId: string, mountContext?: ContributorTagMountContext) => {
39
+ host: HTMLSpanElement;
40
+ mount: ContributorTagMount | undefined;
41
+ } | undefined;
42
+ /**
43
+ * A widget hosting the contributor tag of one inline diff range, placed on the first character the
44
+ * range highlights.
45
+ *
46
+ * The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
47
+ * element ProseMirror drew it into — see `mountContributorTag`.
48
+ */
49
+ export declare const createContributorTagWidget: ({ doc, from, to, diffId, mountContext, }: {
50
+ diffId: string;
51
+ doc: PMNode;
52
+ from: number;
53
+ mountContext?: ContributorTagMountContext;
54
+ to: number;
55
+ }) => Decoration | undefined;