@atlaskit/editor-plugin-show-diff 14.5.2 → 15.0.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 (74) hide show
  1. package/CHANGELOG.md +28 -6
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +3 -9
  3. package/dist/cjs/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  4. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +21 -4
  5. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +6 -56
  6. package/dist/cjs/pm-plugins/decorations/colorSchemes/schemes.js +6 -20
  7. package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +4 -6
  8. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  9. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +14 -29
  10. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  11. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -25
  12. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  13. package/dist/cjs/pm-plugins/main.js +4 -4
  14. package/dist/cjs/showDiffPlugin.js +4 -5
  15. package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +122 -0
  16. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +140 -0
  17. package/dist/cjs/ui/ContributorTag/contributorTagController.js +500 -0
  18. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +46 -0
  19. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +3 -5
  20. package/dist/es2019/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  21. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +20 -3
  22. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +6 -55
  23. package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +14 -26
  24. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +4 -6
  25. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  26. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +16 -30
  27. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  28. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -17
  29. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  30. package/dist/es2019/pm-plugins/main.js +4 -4
  31. package/dist/es2019/showDiffPlugin.js +4 -5
  32. package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +110 -0
  33. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +136 -0
  34. package/dist/es2019/ui/ContributorTag/contributorTagController.js +403 -0
  35. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +40 -0
  36. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +3 -9
  37. package/dist/esm/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  38. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +21 -4
  39. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +6 -57
  40. package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +7 -20
  41. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +4 -6
  42. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  43. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +15 -30
  44. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  45. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -25
  46. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  47. package/dist/esm/pm-plugins/main.js +4 -4
  48. package/dist/esm/showDiffPlugin.js +4 -5
  49. package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +115 -0
  50. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +135 -0
  51. package/dist/esm/ui/ContributorTag/contributorTagController.js +493 -0
  52. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +40 -0
  53. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +3 -3
  54. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +2 -2
  55. package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +1 -1
  56. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +13 -16
  57. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +11 -9
  58. package/dist/types/pm-plugins/main.d.ts +1 -2
  59. package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +25 -0
  60. package/dist/types/ui/ContributorTag/contributorAvatarRenderer.d.ts +22 -0
  61. package/dist/types/ui/ContributorTag/contributorTagController.d.ts +85 -0
  62. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +12 -0
  63. package/package.json +9 -13
  64. package/dist/cjs/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  65. package/dist/cjs/ui/ContributorTag/ContributorTag.js +0 -383
  66. package/dist/cjs/ui/ContributorTag/ContributorTagWidget.js +0 -50
  67. package/dist/es2019/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  68. package/dist/es2019/ui/ContributorTag/ContributorTag.js +0 -312
  69. package/dist/es2019/ui/ContributorTag/ContributorTagWidget.js +0 -43
  70. package/dist/esm/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  71. package/dist/esm/ui/ContributorTag/ContributorTag.js +0 -375
  72. package/dist/esm/ui/ContributorTag/ContributorTagWidget.js +0 -43
  73. package/dist/types/ui/ContributorTag/ContributorTag.d.ts +0 -37
  74. package/dist/types/ui/ContributorTag/ContributorTagWidget.d.ts +0 -22
