@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
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
7
  exports.getMarkChangeRanges = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
7
9
  var _transform = require("@atlaskit/editor-prosemirror/transform");
10
+ var _attributions = require("../colorSchemes/attributions");
11
+ var _mapStepRangeToFinal = require("./mapStepRangeToFinal");
8
12
  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
13
  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
14
  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; }
@@ -28,13 +32,17 @@ var extractMarkStep = function extractMarkStep(step) {
28
32
  return undefined;
29
33
  };
30
34
  var getMarkChangeRanges = exports.getMarkChangeRanges = function getMarkChangeRanges(steps) {
35
+ var stepAttributions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
36
+ var stepMaps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
31
37
  var resultRanges = [];
32
38
  var lastOp;
33
- var _iterator = _createForOfIteratorHelper(steps),
39
+ var _iterator = _createForOfIteratorHelper(steps.entries()),
34
40
  _step;
35
41
  try {
36
42
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
37
- var step = _step.value;
43
+ var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
44
+ stepIndex = _step$value[0],
45
+ step = _step$value[1];
38
46
  var op = extractMarkStep(step);
39
47
  if (!op) {
40
48
  continue;
@@ -44,9 +52,15 @@ var getMarkChangeRanges = exports.getMarkChangeRanges = function getMarkChangeRa
44
52
  if (lastOp && lastOp.from === op.from && lastOp.to === op.to && lastOp.markName === op.markName && lastOp.type !== op.type) {
45
53
  resultRanges.pop();
46
54
  } else {
55
+ var finalRange = (0, _mapStepRangeToFinal.mapStepRangeToFinal)(op.from, op.to, stepIndex, stepMaps);
56
+ if (!finalRange) {
57
+ lastOp = op;
58
+ continue;
59
+ }
47
60
  resultRanges.push({
48
- fromB: op.from,
49
- toB: op.to
61
+ attributionKey: (0, _attributions.getAttributionKey)(stepAttributions[stepIndex]),
62
+ fromB: finalRange.fromB,
63
+ toB: finalRange.toB
50
64
  });
51
65
  }
52
66
  lastOp = op;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.mapStepRangeToFinal = void 0;
7
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
8
+ // ProseMirror's assoc value chooses which side of an insertion a mapped boundary stays on.
9
+ var FROM_ASSOC = -1;
10
+ var TO_ASSOC = 1;
11
+
12
+ /** Maps a step's range into final-document coordinates when the rollout path supplies step maps. */
13
+ var mapStepRangeToFinal = exports.mapStepRangeToFinal = function mapStepRangeToFinal(from, to, stepIndex, stepMaps) {
14
+ if (!stepMaps) {
15
+ return {
16
+ fromB: from,
17
+ toB: to
18
+ };
19
+ }
20
+
21
+ // The range is already in the document immediately after this step, so skip its own map and
22
+ // apply only the maps from subsequent steps.
23
+ var firstSubsequentStepIndex = stepIndex + 1;
24
+ var finalRange = new _transform.Mapping(stepMaps.slice(firstSubsequentStepIndex));
25
+ var fromB = finalRange.mapResult(from, FROM_ASSOC);
26
+ var toB = finalRange.mapResult(to, TO_ASSOC);
27
+ if (fromB.deleted && toB.deleted) {
28
+ return undefined;
29
+ }
30
+ return {
31
+ fromB: fromB.pos,
32
+ toB: toB.pos
33
+ };
34
+ };
@@ -12,6 +12,7 @@ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
12
12
  var _state2 = require("@atlaskit/editor-prosemirror/state");
13
13
  var _transformOverride = require("@atlaskit/editor-prosemirror/transform-override");
14
14
  var _view = require("@atlaskit/editor-prosemirror/view");
15
+ var _announceActiveDiff = require("../ui/DiffNavigation/announceActiveDiff");
15
16
  var _calculateDiffDecorations = require("./calculateDiff/calculateDiffDecorations");
16
17
  var _decorationKeys = require("./decorations/decorationKeys");
17
18
  var _enforceCustomStepRegisters = require("./enforceCustomStepRegisters");
@@ -279,6 +280,23 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
279
280
  api === null || api === void 0 || api.core.actions.execute((0, _expand.toggleExpandRange)(activeDecoration.from, activeDecoration.to, true));
280
281
  }
281
282
  cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToDecoration)(view, scrollableDecorations, pluginState.activeIndex);
283
+
284
+ // Stepping only scrolls — no focus moves and no content changes — so without this a
285
+ // screen-reader user gets silence. Announced through the live region rather than by
286
+ // focusing the change, since the "next change" control has to stay focused to be
287
+ // pressed again. After the scroll above, so the announcement never precedes it.
288
+ if ((0, _isExtendedEnabled.isExtendedEnabled)(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
289
+ var announcement = (0, _announceActiveDiff.getActiveDiffAnnouncement)({
290
+ activeIndex: pluginState.activeIndex,
291
+ contributorTags: pluginState.contributorTags,
292
+ decorations: scrollableDecorations,
293
+ intl: getIntl()
294
+ });
295
+ if (announcement) {
296
+ var _api$accessibilityUti;
297
+ api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(announcement);
298
+ }
299
+ }
282
300
  }
283
301
  },
