@atlaskit/editor-plugin-show-diff 14.0.4 → 14.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 (60) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +215 -103
  3. package/dist/cjs/pm-plugins/calculateDiff/diffBySteps.js +20 -9
  4. package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +89 -0
  5. package/dist/cjs/pm-plugins/calculateDiff/simplifySteps.js +81 -4
  6. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +68 -0
  7. package/dist/cjs/pm-plugins/decorations/colorSchemes/schemes.js +26 -2
  8. package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +5 -1
  9. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +7 -2
  10. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +2 -2
  11. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +4 -2
  12. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +59 -30
  13. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +14 -10
  14. package/dist/cjs/pm-plugins/main.js +1 -0
  15. package/dist/cjs/showDiffPlugin.js +36 -14
  16. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +130 -22
  17. package/dist/es2019/pm-plugins/calculateDiff/diffBySteps.js +17 -9
  18. package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +64 -0
  19. package/dist/es2019/pm-plugins/calculateDiff/simplifySteps.js +64 -1
  20. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +37 -0
  21. package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +19 -2
  22. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +1 -0
  23. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +6 -3
  24. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +3 -3
  25. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +6 -3
  26. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +55 -30
  27. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +12 -9
  28. package/dist/es2019/pm-plugins/main.js +1 -0
  29. package/dist/es2019/showDiffPlugin.js +23 -1
  30. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +216 -104
  31. package/dist/esm/pm-plugins/calculateDiff/diffBySteps.js +20 -9
  32. package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +82 -0
  33. package/dist/esm/pm-plugins/calculateDiff/simplifySteps.js +79 -5
  34. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +61 -0
  35. package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +24 -1
  36. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +1 -0
  37. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +8 -3
  38. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +3 -3
  39. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +5 -3
  40. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +59 -30
  41. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +14 -9
  42. package/dist/esm/pm-plugins/main.js +1 -0
  43. package/dist/esm/showDiffPlugin.js +36 -14
  44. package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
  45. package/dist/types/pm-plugins/calculateDiff/diffBySteps.d.ts +2 -1
  46. package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +7 -0
  47. package/dist/types/pm-plugins/calculateDiff/simplifySteps.d.ts +10 -0
  48. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +12 -0
  49. package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +5 -3
  50. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +6 -1
  51. package/dist/types/pm-plugins/decorations/createBlockChangedDecoration.d.ts +2 -1
  52. package/dist/types/pm-plugins/decorations/createChangedRowDecorationWidgets.d.ts +2 -1
  53. package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +2 -1
  54. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +2 -1
  55. package/dist/types/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +12 -2
  56. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +2 -1
  57. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.d.ts +2 -1
  58. package/dist/types/pm-plugins/main.d.ts +3 -1
  59. package/dist/types/showDiffPluginType.d.ts +29 -1
  60. package/package.json +10 -7
@@ -1,7 +1,7 @@
1
1
  import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
2
2
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
3
3
  import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
