@atlaskit/editor-plugin-show-diff 16.0.1 → 16.0.3

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 (53) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/afm-products/tsconfig.json +3 -0
  3. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +91 -51
  4. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +20 -1
  5. package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +121 -21
  6. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +19 -8
  7. package/dist/cjs/pm-plugins/decorations/utils/getMarkChangeRanges.js +18 -4
  8. package/dist/cjs/pm-plugins/decorations/utils/mapStepRangeToFinal.js +34 -0
  9. package/dist/cjs/pm-plugins/main.js +18 -0
  10. package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +51 -5
  11. package/dist/cjs/ui/ContributorTag/contributorLabel.js +55 -0
  12. package/dist/cjs/ui/ContributorTag/contributorTagController.js +40 -43
  13. package/dist/cjs/ui/ContributorTag/messages.js +5 -0
  14. package/dist/cjs/ui/DiffNavigation/announceActiveDiff.js +54 -0
  15. package/dist/cjs/ui/DiffNavigation/messages.js +19 -0
  16. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +48 -13
  17. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +18 -1
  18. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +70 -10
  19. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +16 -0
  20. package/dist/es2019/pm-plugins/decorations/utils/getMarkChangeRanges.js +12 -4
  21. package/dist/es2019/pm-plugins/decorations/utils/mapStepRangeToFinal.js +28 -0
  22. package/dist/es2019/pm-plugins/main.js +18 -0
  23. package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +48 -4
  24. package/dist/es2019/ui/ContributorTag/contributorLabel.js +49 -0
  25. package/dist/es2019/ui/ContributorTag/contributorTagController.js +39 -44
  26. package/dist/es2019/ui/ContributorTag/messages.js +5 -0
  27. package/dist/es2019/ui/DiffNavigation/announceActiveDiff.js +49 -0
  28. package/dist/es2019/ui/DiffNavigation/messages.js +13 -0
  29. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +91 -51
  30. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +20 -1
  31. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +121 -21
  32. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +19 -8
  33. package/dist/esm/pm-plugins/decorations/utils/getMarkChangeRanges.js +17 -4
  34. package/dist/esm/pm-plugins/decorations/utils/mapStepRangeToFinal.js +28 -0
  35. package/dist/esm/pm-plugins/main.js +18 -0
  36. package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +50 -4
  37. package/dist/esm/ui/ContributorTag/contributorLabel.js +49 -0
  38. package/dist/esm/ui/ContributorTag/contributorTagController.js +41 -44
  39. package/dist/esm/ui/ContributorTag/messages.js +5 -0
  40. package/dist/esm/ui/DiffNavigation/announceActiveDiff.js +48 -0
  41. package/dist/esm/ui/DiffNavigation/messages.js +13 -0
  42. package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +14 -1
  43. package/dist/types/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +3 -0
  44. package/dist/types/pm-plugins/decorations/utils/getMarkChangeRanges.d.ts +4 -1
  45. package/dist/types/pm-plugins/decorations/utils/mapStepRangeToFinal.d.ts +8 -0
  46. package/dist/types/showDiffPluginType.d.ts +7 -1
  47. package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +12 -2
  48. package/dist/types/ui/ContributorTag/contributorLabel.d.ts +13 -0
  49. package/dist/types/ui/ContributorTag/contributorTagController.d.ts +11 -0
  50. package/dist/types/ui/ContributorTag/messages.d.ts +1 -0
  51. package/dist/types/ui/DiffNavigation/announceActiveDiff.d.ts +18 -0
  52. package/dist/types/ui/DiffNavigation/messages.d.ts +5 -0
  53. package/package.json +5 -4
@@ -7,6 +7,7 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
7
7
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
8
8
  import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform-override';
9
9
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
10
+ import { getActiveDiffAnnouncement } from '../ui/DiffNavigation/announceActiveDiff';
10
11
  import { calculateDiffDecorations } from './calculateDiff/calculateDiffDecorations';
11
12
  import { isDiffDecoration, isDiffDecorationSpec } from './decorations/decorationKeys';
12
13
  import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