284
302
  destroy: function destroy() {
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.buildContributorTagDom = exports.TAG_EXIT_FALLBACK_MS = exports.MAX_TAG_WIDTH = exports.CONTRIBUTOR_TAG_Z_INDEX = exports.CONTRIBUTOR_TAG_TESTID = exports.CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = exports.CONTRIBUTOR_TAG_NAME_TESTID = exports.CONTRIBUTOR_TAG_CLASS = void 0;
7
+ exports.buildContributorTagDom = exports.TAG_EXIT_FALLBACK_MS = exports.MAX_TAG_WIDTH = exports.CONTRIBUTOR_TAG_Z_INDEX = exports.CONTRIBUTOR_TAG_TOOLTIP_STYLES = exports.CONTRIBUTOR_TAG_TESTID = exports.CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = exports.CONTRIBUTOR_TAG_NAME_TESTID = exports.CONTRIBUTOR_TAG_CLASS = void 0;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
10
10
  var CONTRIBUTOR_TAG_TESTID = exports.CONTRIBUTOR_TAG_TESTID = 'diff-contributor-tag';
@@ -49,6 +49,30 @@ var CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = exports.CONTRIBUTOR_TAG_REVEALED_ATTRIB
49
49
  */
50
50
  var TAG_EXIT_FALLBACK_MS = exports.TAG_EXIT_FALLBACK_MS = 1200;
51
51
 
52
+ /**
53
+ * The tooltip's look, inline rather than through `VANILLA_TOOLTIP_DEFAULT_CLASS`: this tooltip is
54
+ * hoisted out of the tag, and that class's rule is scoped under `.ProseMirror` — see
55
+ * `resolveTooltipContainer`. Otherwise `vanillaTooltipDefaultStyles` in editor-core, which is the
56
+ * look every other vanilla tooltip has; keep the two in step.
57
+ */
58
+ var CONTRIBUTOR_TAG_TOOLTIP_STYLES = exports.CONTRIBUTOR_TAG_TOOLTIP_STYLES = {
59
+ boxSizing: 'border-box',
60
+ maxWidth: '240px',
61
+ backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
62
+ // A `popover` is given one by the UA stylesheet.
63
+ border: 'none',
64
+ borderRadius: "var(--ds-radius-small, 3px)",
65
+ color: "var(--ds-text-inverse, #FFFFFF)",
66
+ 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)",
67
+ fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
68
+ overflowWrap: 'break-word',
69
+ paddingBlock: "var(--ds-space-050, 4px)",
70
+ paddingInline: "var(--ds-space-075, 6px)",
71
+ whiteSpace: 'normal',
72
+ // A tooltip is never a hit target — it hangs over the document's own content.
73
+ pointerEvents: 'none'
74
+ };
75
+
52
76
  // Positioned against the host widget its decoration renders on the change's first character:
53
77
  // `bottom: 100%` lifts the tag onto the line above, `inset-inline-start` starts it on that
54
78
  // character.
