@atlaskit/editor-plugin-show-diff 15.0.0 → 15.1.1

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 (63) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/afm-cc/tsconfig.json +0 -12
  3. package/afm-products/tsconfig.json +0 -12
  4. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +131 -79
  5. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +32 -0
  6. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +87 -25
  7. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +27 -4
  8. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +17 -3
  9. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
  10. package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +43 -14
  11. package/dist/cjs/pm-plugins/decorations/revealStyles.js +156 -0
  12. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -5
  13. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +4 -2
  14. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +2 -1
  15. package/dist/cjs/pm-plugins/main.js +28 -10
  16. package/dist/cjs/pm-plugins/resolveDiffContributors.js +4 -0
  17. package/dist/cjs/pm-plugins/revealAnimation.js +524 -0
  18. package/dist/cjs/showDiffPlugin.js +42 -7
  19. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +59 -9
  20. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +30 -0
  21. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +62 -4
  22. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +25 -3
  23. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +18 -1
  24. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
  25. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +36 -9
  26. package/dist/es2019/pm-plugins/decorations/revealStyles.js +139 -0
  27. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -6
  28. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +6 -3
  29. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +5 -2
  30. package/dist/es2019/pm-plugins/main.js +28 -9
  31. package/dist/es2019/pm-plugins/resolveDiffContributors.js +4 -0
  32. package/dist/es2019/pm-plugins/revealAnimation.js +495 -0
  33. package/dist/es2019/showDiffPlugin.js +37 -7
  34. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +131 -79
  35. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +30 -0
  36. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +87 -25
  37. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +27 -4
  38. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +17 -3
  39. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
  40. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +43 -14
  41. package/dist/esm/pm-plugins/decorations/revealStyles.js +149 -0
  42. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -5
  43. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +4 -2
  44. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +2 -1
  45. package/dist/esm/pm-plugins/main.js +28 -10
  46. package/dist/esm/pm-plugins/resolveDiffContributors.js +4 -0
  47. package/dist/esm/pm-plugins/revealAnimation.js +518 -0
  48. package/dist/esm/showDiffPlugin.js +42 -7
  49. package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
  50. package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +2 -1
  51. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +24 -0
  52. package/dist/types/pm-plugins/decorations/createBlockChangedDecoration.d.ts +5 -1
  53. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +9 -3
  54. package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +3 -2
  55. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +3 -2
  56. package/dist/types/pm-plugins/decorations/revealStyles.d.ts +49 -0
  57. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +4 -3
  58. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.d.ts +1 -1
  59. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.d.ts +1 -1
  60. package/dist/types/pm-plugins/main.d.ts +6 -1
  61. package/dist/types/pm-plugins/revealAnimation.d.ts +33 -0
  62. package/dist/types/showDiffPluginType.d.ts +29 -1
  63. package/package.json +10 -4