@@ -272,6 +273,23 @@ export var createPlugin = function createPlugin(config, getIntl, api, onEditorVi
272
273
  api === null || api === void 0 || api.core.actions.execute(toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
273
274
  }
274
275
  cancelPendingScrollToDecoration = scrollToDecoration(view, scrollableDecorations, pluginState.activeIndex);
276
+
277
+ // Stepping only scrolls — no focus moves and no content changes — so without this a
278
+ // screen-reader user gets silence. Announced through the live region rather than by
279
+ // focusing the change, since the "next change" control has to stay focused to be
280
+ // pressed again. After the scroll above, so the announcement never precedes it.
281
+ if (isExtendedEnabled(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
282
+ var announcement = getActiveDiffAnnouncement({
283
+ activeIndex: pluginState.activeIndex,
284
+ contributorTags: pluginState.contributorTags,
285
+ decorations: scrollableDecorations,
286
+ intl: getIntl()
287
+ });
288
+ if (announcement) {
289
+ var _api$accessibilityUti;
290
+ api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(announcement);
291
+ }
292
+ }
275
293
  }
276
294
  },
277
295
  destroy: function destroy() {
@@ -42,6 +42,30 @@ export var CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = 'data-revealed';
42
42
  */
43
43
  export var TAG_EXIT_FALLBACK_MS = 1200;
44
44
 
45
+ /**
46
+ * The tooltip's look, inline rather than through `VANILLA_TOOLTIP_DEFAULT_CLASS`: this tooltip is
47
+ * hoisted out of the tag, and that class's rule is scoped under `.ProseMirror` — see
48
+ * `resolveTooltipContainer`. Otherwise `vanillaTooltipDefaultStyles` in editor-core, which is the
49
+ * look every other vanilla tooltip has; keep the two in step.
50
+ */
51
+ export var CONTRIBUTOR_TAG_TOOLTIP_STYLES = {
52
+ boxSizing: 'border-box',
53
+ maxWidth: '240px',
54
+ backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
55
+ // A `popover` is given one by the UA stylesheet.
56
+ border: 'none',
57
+ borderRadius: "var(--ds-radius-small, 3px)",
58
+ color: "var(--ds-text-inverse, #FFFFFF)",
59
+ font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
60
+ fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
61
+ overflowWrap: 'break-word',
62
+ paddingBlock: "var(--ds-space-050, 4px)",
63
+ paddingInline: "var(--ds-space-075, 6px)",
64
+ whiteSpace: 'normal',
65
+ // A tooltip is never a hit target — it hangs over the document's own content.
66
+ pointerEvents: 'none'
67
+ };
68
+
45
69
  // Positioned against the host widget its decoration renders on the change's first character:
46
70
  // `bottom: 100%` lifts the tag onto the line above, `inset-inline-start` starts it on that
47
71
  // character.
@@ -125,6 +149,19 @@ var srLabelStyle = convertToInlineCss({
125
149
  userSelect: 'none',
126
150
  whiteSpace: 'nowrap'
127
151
  });
152
+
153
+ /**
154
+ * Ids for the hidden label that the tag's `aria-labelledby` points at. Namespaced and random because
155
+ * the tag renders inside the document and shares the host page's id space — the same reason
156
+ * `VanillaTooltip` does it this way. The counter is a fallback for non-secure contexts, where
157
+ * `crypto.randomUUID` is unavailable.
158
+ */
159
+ var generateLabelId = function () {
160
+ var count = 0;
161
+ return function () {
162
+ return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "".concat(CONTRIBUTOR_TAG_CLASS, "-label-").concat(crypto.randomUUID()) : "".concat(CONTRIBUTOR_TAG_CLASS, "-label-").concat(count += 1);
163
+ };
164
+ }();
128
165
  var createSpan = function createSpan(doc, style) {
129
166
  var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
130
167
  var span = doc.createElement('span');
@@ -144,21 +181,30 @@ var createSpan = function createSpan(doc, style) {
144
181
  * would need a cast per reference below.
145
182
  */
146
183
  export var buildContributorTagDom = function buildContributorTagDom(doc) {
184
+ var labelId = generateLabelId();
147
185
  var root = createSpan(doc, constraintStyle);
148
186
  var tag = createSpan(doc, tagStyle, {
149
187
  class: CONTRIBUTOR_TAG_CLASS,
150
188
  'data-testid': CONTRIBUTOR_TAG_TESTID,
151
- // Deliberately no `aria-label`: it would be announced ahead of the element's contents, but
152
- // design requires the contributor *after* the change — hence the visually hidden label.
189
+ // Named by the hidden label rather than `aria-label`, which would be a second copy of the same
190
+ // sentence. `role="note"` takes its name from the author, not its contents, so without this
191
+ // the tag is a focus stop with no accessible name.
192
+ 'aria-labelledby': labelId,
153
193
  role: 'note',
154
- // Focusable so keyboard and screen-reader users can reach the tag and its tooltip.
194
+ // Focusable so keyboard and screen-reader users can reach the tag and its tooltip. Its focus
195
+ // ring is drawn inward — see `contributorTagStyles` in editor-core.
155
196
  tabindex: '0'
156
197
  });
157
198
  var avatars = createSpan(doc, avatarsStyle);
199
+ // `aria-hidden`: the hidden label restates this name in a full sentence, so leaving it in the
200
+ // tree announced the contributor twice ("Priya Changed by Priya").
158
201
  var name = createSpan(doc, nameStyle, {
202
+ 'aria-hidden': 'true',
159
203
  'data-testid': CONTRIBUTOR_TAG_NAME_TESTID
160
204
  });
161
- var srLabel = createSpan(doc, srLabelStyle);
205
+ var srLabel = createSpan(doc, srLabelStyle, {
206
+ id: labelId
207
+ });
162
208
  tag.append(avatars, name, srLabel);
163
209
  root.appendChild(tag);
164
210
  return {
@@ -0,0 +1,49 @@
1
+ import { contributorTagMessages } from './messages';
2
+ var getContributorName = function getContributorName(contributor, formatMessage) {
3
+ var _contributor$name, _contributor$agentKin;
4
+ var name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
5
+ if (name) {
6
+ return name;
7
+ }
8
+
9
+ // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
10
+ // agent falls back to a generic label rather than rendering a nameless tag.
11
+ if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
12
+ return formatMessage(contributorTagMessages.externalAgentName);
13
+ }
14
+ return '';
15
+ };
16
+
17
+ /**
18
+ * Whether the label has to spell out that the contributor is an agent. A sighted user reads that off
19
+ * the `aria-hidden` avatar, so the label is the only other carrier.
20
+ *
21
+ * Excluded: a connected pair, which says it via `changedByConnected`, and an unnamed external agent,
22
+ * whose name `getContributorName` has already resolved to "External agent".
23
+ */
24
+ var isUnaccompaniedNamedAgent = function isUnaccompaniedNamedAgent(model) {
25
+ var _model$contributor$na;
26
+ return !model.connectedContributor && model.contributor.kind === 'agent' && Boolean((_model$contributor$na = model.contributor.name) === null || _model$contributor$na === void 0 ? void 0 : _model$contributor$na.trim());
27
+ };
28
+ /**
29
+ * The two strings a tag's model resolves to. Shared with `getActiveDiffAnnouncement`, so stepping
30
+ * onto a change says the same sentence as the tag pinned to it rather than a copy free to drift.
31
+ */
32
+ export var formatContributorLabel = function formatContributorLabel(model, formatMessage) {
33
+ var isPrimaryAgent = model.contributor.kind === 'agent';
34
+ var primaryName = getContributorName(model.contributor, formatMessage);
35
+ var secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
36
+ var agentName = isPrimaryAgent ? primaryName : secondaryName;
37
+ var userName = isPrimaryAgent ? secondaryName : primaryName;
38
+ return {
39
+ fullLabel: model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
40
+ agentName: agentName,
41
+ userName: userName
42
+ }) : formatMessage(isUnaccompaniedNamedAgent(model) ? contributorTagMessages.changedByAgent : contributorTagMessages.changedBy, {
43
+ name: primaryName
44
+ }),
45
+ // A connected pair only ever shows the agent; the pair is spelled out in `fullLabel`, because
46
+ // the tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
47
+ visibleName: model.connectedContributor ? agentName || primaryName : primaryName
48
+ };
49
+ };
@@ -6,24 +6,9 @@ import { bind, bindAll } from 'bind-event-listener';
6
6
  import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
7
7
  import { getAccentTokens } from '../../pm-plugins/decorations/colorSchemes/factory';
8
8
  import { colorSchemeRegistry } from '../../pm-plugins/decorations/colorSchemes/schemes';
9
- import { buildContributorTagDom, CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, TAG_EXIT_FALLBACK_MS } from './buildContributorTagDom';
9
+ import { buildContributorTagDom, CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, CONTRIBUTOR_TAG_TOOLTIP_STYLES, TAG_EXIT_FALLBACK_MS } from './buildContributorTagDom';
10
10
  import { contributorAvatarRenderer } from './contributorAvatarRenderer';
11
- import { contributorTagMessages } from './messages';
12
- var getContributorName = function getContributorName(contributor, formatMessage) {
13
- var _contributor$name, _contributor$agentKin;
14
- var name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
15
- if (name) {
16
- return name;
17
- }
18
-
19
- // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
20
- // agent falls back to a generic label rather than rendering a nameless tag.
21
- if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
22
- return formatMessage(contributorTagMessages.externalAgentName);
23
- }
24
- return '';
25
- };
26
-
11
+ import { formatContributorLabel } from './contributorLabel';
27
12
  /**
28
13
  * Resolves the tag's accent from the same scheme value the highlight was drawn from, so the colour
29
14
  * the two carry can never drift — only the tone does, which is `getAccentTokens`'s to pick.
@@ -106,8 +91,8 @@ var fadeTagOut = function fadeTagOut(tag, release) {
106
91
  /** The contributors a tag draws, keyed so a republished model can be recognised as the same pair. */
107
92
  var identityOf = function identityOf(model) {
108
93
  return [model === null || model === void 0 ? void 0 : model.contributor, model === null || model === void 0 ? void 0 : model.connectedContributor].map(function (contributor) {
109
- var _contributor$agentKin2, _contributor$avatarUr;
110
- return contributor ? "".concat(contributor.kind, ":").concat((_contributor$agentKin2 = contributor.agentKind) !== null && _contributor$agentKin2 !== void 0 ? _contributor$agentKin2 : '', ":").concat(contributor.name, ":").concat((_contributor$avatarUr = contributor.avatarUrl) !== null && _contributor$avatarUr !== void 0 ? _contributor$avatarUr : '') : '';
94
+ var _contributor$agentKin, _contributor$avatarUr;
95
+ return contributor ? "".concat(contributor.kind, ":").concat((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : '', ":").concat(contributor.name, ":").concat((_contributor$avatarUr = contributor.avatarUrl) !== null && _contributor$avatarUr !== void 0 ? _contributor$avatarUr : '') : '';
111
96
  }).join('|');
112
97
  };
113
98
 
@@ -348,24 +333,12 @@ export var ContributorTagController = /*#__PURE__*/function () {
348
333
  if (!this.dom) {
349
334
  return;
350
335
  }
351
- var _this$options$getIntl = this.options.getIntl(),
352
- formatMessage = _this$options$getIntl.formatMessage;
353
- var isPrimaryAgent = model.contributor.kind === 'agent';
354
- var primaryName = getContributorName(model.contributor, formatMessage);
355
- var secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
356
- var agentName = isPrimaryAgent ? primaryName : secondaryName;
357
- var userName = isPrimaryAgent ? secondaryName : primaryName;
358
-
359
- // A connected pair only ever shows the agent; the pair is spelled out in the label, because the
360
- // tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
361
- this.dom.name.textContent = model.connectedContributor ? agentName || primaryName : primaryName;
362
- this.fullLabel = model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
363
- agentName: agentName,
364
- userName: userName
365
- }) : formatMessage(contributorTagMessages.changedBy, {
366
- name: primaryName
367
- });
368
- this.dom.srLabel.textContent = this.fullLabel;
336
+ var _formatContributorLab = formatContributorLabel(model, this.options.getIntl().formatMessage),
337
+ fullLabel = _formatContributorLab.fullLabel,
338
+ visibleName = _formatContributorLab.visibleName;
339
+ this.dom.name.textContent = visibleName;
340
+ this.fullLabel = fullLabel;
341
+ this.dom.srLabel.textContent = fullLabel;
369
342
  }