4
-
4
+ import { getAttributionKey } from '../decorations/colorSchemes/attributions';
5
5
  /**
6
6
  * Attempts to merge two consecutive ReplaceStep operations.
7
7
  * This merges steps where:
@@ -41,6 +41,69 @@ export function simplifySteps(steps, originalDoc) {
41
41
  return acc;
42
42
  }, []);
43
43
  }
44
+ const mergeAttributions = (first, second) => {
45
+ var _second$wasOffline;
46
+ if (!first) {
47
+ return second;
48
+ }
49
+ if (!second) {
50
+ return first;
51
+ }
52
+ return {
53
+ ...first,
54
+ ...second,
55
+ wasOffline: first.wasOffline === true || second.wasOffline === true ? true : (_second$wasOffline = second.wasOffline) !== null && _second$wasOffline !== void 0 ? _second$wasOffline : first.wasOffline
56
+ };
57
+ };
58
+
59
+ /**
60
+ * Attribution-preserving variant of `simplifySteps`.
61
+ *
62
+ * Steps may only merge when they have the same effective actor identity. Keeping the attribution
63
+ * coupled to the step also ensures filtering a no-op/analytics step cannot shift array indexes.
64
+ */
65
+ export function simplifyStepsWithAttribution(steps, stepAttributions, originalDoc) {
66
+ const stepsToFilter = removeUnusedAttributedSteps(steps.map((step, index) => ({
67
+ step,
68
+ stepAttribution: stepAttributions[index]
69
+ })), originalDoc);
70
+ return stepsToFilter.filter(({
71
+ step
72
+ }) => !(step instanceof AnalyticsStep)).reduce((acc, current) => {
73
+ var _previous$step$merge, _previous$step$merge2, _previous$step;
74
+ const previous = acc[acc.length - 1];
75
+ const isSameIdentity = previous && getAttributionKey(previous.stepAttribution) === getAttributionKey(current.stepAttribution);
76
+ const merged = isSameIdentity ? (_previous$step$merge = (_previous$step$merge2 = (_previous$step = previous.step).merge) === null || _previous$step$merge2 === void 0 ? void 0 : _previous$step$merge2.call(_previous$step, current.step)) !== null && _previous$step$merge !== void 0 ? _previous$step$merge : mergeReplaceSteps(previous.step, current.step) : null;
77
+ if (merged) {
78
+ acc[acc.length - 1] = {
79
+ step: merged,
80
+ stepAttribution: mergeAttributions(previous.stepAttribution, current.stepAttribution)
81
+ };
82
+ } else {
83
+ acc.push(current);
84
+ }
85
+ return acc;
86
+ }, []);
87
+ }
88
+
89
+ /**
90
+ * Does a first pass to remove steps that don't impact the document
91
+ */
92
+ function removeUnusedAttributedSteps(stepsWithAttribution, originalDoc) {
93
+ const finalSteps = [];
94
+ let firstPassDoc = originalDoc;
95
+ for (const stepWithAttribution of stepsWithAttribution) {
96
+ const {
97
+ step
98
+ } = stepWithAttribution;
99
+ const result = step.apply(firstPassDoc);
100
+ if (result.failed === null && result.doc && !areNodesEqualIgnoreAttrs(firstPassDoc, result.doc, ['localId'])) {
101
+ finalSteps.push(stepWithAttribution);
102
+ firstPassDoc = result.doc;
103
+ }
104
+ }
105
+ return finalSteps;
106
+ }
44
107
 
