@atlaskit/editor-plugin-show-diff 14.3.2 → 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/afm-cc/tsconfig.json +18 -0
- package/afm-products/tsconfig.json +18 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +47 -26
- package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +61 -9
- package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +77 -20
- package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +10 -0
- package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/cjs/pm-plugins/decorations/createAnchorDecorationWidgets.js +52 -7
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +172 -0
- package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
- package/dist/cjs/pm-plugins/decorations/decorationKeys.js +31 -5
- package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +128 -0
- package/dist/cjs/pm-plugins/main.js +57 -6
- package/dist/cjs/pm-plugins/resolveDiffContributors.js +143 -0
- package/dist/cjs/showDiffPlugin.js +21 -6
- package/dist/cjs/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/cjs/ui/ContributorTag/ContributorTag.js +383 -0
- package/dist/cjs/ui/ContributorTag/ContributorTagWidget.js +50 -0
- package/dist/cjs/ui/ContributorTag/messages.js +24 -0
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +48 -27
- package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +39 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +53 -11
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +9 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/es2019/pm-plugins/decorations/createAnchorDecorationWidgets.js +43 -1
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +163 -0
- package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +38 -5
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +38 -6
- package/dist/es2019/pm-plugins/decorations/decorationKeys.js +30 -2
- package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +101 -0
- package/dist/es2019/pm-plugins/main.js +52 -3
- package/dist/es2019/pm-plugins/resolveDiffContributors.js +128 -0
- package/dist/es2019/showDiffPlugin.js +20 -5
- package/dist/es2019/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/es2019/ui/ContributorTag/ContributorTag.js +312 -0
- package/dist/es2019/ui/ContributorTag/ContributorTagWidget.js +43 -0
- package/dist/es2019/ui/ContributorTag/messages.js +18 -0
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +49 -28
- package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +60 -8
- package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +77 -19
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +9 -0
- package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/esm/pm-plugins/decorations/createAnchorDecorationWidgets.js +51 -6
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +166 -0
- package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
- package/dist/esm/pm-plugins/decorations/decorationKeys.js +30 -5
- package/dist/esm/pm-plugins/decorations/extractContributorTags.js +122 -0
- package/dist/esm/pm-plugins/main.js +57 -6
- package/dist/esm/pm-plugins/resolveDiffContributors.js +137 -0
- package/dist/esm/showDiffPlugin.js +21 -6
- package/dist/esm/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/esm/ui/ContributorTag/ContributorTag.js +375 -0
- package/dist/esm/ui/ContributorTag/ContributorTagWidget.js +43 -0
- package/dist/esm/ui/ContributorTag/messages.js +18 -0
- package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
- package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +7 -1
- package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +10 -0
- package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +24 -2
- package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +7 -1
- package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +1 -2
- package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +2 -1
- package/dist/types/pm-plugins/decorations/createAnchorDecorationWidgets.d.ts +14 -0
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +55 -0
- package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/decorationKeys.d.ts +28 -3
- package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +9 -0
- package/dist/types/pm-plugins/main.d.ts +8 -2
- package/dist/types/pm-plugins/resolveDiffContributors.d.ts +7 -0
- package/dist/types/showDiffPluginType.d.ts +65 -2
- package/dist/types/ui/ContributorTag/ContributorTag.d.ts +37 -0
- package/dist/types/ui/ContributorTag/ContributorTagWidget.d.ts +22 -0
- package/dist/types/ui/ContributorTag/messages.d.ts +6 -0
- package/package.json +16 -7
|
@@ -12,8 +12,9 @@ import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorati
|
|
|
12
12
|
import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
|
|
13
13
|
import { createInlineChangedDecoration } from '../decorations/createInlineChangedDecoration';
|
|
14
14
|
import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
|
|
15
|
-
import { createAttributionColorMap, getAttributionKey, getColorSchemeForChange, isAttributionColoringEnabled } from '../decorations/colorSchemes/attributions';
|
|
15
|
+
import { createAttributionColorMap, getAttributionKey, getAttributionKeyForChange, getColorSchemeForChange, isAttributionColoringEnabled, isContributorTagsEnabled } from '../decorations/colorSchemes/attributions';
|
|
16
16
|
import { extractDiffDescriptors } from '../decorations/decorationKeys';
|
|
17
|
+
import { extractContributorTags } from '../decorations/extractContributorTags';
|
|
17
18
|
import { getAttrChangeRanges, stepIsValidAttrChange } from '../decorations/utils/getAttrChangeRanges';
|
|
18
19
|
import { getMarkChangeRanges } from '../decorations/utils/getMarkChangeRanges';
|
|
19
20
|
import { getDefaultDiffType, isExtendedEnabled } from '../isExtendedEnabled';
|
|
@@ -22,7 +23,7 @@ import { groupChangesByBlock } from './groupChangesByBlock';
|
|
|
22
23
|
import { isMarkOnlyChange } from './isMarkOnlyChange';
|
|
23
24
|
import { optimizeChanges } from './optimizeChanges';
|
|
24
25
|
import { selectTokenEncoder } from './selectTokenEncoder';
|
|
25
|
-
import { simplifyChangesWithAttribution } from './simplifyChangesWithAttribution';
|
|
26
|
+
import { collapseOverlappingChanges, simplifyChangesWithAttribution } from './simplifyChangesWithAttribution';
|
|
26
27
|
import { simplifySteps, simplifyStepsWithAttribution } from './simplifySteps';
|
|
27
28
|
import { classifySmartChanges } from './smart/classifySmartChanges';
|
|
28
29
|
import { smartChangeLevel } from './smart/helpers';
|
|
@@ -68,20 +69,13 @@ const getAttributedChanges = ({
|
|
|
68
69
|
originalDoc,
|
|
69
70
|
steppedDoc,
|
|
70
71
|
diffType,
|
|
71
|
-
tr
|
|
72
|
-
intl,
|
|
73
|
-
smartThresholds
|
|
72
|
+
tr
|
|
74
73
|
}) => {
|
|
74
|
+
// `smart` is deliberately absent here, even though `getChanges` applies it: the classifier drops
|
|
75
|
+
// the step attribution and promotes ranges to sentence/paragraph/node granularity, so a promoted
|
|
76
|
+
// range covers several actors' steps and the latest-writer rule credits all of it to one of them.
|
|
77
|
+
// The attributed changeset keeps step granularity instead.
|
|
75
78
|
if (isExtendedEnabled(diffType)) {
|
|
76
|
-
if (diffType === 'smart' && fg('platform_editor_ai_smart_diff')) {
|
|
77
|
-
return classifySmartChanges({
|
|
78
|
-
changes: simplifyChangesWithAttribution(attributedChanges, tr.doc),
|
|
79
|
-
originalDoc,
|
|
80
|
-
newDoc: tr.doc,
|
|
81
|
-
locale: intl.locale,
|
|
82
|
-
thresholds: smartThresholds
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
79
|
if (diffType === 'step') {
|
|
86
80
|
return attributedChanges;
|
|
87
81
|
}
|
|
@@ -248,16 +242,19 @@ const calculateDiffDecorationsInner = ({
|
|
|
248
242
|
showIndicators = false,
|
|
249
243
|
smartThresholds,
|
|
250
244
|
deletedDiffPlacement = 'top',
|
|
251
|
-
inlineDeletedDiffPlacement = 'before'
|
|
245
|
+
inlineDeletedDiffPlacement = 'before',
|
|
246
|
+
tagMountContext
|
|
252
247
|
}) => {
|
|
253
248
|
const {
|
|
254
249
|
originalDoc,
|
|
255
250
|
steps,
|
|
256
251
|
stepAttributions,
|
|
252
|
+
contributors,
|
|
257
253
|
isDisplayingChanges
|
|
258
254
|
} = pluginState;
|
|
259
255
|
if (!originalDoc || !isDisplayingChanges) {
|
|
260
256
|
return {
|
|
257
|
+
contributorTags: [],
|
|
261
258
|
decorations: DecorationSet.empty,
|
|
262
259
|
diffDescriptors: []
|
|
263
260
|
};
|
|
@@ -271,9 +268,12 @@ const calculateDiffDecorationsInner = ({
|
|
|
271
268
|
} = state;
|
|
272
269
|
let steppedDoc = originalDoc;
|
|
273
270
|
const attributionColoringEnabled = isAttributionColoringEnabled(stepAttributions);
|
|
271
|
+
const showContributorTags = isContributorTagsEnabled(stepAttributions, contributors);
|
|
272
|
+
// Tags need the attributed changeset even on diffs with too few actors to be worth colouring.
|
|
273
|
+
const attributedChangesetEnabled = attributionColoringEnabled || showContributorTags;
|
|
274
274
|
let simplifiedSteps;
|
|
275
275
|
let simplifiedStepAttributions = [];
|
|
276
|
-
if (
|
|
276
|
+
if (attributedChangesetEnabled) {
|
|
277
277
|
const simplifiedStepsWithAttribution = simplifyStepsWithAttribution(steps, stepAttributions !== null && stepAttributions !== void 0 ? stepAttributions : [], originalDoc);
|
|
278
278
|
simplifiedSteps = simplifiedStepsWithAttribution.map(({
|
|
279
279
|
step
|
|
@@ -350,6 +350,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
350
350
|
});
|
|
351
351
|
if (!recoveredViaContentEquality) {
|
|
352
352
|
return {
|
|
353
|
+
contributorTags: [],
|
|
353
354
|
decorations: DecorationSet.empty,
|
|
354
355
|
diffDescriptors: []
|
|
355
356
|
};
|
|
@@ -363,7 +364,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
363
364
|
let changes;
|
|
364
365
|
let attributedChanges = [];
|
|
365
366
|
let attributionColors;
|
|
366
|
-
if (
|
|
367
|
+
if (attributedChangesetEnabled) {
|
|
367
368
|
const changeset = ChangeSet.create(originalDoc, (left, right) => getAttributionKey(left) === getAttributionKey(right) ? left.stepIndex > right.stepIndex ? left : right :
|
|
368
369
|
// `prosemirror-changeset` uses null as its incompatible-data sentinel even
|
|
369
370
|
// though its public combine callback is typed as returning Data.
|
|
@@ -372,17 +373,22 @@ const calculateDiffDecorationsInner = ({
|
|
|
372
373
|
stepIndex
|
|
373
374
|
})));
|
|
374
375
|
attributedChanges = diffBySteps(originalDoc, simplifiedSteps, simplifiedStepAttributions);
|
|
375
|
-
|
|
376
|
+
// The attributed pipeline gives no guarantee that its output ranges are disjoint — see
|
|
377
|
+
// `collapseOverlappingChanges`.
|
|
378
|
+
const uncollapsedChanges = getAttributedChanges({
|
|
376
379
|
changeset,
|
|
377
380
|
originalDoc,
|
|
378
381
|
steppedDoc,
|
|
379
382
|
diffType,
|
|
380
383
|
tr,
|
|
381
|
-
attributedChanges
|
|
382
|
-
intl,
|
|
383
|
-
smartThresholds
|
|
384
|
+
attributedChanges
|
|
384
385
|
});
|
|
385
|
-
|
|
386
|
+
changes = collapseOverlappingChanges(uncollapsedChanges);
|
|
387
|
+
|
|
388
|
+
// Still colours-only: a tags-only diff keeps the plain colour scheme.
|
|
389
|
+
if (attributionColoringEnabled) {
|
|
390
|
+
attributionColors = createAttributionColorMap(simplifiedStepAttributions);
|
|
391
|
+
}
|
|
386
392
|
} else {
|
|
387
393
|
const changeset = ChangeSet.create(originalDoc, undefined, tokenEncoder).addSteps(steppedDoc, stepMaps, tr.doc);
|
|
388
394
|
changes = getChanges({
|
|
@@ -409,6 +415,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
409
415
|
const isInserted = !isInverted;
|
|
410
416
|
const createDecorationsForChange = change => {
|
|
411
417
|
const changeColorScheme = attributionColors ? getColorSchemeForChange(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
|
|
418
|
+
const attributionKey = showContributorTags ? getAttributionKeyForChange(change, attributedChanges) : undefined;
|
|
412
419
|
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
413
420
|
|
|
414
421
|
// The deleted side of a mark-only change is a byte-identical copy of the new text, so it is
|
|
@@ -462,6 +469,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
462
469
|
continue;
|
|
463
470
|
}
|
|
464
471
|
decorations.push(...createInlineChangedDecoration({
|
|
472
|
+
attributionKey,
|
|
465
473
|
change: {
|
|
466
474
|
fromB: from,
|
|
467
475
|
toB: to
|
|
@@ -473,15 +481,18 @@ const calculateDiffDecorationsInner = ({
|
|
|
473
481
|
...(isExtendedEnabled(diffType) && {
|
|
474
482
|
isInserted,
|
|
475
483
|
shouldHideDeleted,
|
|
484
|
+
showContributorTags,
|
|
476
485
|
showIndicators,
|
|
477
486
|
hideAddedDiffsUnderline,
|
|
478
487
|
hasDeletedWidget: willRenderDeletedWidget,
|
|
479
|
-
isDeletedWidgetBelow
|
|
488
|
+
isDeletedWidgetBelow,
|
|
489
|
+
tagMountContext
|
|
480
490
|
})
|
|
481
491
|
}));
|
|
482
492
|
}
|
|
483
493
|
} else {
|
|
484
494
|
decorations.push(...createInlineChangedDecoration({
|
|
495
|
+
attributionKey,
|
|
485
496
|
change,
|
|
486
497
|
doc: tr.doc,
|
|
487
498
|
colorScheme: changeColorScheme,
|
|
@@ -490,10 +501,12 @@ const calculateDiffDecorationsInner = ({
|
|
|
490
501
|
...(isExtendedEnabled(diffType) && {
|
|
491
502
|
isInserted,
|
|
492
503
|
shouldHideDeleted,
|
|
504
|
+
showContributorTags,
|
|
493
505
|
showIndicators,
|
|
494
506
|
hideAddedDiffsUnderline,
|
|
495
507
|
hasDeletedWidget: willRenderDeletedWidget,
|
|
496
|
-
isDeletedWidgetBelow
|
|
508
|
+
isDeletedWidgetBelow,
|
|
509
|
+
tagMountContext
|
|
497
510
|
})
|
|
498
511
|
}));
|
|
499
512
|
}
|
|
@@ -517,6 +530,8 @@ const calculateDiffDecorationsInner = ({
|
|
|
517
530
|
const shouldHideDeleted = isExtendedEnabled(diffType) ? !isInverted && hideDeletedDiffs && change.inserted.length > 0 : false;
|
|
518
531
|
if (!shouldHideDeleted) {
|
|
519
532
|
decorations.push(...createNodeChangedDecorationWidget({
|
|
533
|
+
// The deleted side has no inline decoration, so the widget carries the actor.
|
|
534
|
+
attributionKey,
|
|
520
535
|
change,
|
|
521
536
|
doc: originalDoc,
|
|
522
537
|
nodeViewSerializer,
|
|
@@ -524,6 +539,8 @@ const calculateDiffDecorationsInner = ({
|
|
|
524
539
|
newDoc: tr.doc,
|
|
525
540
|
intl,
|
|
526
541
|
activeIndexPos,
|
|
542
|
+
showContributorTags,
|
|
543
|
+
tagMountContext,
|
|
527
544
|
...(isExtendedEnabled(diffType) && {
|
|
528
545
|
isInserted: !isInserted,
|
|
529
546
|
diffType,
|
|
@@ -635,12 +652,14 @@ const calculateDiffDecorationsInner = ({
|
|
|
635
652
|
});
|
|
636
653
|
const decorationSet = DecorationSet.empty.add(tr.doc, decorations);
|
|
637
654
|
return {
|
|
655
|
+
contributorTags: showContributorTags ? extractContributorTags(decorationSet, contributors) : [],
|
|
638
656
|
decorations: decorationSet,
|
|
639
657
|
diffDescriptors: extractDiffDescriptors(decorationSet)
|
|
640
658
|
};
|
|
641
659
|
};
|
|
642
660
|
export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner,
|
|
643
|
-
// Cache results unless relevant inputs change
|
|
661
|
+
// Cache results unless relevant inputs change. `tagMountContext` is deliberately absent: it is
|
|
662
|
+
// built once per plugin instance, so it can never be the reason a diff needs recalculating.
|
|
644
663
|
([{
|
|
645
664
|
pluginState,
|
|
646
665
|
state,
|
|
@@ -674,7 +693,9 @@ export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner
|
|
|
674
693
|
const originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
|
|
675
694
|
if (isExtendedEnabled(diffType)) {
|
|
676
695
|
var _ref;
|
|
677
|
-
return (_ref = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) &&
|
|
696
|
+
return (_ref = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) &&
|
|
697
|
+
// Contributors can arrive after the first calculation; without this, tags never appear.
|
|
698
|
+
isEqual(pluginState.contributors, lastPluginState.contributors) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement) !== null && _ref !== void 0 ? _ref : false;
|
|
678
699
|
}
|
|
679
|
-
return (_ref2 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref2 !== void 0 ? _ref2 : false;
|
|
700
|
+
return (_ref2 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) && isEqual(pluginState.contributors, lastPluginState.contributors) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref2 !== void 0 ? _ref2 : false;
|
|
680
701
|
});
|
|
@@ -17,6 +17,45 @@ const getAttributionIdentity = change => {
|
|
|
17
17
|
mergeable: true
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
+
const byNewDocRange = (left, right) => left.fromB - right.fromB || left.toB - right.toB;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
|
|
24
|
+
* independently, so runs either side of an unattributed step can come back covering the same
|
|
25
|
+
* characters — which stacks two inline decorations and renders one contributor tag per copy.
|
|
26
|
+
*
|
|
27
|
+
* Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
|
|
28
|
+
* shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
|
|
29
|
+
* is not credited to it.
|
|
30
|
+
*/
|
|
31
|
+
export const collapseOverlappingChanges = changes => {
|
|
32
|
+
if (changes.length <= 1) {
|
|
33
|
+
return changes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A zero-length new-document range paints no inline decoration, so it is kept aside rather than
|
|
37
|
+
// folded into a neighbouring insertion.
|
|
38
|
+
const emptyRanges = changes.filter(change => change.toB <= change.fromB);
|
|
39
|
+
const collapsed = [];
|
|
40
|
+
for (const change of changes.filter(change => change.toB > change.fromB).sort(byNewDocRange)) {
|
|
41
|
+
const current = collapsed[collapsed.length - 1];
|
|
42
|
+
if (current && change.fromB < current.toB) {
|
|
43
|
+
collapsed[collapsed.length - 1] = {
|
|
44
|
+
fromA: Math.min(current.fromA, change.fromA),
|
|
45
|
+
toA: Math.max(current.toA, change.toA),
|
|
46
|
+
fromB: Math.min(current.fromB, change.fromB),
|
|
47
|
+
toB: Math.max(current.toB, change.toB),
|
|
48
|
+
deleted: [...current.deleted, ...change.deleted],
|
|
49
|
+
inserted: [...current.inserted, ...change.inserted]
|
|
50
|
+
};
|
|
51
|
+
} else {
|
|
52
|
+
collapsed.push({
|
|
53
|
+
...change
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [...collapsed, ...emptyRanges].sort(byNewDocRange);
|
|
58
|
+
};
|
|
20
59
|
|
|
21
60
|
/**
|
|
22
61
|
* Simplifies and optimizes consecutive changes without crossing an attribution boundary.
|
|
@@ -2,6 +2,11 @@ import { getParticipantColor } from '@atlaskit/editor-shared-styles/utils';
|
|
|
2
2
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
3
3
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
4
4
|
import { PARTICIPANT_COLOR_SCHEMES } from './types';
|
|
5
|
+
|
|
6
|
+
/** A contributor with both of its identities collapsed to internal attribution keys. */
|
|
7
|
+
|
|
8
|
+
/** Contributors addressed by attribution key. The key format is internal. */
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Normalise the attribution identity once so a valid lookup key always has a valid participant
|
|
7
12
|
* colour seed. `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared
|
|
@@ -36,6 +41,35 @@ export const getAttributionKey = attribution => {
|
|
|
36
41
|
var _getAttributionIdenti;
|
|
37
42
|
return (_getAttributionIdenti = getAttributionIdentity(attribution)) === null || _getAttributionIdenti === void 0 ? void 0 : _getAttributionIdenti.key;
|
|
38
43
|
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Public contributor list to internal key-addressed record. A contributor whose attribution carries
|
|
47
|
+
* no identity is dropped, since it could never match a step. Later entries win.
|
|
48
|
+
*/
|
|
49
|
+
export const resolveContributors = contributors => {
|
|
50
|
+
if (!(contributors !== null && contributors !== void 0 && contributors.length)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const resolved = {};
|
|
54
|
+
for (const {
|
|
55
|
+
attribution,
|
|
56
|
+
connectedTo,
|
|
57
|
+
...tagContributor
|
|
58
|
+
} of contributors) {
|
|
59
|
+
const key = getAttributionKey(attribution);
|
|
60
|
+
if (!key) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const connectedToKey = connectedTo ? getAttributionKey(connectedTo) : undefined;
|
|
64
|
+
resolved[key] = {
|
|
65
|
+
...tagContributor,
|
|
66
|
+
...(connectedToKey ? {
|
|
67
|
+
connectedToKey
|
|
68
|
+
} : {})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
72
|
+
};
|
|
39
73
|
export const areAttributionColorGatesEnabled = () => fg('confluence_ncs_step_diffing_version_history') && isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor');
|
|
40
74
|
export const isAttributionColoringEnabled = stepAttributions => {
|
|
41
75
|
if (!areAttributionColorGatesEnabled() || !(stepAttributions !== null && stepAttributions !== void 0 && stepAttributions.length)) {
|
|
@@ -44,6 +78,12 @@ export const isAttributionColoringEnabled = stepAttributions => {
|
|
|
44
78
|
const attributionKeys = new Set(stepAttributions.map(getAttributionKey).filter(key => key !== undefined));
|
|
45
79
|
return attributionKeys.size > 1;
|
|
46
80
|
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Shares the colours' gates, but with a lower bar: colouring needs two actors to tell apart, whereas
|
|
84
|
+
* a tag only has to name one.
|
|
85
|
+
*/
|
|
86
|
+
export const isContributorTagsEnabled = (stepAttributions, contributors) => areAttributionColorGatesEnabled() && Boolean(stepAttributions === null || stepAttributions === void 0 ? void 0 : stepAttributions.some(attribution => getAttributionKey(attribution) !== undefined)) && contributors !== undefined && Object.keys(contributors).length > 0;
|
|
47
87
|
export const createAttributionColorMap = stepAttributions => {
|
|
48
88
|
const colors = new Map();
|
|
49
89
|
for (const attribution of stepAttributions) {
|
|
@@ -71,17 +111,19 @@ const changesOverlap = (change, attributedChange) => {
|
|
|
71
111
|
const deletedOverlap = change.deleted.length > 0 && attributedChange.deleted.length > 0 && rangesOverlap(change.fromA, change.toA, attributedChange.fromA, attributedChange.toA);
|
|
72
112
|
return insertedOverlap || deletedOverlap;
|
|
73
113
|
};
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
114
|
+
const getAttributionKeyForChanges = changes => getAttributionKey(getLatestAttribution(changes));
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The attribution key owning a change. Inline and step changes retain direct attribution spans, so
|
|
118
|
+
* resolve those first. Grouped and smart-classified changes can lose that data; for those, fall
|
|
119
|
+
* back to the latest overlapping attributed step.
|
|
120
|
+
*/
|
|
121
|
+
export const getAttributionKeyForChange = (change, attributedChanges) => {
|
|
122
|
+
var _getAttributionKeyFor;
|
|
123
|
+
return (_getAttributionKeyFor = getAttributionKeyForChanges([change])) !== null && _getAttributionKeyFor !== void 0 ? _getAttributionKeyFor : getAttributionKeyForChanges(attributedChanges.filter(attributedChange => changesOverlap(change, attributedChange)));
|
|
77
124
|
};
|
|
78
125
|
export const getColorSchemeForChange = (change, attributedChanges, attributionColors, fallback) => {
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
(_ref = (_getParticipantColorS = getParticipantColorSchemeForChanges([change], attributionColors)) !== null && _getParticipantColorS !== void 0 ? _getParticipantColorS :
|
|
83
|
-
// Grouping and smart classification can produce changes without direct attribution data. For
|
|
84
|
-
// those transformed changes, retain the overlap-based lookup as a fallback.
|
|
85
|
-
getParticipantColorSchemeForChanges(attributedChanges.filter(attributedChange => changesOverlap(change, attributedChange)), attributionColors)) !== null && _ref !== void 0 ? _ref : fallback
|
|
86
|
-
);
|
|
126
|
+
var _attributionColors$ge;
|
|
127
|
+
const key = getAttributionKeyForChange(change, attributedChanges);
|
|
128
|
+
return key ? (_attributionColors$ge = attributionColors.get(key)) !== null && _attributionColors$ge !== void 0 ? _attributionColors$ge : fallback : fallback;
|
|
87
129
|
};
|
|
@@ -142,6 +142,15 @@ function textAccent(color) {
|
|
|
142
142
|
return isParticipantColorScheme(color) ? participantColorTokenSets[color].textAccent : textAccentMap[color];
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/** Presentation tokens for one diff colour, shared by highlights and contributor tags. */
|
|
146
|
+
export function getAccentTokens(color) {
|
|
147
|
+
return {
|
|
148
|
+
background: bgSubtlest(color),
|
|
149
|
+
border: borderAccent(color),
|
|
150
|
+
text: textAccent(color)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
145
154
|
/** Ring colour for an active (scroll-target) deleted block outline. */
|
|
146
155
|
function deletedBlockOutlineActiveRing(colors) {
|
|
147
156
|
return colors.deletedBlockOutlineActiveEmphasis === 'border' ? borderAccent(colors.deleteActiveColor) : bgSubtlerPressed(colors.deleteActiveColor);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Schemes consumers may explicitly request through the show-diff API. */
|
|
2
|
+
|
|
1
3
|
/** Attribution-only participant slots selected by the plugin, never by consumers. */
|
|
2
4
|
export const PARTICIPANT_COLOR_SCHEMES = ['red-bolder', 'blue-bolder', 'green-bolder', 'yellow-bolder', 'purple-bolder', 'magenta-bolder', 'teal-bolder', 'orange-bolder', 'lime-bolder', 'gray-bolder', 'blue-subtle', 'red-subtle', 'orange-subtle', 'yellow-subtle', 'green-subtle', 'teal-subtle', 'purple-subtle', 'magenta-subtle'];
|
|
3
5
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
|
|
2
2
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
4
|
import { buildAnchorDecorationKey, buildAnchorDecorationSpec, AnchorDocMarginKey, AnchorTypeKey } from './decorationKeys';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -315,7 +316,7 @@ export const createBlockIndicatorAnchorWidgets = ({
|
|
|
315
316
|
* inward into the neighbouring cell (`direction: 1` forward, `-1` back). Positions
|
|
316
317
|
* not on a row boundary are returned as-is.
|
|
317
318
|
*/
|
|
318
|
-
const clampAnchorPosIntoCell = (doc, pos, direction) => {
|
|
319
|
+
export const clampAnchorPosIntoCell = (doc, pos, direction) => {
|
|
319
320
|
const $pos = doc.resolve(pos);
|
|
320
321
|
if ($pos.parent.type.name !== 'tableRow') {
|
|
321
322
|
return pos;
|
|
@@ -329,6 +330,31 @@ const clampAnchorPosIntoCell = (doc, pos, direction) => {
|
|
|
329
330
|
return direction === 1 ? pos + 2 : pos - 2;
|
|
330
331
|
};
|
|
331
332
|
|
|
333
|
+
/**
|
|
334
|
+
* A zero-size inline span carrying nothing but the `anchor-name` an overlay aligns against. A
|
|
335
|
+
* fragmented inline element reports the union of its line fragments, so aligning against one
|
|
336
|
+
* horizontally gives the enclosing block's content edge; a zero-size span is always a single
|
|
337
|
+
* fragment.
|
|
338
|
+
*/
|
|
339
|
+
export const createAnchorNameSpan = anchorKey => {
|
|
340
|
+
const span = document.createElement('span');
|
|
341
|
+
span.style.setProperty('anchor-name', `--${anchorKey}`);
|
|
342
|
+
return span;
|
|
343
|
+
};
|
|
344
|
+
const createAnchorSpanWidget = ({
|
|
345
|
+
pos,
|
|
346
|
+
diffId,
|
|
347
|
+
anchorType,
|
|
348
|
+
side
|
|
349
|
+
}) => Decoration.widget(pos, () => createAnchorNameSpan(buildAnchorDecorationKey({
|
|
350
|
+
diffId,
|
|
351
|
+
anchorType
|
|
352
|
+
})), buildAnchorDecorationSpec({
|
|
353
|
+
diffId,
|
|
354
|
+
anchorType,
|
|
355
|
+
side
|
|
356
|
+
}));
|
|
357
|
+
|
|
332
358
|
/**
|
|
333
359
|
* Invisible `from`/`to` (and optional `left`) anchor widgets for one inline diff
|
|
334
360
|
* range, so the `IndicatorBar` can align itself via CSS anchor positioning.
|
|
@@ -355,6 +381,22 @@ export const createInlineIndicatorAnchorWidgets = ({
|
|
|
355
381
|
* IndicatorBar can determine top/bottom even if
|
|
356
382
|
* the inline decoration is broken up by marks / between blocks.
|
|
357
383
|
*/
|
|
384
|
+
// Gated purely so the `createAnchorSpanWidget` rewrite can be rolled back independently of the
|
|
385
|
+
// contributor-tag anchors it was extracted for: both branches build the same two widgets at the
|
|
386
|
+
// same positions. Drop the fallback with the gate.
|
|
387
|
+
if (fg('confluence_ncs_step_diffing_version_history')) {
|
|
388
|
+
return [createAnchorSpanWidget({
|
|
389
|
+
pos: fromPos,
|
|
390
|
+
diffId,
|
|
391
|
+
anchorType: AnchorTypeKey.from,
|
|
392
|
+
side: 1
|
|
393
|
+
}), createAnchorSpanWidget({
|
|
394
|
+
pos: toPos,
|
|
395
|
+
diffId,
|
|
396
|
+
anchorType: AnchorTypeKey.to,
|
|
397
|
+
side: -1
|
|
398
|
+
}), ...maybeLeftAnchor];
|
|
399
|
+
}
|
|
358
400
|
const fromAnchorKey = buildAnchorDecorationKey({
|
|
359
401
|
diffId,
|
|
360
402
|
anchorType: AnchorTypeKey.from
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
4
|
+
import { ContributorTagWidget } from '../../ui/ContributorTag/ContributorTagWidget';
|
|
5
|
+
import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
|
|
6
|
+
import { buildContributorTagDecorationSpec, DecorationFamily } from './decorationKeys';
|
|
7
|
+
|
|
8
|
+
/** Marks the host element of one tag, so a tag can be found in the document by its own diff. */
|
|
9
|
+
export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
|
|
10
|
+
|
|
11
|
+
// Contributor tags only render behind this gate — see `areAttributionColorGatesEnabled`.
|
|
12
|
+
const isContributorTagWidgetEnabled = () => fg('confluence_ncs_step_diffing_version_history');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Everything a tag needs to mount itself into the document, plumbed down from the plugin. Absent in
|
|
16
|
+
* unit tests that build decorations directly: the hosts are still emitted, they just stay empty.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A mounted tag, so whoever emitted the host can take it down again. */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unique per mount rather than per diff, because ProseMirror draws a replacement widget before it
|
|
23
|
+
* destroys the widget being replaced: on a shared key the outgoing widget's `destroy` would
|
|
24
|
+
* unregister the incoming widget's portal.
|
|
25
|
+
*/
|
|
26
|
+
let nextContributorTagPortalId = 0;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders the tag into `host` through the editor's portal provider, the way the rest of the editor
|
|
30
|
+
* mounts React into decorations. The tag resolves its own model from plugin state, so a redraw of
|
|
31
|
+
* the host re-parents the tag instead of stranding it, and `isActive` changes arrive as a re-render.
|
|
32
|
+
*/
|
|
33
|
+
export const mountContributorTag = ({
|
|
34
|
+
diffId,
|
|
35
|
+
host,
|
|
36
|
+
mountContext
|
|
37
|
+
}) => {
|
|
38
|
+
if (!mountContext) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const {
|
|
42
|
+
api,
|
|
43
|
+
getEditorRoot,
|
|
44
|
+
portalProviderAPI
|
|
45
|
+
} = mountContext;
|
|
46
|
+
const key = `${DecorationFamily.contributorTag}-${diffId}-${nextContributorTagPortalId++}`;
|
|
47
|
+
portalProviderAPI.render(() => /*#__PURE__*/createElement(ContributorTagWidget, {
|
|
48
|
+
api,
|
|
49
|
+
diffId,
|
|
50
|
+
getEditorRoot
|
|
51
|
+
}), host, key);
|
|
52
|
+
return {
|
|
53
|
+
key,
|
|
54
|
+
portalProviderAPI
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export const unmountContributorTag = mount => {
|
|
58
|
+
mount === null || mount === void 0 ? void 0 : mount.portalProviderAPI.remove(mount.key);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A zero-size inline host for one contributor tag. `position: relative` makes it the containing
|
|
63
|
+
* block of the absolutely positioned tag, so the tag sits on the change with no measurement.
|
|
64
|
+
*/
|
|
65
|
+
const buildContributorTagHost = diffId => {
|
|
66
|
+
const host = document.createElement('span');
|
|
67
|
+
host.setAttribute(CONTRIBUTOR_TAG_HOST_ATTRIBUTE, diffId);
|
|
68
|
+
host.style.setProperty('position', 'relative');
|
|
69
|
+
return host;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The host for a tag on deleted content, which renders inside its own widget decoration rather than
|
|
74
|
+
* needing one of its own. Unmount with `unmountContributorTag` from that widget's `destroy`.
|
|
75
|
+
*/
|
|
76
|
+
export const createContributorTagHost = (diffId, mountContext) => {
|
|
77
|
+
if (!isContributorTagWidgetEnabled()) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const host = buildContributorTagHost(diffId);
|
|
81
|
+
return {
|
|
82
|
+
host,
|
|
83
|
+
mount: mountContributorTag({
|
|
84
|
+
diffId,
|
|
85
|
+
host,
|
|
86
|
+
mountContext
|
|
87
|
+
})
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The start of the inline content that `[from, to)` actually highlights.
|
|
93
|
+
*
|
|
94
|
+
* `from` is not sufficient: a range can begin at the tail of one paragraph and only cover text in a
|
|
95
|
+
* later block, and ProseMirror clips `Decoration.inline` to the content it can paint — so a host
|
|
96
|
+
* left at `from` would sit lines away from the highlight.
|
|
97
|
+
*/
|
|
98
|
+
const resolveInlineContentStart = (doc, from, to) => {
|
|
99
|
+
var _edge;
|
|
100
|
+
let edge;
|
|
101
|
+
doc.nodesBetween(from, to, (node, nodePos) => {
|
|
102
|
+
if (!node.isTextblock) {
|
|
103
|
+
return edge === undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The slice of this block the range actually covers; empty means nothing paintable.
|
|
107
|
+
const contentFrom = Math.max(nodePos + 1, from);
|
|
108
|
+
const contentTo = Math.min(nodePos + 1 + node.content.size, to);
|
|
109
|
+
if (contentTo > contentFrom && edge === undefined) {
|
|
110
|
+
edge = contentFrom;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Textblocks have no block children to descend into.
|
|
114
|
+
return false;
|
|
115
|
+
});
|
|
116
|
+
return (_edge = edge) !== null && _edge !== void 0 ? _edge : from;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A widget hosting the contributor tag of one inline diff range, placed on the first character the
|
|
121
|
+
* range highlights.
|
|
122
|
+
*
|
|
123
|
+
* The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
|
|
124
|
+
* element ProseMirror drew it into — see `mountContributorTag`.
|
|
125
|
+
*/
|
|
126
|
+
export const createContributorTagWidget = ({
|
|
127
|
+
doc,
|
|
128
|
+
from,
|
|
129
|
+
to,
|
|
130
|
+
diffId,
|
|
131
|
+
mountContext
|
|
132
|
+
}) => {
|
|
133
|
+
if (!isContributorTagWidgetEnabled()) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Reassigned when ProseMirror redraws this decoration, which it may do more than once for the
|
|
138
|
+
// same `Decoration` instance.
|
|
139
|
+
let mount;
|
|
140
|
+
return Decoration.widget(
|
|
141
|
+
// Keep the host out of the table row's grid (EDITOR-8442).
|
|
142
|
+
clampAnchorPosIntoCell(doc, resolveInlineContentStart(doc, from, to), 1), () => {
|
|
143
|
+
const host = buildContributorTagHost(diffId);
|
|
144
|
+
mount = mountContributorTag({
|
|
145
|
+
diffId,
|
|
146
|
+
host,
|
|
147
|
+
mountContext
|
|
148
|
+
});
|
|
149
|
+
return host;
|
|
150
|
+
}, {
|
|
151
|
+
...buildContributorTagDecorationSpec(diffId),
|
|
152
|
+
side: 1,
|
|
153
|
+
// The tag is chrome: it inherits no surrounding mark, and its own events are not the
|
|
154
|
+
// document's to handle.
|
|
155
|
+
marks: [],
|
|
156
|
+
ignoreSelection: true,
|
|
157
|
+
stopEvent: () => true,
|
|
158
|
+
destroy: () => {
|
|
159
|
+
unmountContributorTag(mount);
|
|
160
|
+
mount = undefined;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
};
|