@@ -0,0 +1,139 @@
1
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
+ import { buildDeletedInlineStyleStandard, getDeletedInlineRevealColors, getInsertedInlineRevealColors } from './colorSchemes/factory';
4
+ import { colorSchemeRegistry } from './colorSchemes/schemes';
5
+ import { deletedContentStyle, deletedContentStyleActive } from './colorSchemes/standard';
6
+ /**
7
+ * Painting strategy for reveal highlights. Highlights are `linear-gradient`s with animated
8
+ * `background-size` for the wipe effect. Colours live on elements as custom properties so the
9
+ * attribution palette works per-contributor.
10
+ */
11
+
12
+ /** Marks an element as taking part in the reveal, and says which side it represents. */
13
+ export const REVEAL_ATTR = 'data-show-diff-reveal';
14
+ /** Wipe colour, read by the animation as the gradient's colour stop. */
15
+ export const REVEAL_BG_VAR = '--show-diff-reveal-bg';
16
+
17
+ /** Underline colour, brought in on the same schedule as the highlight. */
18
+ export const REVEAL_BORDER_VAR = '--show-diff-reveal-border';
19
+
20
+ /** Only schemes with background highlights can be revealed; 'traditional' uses underline only. */
21
+ const schemeSupportsReveal = colorScheme => colorScheme !== 'traditional';
22
+
23
+ /** Convert a flat background colour into a wipeable gradient (zero width initially). */
24
+ export const buildWipeableBackground = color => convertToInlineCss({
25
+ backgroundColor: 'transparent',
26
+ backgroundImage: `linear-gradient(${color}, ${color})`,
27
+ backgroundRepeat: 'no-repeat',
28
+ backgroundSize: '0% 100%',
29
+ backgroundPosition: '0 0',
30
+ // Each line fragment of a wrapped highlight gets its own background box, so they wipe
31
+ // together rather than the gradient stretching across the whole wrapped run.
32
+ WebkitBoxDecorationBreak: 'clone',
33
+ boxDecorationBreak: 'clone'
34
+ });
35
+
36
+ /** Base padding and transparent underline placeholder (no layout shift on reveal). */
37
+ const revealBaseStyle = convertToInlineCss({
38
+ padding: `1px 0 2px`,
39
+ borderBottom: `2px solid transparent`
40
+ });
41
+ const buildRevealVariables = ({
42
+ background,
43
+ border
44
+ }) => convertToInlineCss({
45
+ [REVEAL_BG_VAR]: background,
46
+ [REVEAL_BORDER_VAR]: border
47
+ });
48
+
49
+ /** Configuration that affects both resting state and reveal endpoint. */
50
+
51
+ /** Reveal colours from the refactored scheme registry (attribution palette ready). */
52
+ const revealColorsRefactored = (colors, {
53
+ hideAddedDiffsUnderline,
54
+ isActive,
55
+ isInserted
56
+ }) => isInserted ? getInsertedInlineRevealColors(colors, isActive, hideAddedDiffsUnderline) : getDeletedInlineRevealColors(colors);
57
+
58
+ /**
59
+ * Legacy cohort reveal colours. Values MUST match `revealColorsRefactored()` output for
60
+ * `standard` scheme (test: `revealStyles.ts` in test package).
61
+ */
62
+ const revealColorsLegacy = ({
63
+ hideAddedDiffsUnderline,
64
+ isActive,
65
+ isInserted
66
+ }) => isInserted ? {
67
+ // Mirrors `editingStyleExtended` / `editingStyleActiveExtended` and their
68
+ // `*NoUnderline` variants in `colorSchemes/standard.ts`.
69
+ background: isActive ? "var(--ds-background-accent-purple-subtler-pressed, #D8A0F7)" : "var(--ds-background-accent-purple-subtlest, #F8EEFE)",
70
+ border: hideAddedDiffsUnderline ? 'transparent' : "var(--ds-border-accent-purple, #AF59E1)"
71
+ } : {
72
+ // Mirrors `deletedInlineContentStyleExtended` in `colorSchemes/standard.ts`.
73
+ background: "var(--ds-background-accent-gray-subtlest, #F0F1F2)",
74
+ border: "var(--ds-border-accent-gray, #7D818A)"
75
+ };
76
+ const resolveRevealColors = (colors, variant) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? revealColorsRefactored(colors, variant) : revealColorsLegacy(variant);
77
+ const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
78
+
79
+ /** Deleted text appearance (grey and strikethrough) without the highlight. */
80
+ const deletedTextOnlyStyle = (colors, isActive) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? buildDeletedInlineStyleStandard(colors, isActive ? 'active' : 'default') : isActive ? deletedContentStyleActive : deletedContentStyle;
81
+
82
+ /**
83
+ * Resolve reveal attribute and style for a decoration. Returned style REPLACES the decoration's
84
+ * highlight (not an addition) so the animation has something to wipe. Gate resolved in showDiff
85
+ * command for single shared answer between decorations and animation.
86
+ */
87
+ export const resolveRevealStyle = ({
88
+ colorScheme,
89
+ hideAddedDiffsUnderline = false,
90
+ includeDeletedTextStyle = true,
91
+ isActive,
92
+ isInserted,
93
+ reveal
94
+ }) => {
95
+ if ((reveal === null || reveal === void 0 ? void 0 : reveal.mode) !== 'phased' || !schemeSupportsReveal(colorScheme)) {
96
+ return undefined;
97
+ }
98
+ const colors = getColorScheme(colorScheme);
99
+ const {
100
+ background,
101
+ border
102
+ } = resolveRevealColors(colors, {
103
+ hideAddedDiffsUnderline,
104
+ isActive,
105
+ isInserted
106
+ });
107
+ return {
108
+ role: isInserted ? 'added' : 'deleted',
109
+ style: (isInserted || !includeDeletedTextStyle ? '' : deletedTextOnlyStyle(colors, isActive)) + revealBaseStyle + buildWipeableBackground(background) + buildRevealVariables({
110
+ background,
111
+ border
112
+ })
113
+ };
114
+ };
115
+
116
+ /** Reveal style for imperatively-built elements (deleted-content widget wrappers). */
117
+ export const applyRevealToElement = ({
118
+ colorScheme,
119
+ element,
120
+ hideAddedDiffsUnderline,
121
+ isActive,
122
+ isInserted,
123
+ reveal
124
+ }) => {
125
+ const resolved = resolveRevealStyle({
126
+ colorScheme,
127
+ hideAddedDiffsUnderline,
128
+ isActive,
129
+ isInserted,
130
+ reveal,
131
+ // The widget supplies the deleted text's own grey/strikethrough styling already.
132
+ includeDeletedTextStyle: false
133
+ });
134
+ if (!resolved) {
135
+ return '';
136
+ }
137
+ element.setAttribute(REVEAL_ATTR, resolved.role);
138
+ return resolved.style;
139
+ };
@@ -4,6 +4,7 @@ import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-uti
4
4
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
6
  import { isExtendedEnabled } from '../../isExtendedEnabled';