@@ -132,6 +156,19 @@ var srLabelStyle = (0, _lazyNodeView.convertToInlineCss)({
132
156
  userSelect: 'none',
133
157
  whiteSpace: 'nowrap'
134
158
  });
159
+
160
+ /**
161
+ * Ids for the hidden label that the tag's `aria-labelledby` points at. Namespaced and random because
162
+ * the tag renders inside the document and shares the host page's id space — the same reason
163
+ * `VanillaTooltip` does it this way. The counter is a fallback for non-secure contexts, where
164
+ * `crypto.randomUUID` is unavailable.
165
+ */
166
+ var generateLabelId = function () {
167
+ var count = 0;
168
+ return function () {
169
+ return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "".concat(CONTRIBUTOR_TAG_CLASS, "-label-").concat(crypto.randomUUID()) : "".concat(CONTRIBUTOR_TAG_CLASS, "-label-").concat(count += 1);
170
+ };
171
+ }();
135
172
  var createSpan = function createSpan(doc, style) {
136
173
  var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
137
174
  var span = doc.createElement('span');
@@ -151,21 +188,30 @@ var createSpan = function createSpan(doc, style) {
151
188
  * would need a cast per reference below.
152
189
  */
153
190
  var buildContributorTagDom = exports.buildContributorTagDom = function buildContributorTagDom(doc) {
191
+ var labelId = generateLabelId();
154
192
  var root = createSpan(doc, constraintStyle);
155
193
  var tag = createSpan(doc, tagStyle, {
156
194
  class: CONTRIBUTOR_TAG_CLASS,
157
195
  'data-testid': CONTRIBUTOR_TAG_TESTID,
158
- // Deliberately no `aria-label`: it would be announced ahead of the element's contents, but
159
- // design requires the contributor *after* the change — hence the visually hidden label.
196
+ // Named by the hidden label rather than `aria-label`, which would be a second copy of the same
197
+ // sentence. `role="note"` takes its name from the author, not its contents, so without this
198
+ // the tag is a focus stop with no accessible name.
199
+ 'aria-labelledby': labelId,
160
200
  role: 'note',
161
- // Focusable so keyboard and screen-reader users can reach the tag and its tooltip.
201
+ // Focusable so keyboard and screen-reader users can reach the tag and its tooltip. Its focus
202
+ // ring is drawn inward — see `contributorTagStyles` in editor-core.
162
203
  tabindex: '0'
163
204
  });
164
205
  var avatars = createSpan(doc, avatarsStyle);
206
+ // `aria-hidden`: the hidden label restates this name in a full sentence, so leaving it in the
207
+ // tree announced the contributor twice ("Priya Changed by Priya").
165
208
  var name = createSpan(doc, nameStyle, {
209
+ 'aria-hidden': 'true',
166
210
  'data-testid': CONTRIBUTOR_TAG_NAME_TESTID
167
211
  });
168
- var srLabel = createSpan(doc, srLabelStyle);
212
+ var srLabel = createSpan(doc, srLabelStyle, {
213
+ id: labelId
214
+ });
169
215
  tag.append(avatars, name, srLabel);
170
216
  root.appendChild(tag);
171
217
  return {
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.formatContributorLabel = void 0;
7
+ var _messages = require("./messages");
8
+ var getContributorName = function getContributorName(contributor, formatMessage) {
9
+ var _contributor$name, _contributor$agentKin;
10
+ var name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
11
+ if (name) {
12
+ return name;
13
+ }
14
+
15
+ // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
16
+ // agent falls back to a generic label rather than rendering a nameless tag.
17
+ if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
18
+ return formatMessage(_messages.contributorTagMessages.externalAgentName);
19
+ }
20
+ return '';
21
+ };
22
+
23
+ /**
24
+ * Whether the label has to spell out that the contributor is an agent. A sighted user reads that off
25
+ * the `aria-hidden` avatar, so the label is the only other carrier.
26
+ *
27
+ * Excluded: a connected pair, which says it via `changedByConnected`, and an unnamed external agent,
28
+ * whose name `getContributorName` has already resolved to "External agent".
29
+ */
30
+ var isUnaccompaniedNamedAgent = function isUnaccompaniedNamedAgent(model) {
31
+ var _model$contributor$na;
32
+ 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());
33
+ };
34
+ /**
35
+ * The two strings a tag's model resolves to. Shared with `getActiveDiffAnnouncement`, so stepping
36
+ * onto a change says the same sentence as the tag pinned to it rather than a copy free to drift.
37
+ */
38
+ var formatContributorLabel = exports.formatContributorLabel = function formatContributorLabel(model, formatMessage) {
39
+ var isPrimaryAgent = model.contributor.kind === 'agent';
40
+ var primaryName = getContributorName(model.contributor, formatMessage);
41
+ var secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
42
+ var agentName = isPrimaryAgent ? primaryName : secondaryName;
43
+ var userName = isPrimaryAgent ? secondaryName : primaryName;
44
+ return {
45
+ fullLabel: model.connectedContributor ? formatMessage(_messages.contributorTagMessages.changedByConnected, {
46
+ agentName: agentName,
47
+ userName: userName
48
+ }) : formatMessage(isUnaccompaniedNamedAgent(model) ? _messages.contributorTagMessages.changedByAgent : _messages.contributorTagMessages.changedBy, {
49
+ name: primaryName
50
+ }),
51
+ // A connected pair only ever shows the agent; the pair is spelled out in `fullLabel`, because
52
+ // the tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
53
+ visibleName: model.connectedContributor ? agentName || primaryName : primaryName
54
+ };
55
+ };
@@ -15,22 +15,7 @@ var _factory = require("../../pm-plugins/decorations/colorSchemes/factory");
15
15
  var _schemes = require("../../pm-plugins/decorations/colorSchemes/schemes");
16
16
  var _buildContributorTagDom = require("./buildContributorTagDom");
17
17
  var _contributorAvatarRenderer = require("./contributorAvatarRenderer");
18
- var _messages = require("./messages");
19
- var getContributorName = function getContributorName(contributor, formatMessage) {
20
- var _contributor$name, _contributor$agentKin;
21
- var name = (_contributor$name = contributor.name) === null || _contributor$name === void 0 ? void 0 : _contributor$name.trim();
22
- if (name) {
23
- return name;
24
- }
25
-
26
- // `agentKind` defaults to `'external'` per the plugin type contract; an unidentified external
27
- // agent falls back to a generic label rather than rendering a nameless tag.
28
- if (contributor.kind === 'agent' && ((_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external') === 'external') {
29
- return formatMessage(_messages.contributorTagMessages.externalAgentName);
30
- }
31
- return '';
32
- };
33
-
18
+ var _contributorLabel = require("./contributorLabel");
34
19
  /**
35
20
  * Resolves the tag's accent from the same scheme value the highlight was drawn from, so the colour
36
21
  * the two carry can never drift — only the tone does, which is `getAccentTokens`'s to pick.
@@ -113,8 +98,8 @@ var fadeTagOut = function fadeTagOut(tag, release) {
113
98
  /** The contributors a tag draws, keyed so a republished model can be recognised as the same pair. */
114
99
  var identityOf = function identityOf(model) {
115
100
  return [model === null || model === void 0 ? void 0 : model.contributor, model === null || model === void 0 ? void 0 : model.connectedContributor].map(function (contributor) {
116
- var _contributor$agentKin2, _contributor$avatarUr;
117
- 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 : '') : '';
101
+ var _contributor$agentKin, _contributor$avatarUr;
102
+ 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 : '') : '';
118
103
  }).join('|');
119
104
  };
120
105
 
@@ -355,24 +340,12 @@ var ContributorTagController = exports.ContributorTagController = /*#__PURE__*/f
355
340
  if (!this.dom) {
356
341
  return;
357
342
  }
358
- var _this$options$getIntl = this.options.getIntl(),
359
- formatMessage = _this$options$getIntl.formatMessage;
360
- var isPrimaryAgent = model.contributor.kind === 'agent';
361
- var primaryName = getContributorName(model.contributor, formatMessage);
362
- var secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
363
- var agentName = isPrimaryAgent ? primaryName : secondaryName;
364
- var userName = isPrimaryAgent ? secondaryName : primaryName;
365
-
366
- // A connected pair only ever shows the agent; the pair is spelled out in the label, because the
367
- // tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
368
- this.dom.name.textContent = model.connectedContributor ? agentName || primaryName : primaryName;
369
- this.fullLabel = model.connectedContributor ? formatMessage(_messages.contributorTagMessages.changedByConnected, {
370
- agentName: agentName,
371
- userName: userName
372
- }) : formatMessage(_messages.contributorTagMessages.changedBy, {
373
- name: primaryName
374
- });
375
- this.dom.srLabel.textContent = this.fullLabel;
343
+ var _formatContributorLab = (0, _contributorLabel.formatContributorLabel)(model, this.options.getIntl().formatMessage),
344
+ fullLabel = _formatContributorLab.fullLabel,
345
+ visibleName = _formatContributorLab.visibleName;
346
+ this.dom.name.textContent = visibleName;
347
+ this.fullLabel = fullLabel;
348
+ this.dom.srLabel.textContent = fullLabel;
376
349
  }
377
350
  }, {
378
351
  key: "accentOf",
@@ -438,15 +411,39 @@ var ContributorTagController = exports.ContributorTagController = /*#__PURE__*/f
438
411
  if (!content || !this.dom) {
439
412
  return;
440
413
  }
441
- this.tooltip = new _vanillaTooltip.VanillaTooltip(this.dom.tag, content, undefined,
442
- // `ak-editor-vanilla-tooltip-default` opts into the shared `VanillaTooltip` look, defined as
443
- // `vanillaTooltipDefaultStyles` in both EditorContentContainer stylesheets — keep in sync when
444
- // renaming. Those styles are scoped under `.ProseMirror`, and the tag renders inside it.
445
- 'ak-editor-vanilla-tooltip-default');
414
+ this.tooltip = new _vanillaTooltip.VanillaTooltip(this.dom.tag, content,
415
+ // Generated id.
416
+ undefined,
417
+ // No class: the look is inline, because a hoisted tooltip is outside the `.ProseMirror`
418
+ // scope `VANILLA_TOOLTIP_DEFAULT_CLASS` is keyed on — see `CONTRIBUTOR_TAG_TOOLTIP_STYLES`.
419
+ '',
420
+ // Default delay, no `onShow`, and the default `top` placement.
421
+ undefined, _buildContributorTagDom.CONTRIBUTOR_TAG_TOOLTIP_STYLES, undefined, undefined, this.resolveTooltipContainer());
446
422
  // `VanillaTooltip` sets `aria-describedby` on its trigger, which would announce the label a
447
- // second time — the visually hidden child already announces it, after the change as design
448
- // requires.
423
+ // second time — the hidden child already announces it, and names the tag with it.
449
424
  this.dom.tag.removeAttribute('aria-describedby');
425
+ // Out of the tag but still in the document, so its text would otherwise be a third copy of the
426
+ // same sentence. It stays visible for sighted users, who need the part of the name the tag
427
+ // clipped.
428
+ this.tooltip.element.setAttribute('aria-hidden', 'true');
429
+ }
430
+
431
+ /**
432
+ * Where the tooltip is appended, rather than inside the tag.
433
+ *
434
+ * Popper and the browser only agree on a top-layer popover's origin when nothing above it is
435
+ * transformed, and the tag hangs under the editor's own transformed nodes — inside a wide image
436
+ * that is `.rich-media-item`, whose `translateX(-50%)` threw the tooltip off screen
437
+ * (EDITOR-8971). The content area is the nearest ancestor with none of that above it, and keeps
438
+ * the tooltip inside the editor it belongs to; the document body covers an appearance that has
439
+ * no content area.
440
+ */
441
+ }, {
442
+ key: "resolveTooltipContainer",
443
+ value: function resolveTooltipContainer() {
444
+ var _editorRoot$closest;
445
+ var editorRoot = this.options.getEditorRoot();
446
+ 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;
450
447
  }
451
448
 
452
449
  /**
@@ -11,6 +11,11 @@ var contributorTagMessages = exports.contributorTagMessages = (0, _reactIntl.def
11
11
  defaultMessage: 'Changed by {name}',
12
12
  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.'
13
13
  },
14
+ changedByAgent: {
15
+ id: 'editor-plugin-show-diff.ContributorTag.changedByAgent',
16
+ defaultMessage: 'Changed by {name}, an AI agent',
17
+ 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.'
18
+ },
14
19
  changedByConnected: {
15
20
  id: 'editor-plugin-show-diff.ContributorTag.changedByConnected',
16
21
  defaultMessage: 'Changed by {agentName} with {userName}',
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.getActiveDiffAnnouncement = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _decorationKeys = require("../../pm-plugins/decorations/decorationKeys");
10
+ var _contributorLabel = require("../ContributorTag/contributorLabel");
11
+ var _messages = require("./messages");
12
+ 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; }
13
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
14
+ /**
15
+ * The tag that captions the given decoration, if any. `linkedDiffIds` is checked as well as
16
+ * `diffId`, because a replacement's two decorations share one tag and navigation can land on either.
17
+ */
18
+ var findTagFor = function findTagFor(decoration, contributorTags) {
19
+ if (!(contributorTags !== null && contributorTags !== void 0 && contributorTags.length) || !(0, _decorationKeys.isDiffDecoration)(decoration)) {
20
+ return undefined;
21
+ }
22
+ var diffId = decoration.spec.diffId;
23
+ return contributorTags.find(function (tag) {
24
+ var _tag$linkedDiffIds;
25
+ return tag.diffId === diffId || ((_tag$linkedDiffIds = tag.linkedDiffIds) === null || _tag$linkedDiffIds === void 0 ? void 0 : _tag$linkedDiffIds.includes(diffId));
26
+ });
27
+ };
28
+
29
+ /**
30
+ * What a screen reader should say once the reader has stepped onto a change: where they are in the
31
+ * diff, and — when the plugin can credit the change — who made it. Stepping otherwise only scrolls,
32
+ * which is silent.
33
+ *
34
+ * Returns `undefined` for an index that resolves no change, so the caller says nothing at all
35
+ * rather than "Change 4 of 3".
36
+ */
37
+ var getActiveDiffAnnouncement = exports.getActiveDiffAnnouncement = function getActiveDiffAnnouncement(_ref) {
38
+ var activeIndex = _ref.activeIndex,
39
+ contributorTags = _ref.contributorTags,
40
+ decorations = _ref.decorations,
41
+ intl = _ref.intl;
42
+ var activeDecoration = decorations[activeIndex];
43
+ if (!activeDecoration) {
44
+ return undefined;
45
+ }
46
+ var position = {
47
+ index: activeIndex + 1,
48
+ total: decorations.length
49
+ };
50
+ var tag = findTagFor(activeDecoration, contributorTags);
51
+ return tag ? intl.formatMessage(_messages.diffNavigationMessages.activeChangeWithContributor, _objectSpread(_objectSpread({}, position), {}, {
52
+ contributor: (0, _contributorLabel.formatContributorLabel)(tag, intl.formatMessage).fullLabel
53
+ })) : intl.formatMessage(_messages.diffNavigationMessages.activeChange, position);
54
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.diffNavigationMessages = void 0;
7
+ var _reactIntl = require("react-intl");
8
+ var diffNavigationMessages = exports.diffNavigationMessages = (0, _reactIntl.defineMessages)({
9
+ activeChange: {
10
+ id: 'editor-plugin-show-diff.DiffNavigation.activeChange',
11
+ defaultMessage: 'Change {index} of {total}',
12
+ 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.'
13
+ },
14
+ activeChangeWithContributor: {
15
+ id: 'editor-plugin-show-diff.DiffNavigation.activeChangeWithContributor',
16
+ defaultMessage: 'Change {index} of {total}. {contributor}',
17
+ 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.'
18
+ }
19
+ });
@@ -17,6 +17,7 @@ import { extractDiffDescriptors } from '../decorations/decorationKeys';
17
17
  import { extractContributorTags } from '../decorations/extractContributorTags';
18
18
  import { getAttrChangeRanges, stepIsValidAttrChange } from '../decorations/utils/getAttrChangeRanges';
19
19
  import { getMarkChangeRanges } from '../decorations/utils/getMarkChangeRanges';
20
+ import { getScrollableDecorations } from '../getScrollableDecorations';
20
21
  import { getDefaultDiffType, isExtendedEnabled } from '../isExtendedEnabled';
21
22
  import { diffBySteps } from './diffBySteps';
22
23
  import { groupChangesByBlock } from './groupChangesByBlock';
@@ -316,11 +317,12 @@ const calculateDiffDecorationsInner = ({
316
317
  }
317
318
  const pendingAttrSteps = [];
318
319
  const stepMaps = [];
319
- for (const step of simplifiedSteps) {
320
+ for (const [stepIndex, step] of simplifiedSteps.entries()) {
320
321
  const result = step.apply(steppedDoc);
321
322
  if (result.failed === null && result.doc) {
322
323
  if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
323
324
  pendingAttrSteps.push({
325
+ attributionKey: getAttributionKey(simplifiedStepAttributions[stepIndex]),
324
326
  afterNode: result.doc.nodeAt(step.pos),
325
327
  beforeNode: steppedDoc.nodeAt(step.pos),
326
328
  mapIndex: stepMaps.length,
@@ -335,6 +337,7 @@ const calculateDiffDecorationsInner = ({
335
337
  // The fallback deliberately preserves the legacy raw-position lookups for rollout safety.
336
338
  const attrStepContexts = fg('platform_editor_reduce_diff_attr_sensitivity') ? pendingAttrSteps.flatMap(({
337
339
  afterNode,
340
+ attributionKey,
338
341
  beforeNode,
339
342
  mapIndex,
340
343
  step
@@ -345,6 +348,7 @@ const calculateDiffDecorationsInner = ({
345
348
  }
346
349
  const originalPosition = new Mapping(stepMaps.slice(0, mapIndex)).invert().mapResult(step.pos);
347
350
  return [{
351
+ attributionKey,
348
352
  afterNode,
349
353
  beforeNode,
350
354
  finalPos: finalPosition.pos,
@@ -354,8 +358,10 @@ const calculateDiffDecorationsInner = ({
354
358
  step
355
359
  }];
356
360
  }) : pendingAttrSteps.map(({
361
+ attributionKey,
357
362
  step
358
363
  }) => ({
364
+ attributionKey,
359
365
  afterNode: tr.doc.nodeAt(step.pos),
360
366
  beforeNode: originalDoc.nodeAt(step.pos),
361
367
  finalPos: step.pos,
@@ -608,31 +614,48 @@ const calculateDiffDecorationsInner = ({
608
614
  // smart path. Only add the legacy step-range decoration when no equivalent changeset range was
609
615
  // produced; otherwise the same content receives two inline decorations (and two navigation
610
616
  // changes), usually one attribution colour and one default purple decoration.
611
- getMarkChangeRanges(steps).filter(({
617
+ getMarkChangeRanges(simplifiedSteps, simplifiedStepAttributions, fg('confluence_ncs_step_diffing_version_history') ? stepMaps : undefined).filter(({
612
618
  fromB,
613
619
  toB
614
620
  }) => !fg('confluence_ncs_step_diffing_version_history') || !changes.some(change => change.fromB === fromB && change.toB === toB)).forEach(change => {
621
+ var _attributionColors$ge, _attributionColors;
622
+ const changeColorScheme = change.attributionKey ? (_attributionColors$ge = (_attributionColors = attributionColors) === null || _attributionColors === void 0 ? void 0 : _attributionColors.get(change.attributionKey)) !== null && _attributionColors$ge !== void 0 ? _attributionColors$ge : colorScheme : colorScheme;
623
+ const attributionKey = showContributorTags ? change.attributionKey : undefined;
615
624
  const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
616
625
  decorations.push(...createInlineChangedDecoration({
626
+ attributionKey,
617
627
  change,
618
- colorScheme,
628
+ colorScheme: changeColorScheme,
629
+ doc: tr.doc,
619
630
  diffType,
620
631
  isActive,
621
632
  isInserted: true,
622
- reveal
633
+ reveal,
634
+ showContributorTags,
635
+ tagMountContext
623
636
  }));
624
637
  });
625
- getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).forEach(change => {
638
+ getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).filter(({
639
+ fromB,
640
+ toB
641
+ }) => !fg('confluence_ncs_step_diffing_version_history') || !changes.some(change => change.fromB === fromB && change.toB === toB)).forEach(change => {
642
+ var _attributionColors$ge2, _attributionColors2;
643
+ const changeColorScheme = change.attributionKey ? (_attributionColors$ge2 = (_attributionColors2 = attributionColors) === null || _attributionColors2 === void 0 ? void 0 : _attributionColors2.get(change.attributionKey)) !== null && _attributionColors$ge2 !== void 0 ? _attributionColors$ge2 : colorScheme : colorScheme;
644
+ const attributionKey = showContributorTags ? change.attributionKey : undefined;
626
645
  if (change.isInline) {
627
646
  // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
628
647
  const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
629
648
  decorations.push(...createInlineChangedDecoration({
649
+ attributionKey,
630
650
  change,
631
- colorScheme,
651
+ colorScheme: changeColorScheme,
652
+ doc: tr.doc,
632
653
  isActive,
633
654
  isInserted: true,
634
655
  isAtomicInlineNode: true,
635
656
  inlineNodeName: change.inlineNodeName,
657
+ showContributorTags,
658
+ tagMountContext,
636
659
  // Suppress the border-bottom underline for atomic inline nodeviews —
637
660
  // it doesn't render on custom nodeview DOM elements and is unnecessary noise.
638
661
  hideAddedDiffsUnderline: true,
@@ -650,12 +673,15 @@ const calculateDiffDecorationsInner = ({
650
673
  deleted: []
651
674
  },
652
675
  doc: originalDoc,
676
+ attributionKey,
653
677
  nodeViewSerializer,
654
- colorScheme,
678
+ colorScheme: changeColorScheme,
655
679
  newDoc: tr.doc,
656
680
  intl,
657
681
  activeIndexPos,
658
- isInserted: false
682
+ isInserted: false,
683
+ showContributorTags,
684
+ tagMountContext
659
685
  }));
660
686
  }
661
687
  } else {
@@ -663,11 +689,14 @@ const calculateDiffDecorationsInner = ({
663
689
  doc: tr.doc,
664
690
  from: change.fromB,
665
691
  to: change.toB,
666
- colorScheme,
692
+ attributionKey,
693
+ colorScheme: changeColorScheme,
667
694
  isInserted: true,
668
695
  activeIndexPos,
669
696
  intl,
670
- showIndicators
697
+ showContributorTags,
698
+ showIndicators,
699
+ tagMountContext
671
700
  }));
672
701
  // If the original node position is known (e.g. a panel type change), also render
673
702
  // the old block node as a "deleted" widget so the reviewer sees the before/after.
@@ -684,7 +713,8 @@ const calculateDiffDecorationsInner = ({
684
713
  },
685
714
  doc: originalDoc,
686
715
  nodeViewSerializer,
687
- colorScheme,
716
+ attributionKey,
717
+ colorScheme: changeColorScheme,
688
718
  newDoc: tr.doc,
689
719
  intl,
690
720
  activeIndexPos,
@@ -694,14 +724,19 @@ const calculateDiffDecorationsInner = ({
694
724
  hideAddedDiffsUnderline,
695
725
  placeBelow
696
726
  }),
697
- showIndicators
727
+ showIndicators,
728
+ showContributorTags,
729
+ tagMountContext
698
730
  }));
699
731
  }
700
732
  }
701
733
  });
702
734
  const decorationSet = DecorationSet.empty.add(tr.doc, decorations);
703
735
  return {
704
- contributorTags: showContributorTags ? extractContributorTags(decorationSet, contributors) : [],
736
+ contributorTags: showContributorTags ? extractContributorTags(decorationSet, contributors, activeIndexPos,
737
+ // The stops the step buttons walk, so a stop cannot hold a second tag for one
738
+ // contributor that navigation can never reach.
739
+ getScrollableDecorations(decorationSet, tr.doc, diffType)) : [],
705
740
  decorations: decorationSet,
706
741
  diffDescriptors: extractDiffDescriptors(decorationSet)
707
742
  };