@@ -0,0 +1,403 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { bindAll } from 'bind-event-listener';
3
+ import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
4
+ import { getAccentTokens } from '../../pm-plugins/decorations/colorSchemes/factory';
5
+ import { colorSchemeRegistry } from '../../pm-plugins/decorations/colorSchemes/schemes';
6
+ import { buildContributorTagDom, MIN_TAG_WIDTH } from './buildContributorTagDom';
7
+ import { contributorAvatarRenderer } from './contributorAvatarRenderer';
8
+ import { contributorTagMessages } from './messages';
9
+ const getContributorName = (contributor, formatMessage) => {
10
+ var _contributor$name, _contributor$agentKin;
11
+ const name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
12
+ if (name) {
13
+ return name;
14
+ }
15
+
16
+ // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
17
+ // agent falls back to a generic label rather than rendering a nameless tag.
18
+ if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
19
+ return formatMessage(contributorTagMessages.externalAgentName);
20
+ }
21
+ return '';
22
+ };
23
+
24
+ /**
25
+ * Resolves the tag's accent from the same scheme value and token maps the highlight used, so the
26
+ * two can never drift.
27
+ */
28
+ const getTagAccent = ({
29
+ colorScheme,
30
+ isActive,
31
+ isInserted
32
+ }) => {
33
+ const colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
34
+ const accent = isInserted ? isActive ? colors.insertActiveColor : colors.insertColor : isActive ? colors.deleteActiveColor : colors.deleteColor;
35
+ return getAccentTokens(accent);
36
+ };
37
+
38
+ /** The line the change starts on: topmost of its fragments, leftmost among those. */
39
+ const findChangeStartLine = highlights => {
40
+ let start;
41
+ for (const highlight of highlights) {
42
+ for (const rect of highlight.getClientRects()) {
43
+ if (!start || rect.top < start.top - 1 || Math.abs(rect.top - start.top) <= 1 && rect.left < start.left) {
44
+ start = rect;
45
+ }
46
+ }
47
+ }
48
+ return start;
49
+ };
50
+
51
+ /** The contributors a tag draws, keyed so a republished model can be recognised as the same pair. */
52
+ const identityOf = model => [model === null || model === void 0 ? void 0 : model.contributor, model === null || model === void 0 ? void 0 : model.connectedContributor].map(contributor => {
53
+ var _contributor$agentKin2, _contributor$avatarUr;
54
+ return contributor ? `${contributor.kind}:${(_contributor$agentKin2 = contributor.agentKind) !== null && _contributor$agentKin2 !== void 0 ? _contributor$agentKin2 : ''}:${contributor.name}:${(_contributor$avatarUr = contributor.avatarUrl) !== null && _contributor$avatarUr !== void 0 ? _contributor$avatarUr : ''}` : '';
55
+ }).join('|');
56
+
57
+ /**
58
+ * A small tag naming who made one diff, drawn into the host element its decoration places on the
59
+ * change's first character and positioned above it.
60
+ *
61
+ * The model is resolved from plugin state here rather than passed in, because ProseMirror skips
62
+ * `toDOM` for a widget it reuses: stepping to the next change republishes the tags without redrawing
63
+ * their hosts, so anything the tag reacts to — `isActive` above all — has to arrive through a
64
+ * subscription of its own (EDITOR-8702). Draws nothing when this diff has no tag, which is the case
65
+ * for the deleted half of a replacement: that half is folded into the inline tag.
66
+ */
67
+ export class ContributorTagController {
68
+ constructor(options) {
69
+ _defineProperty(this, "avatars", []);
70
+ /** Guards a deferred bind that a later model change has already superseded. */
71
+ _defineProperty(this, "bindToken", 0);
72
+ _defineProperty(this, "fullLabel", '');
73
+ _defineProperty(this, "isDestroyed", false);
74
+ _defineProperty(this, "isHighlightHovered", false);
75
+ _defineProperty(this, "isHoveredOrFocused", false);
76
+ _defineProperty(this, "unbindHighlights", []);
77
+ this.options = options;
78
+ }
79
+ mount(host) {
80
+ var _this$options$api$sho, _this$options$api, _sharedState$currentS;
81
+ this.host = host;
82
+ const {
83
+ sharedState
84
+ } = (_this$options$api$sho = (_this$options$api = this.options.api) === null || _this$options$api === void 0 ? void 0 : _this$options$api.showDiff) !== null && _this$options$api$sho !== void 0 ? _this$options$api$sho : {};
85
+ this.unsubscribe = sharedState === null || sharedState === void 0 ? void 0 : sharedState.onChange(({
86
+ nextSharedState
87
+ }) => {
88
+ this.applyModel(this.findModel(nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.contributorTags));
89
+ });
90
+ this.applyModel(this.findModel(sharedState === null || sharedState === void 0 ? void 0 : (_sharedState$currentS = sharedState.currentState()) === null || _sharedState$currentS === void 0 ? void 0 : _sharedState$currentS.contributorTags));
91
+ }
92
+ destroy() {
93
+ var _this$unsubscribe;
94
+ this.isDestroyed = true;
95
+ (_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 ? void 0 : _this$unsubscribe.call(this);
96
+ this.unsubscribe = undefined;
97
+ this.teardownTag();
98
+ this.host = undefined;
99
+ }
100
+ findModel(tags) {
101
+ return tags === null || tags === void 0 ? void 0 : tags.find(tag => tag.diffId === this.options.diffId);
102
+ }
103
+ applyModel(next) {
104
+ if (this.isDestroyed || next === this.model) {
105
+ return;
106
+ }
107
+ const previous = this.model;
108
+ this.model = next;
109
+ const {
110
+ host
111
+ } = this;
112
+ if (!next || !host) {
113
+ this.teardownTag();
114
+ return;
115
+ }
116
+ if (!this.dom) {
117
+ this.dom = buildContributorTagDom(host.ownerDocument);
118
+ this.bindTag(this.dom);
119
+ host.appendChild(this.dom.root);
120
+ }
121
+ if (!previous || identityOf(previous) !== identityOf(next)) {
122
+ this.renderAvatars(next);
123
+ }
124
+ this.renderLabels(next);
125
+ this.applyAccent(next);
126
+ this.syncHighlightBindings(next);
127
+ this.applyStyles();
128
+ this.syncTooltip();
129
+ }
130
+
131
+ /** Takes the tag's DOM down, keeping the subscription so a republished model redraws it. */
132
+ teardownTag() {
133
+ var _this$unbindTag, _this$tooltip, _this$dom;
134
+ this.unbindHighlightState();
135
+ this.boundSelector = undefined;
136
+ (_this$unbindTag = this.unbindTag) === null || _this$unbindTag === void 0 ? void 0 : _this$unbindTag.call(this);
137
+ this.unbindTag = undefined;
138
+ (_this$tooltip = this.tooltip) === null || _this$tooltip === void 0 ? void 0 : _this$tooltip.destroy();
139
+ this.tooltip = undefined;
140
+ this.tooltipContent = undefined;
141
+ this.avatars.forEach(avatar => avatar.destroy());
142
+ this.avatars = [];
143
+ (_this$dom = this.dom) === null || _this$dom === void 0 ? void 0 : _this$dom.root.remove();
144
+ this.dom = undefined;
145
+ this.maxWidthPx = undefined;
146
+ this.isHoveredOrFocused = false;
147
+ // Both hover flags belong to the listeners just unbound: a republished model rebinds them, and
148
+ // stale `true` would draw the tag as visible with the pointer nowhere near it.
149
+ this.isHighlightHovered = false;
150
+ }
151
+ bindTag({
152
+ tag
153
+ }) {
154
+ const setHoveredOrFocused = isHoveredOrFocused => () => {
155
+ this.isHoveredOrFocused = isHoveredOrFocused;
156
+ this.applyStyles();
157
+ };
158
+ this.unbindTag = bindAll(tag, [{
159
+ type: 'mouseover',
160
+ listener: setHoveredOrFocused(true)
161
+ }, {
162
+ type: 'mouseout',
163
+ listener: setHoveredOrFocused(false)
164
+ }, {
165
+ type: 'focus',
166
+ listener: setHoveredOrFocused(true)
167
+ }, {
168
+ type: 'blur',
169
+ listener: setHoveredOrFocused(false)
170
+ }]);
171
+ }
172
+ renderAvatars(model) {
173
+ var _this$dom2;
174
+ const {
175
+ avatars: container
176
+ } = (_this$dom2 = this.dom) !== null && _this$dom2 !== void 0 ? _this$dom2 : {};
177
+ if (!container) {
178
+ return;
179
+ }
180
+ this.avatars.forEach(avatar => avatar.destroy());
181
+ this.avatars = [];
182
+ const doc = container.ownerDocument;
183
+ const {
184
+ background
185
+ } = this.accentOf(model);
186
+ const isPrimaryAgent = model.contributor.kind === 'agent';
187
+ // Resolved by `kind` so a reverse `connectedTo` (an attributed user pointing at its agent)
188
+ // renders identically.
189
+ const user = isPrimaryAgent ? model.connectedContributor : model.contributor;
190
+ const agent = isPrimaryAgent ? model.contributor : model.connectedContributor;
191
+
192
+ // Hand-rolled avatar stack: `@atlaskit/avatar-group` cannot render below 24px and the tag uses
193
+ // 16px avatars.
194
+ const stack = model.connectedContributor ? [...(user ? [{
195
+ contributor: user,
196
+ stackIndex: 1
197
+ }] : []), ...(agent ? [{
198
+ contributor: agent,
199
+ stackIndex: 0
200
+ }] : [])] : [{
201
+ contributor: model.contributor
202
+ }];
203
+ stack.forEach(({
204
+ contributor,
205
+ stackIndex
206
+ }, index) => {
207
+ const avatar = contributorAvatarRenderer({
208
+ contributor,
209
+ doc,
210
+ ringColor: background,
211
+ stackIndex
212
+ });
213
+ if (index < stack.length - 1) {
214
+ // Overlaps the leading avatar of a connected pair onto the one it sits in front of.
215
+ avatar.element.style.setProperty('margin-inline-end', "var(--ds-space-negative-050, -4px)");
216
+ }
217
+ container.appendChild(avatar.element);
218
+ this.avatars.push(avatar);
219
+ });
220
+ }
221
+ renderLabels(model) {
222
+ if (!this.dom) {
223
+ return;
224
+ }
225
+ const {
226
+ formatMessage
227
+ } = this.options.getIntl();
228
+ const isPrimaryAgent = model.contributor.kind === 'agent';
229
+ const primaryName = getContributorName(model.contributor, formatMessage);
230
+ const secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
231
+ const agentName = isPrimaryAgent ? primaryName : secondaryName;
232
+ const userName = isPrimaryAgent ? secondaryName : primaryName;
233
+
234
+ // A connected pair only ever shows the agent; the pair is spelled out in the label, because
235
+ // the tag is never wider than the highlight.
236
+ this.dom.name.textContent = model.connectedContributor ? agentName || primaryName : primaryName;
237
+ this.fullLabel = model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
238
+ agentName,
239
+ userName
240
+ }) : formatMessage(contributorTagMessages.changedBy, {
241
+ name: primaryName
242
+ });
243
+ this.dom.srLabel.textContent = this.fullLabel;
244
+ }
245
+ accentOf(model) {
246
+ var _model$isInserted;
247
+ return getTagAccent({
248
+ colorScheme: model.colorScheme,
249
+ isActive: Boolean(model.isActive),
250
+ isInserted: (_model$isInserted = model.isInserted) !== null && _model$isInserted !== void 0 ? _model$isInserted : true
251
+ });
252
+ }
253
+ applyAccent(model) {
254
+ if (!this.dom) {
255
+ return;
256
+ }
257
+ const accent = this.accentOf(model);
258
+ this.dom.tag.style.setProperty('background-color', accent.background);
259
+ this.dom.tag.style.setProperty('border-bottom-color', accent.border);
260
+ this.dom.name.style.setProperty('color', accent.text);
261
+ this.avatars.forEach(avatar => avatar.setRingColor(accent.background));
262
+ }
263
+
264
+ /** Width clamp and visibility — the two things that are not carried by the model alone. */
265
+ applyStyles() {
266
+ var _this$model;
267
+ if (!this.dom) {
268
+ return;
269
+ }
270
+ this.dom.root.style.setProperty('max-width',
271
+ // Clamped to the highlight's own width, floored at `MIN_TAG_WIDTH` so a one-character
272
+ // change still shows an avatar.
273
+ this.maxWidthPx === undefined ? '' : `max(${Math.round(this.maxWidthPx)}px, ${MIN_TAG_WIDTH})`);
274
+ const isVisible = Boolean((_this$model = this.model) === null || _this$model === void 0 ? void 0 : _this$model.isActive) || this.isHighlightHovered || this.isHoveredOrFocused;
275
+ this.dom.tag.style.setProperty('opacity', isVisible ? '1' : '0');
276
+ this.dom.tag.style.setProperty('pointer-events', isVisible ? 'auto' : 'none');
277
+ }
278
+
279
+ /**
280
+ * The full label is always the tooltip, whether or not the name it draws is clipped: the tag is
281
+ * never wider than the highlight, so a name that fits at one width is ellipsised at the next, and
282
+ * a connected pair spells out a contributor the tag does not show at any width.
283
+ *
284
+ * `VanillaTooltip` has no "set new content" call, so it is rebuilt when the label changes — the
285
+ * shape of `syncVanillaDisabledTooltip` in `mentionNodeView`.
286
+ */
287
+ syncTooltip() {
288
+ var _this$tooltip2;
289
+ const content = this.dom ? this.fullLabel : undefined;
290
+ if (content === this.tooltipContent) {
291
+ return;
292
+ }
293
+ (_this$tooltip2 = this.tooltip) === null || _this$tooltip2 === void 0 ? void 0 : _this$tooltip2.destroy();
294
+ this.tooltip = undefined;
295
+ this.tooltipContent = content;
296
+ if (!content || !this.dom) {
297
+ return;
298
+ }
299
+ this.tooltip = new VanillaTooltip(this.dom.tag, content, undefined,
300
+ // `ak-editor-vanilla-tooltip-default` opts into the shared `VanillaTooltip` look, defined as
301
+ // `vanillaTooltipDefaultStyles` in both EditorContentContainer stylesheets — keep in sync when
302
+ // renaming. Those styles are scoped under `.ProseMirror`, and the tag renders inside it.
303
+ 'ak-editor-vanilla-tooltip-default');
304
+ // `VanillaTooltip` sets `aria-describedby` on its trigger, which would announce the label a
305
+ // second time — the visually hidden child already announces it, after the change as design
306
+ // requires.
307
+ this.dom.tag.removeAttribute('aria-describedby');
308
+ }
309
+
310
+ /**
311
+ * Observes the highlight this tag describes, for what CSS here cannot see: its hover state (the
312
+ * highlight is a ProseMirror decoration in a different DOM subtree, so listeners are bound
313
+ * imperatively via `data-diff-id`) and the width the tag is clamped to.
314
+ *
315
+ * Deliberately not routed through plugin state: a transaction per pointer move would re-run the
316
+ * memoised decoration calculation.
317
+ */
318
+ syncHighlightBindings(model) {
319
+ var _model$linkedDiffIds;
320
+ const {
321
+ diffId
322
+ } = this.options;
323
+ const selector = [diffId, ...((_model$linkedDiffIds = model.linkedDiffIds) !== null && _model$linkedDiffIds !== void 0 ? _model$linkedDiffIds : [])].map(id => `[data-diff-id="${id}"]`).join(', ');
324
+ if (selector === this.boundSelector) {
325
+ return;
326
+ }
327
+ this.boundSelector = selector;
328
+ this.unbindHighlightState();
329
+
330
+ // ProseMirror draws the tag's host in the same pass as the highlights the tag measures, so
331
+ // those may not be in the document yet. A microtask lands once that pass has finished.
332
+ const pendingBind = ++this.bindToken;
333
+ queueMicrotask(() => {
334
+ if (!this.isDestroyed && pendingBind === this.bindToken) {
335
+ this.bindHighlightState(selector);
336
+ }
337
+ });
338
+ }
339
+ bindHighlightState(selector) {
340
+ // Both queries are scoped to this editor's content root, so a tag can only ever bind to the
341
+ // decorations its own plugin instance rendered — another editor on the page, or one nested
342
+ // inside this one, cannot supply the geometry.
343
+ const editorRoot = this.options.getEditorRoot();
344
+ const highlights = editorRoot === null || editorRoot === void 0 ? void 0 : editorRoot.querySelectorAll(selector);
345
+ if (!editorRoot || !(highlights !== null && highlights !== void 0 && highlights.length)) {
346
+ return;
347
+ }
348
+
349
+ // The width comes from this tag's OWN decoration; sibling halves of the change are here for
350
+ // hover only.
351
+ const ownElements = Array.from(editorRoot.querySelectorAll(`[data-diff-id="${this.options.diffId}"]`));
352
+ const measure = () => {
353
+ var _findChangeStartLine;
354
+ // Counted by distinct line box, not by client rect: an inline node inside the change (a
355
+ // mention, emoji, date) splits the highlight into several rects on the same line, and
356
+ // treating that as a wrap would drop the width clamp on changes that never wrapped.
357
+ const lineTops = new Set();
358
+ for (const highlight of ownElements) {
359
+ for (const rect of highlight.getClientRects()) {
360
+ lineTops.add(Math.round(rect.top));
361
+ }
362
+ }
363
+
364
+ // A wrapped change's first line says nothing useful about how wide the tag may be.
365
+ this.maxWidthPx = lineTops.size > 1 ? undefined : (_findChangeStartLine = findChangeStartLine(ownElements)) === null || _findChangeStartLine === void 0 ? void 0 : _findChangeStartLine.width;
366
+ this.applyStyles();
367
+ };
368
+ measure();
369
+
370
+ // An inline element cannot be observed itself (ResizeObserver skips boxes with no principal
371
+ // box), so the block it sits in is observed instead.
372
+ this.resizeObserver = new ResizeObserver(measure);
373
+ for (const parent of new Set(ownElements.map(({
374
+ parentElement
375
+ }) => parentElement))) {
376
+ if (parent) {
377
+ this.resizeObserver.observe(parent);
378
+ }
379
+ }
380
+
381
+ // `bindAll` takes a single target, and a change can render more than one highlight.
382
+ this.unbindHighlights = Array.from(highlights).map(highlight => bindAll(highlight, [{
383
+ type: 'mouseenter',
384
+ listener: () => {
385
+ this.isHighlightHovered = true;
386
+ this.applyStyles();
387
+ }
388
+ }, {
389
+ type: 'mouseleave',
390
+ listener: () => {
391
+ this.isHighlightHovered = false;
392
+ this.applyStyles();
393
+ }
394
+ }]));
395
+ }
396
+ unbindHighlightState() {
397
+ var _this$resizeObserver;
398
+ (_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 ? void 0 : _this$resizeObserver.disconnect();
399
+ this.resizeObserver = undefined;
400
+ this.unbindHighlights.forEach(unbind => unbind());
401
+ this.unbindHighlights = [];
402
+ }
403
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The icons a contributor tag can draw, as inline SVG.
3
+ *
4
+ * Neither `@atlaskit/icon`, `@atlaskit/icon-lab` nor `@atlaskit/logo` exposes a raw-SVG entry
5
+ * point, so the markup is copied here rather than kept as a React island for three glyphs. Keep in
6
+ * sync with the sources named on each constant.
7
+ *
8
+ * The Rovo hex is the `brand` appearance, whose `--rovo-*-color` variables resolve to `initial` in
9
+ * both themes — the fallbacks below are the rendered colours, so no theme observer is needed.
10
+ */
11
+
12
+ /** `@atlaskit/icon/core/person`, at the 16px the avatar renders it. */
13
+ const PERSON_ICON_SVG = `<svg width="16" height="16" fill="none" viewBox="0 0 16 16" role="presentation" style="display:block"><path fill="currentcolor" fill-rule="evenodd" d="M8 1.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4 4a4 4 0 1 1 8 0 4 4 0 0 1-8 0m-2 9a3.75 3.75 0 0 1 3.75-3.75h4.5A3.75 3.75 0 0 1 14 13v2h-1.5v-2a2.25 2.25 0 0 0-2.25-2.25h-4.5A2.25 2.25 0 0 0 3.5 13v2H2z" clip-rule="evenodd"/></svg>`;
14
+
15
+ /** `@atlaskit/icon-lab/core/ai-bot`, at 12px so it fits inside the 16px hexagon. */
16
+ const AI_BOT_ICON_SVG = `<svg width="12" height="12" fill="none" viewBox="0 0 16 16" role="presentation" style="display:block"><path fill="currentcolor" d="M13 5.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5V13a.5.5 0 0 0 .5.5h9a.5.5 0 0 0 .5-.5zm-6.5 2v3H5v-3zm4.5 0v3H9.5v-3zm3.5 1H16V10h-1.5v3a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2v-3H0V8.5h1.5v-3a2 2 0 0 1 2-2h3.75v-2H4V0h4a.75.75 0 0 1 .75.75V3.5h3.75a2 2 0 0 1 2 2z"/></svg>`;
17
+
18
+ /** `RovoHexIcon` from `@atlaskit/logo/rovo-hex/icon`, at the 16px `xxsmall` renders. */
19
+ const ROVO_HEX_ICON_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" role="presentation" style="display:block"><path fill="#1868db" fill-rule="evenodd" d="m13.227 16.789-7.154 4.169-3.403-1.96a2.72 2.72 0 0 1-1.362-2.355V7.366c0-.973.518-1.87 1.361-2.354l6.044-3.487-.038.114a3.6 3.6 0 0 0-.172 1.088v9.278c0 1.274.68 2.45 1.786 3.085z" clip-rule="evenodd"/><path fill="#6a9a23" fill-rule="evenodd" d="m11.296 15.671-7.193 4.153 6.607 3.812a2.73 2.73 0 0 0 2.676.026l.048-.033a2.73 2.73 0 0 0 1.232-1.525 2.7 2.7 0 0 0 .127-.822v-3.594z" clip-rule="evenodd"/><path fill="#af59e1" fill-rule="evenodd" d="m21.477 5.003-4.404-2.539-5.904 4.89 2.69 1.556a3.56 3.56 0 0 1 1.785 3.086v9.277a3.6 3.6 0 0 1-.21 1.202l6.044-3.486a2.71 2.71 0 0 0 1.362-2.355V7.357c0-.97-.521-1.87-1.363-2.354" clip-rule="evenodd"/><path fill="#fca700" fill-rule="evenodd" d="M12.74 8.266 9.353 6.312V2.718c0-.283.044-.56.127-.821A2.73 2.73 0 0 1 10.71.372V.37a.4.4 0 0 0 .048-.033 2.73 2.73 0 0 1 2.676.026l6.499 3.75z" clip-rule="evenodd"/></svg>`;
20
+ const markup = {
21
+ aiBot: AI_BOT_ICON_SVG,
22
+ person: PERSON_ICON_SVG,
23
+ rovoHex: ROVO_HEX_ICON_SVG
24
+ };
25
+
26
+ /**
27
+ * Parsing the markup is the same work for every tag, so each icon is parsed once into a template
28
+ * and cloned per use — appending a `DocumentFragment` moves its children out of it, so a shared
29
+ * fragment would leave every icon after the first empty.
30
+ */
31
+ const templates = {};
32
+ export const getContributorTagIcon = (icon, doc) => {
33
+ let template = templates[icon];
34
+ if (!template) {
35
+ template = doc.createElement('template');
36
+ template.innerHTML = markup[icon];
37
+ templates[icon] = template;
38
+ }
39
+ return template.content.cloneNode(true);
40
+ };
@@ -9,7 +9,6 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
9
9
  import isEqual from 'lodash/isEqual';
10
10
  import memoizeOne from 'memoize-one';
11
11
  import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
12
- import { isExperimentEnabled } from '@atlaskit/editor-common/deprecated-platform-feature-experiments';
13
12
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
14
13
  import { Mapping } from '@atlaskit/editor-prosemirror/transform';
15
14
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
@@ -618,26 +617,21 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
618
617
  if (change.isInline) {
619
618
  // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
620
619
  var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
621
- var isAtomicInlineNodeFlag = isExperimentEnabled('platform_editor_improve_inline_diffs', function () {
622
- return expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true);
623
- });
624
620
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration({
625
621
  change: change,
626
622
  colorScheme: colorScheme,
627
623
  isActive: isActive,
628
624
  isInserted: true,
629
- isAtomicInlineNode: isAtomicInlineNodeFlag,
625
+ isAtomicInlineNode: true,
630
626
  inlineNodeName: change.inlineNodeName,
631
627
  // Suppress the border-bottom underline for atomic inline nodeviews —
632
628
  // it doesn't render on custom nodeview DOM elements and is unnecessary noise.
633
- hideAddedDiffsUnderline: isAtomicInlineNodeFlag,
629
+ hideAddedDiffsUnderline: true,
634
630
  diffType: diffType
635
631
  })));