45
108
  /**
46
109
  * Does a first pass to remove steps that don't impact the document
@@ -0,0 +1,37 @@
1
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
2
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
3
+ const ATTRIBUTION_COLOR_PALETTE = ['purple', 'blue', 'teal', 'magenta', 'orange', 'green'];
4
+
5
+ /** `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared users. */
6
+ export const getAttributionKey = attribution => {
7
+ var _attribution$userId, _attribution$agentId, _attribution$agentTyp;
8
+ const userId = attribution === null || attribution === void 0 ? void 0 : (_attribution$userId = attribution.userId) === null || _attribution$userId === void 0 ? void 0 : _attribution$userId.trim();
9
+ const agentId = attribution === null || attribution === void 0 ? void 0 : (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
10
+ const agentType = attribution === null || attribution === void 0 ? void 0 : (_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim();
11
+ if (!userId && !agentId && !agentType) {
12
+ return undefined;
13
+ }
14
+ const agentIdentity = agentId || (agentType ? `type:${agentType}` : '');
15
+ return `user:${userId !== null && userId !== void 0 ? userId : ''}|agent:${agentIdentity}`;
16
+ };
17
+ export const areAttributionColorGatesEnabled = () => fg('confluence_ncs_step_diffing_version_history') && isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor');
18
+ export const isAttributionColoringEnabled = stepAttributions => areAttributionColorGatesEnabled() && (stepAttributions === null || stepAttributions === void 0 ? void 0 : stepAttributions.some(attribution => getAttributionKey(attribution) !== undefined)) === true;
19
+ export const createAttributionColorMap = stepAttributions => {
20
+ const keys = Array.from(new Set(stepAttributions.map(getAttributionKey).filter(key => key !== undefined))).sort();
21
+ return new Map(keys.map((key, index) => [key, ATTRIBUTION_COLOR_PALETTE[index % ATTRIBUTION_COLOR_PALETTE.length]]));
22
+ };
23
+ const getLatestAttribution = changes => {
24
+ const data = changes.flatMap(change => [...change.deleted, ...change.inserted]).map(span => span.data).filter(value => typeof value === 'object' && value !== null && 'stepIndex' in value && typeof value.stepIndex === 'number');
25
+ return data.reduce((latest, attribution) => !latest || attribution.stepIndex > latest.stepIndex ? attribution : latest, undefined);
26
+ };
27
+ const rangesOverlap = (fromA, toA, fromB, toB) => fromA < toB && fromB < toA;
28
+ const changesOverlap = (change, attributedChange) => {
29
+ const insertedOverlap = change.inserted.length > 0 && attributedChange.inserted.length > 0 && rangesOverlap(change.fromB, change.toB, attributedChange.fromB, attributedChange.toB);
30
+ const deletedOverlap = change.deleted.length > 0 && attributedChange.deleted.length > 0 && rangesOverlap(change.fromA, change.toA, attributedChange.fromA, attributedChange.toA);
31
+ return insertedOverlap || deletedOverlap;
32
+ };
33
+ export const getColorSchemeForChange = (change, attributedChanges, attributionColors, fallback) => {
34
+ var _attributionColors$ge;
35
+ const key = getAttributionKey(getLatestAttribution(attributedChanges.filter(attributedChange => changesOverlap(change, attributedChange))));
36
+ return key ? (_attributionColors$ge = attributionColors.get(key)) !== null && _attributionColors$ge !== void 0 ? _attributionColors$ge : fallback : fallback;
37
+ };
@@ -52,9 +52,26 @@ export const standardScheme = {
52
52
  deletedQuoteNodeActiveTone: 'borderAccent',
53
53
  strikesDeletedEmbedCard: false
54
54
  };
55
+ const createAttributionScheme = color => ({
56
+ ...standardScheme,
57
+ insertColor: color,
58
+ insertActiveColor: color,
59
+ deleteColor: color,
60
+ deleteActiveColor: color,
61
+ deletedCellColor: color
62
+ });
55
63
 
56
- /** Maps the public ColorScheme string to its data object. Adding a scheme = one entry here. */
64
+ /** Maps public schemes and attribution-only accents to their data objects. */
57
65
  export const colorSchemeRegistry = {
66
+ blue: createAttributionScheme('blue'),
67
+ green: createAttributionScheme('green'),
68
+ magenta: createAttributionScheme('magenta'),
69
+ orange: createAttributionScheme('orange'),
70
+ purple: createAttributionScheme('purple'),
71
+ teal: createAttributionScheme('teal'),
58
72
  traditional: traditionalScheme,
59
73
  standard: standardScheme
60
- };
74
+ };
75
+
76
+ /** Attribution accents are impossible in the refactor-off cohort; fall back defensively. */
77
+ export const getLegacyColorScheme = colorScheme => colorScheme === 'standard' || colorScheme === 'traditional' ? colorScheme : undefined;
@@ -4,7 +4,7 @@ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-e
4
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
5
  import { isExtendedEnabled } from '../isExtendedEnabled';
6
6
  import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
7
- import { colorSchemeRegistry } from './colorSchemes/schemes';
7
+ import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
8
8
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
9
9
  import { buildDiffDecorationSpec } from './decorationKeys';
10
10
  import { getBlockNodeStyleLegacy, resolveCellOverlayStyleLegacy } from './createBlockChangedDecoration.styles.legacy';
@@ -95,7 +95,10 @@ const getBlockNodeStyleNext = ({
95
95
  * OFF cohort of `platform_editor_show_diff_color_scheme_refactor`. Both are built to emit the
96
96
  * same style strings for both shipped schemes; see EDITOR-8281.
97
97
  */
98
- const getBlockNodeStyle = props => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getBlockNodeStyleNext(props) : getBlockNodeStyleLegacy(props);
98
+ const getBlockNodeStyle = props => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getBlockNodeStyleNext(props) : getBlockNodeStyleLegacy({
99
+ ...props,
100
+ colorScheme: getLegacyColorScheme(props.colorScheme)
101
+ });
99
102
 
100
103
  /**
101
104
  * A table cell is "empty" only when it has no content at all — no text AND no
@@ -165,7 +168,7 @@ export const createBlockChangedDecoration = ({
165
168
  const isEmptyCellBeingFilled = !isInserted && !!cellNode && isCellEmpty(cellNode);
166
169
  const useAddedStyle = isInserted || isEmptyCellBeingFilled;
167
170
  const cellOverlayStyle = isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? useAddedStyle ? isRoundedTable ? buildAddedCellOverlayRoundedStyle(colors) : buildAddedCellOverlayStyle(colors) : isRoundedTable ? buildDeletedCellOverlayRoundedStyle(colors) : buildDeletedCellOverlayStyle(colors) : resolveCellOverlayStyleLegacy({
168
- colorScheme,
171
+ colorScheme: getLegacyColorScheme(colorScheme),
169
172
  isRoundedTable,
170
173
  useAddedStyle
171
174
  });
@@ -6,7 +6,7 @@ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-e
6
6
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
7
  import { isExtendedEnabled } from '../isExtendedEnabled';
8
8
  import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildDeletedRowStyle } from './colorSchemes/factory';
9
- import { colorSchemeRegistry } from './colorSchemes/schemes';
9
+ import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
10
10
  import { resolveCellOverlayStyleLegacy, resolveDeletedRowStyleLegacy } from './createChangedRowDecorationWidgets.styles.legacy';
11
11
  import { buildDiffDecorationSpec } from './decorationKeys';
12
12
  import { findSafeInsertPos } from './utils/findSafeInsertPos';
@@ -120,7 +120,7 @@ const createChangedRowDOM = (rowNode, cellEdgeAttrs, nodeViewSerializer, colorSc
120
120
 
121
121
  // Inserted rows keep their natural styling; the row strikethrough is deletions only.
122
122
  if (!isExtendedEnabled(diffType) || !isInserted) {
123
- tr.setAttribute('style', isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? buildDeletedRowStyle(colors) : resolveDeletedRowStyleLegacy(colorScheme));
123
+ tr.setAttribute('style', isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? buildDeletedRowStyle(colors) : resolveDeletedRowStyleLegacy(getLegacyColorScheme(colorScheme)));
124
124
  }
125
125
  tr.setAttribute('data-testid', 'show-diff-deleted-row');
126
126
 
@@ -136,7 +136,7 @@ const createChangedRowDOM = (rowNode, cellEdgeAttrs, nodeViewSerializer, colorSc
136
136
  const overlay = document.createElement('span');
137
137
  const isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
138
138
  const overlayStyle = isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? isInserted ? isRoundedTable ? buildAddedCellOverlayRoundedStyle(colors) : buildAddedCellOverlayStyle(colors) : isRoundedTable ? buildDeletedCellOverlayRoundedStyle(colors) : buildDeletedCellOverlayStyle(colors) : resolveCellOverlayStyleLegacy({
139
- colorScheme,
139
+ colorScheme: getLegacyColorScheme(colorScheme),
140
140
  isInserted,
141
141
  isRoundedTable
142
142
  });
@@ -3,7 +3,7 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
3
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
4
4
  import { isExtendedEnabled } from '../isExtendedEnabled';
5
5
  import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
6
- import { colorSchemeRegistry } from './colorSchemes/schemes';
6
+ import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
7
7
  import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
8
8
  import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy';
9
9
  import { buildDiffDecorationSpec } from './decorationKeys';
@@ -64,7 +64,10 @@ const resolveInlineChangedStyleRefactored = ({
64
64
  * Single gate for the inline `style` string: registry + factory when the refactor is on, the
65
65
  * verbatim pre-refactor per-scheme constants when it is off.
66
66
  */
67
- const resolveInlineChangedStyle = args => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveInlineChangedStyleRefactored(args) : resolveInlineChangedStyleLegacy(args);
67
+ const resolveInlineChangedStyle = args => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveInlineChangedStyleRefactored(args) : resolveInlineChangedStyleLegacy({
68
+ ...args,
69
+ colorScheme: getLegacyColorScheme(args.colorScheme)
70
+ });
68
71
 
69
72
  /**
70
73
  * Single gate for the atomic-inline-node decoration attributes. The two cohorts differ in the DOM:
@@ -76,7 +79,7 @@ const resolveAtomicInlineAttrs = (inlineNodeName, colorScheme, colors) => isExpe
76
79
  className: getAtomicInlineNodeClassName(inlineNodeName),
77
80
  styleSuffix: buildAtomicInlineChangedCSSVariables(colors)
78
81
  } : {
79
- className: getAtomicInlineNodeClassNameLegacy(inlineNodeName, colorScheme),
82
+ className: getAtomicInlineNodeClassNameLegacy(inlineNodeName, getLegacyColorScheme(colorScheme)),
80
83
  styleSuffix: ''
81
84
  };
82
85
 
@@ -1,7 +1,10 @@
1
+ import isEqual from 'lodash/isEqual';
2
+ import omit from 'lodash/omit';
1
3
  import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
2
4
  import { isExperimentEnabled } from '@atlaskit/editor-common/deprecated-platform-feature-experiments';
3
5
  import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
4
6
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
7
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
5
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
9
  import { getRequiredIncludedDiffableAttrs, isDiffableAttr } from './diffableAttrs';
7
10
  const filterUndefined = x => !!x;
@@ -27,29 +30,49 @@ const isInlineAttrChangeNodeName = nodeName => nodeName in inlineNodeAttrMap;
27
30
  // Extension node type names. Their "any attr except localId" exclude rule lives
28
31
  // in the shared `diffableAttrs` map and is applied via `isDiffableAttr` below.
29
32
  const extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
33
+ const transientExtensionAttrPaths = ['localId', 'parameters.macroParams._parentId'];
34
+
35
+ /**
36
+ * Removes extension metadata that can change between document versions without changing the
37
+ * extension's user-visible configuration.
38
+ */
39
+ const getComparableExtensionAttrs = node => omit(node.attrs, transientExtensionAttrPaths);
40
+ const haveSameRelevantExtensionAttrs = (beforeNode, afterNode) => isEqual(getComparableExtensionAttrs(beforeNode), getComparableExtensionAttrs(afterNode));
30
41
  const getStepAttrs = step => {
31
42
  if (step instanceof AttrStep) {
32
43
  return [step.attr];
33
44
  }
34
- if (step instanceof SetAttrsStep && step.attrs) {
45
+ if (step.attrs) {
35
46
  return Object.keys(step.attrs);
36
47
  }
37
48
  return [];
38
49
  };
39
- export const getAttrChangeRanges = (doc, steps, originalDoc) => {
40
- return steps.map(step => {
50
+ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
51
+ return attrStepContexts.map(attrStepContext => {
52
+ const {
53
+ afterNode,
54
+ beforeNode,
55
+ finalPos,
56
+ originalPos,
57
+ step
58
+ } = attrStepContext;
41
59
  if (!(step instanceof AttrStep) && !(step instanceof SetAttrsStep)) {
42
60
  return undefined;
43
61
  }
44
62
  const stepAttrs = getStepAttrs(step);
45
- const $pos = doc.resolve(step.pos);
46
- const nodeAtPos = doc.nodeAt(step.pos);
63
+ // SetAttrsStep contains the complete replacement attrs, not only the attrs that changed.
64
+ // Keep the legacy payload-based checks until the rollout gate is enabled, and fall back
65
+ // to them if either step-time node is unavailable.
66
+ const attrsToCheck = fg('platform_editor_reduce_diff_attr_sensitivity') && beforeNode && afterNode ? stepAttrs.filter(attrName => !isEqual(beforeNode.attrs[attrName], afterNode.attrs[attrName])) : stepAttrs;
67
+ const $pos = doc.resolve(finalPos);
68
+ const nodeAtPos = doc.nodeAt(finalPos);
69
+ const originalNodeAtPos = originalPos === undefined ? null : originalDoc.nodeAt(originalPos);
47
70
 
48
71
  // date node: timestamp attribute change — highlight the date node itself (inline)
49
- if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date' && !isExperimentEnabled('platform_editor_improve_inline_diffs', () => expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true))) {
72
+ if (attrsToCheck.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date' && !isExperimentEnabled('platform_editor_improve_inline_diffs', () => expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true))) {
50
73
  return {
51
- fromB: step.pos,
52
- toB: step.pos + nodeAtPos.nodeSize,
74
+ fromB: finalPos,
75
+ toB: finalPos + nodeAtPos.nodeSize,
53
76
  isInline: true,
54
77
  inlineNodeName: 'date'
55
78
  };
@@ -62,16 +85,15 @@ export const getAttrChangeRanges = (doc, steps, originalDoc) => {
62
85
  const nodeName = nodeAtPos.type.name;
63
86
  if (isInlineAttrChangeNodeName(nodeName)) {
64
87
  const watchedAttrs = inlineNodeAttrMap[nodeName];
65
- if (stepAttrs.some(v => watchedAttrs.includes(v))) {
66
- const originalNodeAtPos = originalDoc === null || originalDoc === void 0 ? void 0 : originalDoc.nodeAt(step.pos);
88
+ if (attrsToCheck.some(v => watchedAttrs.includes(v))) {
67
89
  return {
68
- fromB: step.pos,
69
- toB: step.pos + nodeAtPos.nodeSize,
90
+ fromB: finalPos,
91
+ toB: finalPos + nodeAtPos.nodeSize,
70
92
  isInline: true,
71
93
  inlineNodeName: nodeName,
72
- ...(originalNodeAtPos && {
73
- fromA: step.pos,
74
- toA: step.pos + originalNodeAtPos.nodeSize
94
+ ...(originalPos !== undefined && originalNodeAtPos && {
95
+ fromA: originalPos,
96
+ toA: originalPos + originalNodeAtPos.nodeSize
75
97
  })
76
98
  };
77
99
  }
@@ -79,10 +101,10 @@ export const getAttrChangeRanges = (doc, steps, originalDoc) => {
79
101
  }
80
102
 
81
103
  // taskItem node: state attribute change — highlight the taskItem node
82
- if (stepAttrs.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
104
+ if (attrsToCheck.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
83
105
  return {
84
- fromB: step.pos,
85
- toB: step.pos + nodeAtPos.nodeSize
106
+ fromB: finalPos,
107
+ toB: finalPos + nodeAtPos.nodeSize
86
108
  };
87
109
  }
88
110
 
@@ -90,30 +112,33 @@ export const getAttrChangeRanges = (doc, steps, originalDoc) => {
90
112
  // (e.g. note -> warning) — highlight the new panel and, when the original node
91
113
  // is resolvable, expose its range (fromA/toA) so the caller can render the old
92
114
  // panel as a "deleted" widget for a before/after comparison.
93
- if (stepAttrs.some(v => panelAttrs.includes(v)) && nodeAtPos && getBaseNodeTypeName(nodeAtPos.type) === 'panel') {
94
- const originalNodeAtPos = originalDoc === null || originalDoc === void 0 ? void 0 : originalDoc.nodeAt(step.pos);
115
+ if (attrsToCheck.some(v => panelAttrs.includes(v)) && nodeAtPos && getBaseNodeTypeName(nodeAtPos.type) === 'panel') {
95
116
  return {
96
- fromB: step.pos,
97
- toB: step.pos + nodeAtPos.nodeSize,
98
- ...(originalNodeAtPos && {
99
- fromA: step.pos,
100
- toA: step.pos + originalNodeAtPos.nodeSize
117
+ fromB: finalPos,
118
+ toB: finalPos + nodeAtPos.nodeSize,
119
+ ...(originalPos !== undefined && originalNodeAtPos && {
120
+ fromA: originalPos,
121
+ toA: originalPos + originalNodeAtPos.nodeSize
101
122
  })
102
123
  };
103
124
  }
104
125
 
105
- // extension nodes: highlight on any diffable attr change (exclude rule in shared map)
106
- if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => isDiffableAttr(nodeAtPos.type.name, v))) {
126
+ // Extension nodes: highlight changes to user-visible configuration. When enabled,
127
+ // transient extension metadata is ignored by the comparison below.
128
+ if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && attrsToCheck.some(v => isDiffableAttr(nodeAtPos.type.name, v))) {
129
+ if (fg('platform_editor_reduce_diff_attr_sensitivity') && beforeNode && afterNode && beforeNode.type === afterNode.type && haveSameRelevantExtensionAttrs(beforeNode, afterNode)) {
130
+ return undefined;
131
+ }
107
132
  const isInline = nodeAtPos.type.name === 'inlineExtension';
108
133
  return {
109
- fromB: step.pos,
110
- toB: step.pos + nodeAtPos.nodeSize,
134
+ fromB: finalPos,
135
+ toB: finalPos + nodeAtPos.nodeSize,
111
136
  isInline
112
137
  };
113
138
  }
114
139
 
115
140
  // media node: id/collection/url attribute change — highlight the mediaSingle parent
116
- if (stepAttrs.some(v => mediaAttrs.includes(v)) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
141
+ if (attrsToCheck.some(v => mediaAttrs.includes(v)) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
117
142
  const startPos = $pos.pos + $pos.parentOffset;
118
143
  return {
119
144
  fromB: startPos,
@@ -14,7 +14,7 @@ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-e
14
14
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
15
  import { isExtendedEnabled } from '../../isExtendedEnabled';
16
16
  import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildAddedCellOverlayStyleNew, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildDeletedInlineContentStyle, buildDeletedInlineContentStyleExtended, buildDeletedLozengeActiveStyle, buildDeletedLozengeStyle, buildDeletedNodeCSSVariables, buildDeletedQuoteNodeWithLozengeStyle, buildDeletedStrikethroughLine, buildDeletedWrappedBlockOutline, buildInsertedBlockNodeStyle, buildInsertedInlineStyle, buildInsertStyleInBlockExtended, buildInsertStyleInBlockExtendedNoUnderline, buildInsertStyleNode } from '../colorSchemes/factory';
17
- import { colorSchemeRegistry, standardScheme } from '../colorSchemes/schemes';
17
+ import { colorSchemeRegistry, getLegacyColorScheme, standardScheme } from '../colorSchemes/schemes';
18
18
  import { getChangedContentStyleLegacy, getChangedNodeStyleLegacy, getDeletedContentStyleLegacy, getDeletedContentStyleUnboundedLegacy, getInsertedContentStyleLegacy, hasRestingDeletedRingLegacy, resolveCellOverlayStyleLegacy, resolveNestedInsertedNodeStyleLegacy, resolveRemovedLozengeStyleLegacy } from './wrapBlockNodeViewStyles.legacy';
19
19
  const DEFAULT_COLOR_SCHEME = 'standard';
20
20
  const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
@@ -108,28 +108,31 @@ const resolveNestedInsertedNodeStyleNext = () => buildInsertStyleNode(nestedCont
108
108
  const hasRestingDeletedRingNext = colorScheme => getColorScheme(colorScheme).deletedNodeEmphasis === 'stateful';
109
109
 
110
110
  /** Inline content style for a changed (inserted or deleted) block node. */
111
- export const getChangedContentStyle = (colorScheme, isActive = false, isInserted = false, diffType, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getChangedContentStyleNext(colorScheme, isActive, isInserted, diffType, hideAddedDiffsUnderline) : getChangedContentStyleLegacy(colorScheme, isActive, isInserted, diffType, hideAddedDiffsUnderline);
111
+ export const getChangedContentStyle = (colorScheme, isActive = false, isInserted = false, diffType, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getChangedContentStyleNext(colorScheme, isActive, isInserted, diffType, hideAddedDiffsUnderline) : getChangedContentStyleLegacy(getLegacyColorScheme(colorScheme), isActive, isInserted, diffType, hideAddedDiffsUnderline);
112
112
 
113
113
  /** Node-specific style for a changed block node, or `undefined` when the node type has none. */
114
- export const getChangedNodeStyle = (nodeName, colorScheme, isInserted = false, isActive = false, diffType, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getChangedNodeStyleNext(nodeName, colorScheme, isInserted, isActive, diffType, hideAddedDiffsUnderline) : getChangedNodeStyleLegacy(nodeName, colorScheme, isInserted, isActive, diffType, hideAddedDiffsUnderline);
114
+ export const getChangedNodeStyle = (nodeName, colorScheme, isInserted = false, isActive = false, diffType, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getChangedNodeStyleNext(nodeName, colorScheme, isInserted, isActive, diffType, hideAddedDiffsUnderline) : getChangedNodeStyleLegacy(nodeName, getLegacyColorScheme(colorScheme), isInserted, isActive, diffType, hideAddedDiffsUnderline);
115
115
 
116
116
  /** The positioned line drawn across deleted content spanning an unbounded range. */
117
- export const getDeletedContentStyleUnbounded = (colorScheme, isActive = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleUnboundedNext(colorScheme, isActive) : getDeletedContentStyleUnboundedLegacy(colorScheme, isActive);
117
+ export const getDeletedContentStyleUnbounded = (colorScheme, isActive = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleUnboundedNext(colorScheme, isActive) : getDeletedContentStyleUnboundedLegacy(getLegacyColorScheme(colorScheme), isActive);
118
118
 
119
119
  /** Inline style for inserted content. */
120
- export const getInsertedContentStyle = (colorScheme, isActive = false, hideAddedDiffsUnderline = false) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getInsertedContentStyleNext(colorScheme, isActive, hideAddedDiffsUnderline) : getInsertedContentStyleLegacy(colorScheme, isActive, hideAddedDiffsUnderline);
120
+ 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);
121
121
 
122
122
  /** Inline style for deleted content. */
123
- export const getDeletedContentStyle = (colorScheme, isActive = false, diffType) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleNext(colorScheme, isActive, diffType) : getDeletedContentStyleLegacy(colorScheme, isActive, diffType);
123
+ export const getDeletedContentStyle = (colorScheme, isActive = false, diffType) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getDeletedContentStyleNext(colorScheme, isActive, diffType) : getDeletedContentStyleLegacy(getLegacyColorScheme(colorScheme), isActive, diffType);
124
124
 
125
125
  /**
126
126
  * Overlay style for one added or deleted table cell. Invariant across the cells of a table, so the
127
127
  * `querySelectorAll('td, th')` loop in `wrapBlockNodeView.ts` resolves it once.
128
128
  */
129
- export const resolveCellOverlayStyle = args => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveCellOverlayStyleNext(args) : resolveCellOverlayStyleLegacy(args);
129
+ export const resolveCellOverlayStyle = args => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveCellOverlayStyleNext(args) : resolveCellOverlayStyleLegacy({
130
+ ...args,
131
+ colorScheme: getLegacyColorScheme(args.colorScheme)
132
+ });
130
133
 
131
134
  /** Inner style for the "Removed" lozenge on a deleted block node. */
132
- export const resolveRemovedLozengeStyle = (colorScheme, isActive) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveRemovedLozengeStyleNext(colorScheme, isActive) : resolveRemovedLozengeStyleLegacy(colorScheme, isActive);
135
+ export const resolveRemovedLozengeStyle = (colorScheme, isActive) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveRemovedLozengeStyleNext(colorScheme, isActive) : resolveRemovedLozengeStyleLegacy(getLegacyColorScheme(colorScheme), isActive);
133
136
 
134
137
  /**
135
138
  * Style for inserted content nested inside a multi-container or list node — the `decisionList` and
@@ -142,7 +145,7 @@ export const resolveNestedInsertedNodeStyle = () => isExperimentEnabled('platfor
142
145
  * Whether the colour scheme draws a resting (non-active) ring on a deleted media/embed node, which
143
146
  * is what the `show-diff-deleted-outline-new` class styles in editor-core's smartCardStyles.
144
147
  */
145
- export const hasRestingDeletedRing = colorScheme => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? hasRestingDeletedRingNext(colorScheme) : hasRestingDeletedRingLegacy(colorScheme);
148
+ export const hasRestingDeletedRing = colorScheme => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? hasRestingDeletedRingNext(colorScheme) : hasRestingDeletedRingLegacy(getLegacyColorScheme(colorScheme));
146
149
 
147
150
  /**
148
151
  * Custom properties consumed by the `show-diff-deleted-node-vars` selectors in editor-core.
@@ -27,6 +27,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
27
27
  const defaultDiffType = getDefaultDiffType();
28
28
  return {
29
29
  steps: [],
30
+ stepAttributions: [],
30
31
  originalDoc: undefined,
31
32
  decorations: DecorationSet.empty,
32
33
  isDisplayingChanges: false,
@@ -4,6 +4,27 @@ import { getDeletedWidgets } from './pm-plugins/getDeletedWidgets';
4
4
  import { getScrollableDecorations } from './pm-plugins/getScrollableDecorations';
5
5
  import { createPlugin, showDiffPluginKey } from './pm-plugins/main';
6
6
  import { IndicatorBarContentComponent } from './ui/IndicatorBar/IndicatorBarContentComponent';
7
+ const normalizeShowDiffParams = params => {
8
+ if (!params || !('stepsWithAttribution' in params)) {
9
+ return params ? {
10
+ ...params,
11
+ stepAttributions: []
12
+ } : params;
13
+ }
14
+ const {
15
+ stepsWithAttribution,
16
+ ...rest
17
+ } = params;
18
+ return {
19
+ ...rest,
20
+ steps: stepsWithAttribution.map(({
21
+ step
22
+ }) => step),
23
+ stepAttributions: stepsWithAttribution.map(({
24
+ stepAttribution
25
+ }) => stepAttribution)
26
+ };
27
+ };
7
28
  export const showDiffPlugin = ({
8
29
  api,
9
30
  config
@@ -30,7 +51,7 @@ export const showDiffPlugin = ({
30
51
  });
31
52
  }
32
53
  return tr.setMeta(showDiffPluginKey, {
33
- ...params,
54
+ ...normalizeShowDiffParams(params),
34
55
  action: 'SHOW_DIFF'
35
56
  });
36
57
  },
@@ -45,6 +66,7 @@ export const showDiffPlugin = ({
45
66
  }
46
67
  return tr.setMeta(showDiffPluginKey, {
47
68
  steps: [],
69
+ stepAttributions: [],
48
70
  action: 'HIDE_DIFF'
49
71
  });
50
72
  },