370
343
  }, {
371
344
  key: "accentOf",
@@ -431,15 +404,39 @@ export var ContributorTagController = /*#__PURE__*/function () {
431
404
  if (!content || !this.dom) {
432
405
  return;
433
406
  }
434
- this.tooltip = new VanillaTooltip(this.dom.tag, content, undefined,
435
- // `ak-editor-vanilla-tooltip-default` opts into the shared `VanillaTooltip` look, defined as
436
- // `vanillaTooltipDefaultStyles` in both EditorContentContainer stylesheets — keep in sync when
437
- // renaming. Those styles are scoped under `.ProseMirror`, and the tag renders inside it.
438
- 'ak-editor-vanilla-tooltip-default');
407
+ this.tooltip = new VanillaTooltip(this.dom.tag, content,
408
+ // Generated id.
409
+ undefined,
410
+ // No class: the look is inline, because a hoisted tooltip is outside the `.ProseMirror`
411
+ // scope `VANILLA_TOOLTIP_DEFAULT_CLASS` is keyed on — see `CONTRIBUTOR_TAG_TOOLTIP_STYLES`.
412
+ '',
413
+ // Default delay, no `onShow`, and the default `top` placement.
414
+ undefined, CONTRIBUTOR_TAG_TOOLTIP_STYLES, undefined, undefined, this.resolveTooltipContainer());
439
415
  // `VanillaTooltip` sets `aria-describedby` on its trigger, which would announce the label a
440
- // second time — the visually hidden child already announces it, after the change as design
441
- // requires.
416
+ // second time — the hidden child already announces it, and names the tag with it.
442
417
  this.dom.tag.removeAttribute('aria-describedby');
418
+ // Out of the tag but still in the document, so its text would otherwise be a third copy of the
419
+ // same sentence. It stays visible for sighted users, who need the part of the name the tag
420
+ // clipped.
421
+ this.tooltip.element.setAttribute('aria-hidden', 'true');
422
+ }
423
+
424
+ /**
425
+ * Where the tooltip is appended, rather than inside the tag.
426
+ *
427
+ * Popper and the browser only agree on a top-layer popover's origin when nothing above it is
428
+ * transformed, and the tag hangs under the editor's own transformed nodes — inside a wide image
429
+ * that is `.rich-media-item`, whose `translateX(-50%)` threw the tooltip off screen
430
+ * (EDITOR-8971). The content area is the nearest ancestor with none of that above it, and keeps
431
+ * the tooltip inside the editor it belongs to; the document body covers an appearance that has
432
+ * no content area.
433
+ */
434
+ }, {
435
+ key: "resolveTooltipContainer",
436
+ value: function resolveTooltipContainer() {
437
+ var _editorRoot$closest;
438
+ var editorRoot = this.options.getEditorRoot();
439
+ return (_editorRoot$closest = editorRoot === null || editorRoot === void 0 ? void 0 : editorRoot.closest('.ak-editor-content-area')) !== null && _editorRoot$closest !== void 0 ? _editorRoot$closest : editorRoot === null || editorRoot === void 0 ? void 0 : editorRoot.ownerDocument.body;
443
440
  }
444
441
 
445
442
  /**
@@ -5,6 +5,11 @@ export var contributorTagMessages = defineMessages({
5
5
  defaultMessage: 'Changed by {name}',
6
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
7
  },
8
+ changedByAgent: {
9
+ id: 'editor-plugin-show-diff.ContributorTag.changedByAgent',
10
+ defaultMessage: 'Changed by {name}, an AI agent',
11
+ description: 'Screen-reader label and tooltip for the tag pinned to a highlighted change in the version-history diff, when a named AI agent made the change rather than a person. The name placeholder is the agent display name.'
12
+ },
8
13
  changedByConnected: {
9
14
  id: 'editor-plugin-show-diff.ContributorTag.changedByConnected',
10
15
  defaultMessage: 'Changed by {agentName} with {userName}',
@@ -0,0 +1,48 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
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
+ 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
+ import { isDiffDecoration } from '../../pm-plugins/decorations/decorationKeys';
5
+ import { formatContributorLabel } from '../ContributorTag/contributorLabel';
6
+ import { diffNavigationMessages } from './messages';
7
+
8
+ /**
9
+ * The tag that captions the given decoration, if any. `linkedDiffIds` is checked as well as
10
+ * `diffId`, because a replacement's two decorations share one tag and navigation can land on either.
11
+ */
12
+ var findTagFor = function findTagFor(decoration, contributorTags) {
13
+ if (!(contributorTags !== null && contributorTags !== void 0 && contributorTags.length) || !isDiffDecoration(decoration)) {
14
+ return undefined;
15
+ }
16
+ var diffId = decoration.spec.diffId;
17
+ return contributorTags.find(function (tag) {
18
+ var _tag$linkedDiffIds;
19
+ return tag.diffId === diffId || ((_tag$linkedDiffIds = tag.linkedDiffIds) === null || _tag$linkedDiffIds === void 0 ? void 0 : _tag$linkedDiffIds.includes(diffId));
20
+ });
21
+ };
22
+
23
+ /**
24
+ * What a screen reader should say once the reader has stepped onto a change: where they are in the
25
+ * diff, and — when the plugin can credit the change — who made it. Stepping otherwise only scrolls,
26
+ * which is silent.
27
+ *
28
+ * Returns `undefined` for an index that resolves no change, so the caller says nothing at all
29
+ * rather than "Change 4 of 3".
30
+ */
31
+ export var getActiveDiffAnnouncement = function getActiveDiffAnnouncement(_ref) {
32
+ var activeIndex = _ref.activeIndex,
33
+ contributorTags = _ref.contributorTags,
34
+ decorations = _ref.decorations,
35
+ intl = _ref.intl;
36
+ var activeDecoration = decorations[activeIndex];
37
+ if (!activeDecoration) {
38
+ return undefined;
39
+ }
40
+ var position = {
41
+ index: activeIndex + 1,
42
+ total: decorations.length
43
+ };
44
+ var tag = findTagFor(activeDecoration, contributorTags);
45
+ return tag ? intl.formatMessage(diffNavigationMessages.activeChangeWithContributor, _objectSpread(_objectSpread({}, position), {}, {
46
+ contributor: formatContributorLabel(tag, intl.formatMessage).fullLabel
47
+ })) : intl.formatMessage(diffNavigationMessages.activeChange, position);
48
+ };
@@ -0,0 +1,13 @@
1
+ import { defineMessages } from 'react-intl';
2
+ export var diffNavigationMessages = defineMessages({
3
+ activeChange: {
4
+ id: 'editor-plugin-show-diff.DiffNavigation.activeChange',
5
+ defaultMessage: 'Change {index} of {total}',
6
+ description: 'Announced to screen readers when the reader steps to the next or previous change in the version-history diff. The index placeholder is the position of the change stepped to, counting from 1; total is how many changes the diff has.'
7
+ },
8
+ activeChangeWithContributor: {
9
+ id: 'editor-plugin-show-diff.DiffNavigation.activeChangeWithContributor',
10
+ defaultMessage: 'Change {index} of {total}. {contributor}',
11
+ description: 'Announced to screen readers when the reader steps to the next or previous change in the version-history diff and that change is credited to someone. The index and total placeholders are as in activeChange. The contributor placeholder is an already-translated sentence (changedBy, changedByAgent or changedByConnected), so it must be kept whole and only repositioned.'
12
+ }
13
+ });
@@ -5,5 +5,18 @@ import type { ResolvedDiffContributors } from './colorSchemes/attributions';
5
5
  * One model per diff decoration whose attribution resolves to a supplied contributor; anything
6
6
  * unattributed, untaggable or unresolved is skipped. A replacement's two decorations are collapsed
7
7
  * into a single tag, on whichever half renders first — see `isSameChange` and `leadsReplacement`.
8
+ *
9
+ * `activeIndexPos`, when given, reveals exactly one of the tags — see `resolveActiveTarget`.
10
+ * Absent, each tag keeps the active state its own decoration was drawn with.
11
+ *
12
+ * `stops`, when given, is the navigation stop list the step buttons walk
13
+ * (`getScrollableDecorations`). One contributor cannot hold two tags in one stop, since only the
14
+ * leading one would ever be reachable. Absent, every decoration keeps its own tag.
8
15
  */
9
- export declare const extractContributorTags: (decorations: DecorationSet, contributors: ResolvedDiffContributors | undefined) => ContributorTagModel[];
16
+ export declare const extractContributorTags: (decorations: DecorationSet, contributors: ResolvedDiffContributors | undefined, activeIndexPos?: {
17
+ from: number;
18
+ to: number;
19
+ }, stops?: ReadonlyArray<{
20
+ from: number;
21
+ to: number;
22
+ }>) => ContributorTagModel[];
@@ -5,6 +5,7 @@ import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
5
5
  export type InlineAttrChangeNodeName = 'date' | 'emoji' | 'mention' | 'status';
6
6
  type AttrChangeStep = AttrStep | SetAttrsStep;
7
7
  export type AttrStepContext = {
8
+ attributionKey?: string;
8
9
  afterNode: PMNode | null;
9
10
  beforeNode: PMNode | null;
10
11
  finalPos: number;
@@ -12,6 +13,8 @@ export type AttrStepContext = {
12
13
  step: AttrChangeStep;
13
14
  };
14
15
  type StepRange = {
16
+ /** Attribution key for the step that produced this range. */
17
+ attributionKey?: string;
15
18
  /**
16
19
  * Position of the original (before) node in the original doc.
17
20
  * Populated for inline attr changes (e.g. emoji, date) so the caller can
@@ -1,7 +1,10 @@
1
1
  import type { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform-override';
2
+ import { type StepMap } from '@atlaskit/editor-prosemirror/transform';
3
+ import type { DiffStepAttribution } from '../../../showDiffPluginType';
2
4
  type StepRange = {
5
+ attributionKey?: string;
3
6
  fromB: number;
4
7
  toB: number;
5
8
  };
6
- export declare const getMarkChangeRanges: (steps: ProseMirrorStep[]) => StepRange[];
9
+ export declare const getMarkChangeRanges: (steps: ProseMirrorStep[], stepAttributions?: Array<DiffStepAttribution | undefined>, stepMaps?: StepMap[]) => StepRange[];
7
10
  export {};
@@ -0,0 +1,8 @@
1
+ import { type StepMap } from '@atlaskit/editor-prosemirror/transform';
2
+ type StepRange = {
3
+ fromB: number;
4
+ toB: number;
5
+ };
6
+ /** Maps a step's range into final-document coordinates when the rollout path supplies step maps. */
7
+ export declare const mapStepRangeToFinal: (from: number, to: number, stepIndex: number, stepMaps?: StepMap[]) => StepRange | undefined;
8
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { StepJson } from '@atlaskit/editor-common/collab';
2
2
  import type { NextEditorPlugin, EditorCommand, OptionalPlugin } from '@atlaskit/editor-common/types';
3
3
  import type { JSONDocNode } from '@atlaskit/editor-json-transformer/types';
4
+ import type { AccessibilityUtilsPlugin } from '@atlaskit/editor-plugin-accessibility-utils';
4
5
  import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
6
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
6
7
  import type { Node } from '@atlaskit/editor-prosemirror/model';
@@ -226,7 +227,12 @@ export type ShowDiffPlugin = NextEditorPlugin<'showDiff', {
226
227
  scrollToPrevious: EditorCommand;
227
228
  showDiff: (config: ShowDiffParams) => EditorCommand;
228
229
  };
229
- dependencies: [OptionalPlugin<AnalyticsPlugin>, OptionalPlugin<UserIntentPlugin>];
230
+ dependencies: [
231
+ OptionalPlugin<AnalyticsPlugin>,
232
+ OptionalPlugin<UserIntentPlugin>,
233
+ /** Carries the live-region announcement made when stepping between changes. */
234
+ OptionalPlugin<AccessibilityUtilsPlugin>
235
+ ];
230
236
  pluginConfiguration: DiffParams | undefined;
231
237
  sharedState: {
232
238
  /**
@@ -34,13 +34,23 @@ export declare const CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = "data-revealed";
34
34
  * number — hence the wide margin. Raise in step with that duration.
35
35
  */
36
36
  export declare const TAG_EXIT_FALLBACK_MS = 1200;
37
+ /**
38
+ * The tooltip's look, inline rather than through `VANILLA_TOOLTIP_DEFAULT_CLASS`: this tooltip is
39
+ * hoisted out of the tag, and that class's rule is scoped under `.ProseMirror` — see
40
+ * `resolveTooltipContainer`. Otherwise `vanillaTooltipDefaultStyles` in editor-core, which is the
41
+ * look every other vanilla tooltip has; keep the two in step.
42
+ */
43
+ export declare const CONTRIBUTOR_TAG_TOOLTIP_STYLES: Readonly<Record<string, string>>;
37
44
  export type ContributorTagDom = {
38
45
  avatars: HTMLSpanElement;
39
46
  name: HTMLSpanElement;
40
47
  root: HTMLSpanElement;
41
48
  /**
42
- * Announced after the name, which is the order design requires. `VanillaTooltip` appends its
43
- * popover to the tag as well, so this is the last *content* child rather than the last node.
49
+ * The tag's single announcement: it names the tag via `aria-labelledby` and is the only child
50
+ * left in the accessibility tree.
51
+ *
52
+ * `VanillaTooltip` appends its popover to the tag as well, so this is the last *content* child
53
+ * rather than the last node.
44
54
  */
45
55
  srLabel: HTMLSpanElement;
46
56
  tag: HTMLSpanElement;
@@ -0,0 +1,13 @@
1
+ import type { IntlShape } from 'react-intl';
2
+ import type { ContributorTagModel } from '../../showDiffPluginType';
3
+ export type ContributorLabel = {
4
+ /** One sentence crediting everyone the change is attributed to — the tag's announcement. */
5
+ fullLabel: string;
6
+ /** The name the tag paints, which `MAX_TAG_WIDTH` may ellipsise. */
7
+ visibleName: string;
8
+ };
9
+ /**
10
+ * The two strings a tag's model resolves to. Shared with `getActiveDiffAnnouncement`, so stepping
11
+ * onto a change says the same sentence as the tag pinned to it rather than a copy free to drift.
12
+ */
13
+ export declare const formatContributorLabel: (model: ContributorTagModel, formatMessage: IntlShape['formatMessage']) => ContributorLabel;
@@ -91,6 +91,17 @@ export declare class ContributorTagController {
91
91
  * shape of `syncVanillaDisabledTooltip` in `mentionNodeView`.
92
92
  */
93
93
  private syncTooltip;
94
+ /**
95
+ * Where the tooltip is appended, rather than inside the tag.
96
+ *
97
+ * Popper and the browser only agree on a top-layer popover's origin when nothing above it is
98
+ * transformed, and the tag hangs under the editor's own transformed nodes — inside a wide image
99
+ * that is `.rich-media-item`, whose `translateX(-50%)` threw the tooltip off screen
100
+ * (EDITOR-8971). The content area is the nearest ancestor with none of that above it, and keeps
101
+ * the tooltip inside the editor it belongs to; the document body covers an appearance that has
102
+ * no content area.
103
+ */
104
+ private resolveTooltipContainer;
94
105
  /**
95
106
  * Observes the highlight this tag describes, for what CSS here cannot see: its hover state. The
96
107
  * highlight is a ProseMirror decoration in a different DOM subtree, so listeners are bound
@@ -1,6 +1,7 @@
1
1
  import { type MessageDescriptor } from 'react-intl';
2
2
  export declare const contributorTagMessages: {
3
3
  changedBy: MessageDescriptor;
4
+ changedByAgent: MessageDescriptor;
4
5
  changedByConnected: MessageDescriptor;
5
6
  externalAgentName: MessageDescriptor;
6
7
  };
@@ -0,0 +1,18 @@
1
+ import type { IntlShape } from 'react-intl';
2
+ import type { Decoration } from '@atlaskit/editor-prosemirror/view';
3
+ import type { ContributorTagModel } from '../../showDiffPluginType';
4
+ /**
5
+ * What a screen reader should say once the reader has stepped onto a change: where they are in the
6
+ * diff, and — when the plugin can credit the change — who made it. Stepping otherwise only scrolls,
7
+ * which is silent.
8
+ *
9
+ * Returns `undefined` for an index that resolves no change, so the caller says nothing at all
10
+ * rather than "Change 4 of 3".
11
+ */
12
+ export declare const getActiveDiffAnnouncement: ({ activeIndex, contributorTags, decorations, intl, }: {
13
+ activeIndex: number;
14
+ contributorTags: ContributorTagModel[] | undefined;
15
+ /** The navigable changes, in document order — `getScrollableDecorations`'s result. */
16
+ decorations: Decoration[];
17
+ intl: IntlShape;
18
+ }) => string | undefined;
@@ -0,0 +1,5 @@
1
+ import { type MessageDescriptor } from 'react-intl';
2
+ export declare const diffNavigationMessages: {
3
+ activeChange: MessageDescriptor;
4
+ activeChangeWithContributor: MessageDescriptor;
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "16.0.1",
3
+ "version": "16.0.3",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -23,6 +23,7 @@
23
23
  "@atlaskit/adf-schema": "^57.4.0",
24
24
  "@atlaskit/avatar": "^28.0.0",
25
25
  "@atlaskit/custom-steps": "^1.1.0",
26
+ "@atlaskit/editor-plugin-accessibility-utils": "^18.0.0",
26
27
  "@atlaskit/editor-plugin-analytics": "^18.0.0",
27
28
  "@atlaskit/editor-plugin-user-intent": "^16.0.0",
28
29
  "@atlaskit/editor-prosemirror": "^8.0.0",
@@ -31,7 +32,7 @@
31
32
  "@atlaskit/platform-feature-experiments": "^0.3.0",
32
33
  "@atlaskit/platform-feature-flags": "^2.2.0",
33
34
  "@atlaskit/primitives": "^22.5.0",
34
- "@atlaskit/tmp-editor-statsig": "^188.0.0",
35
+ "@atlaskit/tmp-editor-statsig": "^190.0.0",
35
36
  "@atlaskit/tokens": "^16.12.0",
36
37
  "@babel/runtime": "^7.0.0",
37
38
  "@compiled/react": "^1.0.2",
@@ -45,7 +46,7 @@
45
46
  "@atlaskit/button": "^25.3.0",
46
47
  "@atlaskit/css": "^1.1.0",
47
48
  "@atlaskit/dropdown-menu": "^18.3.0",
48
- "@atlaskit/editor-core": "^228.0.0",
49
+ "@atlaskit/editor-core": "^228.1.0",
49
50
  "@atlaskit/editor-json-transformer": "^9.8.0",
50
51
  "@atlaskit/editor-plugins": "^16.1.0",
51
52
  "@atlaskit/form": "^17.2.0",
@@ -63,7 +64,7 @@
63
64
  "react-intl": "^7.0.0"
64
65
  },
65
66
  "peerDependencies": {
66
- "@atlaskit/editor-common": "^122.2.0",
67
+ "@atlaskit/editor-common": "^122.3.0",
67
68
  "react": "^18.2.0 || ^19.2.0",
68
69
  "react-dom": "^18.2.0 || ^19.2.0",
69
70
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"