636
632
  // If we have the original node position, also render the old node as a "deleted" widget
637
633
  // so the user can see what it looked like before the change.
638
- if (change.fromA !== undefined && change.toA !== undefined && isExperimentEnabled('platform_editor_improve_inline_diffs', function () {
639
- return expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true);
640
- })) {
634
+ if (change.fromA !== undefined && change.toA !== undefined) {
641
635
  decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget({
642
636
  change: {
643
637
  fromA: change.fromA,
@@ -212,7 +212,6 @@ var TEXT_BLOCK_TYPES = new Set(['paragraph', 'heading']);
212
212
  * layoutColumn → layoutSection, tableCell/tableHeader → tableRow → table.
213
213
  */
214
214
  var RIGID_CHILD_TYPES = new Set(['layoutColumn', 'tableCell', 'tableHeader']);
215
- var TABLE_TYPE = 'table';
216
215
 
217
216
  /**
218
217
  * Emit a single whole-block (node-level) change covering both sides of a group. Used for
@@ -539,7 +538,7 @@ var classifyContainer = function classifyContainer(blockA, blockB, changes, orig
539
538
  var typeName = blockB.node.type.name;
540
539
 
541
540
  // Tables need cell-level counting across rows; handle them specially.
542
- if (typeName === TABLE_TYPE) {
541
+ if (typeName === 'table') {
543
542
  return classifyTable(blockA, blockB, changes, originalDoc, newDoc, locale, thresholds);
544
543
  }
545
544
 
@@ -1,7 +1,7 @@
1
1
  import _typeof from "@babel/runtime/helpers/typeof";
2
- import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
3
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
4
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
5
5
  var _excluded = ["attribution", "connectedTo"];
6
6
  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; }
7
7
  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; }
@@ -17,6 +17,19 @@ import { PARTICIPANT_COLOR_SCHEMES } from './types';
17
17
 
18
18
  /** Contributors addressed by attribution key. The key format is internal. */
19
19
 
20
+ /** Maps the shared 18-slot telepointer palette onto the ten bolder diff colours. */
21
+ var HASHED_PARTICIPANT_COLOR_SCHEMES = [].concat(_toConsumableArray(PARTICIPANT_COLOR_SCHEMES), ['blue', 'red', 'orange', 'yellow', 'green', 'teal', 'purple', 'magenta']);
22
+ var getAvailableColor = function getAvailableColor(hashedColor, allocatedColors) {
23
+ var startIndex = PARTICIPANT_COLOR_SCHEMES.indexOf(hashedColor);
24
+ for (var offset = 0; offset < PARTICIPANT_COLOR_SCHEMES.length; offset++) {
25
+ var color = PARTICIPANT_COLOR_SCHEMES[(startIndex + offset) % PARTICIPANT_COLOR_SCHEMES.length];
26
+ if (color && !allocatedColors.has(color)) {
27
+ return color;
28
+ }
29
+ }
30
+ return hashedColor;
31
+ };
32
+
20
33
  /**
21
34
  * Normalise the attribution identity once so a valid lookup key always has a valid participant
22
35
  * colour seed. `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared
@@ -112,6 +125,7 @@ export var isContributorTagsEnabled = function isContributorTagsEnabled(stepAttr
112
125
  };
113
126
  export var createAttributionColorMap = function createAttributionColorMap(stepAttributions) {
114
127
  var colors = new Map();
128
+ var allocatedColors = new Set();
115
129
  var _iterator2 = _createForOfIteratorHelper(stepAttributions),
116
130
  _step2;
117
131
  try {
@@ -123,10 +137,13 @@ export var createAttributionColorMap = function createAttributionColorMap(stepAt
123
137
  }
124
138
  var _getParticipantColor = getParticipantColor(identity.colorSeed),
125
139
  index = _getParticipantColor.index;
126
- var participantColorScheme = PARTICIPANT_COLOR_SCHEMES[index];
127
- if (participantColorScheme) {
128
- colors.set(identity.key, participantColorScheme);
140
+ var hashedColor = HASHED_PARTICIPANT_COLOR_SCHEMES[index];
141
+ if (!hashedColor) {
142
+ continue;
129
143
  }
144
+ var participantColorScheme = getAvailableColor(hashedColor, allocatedColors);
145
+ colors.set(identity.key, participantColorScheme);
146
+ allocatedColors.add(participantColorScheme);
130
147
  }
131
148
  } catch (err) {
132
149
  _iterator2.e(err);
@@ -2,9 +2,6 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  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; }
3
3
  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; }
4
4
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
5
- import { participantColors } from '@atlaskit/editor-shared-styles/constants';
6
- import { PARTICIPANT_COLOR_SCHEMES } from './types';
7
-
8
5
  // token() is a build-time transform needing static literals, so pre-compute every colour's
9
6
  // tokens here and index by name at runtime.
10
7
 
@@ -80,71 +77,23 @@ var textAccentMap = {
80
77
  gray: "var(--ds-text-accent-gray, #505258)",
81
78
  lime: "var(--ds-text-accent-lime, #4C6B1F)"
82
79
  };
83
-
84
- /** Pressed counterpart of each participant's diff-safe SVG background tint. */
85
- var participantPressedBackgroundMap = {
86
- 'red-bolder': bgSubtlerPressedMap.red,
87
- 'blue-bolder': bgSubtlerPressedMap.blue,
88
- 'green-bolder': bgSubtlerPressedMap.green,
89
- 'yellow-bolder': bgSubtlerPressedMap.yellow,
90
- 'purple-bolder': bgSubtlerPressedMap.purple,
91
- 'magenta-bolder': bgSubtlerPressedMap.magenta,
92
- 'teal-bolder': bgSubtlerPressedMap.teal,
93
- 'orange-bolder': bgSubtlerPressedMap.orange,
94
- 'lime-bolder': bgSubtlerPressedMap.lime,
95
- 'gray-bolder': bgSubtlerPressedMap.gray,
96
- 'blue-subtle': bgSubtlestPressedMap.blue,
97
- 'red-subtle': bgSubtlestPressedMap.red,
98
- 'orange-subtle': bgSubtlestPressedMap.orange,
99
- 'yellow-subtle': bgSubtlestPressedMap.yellow,
100
- 'green-subtle': bgSubtlestPressedMap.green,
101
- 'teal-subtle': bgSubtlestPressedMap.teal,
102
- 'purple-subtle': bgSubtlestPressedMap.purple,
103
- 'magenta-subtle': bgSubtlestPressedMap.magenta
104
- };
105
- /**
106
- * Telepointers can put inverse text directly on their bolder colour. Diff colours sit behind
107
- * arbitrary document content, so use the participant SVG tint and its matching pressed token for
108
- * diff backgrounds. Borders and rings use the exact participant colour, preserving all 18
109
- * bolder/subtle slots while keeping every value in the shared ADS participant palette.
110
- */
111
- var participantColorTokenSets = Object.fromEntries(PARTICIPANT_COLOR_SCHEMES.map(function (scheme, index) {
112
- var participantColor = participantColors[index];
113
- if (!participantColor) {
114
- throw new Error("Missing shared participant colour for show-diff slot ".concat(index));
115
- }
116
- return [scheme, {
117
- backgroundSubtlest: participantColor.svgBackgroundColor,
118
- backgroundSubtlestPressed: participantPressedBackgroundMap[scheme],
119
- backgroundSubtler: participantColor.svgBackgroundColor,
120
- backgroundSubtlerPressed: participantPressedBackgroundMap[scheme],
121
- borderAccent: participantColor.backgroundColor,
122
- // The first ten bolder colours are safe as accents on the editor surface. The
123
- // remaining subtle colours use their paired dark participant text colour.
124
- textAccent: index < 10 ? participantColor.backgroundColor : participantColor.textColor
125
- }];
126
- }));
127
- var participantColorSchemeSet = new Set(PARTICIPANT_COLOR_SCHEMES);
128
- var isParticipantColorScheme = function isParticipantColorScheme(color) {
129
- return participantColorSchemeSet.has(color);
130
- };
131
80
  function bgSubtlest(color) {
132
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlest : bgSubtlestMap[color];
81
+ return bgSubtlestMap[color];
133
82
  }
134
83
  function bgSubtlestPressed(color) {
135
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlestPressed : bgSubtlestPressedMap[color];
84
+ return bgSubtlestPressedMap[color];
136
85
  }
137
86
  function bgSubtler(color) {
138
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtler : bgSubtlerMap[color];
87
+ return bgSubtlerMap[color];
139
88
  }
140
89
  function bgSubtlerPressed(color) {
141
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlerPressed : bgSubtlerPressedMap[color];
90
+ return bgSubtlerPressedMap[color];
142
91
  }
143
92
  function borderAccent(color) {
144
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].borderAccent : borderAccentMap[color];
93
+ return borderAccentMap[color];
145
94
  }
146
95
  function textAccent(color) {
147
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].textAccent : textAccentMap[color];
96
+ return textAccentMap[color];
148
97
  }
149
98
 
150
99
  /** Presentation tokens for one diff colour, shared by highlights and contributor tags. */