7
+ import { applyRevealToElement } from '../revealStyles';
7
8
  import { getAtomicInlineChangedAttrs } from '../createInlineChangedDecoration';
8
9
  import { applyTableCellEdgeAttrs } from './tableCellEdgeAttrs';
9
10
  import { isInlineAttrChangeNodeName } from './getAttrChangeRanges';
@@ -600,10 +601,18 @@ export const injectInnerWrapper = ({
600
601
  colorScheme,
601
602
  isActive,
602
603
  isInserted,
603
- diffType
604
+ diffType,
605
+ reveal
604
606
  }) => {
605
607
  const wrapper = document.createElement('span');
606
- wrapper.setAttribute('style', isInserted ? getInsertedContentStyle(colorScheme, isActive) : getDeletedContentStyle(colorScheme, isActive, diffType));
608
+ const revealStyle = applyRevealToElement({
609
+ colorScheme,
610
+ element: wrapper,
611
+ isActive: isActive !== null && isActive !== void 0 ? isActive : false,
612
+ isInserted: isInserted !== null && isInserted !== void 0 ? isInserted : false,
613
+ reveal
614
+ });
615
+ wrapper.setAttribute('style', (isInserted ? getInsertedContentStyle(colorScheme, isActive) : getDeletedContentStyle(colorScheme, isActive, diffType, Boolean(revealStyle))) + revealStyle);
607
616
  [...node.childNodes].forEach(child => {
608
617
  const removedChild = node.removeChild(child);
609
618
  wrapper.append(removedChild);
@@ -615,23 +624,33 @@ export const injectInnerWrapper = ({
615
624
  /**
616
625
  * Creates a styled span wrapper for inline content within a change decoration.
617
626
  */
618
- export const createContentWrapper = (colorScheme, isActive = false, isInserted = false, diffType) => {
627
+ export const createContentWrapper = (colorScheme, isActive = false, isInserted = false, diffType, reveal) => {
619
628
  const wrapper = document.createElement('span');
620
629
  const baseStyle = convertToInlineCss({
621
630
  position: 'relative',
622
631
  width: 'fit-content'
623
632
  });
633
+ // Empty unless the reveal is running, in which case the static highlight below is withheld so
634
+ // the animation can wipe it in instead.
635
+ const revealStyle = applyRevealToElement({
636
+ colorScheme,
637
+ element: wrapper,
638
+ isActive,
639
+ isInserted,
640
+ reveal
641
+ });
642
+ const deletedStyle = getDeletedContentStyle(colorScheme, isActive, diffType, Boolean(revealStyle));
624
643
  if (isExtendedEnabled(diffType)) {
625
644
  if (isInserted) {
626
- wrapper.setAttribute('style', `${baseStyle}${getInsertedContentStyle(colorScheme, isActive)}`);
645
+ wrapper.setAttribute('style', `${baseStyle}${getInsertedContentStyle(colorScheme, isActive)}${revealStyle}`);
627
646
  } else {
628
- wrapper.setAttribute('style', `${baseStyle}${getDeletedContentStyle(colorScheme, isActive, diffType)}`);
647
+ wrapper.setAttribute('style', `${baseStyle}${deletedStyle}${revealStyle}`);
629
648
  const strikethrough = document.createElement('span');
630
649
  strikethrough.setAttribute('style', getDeletedContentStyleUnbounded(colorScheme, isActive));
631
650
  wrapper.append(strikethrough);
632
651
  }
633
652
  } else {
634
- wrapper.setAttribute('style', `${baseStyle}${getDeletedContentStyle(colorScheme, isActive, diffType)}`);
653
+ wrapper.setAttribute('style', `${baseStyle}${deletedStyle}${revealStyle}`);
635
654
  const strikethrough = document.createElement('span');
636
655
  strikethrough.setAttribute('style', getDeletedContentStyleUnbounded(colorScheme, isActive));
637
656
  wrapper.append(strikethrough);
@@ -77,12 +77,12 @@ const getChangedNodeStyleNext = (nodeName, colorScheme, isInserted = false, isAc
77
77
  };
78
78
  const getDeletedContentStyleUnboundedNext = (colorScheme, isActive = false) => buildDeletedStrikethroughLine(getColorScheme(colorScheme), isActive);
79
79
  const getInsertedContentStyleNext = (colorScheme, isActive = false, hideAddedDiffsUnderline = false) => buildInsertedInlineStyle(getColorScheme(colorScheme), isActive, hideAddedDiffsUnderline);
80
- const getDeletedContentStyleNext = (colorScheme, isActive = false, diffType) => {
80
+ const getDeletedContentStyleNext = (colorScheme, isActive = false, diffType, omitHighlight = false) => {
81
81
  const colors = getColorScheme(colorScheme);
82
82
  const base = buildDeletedInlineContentStyle(colors, isActive ? 'active' : 'new');
83
83
 
84
84
  // glyphTint adds a background highlight and border-bottom over the tint; strikethrough does not.
85
- const needsExtendedHighlight = colors.deletedInlineTreatment === 'glyphTint' && isExtendedEnabled(diffType);
85
+ const needsExtendedHighlight = colors.deletedInlineTreatment === 'glyphTint' && isExtendedEnabled(diffType) && !omitHighlight;
86
86
  return needsExtendedHighlight ? base + buildDeletedInlineContentStyleExtended(colors) : base;
87
87
  };
88
88
  const resolveCellOverlayStyleNext = ({
@@ -119,7 +119,10 @@ export const getDeletedContentStyleUnbounded = (colorScheme, isActive = false) =
119
119
  export const getInsertedContentStyle = (colorScheme, isActive = false, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getInsertedContentStyleNext(colorScheme, isActive, hideAddedDiffsUnderline) : getInsertedContentStyleLegacy(getLegacyColorScheme(colorScheme), isActive, hideAddedDiffsUnderline);
120
120
 
121
121
  /** Inline style for deleted content. */
122
- export const getDeletedContentStyle = (colorScheme, isActive = false, diffType) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleNext(colorScheme, isActive, diffType) : getDeletedContentStyleLegacy(getLegacyColorScheme(colorScheme), isActive, diffType);
122
+ export const getDeletedContentStyle = (colorScheme, isActive = false, diffType,
123
+ // Set while the reveal animation is running: it paints the highlight itself so it can wipe it in
124
+ // left-to-right, and a static background here would leave nothing to reveal.
125
+ omitHighlight = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleNext(colorScheme, isActive, diffType, omitHighlight) : getDeletedContentStyleLegacy(getLegacyColorScheme(colorScheme), isActive, diffType, omitHighlight);
123
126
 
124
127
  /**
125
128
  * Overlay style for one added or deleted table cell. Invariant across the cells of a table, so the
@@ -151,11 +151,14 @@ export const getInsertedContentStyleLegacy = (colorScheme, isActive = false, hid
151
151
  };
152
152
 
153
153
  /** Pre-refactor inline style for deleted content. */
154
- export const getDeletedContentStyleLegacy = (colorScheme, isActive = false, diffType) => {
154
+ export const getDeletedContentStyleLegacy = (colorScheme, isActive = false, diffType,
155
+ // The reveal animation paints the highlight itself so it can wipe it in; a static one here would
156
+ // cover the whole span and leave nothing to reveal. Mirrors the same option on the Next variant.
157
+ omitHighlight = false) => {
155
158
  if (colorScheme === 'traditional') {
156
159
  return getDeletedTraditionalInlineStyle(isActive);
157
160
  }
158
- if (isExtendedEnabled(diffType)) {
161
+ if (isExtendedEnabled(diffType) && !omitHighlight) {
159
162
  return (isActive ? deletedContentStyleActive : deletedContentStyleNew) + deletedInlineContentStyleExtended;
160
163
  }
161
164
  return isActive ? deletedContentStyleActive : deletedContentStyleNew;
@@ -10,6 +10,7 @@ import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
10
10
  import { getScrollableDecorations } from './getScrollableDecorations';
11
11
  import { getDefaultDiffType, isExtendedEnabled } from './isExtendedEnabled';
12
12
  import { NodeViewSerializer } from './NodeViewSerializer';
13
+ import { rebindReveal } from './revealAnimation';
13
14
  import { scrollToDecoration } from './scrollToDiff';
14
15
  export const showDiffPluginKey = new PluginKey('showDiffPlugin');
15
16
  /**
@@ -80,14 +81,24 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
80
81
  const meta = tr.getMeta(showDiffPluginKey);
81
82
  let newPluginState = currentPluginState;
82
83
  if (meta) {
83
- if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
84
- var _newPluginState, _newPluginState2, _newPluginState3, _newPluginState4, _newPluginState5, _newPluginState6, _newPluginState7, _newPluginState8, _newPluginState9, _newPluginState0;
85
- // Update the plugin state with the new metadata
86
- newPluginState = {
84
+ if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'REVEAL_COMPLETE') {
85
+ var _newPluginState, _newPluginState2, _newPluginState3, _newPluginState4, _newPluginState5, _newPluginState6, _newPluginState7, _newPluginState8, _newPluginState9, _newPluginState0, _newPluginState1;
86
+ // REVEAL_COMPLETE repaints with the reveal dropped, so the decorations render their
87
+ // ordinary resting style. Without it the reveal stays in state indefinitely and the
88
+ // next unrelated repaint re-emits the hidden, zero-width highlight with no animation
89
+ // left to bring it in — the highlight simply vanishes.
90
+ newPluginState = meta.action === 'REVEAL_COMPLETE' ? {
91
+ ...currentPluginState,
92
+ reveal: undefined
93
+ } : {
87
94
  ...currentPluginState,
88
95
  ...meta,
89
96
  isDisplayingChanges: true,
90
- activeIndex: undefined
97
+ activeIndex: undefined,
98
+ // Explicit rather than left to the spread: an absent key in `meta` would let
99
+ // the previous paint's choreography leak into this one, so closing the diff
100
+ // would animate too.
101
+ reveal: meta.reveal
91
102
  };
92
103
  // Calculate and store decorations in state
93
104
  const {
@@ -111,13 +122,16 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
111
122
  showIndicators: (_newPluginState6 = newPluginState) === null || _newPluginState6 === void 0 ? void 0 : _newPluginState6.showIndicators,
112
123
  smartThresholds: (_newPluginState7 = newPluginState) === null || _newPluginState7 === void 0 ? void 0 : _newPluginState7.smartThresholds,
113
124
  deletedDiffPlacement: (_newPluginState8 = newPluginState) === null || _newPluginState8 === void 0 ? void 0 : _newPluginState8.deletedDiffPlacement,
114
- inlineDeletedDiffPlacement: (_newPluginState9 = newPluginState) === null || _newPluginState9 === void 0 ? void 0 : _newPluginState9.inlineDeletedDiffPlacement
125
+ inlineDeletedDiffPlacement: (_newPluginState9 = newPluginState) === null || _newPluginState9 === void 0 ? void 0 : _newPluginState9.inlineDeletedDiffPlacement,
126
+ // SHOW_DIFF only. The scroll-to-next recalculation further down deliberately
127
+ // omits this so stepping through changes cannot replay the choreography.
128
+ reveal: (_newPluginState0 = newPluginState) === null || _newPluginState0 === void 0 ? void 0 : _newPluginState0.reveal
115
129
  } : {})
116
130
  });
117
131
  // Update the decorations and their ids
118
132
  newPluginState.decorations = decorations;
119
133
  newPluginState.contributorTags = contributorTags;
120
- if (isExtendedEnabled((_newPluginState0 = newPluginState) === null || _newPluginState0 === void 0 ? void 0 : _newPluginState0.diffType)) {
134
+ if (isExtendedEnabled((_newPluginState1 = newPluginState) === null || _newPluginState1 === void 0 ? void 0 : _newPluginState1.diffType)) {
121
135
  newPluginState.diffDescriptors = diffDescriptors;
122
136
  }
123
137
  } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
@@ -128,6 +142,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
128
142
  isDisplayingChanges: false,
129
143
  activeIndex: undefined,
130
144
  contributorTags: [],
145
+ reveal: undefined,
131
146
  /**
132
147
  * Reset isInverted & diffType state when hiding diffs
133
148
  * Otherwise this should persist for the diff-showing session
@@ -141,9 +156,9 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
141
156
  } : {})
142
157
  };
143
158
  } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_NEXT' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_PREVIOUS') {
144
- var _newPluginState1;
159
+ var _newPluginState10;
145
160
  // Update the active index in plugin state and recalculate decorations
146
- const decorations = getScrollableDecorations(currentPluginState.decorations, newState.doc, (_newPluginState1 = newPluginState) === null || _newPluginState1 === void 0 ? void 0 : _newPluginState1.diffType);
161
+ const decorations = getScrollableDecorations(currentPluginState.decorations, newState.doc, (_newPluginState10 = newPluginState) === null || _newPluginState10 === void 0 ? void 0 : _newPluginState10.diffType);
147
162
  if (decorations.length > 0) {
148
163
  var _currentPluginState$a;
149
164
  // Initialize to -1 if undefined so that the first "next" scroll takes us to index 0 (first change).
@@ -238,6 +253,10 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
238
253
  }
239
254
  const pluginState = showDiffPluginKey.getState(view.state);
240
255
 
256
+ // A repaint rebuilds inline decorations, destroying any animation bound to them, so
257
+ // an in-flight reveal has to be re-attached to the new nodes. No-op when idle.
258
+ rebindReveal(view, view.dom);
259
+
241
260
  // Scroll to the first decoration when scrollIntoView was requested.
242
261
  // Use the same filtered/position-sorted list as the active-index path
243
262
  // so "first" reliably means the topmost scrollable diff in the document.
@@ -62,6 +62,10 @@ const resolveAgent = (attribution, profilesByAccountId) => {
62
62
  * reads as "nobody touched this", so the whole list is dropped and the plain diff renders instead.
63
63
  */
64
64
  export const resolveDiffContributors = (stepsWithAttribution, profiles) => {
65
+ // Profiles opt callers into contributor tags; attribution alone still supplies diff colours.
66
+ if (profiles === undefined) {
67
+ return undefined;
68
+ }
65
69
  const profilesByAccountId = toProfilesByAccountId(profiles);
66
70
  const contributors = [];
67
71
  /** Keyed as the plugin keys changes, so no two entries can collapse onto one another. */