@atlaskit/editor-plugin-show-diff 16.0.0 → 16.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +86 -50
- package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +1 -1
- package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +19 -8
- package/dist/cjs/pm-plugins/decorations/utils/getMarkChangeRanges.js +18 -4
- package/dist/cjs/pm-plugins/decorations/utils/mapStepRangeToFinal.js +34 -0
- package/dist/cjs/pm-plugins/main.js +18 -0
- package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +26 -4
- package/dist/cjs/ui/ContributorTag/contributorLabel.js +55 -0
- package/dist/cjs/ui/ContributorTag/contributorTagController.js +14 -39
- package/dist/cjs/ui/ContributorTag/messages.js +5 -0
- package/dist/cjs/ui/DiffNavigation/announceActiveDiff.js +54 -0
- package/dist/cjs/ui/DiffNavigation/messages.js +19 -0
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +43 -12
- package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +1 -1
- package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +16 -0
- package/dist/es2019/pm-plugins/decorations/utils/getMarkChangeRanges.js +12 -4
- package/dist/es2019/pm-plugins/decorations/utils/mapStepRangeToFinal.js +28 -0
- package/dist/es2019/pm-plugins/main.js +18 -0
- package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +24 -4
- package/dist/es2019/ui/ContributorTag/contributorLabel.js +49 -0
- package/dist/es2019/ui/ContributorTag/contributorTagController.js +14 -39
- package/dist/es2019/ui/ContributorTag/messages.js +5 -0
- package/dist/es2019/ui/DiffNavigation/announceActiveDiff.js +49 -0
- package/dist/es2019/ui/DiffNavigation/messages.js +13 -0
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +86 -50
- package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +1 -1
- package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +19 -8
- package/dist/esm/pm-plugins/decorations/utils/getMarkChangeRanges.js +17 -4
- package/dist/esm/pm-plugins/decorations/utils/mapStepRangeToFinal.js +28 -0
- package/dist/esm/pm-plugins/main.js +18 -0
- package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +26 -4
- package/dist/esm/ui/ContributorTag/contributorLabel.js +49 -0
- package/dist/esm/ui/ContributorTag/contributorTagController.js +14 -39
- package/dist/esm/ui/ContributorTag/messages.js +5 -0
- package/dist/esm/ui/DiffNavigation/announceActiveDiff.js +48 -0
- package/dist/esm/ui/DiffNavigation/messages.js +13 -0
- package/dist/types/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +3 -0
- package/dist/types/pm-plugins/decorations/utils/getMarkChangeRanges.d.ts +4 -1
- package/dist/types/pm-plugins/decorations/utils/mapStepRangeToFinal.d.ts +8 -0
- package/dist/types/showDiffPluginType.d.ts +7 -1
- package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +5 -2
- package/dist/types/ui/ContributorTag/contributorLabel.d.ts +13 -0
- package/dist/types/ui/ContributorTag/messages.d.ts +1 -0
- package/dist/types/ui/DiffNavigation/announceActiveDiff.d.ts +18 -0
- package/dist/types/ui/DiffNavigation/messages.d.ts +5 -0
- package/package.json +5 -4
|
@@ -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
|
|
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$
|
|
117
|
-
return contributor ? "".concat(contributor.kind, ":").concat((_contributor$
|
|
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
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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",
|
|
@@ -427,7 +400,7 @@ var ContributorTagController = exports.ContributorTagController = /*#__PURE__*/f
|
|
|
427
400
|
}, {
|
|
428
401
|
key: "syncTooltip",
|
|
429
402
|
value: function syncTooltip() {
|
|
430
|
-
var _this$tooltip2;
|
|
403
|
+
var _this$tooltip2, _this$dom$tag$querySe;
|
|
431
404
|
var content = this.dom ? this.fullLabel : undefined;
|
|
432
405
|
if (content === this.tooltipContent) {
|
|
433
406
|
return;
|
|
@@ -444,9 +417,11 @@ var ContributorTagController = exports.ContributorTagController = /*#__PURE__*/f
|
|
|
444
417
|
// renaming. Those styles are scoped under `.ProseMirror`, and the tag renders inside it.
|
|
445
418
|
'ak-editor-vanilla-tooltip-default');
|
|
446
419
|
// `VanillaTooltip` sets `aria-describedby` on its trigger, which would announce the label a
|
|
447
|
-
// second time — the
|
|
448
|
-
// requires.
|
|
420
|
+
// second time — the hidden child already announces it, and names the tag with it.
|
|
449
421
|
this.dom.tag.removeAttribute('aria-describedby');
|
|
422
|
+
// The popover is a child of the trigger, so its text would be a third copy of the same
|
|
423
|
+
// sentence. It stays visible for sighted users, who need the part of the name the tag clipped.
|
|
424
|
+
(_this$dom$tag$querySe = this.dom.tag.querySelector(':scope > [role="tooltip"]')) === null || _this$dom$tag$querySe === void 0 || _this$dom$tag$querySe.setAttribute('aria-hidden', 'true');
|
|
450
425
|
}
|
|
451
426
|
|
|
452
427
|
/**
|
|
@@ -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
|
+
});
|
|
@@ -316,11 +316,12 @@ const calculateDiffDecorationsInner = ({
|
|
|
316
316
|
}
|
|
317
317
|
const pendingAttrSteps = [];
|
|
318
318
|
const stepMaps = [];
|
|
319
|
-
for (const step of simplifiedSteps) {
|
|
319
|
+
for (const [stepIndex, step] of simplifiedSteps.entries()) {
|
|
320
320
|
const result = step.apply(steppedDoc);
|
|
321
321
|
if (result.failed === null && result.doc) {
|
|
322
322
|
if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
|
|
323
323
|
pendingAttrSteps.push({
|
|
324
|
+
attributionKey: getAttributionKey(simplifiedStepAttributions[stepIndex]),
|
|
324
325
|
afterNode: result.doc.nodeAt(step.pos),
|
|
325
326
|
beforeNode: steppedDoc.nodeAt(step.pos),
|
|
326
327
|
mapIndex: stepMaps.length,
|
|
@@ -335,6 +336,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
335
336
|
// The fallback deliberately preserves the legacy raw-position lookups for rollout safety.
|
|
336
337
|
const attrStepContexts = fg('platform_editor_reduce_diff_attr_sensitivity') ? pendingAttrSteps.flatMap(({
|
|
337
338
|
afterNode,
|
|
339
|
+
attributionKey,
|
|
338
340
|
beforeNode,
|
|
339
341
|
mapIndex,
|
|
340
342
|
step
|
|
@@ -345,6 +347,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
345
347
|
}
|
|
346
348
|
const originalPosition = new Mapping(stepMaps.slice(0, mapIndex)).invert().mapResult(step.pos);
|
|
347
349
|
return [{
|
|
350
|
+
attributionKey,
|
|
348
351
|
afterNode,
|
|
349
352
|
beforeNode,
|
|
350
353
|
finalPos: finalPosition.pos,
|
|
@@ -354,8 +357,10 @@ const calculateDiffDecorationsInner = ({
|
|
|
354
357
|
step
|
|
355
358
|
}];
|
|
356
359
|
}) : pendingAttrSteps.map(({
|
|
360
|
+
attributionKey,
|
|
357
361
|
step
|
|
358
362
|
}) => ({
|
|
363
|
+
attributionKey,
|
|
359
364
|
afterNode: tr.doc.nodeAt(step.pos),
|
|
360
365
|
beforeNode: originalDoc.nodeAt(step.pos),
|
|
361
366
|
finalPos: step.pos,
|
|
@@ -608,31 +613,48 @@ const calculateDiffDecorationsInner = ({
|
|
|
608
613
|
// smart path. Only add the legacy step-range decoration when no equivalent changeset range was
|
|
609
614
|
// produced; otherwise the same content receives two inline decorations (and two navigation
|
|
610
615
|
// changes), usually one attribution colour and one default purple decoration.
|
|
611
|
-
getMarkChangeRanges(
|
|
616
|
+
getMarkChangeRanges(simplifiedSteps, simplifiedStepAttributions, fg('confluence_ncs_step_diffing_version_history') ? stepMaps : undefined).filter(({
|
|
612
617
|
fromB,
|
|
613
618
|
toB
|
|
614
619
|
}) => !fg('confluence_ncs_step_diffing_version_history') || !changes.some(change => change.fromB === fromB && change.toB === toB)).forEach(change => {
|
|
620
|
+
var _attributionColors$ge, _attributionColors;
|
|
621
|
+
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;
|
|
622
|
+
const attributionKey = showContributorTags ? change.attributionKey : undefined;
|
|
615
623
|
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
616
624
|
decorations.push(...createInlineChangedDecoration({
|
|
625
|
+
attributionKey,
|
|
617
626
|
change,
|
|
618
|
-
colorScheme,
|
|
627
|
+
colorScheme: changeColorScheme,
|
|
628
|
+
doc: tr.doc,
|
|
619
629
|
diffType,
|
|
620
630
|
isActive,
|
|
621
631
|
isInserted: true,
|
|
622
|
-
reveal
|
|
632
|
+
reveal,
|
|
633
|
+
showContributorTags,
|
|
634
|
+
tagMountContext
|
|
623
635
|
}));
|
|
624
636
|
});
|
|
625
|
-
getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).
|
|
637
|
+
getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).filter(({
|
|
638
|
+
fromB,
|
|
639
|
+
toB
|
|
640
|
+
}) => !fg('confluence_ncs_step_diffing_version_history') || !changes.some(change => change.fromB === fromB && change.toB === toB)).forEach(change => {
|
|
641
|
+
var _attributionColors$ge2, _attributionColors2;
|
|
642
|
+
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;
|
|
643
|
+
const attributionKey = showContributorTags ? change.attributionKey : undefined;
|
|
626
644
|
if (change.isInline) {
|
|
627
645
|
// Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
|
|
628
646
|
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
629
647
|
decorations.push(...createInlineChangedDecoration({
|
|
648
|
+
attributionKey,
|
|
630
649
|
change,
|
|
631
|
-
colorScheme,
|
|
650
|
+
colorScheme: changeColorScheme,
|
|
651
|
+
doc: tr.doc,
|
|
632
652
|
isActive,
|
|
633
653
|
isInserted: true,
|
|
634
654
|
isAtomicInlineNode: true,
|
|
635
655
|
inlineNodeName: change.inlineNodeName,
|
|
656
|
+
showContributorTags,
|
|
657
|
+
tagMountContext,
|
|
636
658
|
// Suppress the border-bottom underline for atomic inline nodeviews —
|
|
637
659
|
// it doesn't render on custom nodeview DOM elements and is unnecessary noise.
|
|
638
660
|
hideAddedDiffsUnderline: true,
|
|
@@ -650,12 +672,15 @@ const calculateDiffDecorationsInner = ({
|
|
|
650
672
|
deleted: []
|
|
651
673
|
},
|
|
652
674
|
doc: originalDoc,
|
|
675
|
+
attributionKey,
|
|
653
676
|
nodeViewSerializer,
|
|
654
|
-
colorScheme,
|
|
677
|
+
colorScheme: changeColorScheme,
|
|
655
678
|
newDoc: tr.doc,
|
|
656
679
|
intl,
|
|
657
680
|
activeIndexPos,
|
|
658
|
-
isInserted: false
|
|
681
|
+
isInserted: false,
|
|
682
|
+
showContributorTags,
|
|
683
|
+
tagMountContext
|
|
659
684
|
}));
|
|
660
685
|
}
|
|
661
686
|
} else {
|
|
@@ -663,11 +688,14 @@ const calculateDiffDecorationsInner = ({
|
|
|
663
688
|
doc: tr.doc,
|
|
664
689
|
from: change.fromB,
|
|
665
690
|
to: change.toB,
|
|
666
|
-
|
|
691
|
+
attributionKey,
|
|
692
|
+
colorScheme: changeColorScheme,
|
|
667
693
|
isInserted: true,
|
|
668
694
|
activeIndexPos,
|
|
669
695
|
intl,
|
|
670
|
-
|
|
696
|
+
showContributorTags,
|
|
697
|
+
showIndicators,
|
|
698
|
+
tagMountContext
|
|
671
699
|
}));
|
|
672
700
|
// If the original node position is known (e.g. a panel type change), also render
|
|
673
701
|
// the old block node as a "deleted" widget so the reviewer sees the before/after.
|
|
@@ -684,7 +712,8 @@ const calculateDiffDecorationsInner = ({
|
|
|
684
712
|
},
|
|
685
713
|
doc: originalDoc,
|
|
686
714
|
nodeViewSerializer,
|
|
687
|
-
|
|
715
|
+
attributionKey,
|
|
716
|
+
colorScheme: changeColorScheme,
|
|
688
717
|
newDoc: tr.doc,
|
|
689
718
|
intl,
|
|
690
719
|
activeIndexPos,
|
|
@@ -694,7 +723,9 @@ const calculateDiffDecorationsInner = ({
|
|
|
694
723
|
hideAddedDiffsUnderline,
|
|
695
724
|
placeBelow
|
|
696
725
|
}),
|
|
697
|
-
showIndicators
|
|
726
|
+
showIndicators,
|
|
727
|
+
showContributorTags,
|
|
728
|
+
tagMountContext
|
|
698
729
|
}));
|
|
699
730
|
}
|
|
700
731
|
}
|
|
@@ -127,7 +127,7 @@ export const createAttributionColorMap = stepAttributions => {
|
|
|
127
127
|
}
|
|
128
128
|
const {
|
|
129
129
|
index
|
|
130
|
-
} = getParticipantColor(identity.colorSeed);
|
|
130
|
+
} = getParticipantColor(identity.colorSeed, attribution === null || attribution === void 0 ? void 0 : attribution.agentType);
|
|
131
131
|
const hashedColor = HASHED_PARTICIPANT_COLOR_SCHEMES[index];
|
|
132
132
|
if (!hashedColor) {
|
|
133
133
|
continue;
|
|
@@ -49,6 +49,7 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
49
49
|
return attrStepContexts.map(attrStepContext => {
|
|
50
50
|
const {
|
|
51
51
|
afterNode,
|
|
52
|
+
attributionKey,
|
|
52
53
|
beforeNode,
|
|
53
54
|
finalPos,
|
|
54
55
|
originalPos,
|
|
@@ -74,6 +75,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
74
75
|
const watchedAttrs = inlineNodeAttrMap[nodeName];
|
|
75
76
|
if (attrsToCheck.some(v => watchedAttrs.includes(v))) {
|
|
76
77
|
return {
|
|
78
|
+
...(attributionKey ? {
|
|
79
|
+
attributionKey
|
|
80
|
+
} : {}),
|
|
77
81
|
fromB: finalPos,
|
|
78
82
|
toB: finalPos + nodeAtPos.nodeSize,
|
|
79
83
|
isInline: true,
|
|
@@ -90,6 +94,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
90
94
|
// taskItem node: state attribute change — highlight the taskItem node
|
|
91
95
|
if (attrsToCheck.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
|
|
92
96
|
return {
|
|
97
|
+
...(attributionKey ? {
|
|
98
|
+
attributionKey
|
|
99
|
+
} : {}),
|
|
93
100
|
fromB: finalPos,
|
|
94
101
|
toB: finalPos + nodeAtPos.nodeSize
|
|
95
102
|
};
|
|
@@ -101,6 +108,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
101
108
|
// panel as a "deleted" widget for a before/after comparison.
|
|
102
109
|
if (attrsToCheck.some(v => panelAttrs.includes(v)) && nodeAtPos && getBaseNodeTypeName(nodeAtPos.type) === 'panel') {
|
|
103
110
|
return {
|
|
111
|
+
...(attributionKey ? {
|
|
112
|
+
attributionKey
|
|
113
|
+
} : {}),
|
|
104
114
|
fromB: finalPos,
|
|
105
115
|
toB: finalPos + nodeAtPos.nodeSize,
|
|
106
116
|
...(originalPos !== undefined && originalNodeAtPos && {
|
|
@@ -118,6 +128,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
118
128
|
}
|
|
119
129
|
const isInline = nodeAtPos.type.name === 'inlineExtension';
|
|
120
130
|
return {
|
|
131
|
+
...(attributionKey ? {
|
|
132
|
+
attributionKey
|
|
133
|
+
} : {}),
|
|
121
134
|
fromB: finalPos,
|
|
122
135
|
toB: finalPos + nodeAtPos.nodeSize,
|
|
123
136
|
isInline
|
|
@@ -128,6 +141,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
|
|
|
128
141
|
if (attrsToCheck.some(v => mediaAttrs.includes(v)) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
|
|
129
142
|
const startPos = $pos.pos + $pos.parentOffset;
|
|
130
143
|
return {
|
|
144
|
+
...(attributionKey ? {
|
|
145
|
+
attributionKey
|
|
146
|
+
} : {}),
|
|
131
147
|
fromB: startPos,
|
|
132
148
|
toB: startPos + $pos.parent.nodeSize - 1
|
|
133
149
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AddMarkStep, RemoveMarkStep } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import { getAttributionKey } from '../colorSchemes/attributions';
|
|
3
|
+
import { mapStepRangeToFinal } from './mapStepRangeToFinal';
|
|
2
4
|
const extractMarkStep = step => {
|
|
3
5
|
if (step instanceof AddMarkStep) {
|
|
4
6
|
return {
|
|
@@ -18,10 +20,10 @@ const extractMarkStep = step => {
|
|
|
18
20
|
}
|
|
19
21
|
return undefined;
|
|
20
22
|
};
|
|
21
|
-
export const getMarkChangeRanges = steps => {
|
|
23
|
+
export const getMarkChangeRanges = (steps, stepAttributions = [], stepMaps = []) => {
|
|
22
24
|
const resultRanges = [];
|
|
23
25
|
let lastOp;
|
|
24
|
-
for (const step of steps) {
|
|
26
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
25
27
|
const op = extractMarkStep(step);
|
|
26
28
|
if (!op) {
|
|
27
29
|
continue;
|
|
@@ -31,9 +33,15 @@ export const getMarkChangeRanges = steps => {
|
|
|
31
33
|
if (lastOp && lastOp.from === op.from && lastOp.to === op.to && lastOp.markName === op.markName && lastOp.type !== op.type) {
|
|
32
34
|
resultRanges.pop();
|
|
33
35
|
} else {
|
|
36
|
+
const finalRange = mapStepRangeToFinal(op.from, op.to, stepIndex, stepMaps);
|
|
37
|
+
if (!finalRange) {
|
|
38
|
+
lastOp = op;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
34
41
|
resultRanges.push({
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
attributionKey: getAttributionKey(stepAttributions[stepIndex]),
|
|
43
|
+
fromB: finalRange.fromB,
|
|
44
|
+
toB: finalRange.toB
|
|
37
45
|
});
|
|
38
46
|
}
|
|
39
47
|
lastOp = op;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Mapping } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
// ProseMirror's assoc value chooses which side of an insertion a mapped boundary stays on.
|
|
3
|
+
const FROM_ASSOC = -1;
|
|
4
|
+
const TO_ASSOC = 1;
|
|
5
|
+
|
|
6
|
+
/** Maps a step's range into final-document coordinates when the rollout path supplies step maps. */
|
|
7
|
+
export const mapStepRangeToFinal = (from, to, stepIndex, stepMaps) => {
|
|
8
|
+
if (!stepMaps) {
|
|
9
|
+
return {
|
|
10
|
+
fromB: from,
|
|
11
|
+
toB: to
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The range is already in the document immediately after this step, so skip its own map and
|
|
16
|
+
// apply only the maps from subsequent steps.
|
|
17
|
+
const firstSubsequentStepIndex = stepIndex + 1;
|
|
18
|
+
const finalRange = new Mapping(stepMaps.slice(firstSubsequentStepIndex));
|
|
19
|
+
const fromB = finalRange.mapResult(from, FROM_ASSOC);
|
|
20
|
+
const toB = finalRange.mapResult(to, TO_ASSOC);
|
|
21
|
+
if (fromB.deleted && toB.deleted) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
fromB: fromB.pos,
|
|
26
|
+
toB: toB.pos
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -4,6 +4,7 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
|
4
4
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
5
5
|
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform-override';
|
|
6
6
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
7
|
+
import { getActiveDiffAnnouncement } from '../ui/DiffNavigation/announceActiveDiff';
|
|
7
8
|
import { calculateDiffDecorations } from './calculateDiff/calculateDiffDecorations';
|
|
8
9
|
import { isDiffDecoration, isDiffDecorationSpec } from './decorations/decorationKeys';
|
|
9
10
|
import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
|
|
@@ -285,6 +286,23 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
285
286
|
api === null || api === void 0 ? void 0 : api.core.actions.execute(toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
|
|
286
287
|
}
|
|
287
288
|
cancelPendingScrollToDecoration = scrollToDecoration(view, scrollableDecorations, pluginState.activeIndex);
|
|
289
|
+
|
|
290
|
+
// Stepping only scrolls — no focus moves and no content changes — so without this a
|
|
291
|
+
// screen-reader user gets silence. Announced through the live region rather than by
|
|
292
|
+
// focusing the change, since the "next change" control has to stay focused to be
|
|
293
|
+
// pressed again. After the scroll above, so the announcement never precedes it.
|
|
294
|
+
if (isExtendedEnabled(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
|
|
295
|
+
const announcement = getActiveDiffAnnouncement({
|
|
296
|
+
activeIndex: pluginState.activeIndex,
|
|
297
|
+
contributorTags: pluginState.contributorTags,
|
|
298
|
+
decorations: scrollableDecorations,
|
|
299
|
+
intl: getIntl()
|
|
300
|
+
});
|
|
301
|
+
if (announcement) {
|
|
302
|
+
var _api$accessibilityUti;
|
|
303
|
+
api === null || api === void 0 ? void 0 : (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 ? void 0 : _api$accessibilityUti.actions.ariaNotify(announcement);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
288
306
|
}
|
|
289
307
|
},
|
|
290
308
|
destroy() {
|
|
@@ -124,6 +124,17 @@ const srLabelStyle = convertToInlineCss({
|
|
|
124
124
|
userSelect: 'none',
|
|
125
125
|
whiteSpace: 'nowrap'
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Ids for the hidden label that the tag's `aria-labelledby` points at. Namespaced and random because
|
|
130
|
+
* the tag renders inside the document and shares the host page's id space — the same reason
|
|
131
|
+
* `VanillaTooltip` does it this way. The counter is a fallback for non-secure contexts, where
|
|
132
|
+
* `crypto.randomUUID` is unavailable.
|
|
133
|
+
*/
|
|
134
|
+
const generateLabelId = (() => {
|
|
135
|
+
let count = 0;
|
|
136
|
+
return () => typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? `${CONTRIBUTOR_TAG_CLASS}-label-${crypto.randomUUID()}` : `${CONTRIBUTOR_TAG_CLASS}-label-${count += 1}`;
|
|
137
|
+
})();
|
|
127
138
|
const createSpan = (doc, style, attributes = {}) => {
|
|
128
139
|
const span = doc.createElement('span');
|
|
129
140
|
span.setAttribute('style', style);
|
|
@@ -139,21 +150,30 @@ const createSpan = (doc, style, attributes = {}) => {
|
|
|
139
150
|
* would need a cast per reference below.
|
|
140
151
|
*/
|
|
141
152
|
export const buildContributorTagDom = doc => {
|
|
153
|
+
const labelId = generateLabelId();
|
|
142
154
|
const root = createSpan(doc, constraintStyle);
|
|
143
155
|
const tag = createSpan(doc, tagStyle, {
|
|
144
156
|
class: CONTRIBUTOR_TAG_CLASS,
|
|
145
157
|
'data-testid': CONTRIBUTOR_TAG_TESTID,
|
|
146
|
-
//
|
|
147
|
-
//
|
|
158
|
+
// Named by the hidden label rather than `aria-label`, which would be a second copy of the same
|
|
159
|
+
// sentence. `role="note"` takes its name from the author, not its contents, so without this
|
|
160
|
+
// the tag is a focus stop with no accessible name.
|
|
161
|
+
'aria-labelledby': labelId,
|
|
148
162
|
role: 'note',
|
|
149
|
-
// Focusable so keyboard and screen-reader users can reach the tag and its tooltip.
|
|
163
|
+
// Focusable so keyboard and screen-reader users can reach the tag and its tooltip. Its focus
|
|
164
|
+
// ring is drawn inward — see `contributorTagStyles` in editor-core.
|
|
150
165
|
tabindex: '0'
|
|
151
166
|
});
|
|
152
167
|
const avatars = createSpan(doc, avatarsStyle);
|
|
168
|
+
// `aria-hidden`: the hidden label restates this name in a full sentence, so leaving it in the
|
|
169
|
+
// tree announced the contributor twice ("Priya Changed by Priya").
|
|
153
170
|
const name = createSpan(doc, nameStyle, {
|
|
171
|
+
'aria-hidden': 'true',
|
|
154
172
|
'data-testid': CONTRIBUTOR_TAG_NAME_TESTID
|
|
155
173
|
});
|
|
156
|
-
const srLabel = createSpan(doc, srLabelStyle
|
|
174
|
+
const srLabel = createSpan(doc, srLabelStyle, {
|
|
175
|
+
id: labelId
|
|
176
|
+
});
|
|
157
177
|
tag.append(avatars, name, srLabel);
|
|
158
178
|
root.appendChild(tag);
|
|
159
179
|
return {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { contributorTagMessages } from './messages';
|
|
2
|
+
const getContributorName = (contributor, formatMessage) => {
|
|
3
|
+
var _contributor$name, _contributor$agentKin;
|
|
4
|
+
const 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
|
+
const 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 const formatContributorLabel = (model, formatMessage) => {
|
|
33
|
+
const isPrimaryAgent = model.contributor.kind === 'agent';
|
|
34
|
+
const primaryName = getContributorName(model.contributor, formatMessage);
|
|
35
|
+
const secondaryName = model.connectedContributor ? getContributorName(model.connectedContributor, formatMessage) : '';
|
|
36
|
+
const agentName = isPrimaryAgent ? primaryName : secondaryName;
|
|
37
|
+
const userName = isPrimaryAgent ? secondaryName : primaryName;
|
|
38
|
+
return {
|
|
39
|
+
fullLabel: model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
|
|
40
|
+
agentName,
|
|
41
|
+
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
|
+
};
|