@atlaskit/editor-plugin-show-diff 14.5.2 → 15.0.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 (76) hide show
  1. package/CHANGELOG.md +34 -6
  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 +3 -9
  5. package/dist/cjs/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  6. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +21 -4
  7. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +6 -56
  8. package/dist/cjs/pm-plugins/decorations/colorSchemes/schemes.js +6 -20
  9. package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +4 -6
  10. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  11. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +14 -29
  12. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  13. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -25
  14. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  15. package/dist/cjs/pm-plugins/main.js +4 -4
  16. package/dist/cjs/showDiffPlugin.js +4 -5
  17. package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +122 -0
  18. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +140 -0
  19. package/dist/cjs/ui/ContributorTag/contributorTagController.js +500 -0
  20. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +46 -0
  21. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +3 -5
  22. package/dist/es2019/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  23. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +20 -3
  24. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +6 -55
  25. package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +14 -26
  26. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +4 -6
  27. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  28. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +16 -30
  29. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  30. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -17
  31. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  32. package/dist/es2019/pm-plugins/main.js +4 -4
  33. package/dist/es2019/showDiffPlugin.js +4 -5
  34. package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +110 -0
  35. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +136 -0
  36. package/dist/es2019/ui/ContributorTag/contributorTagController.js +403 -0
  37. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +40 -0
  38. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +3 -9
  39. package/dist/esm/pm-plugins/calculateDiff/smart/classifySmartChanges.js +1 -2
  40. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +21 -4
  41. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +6 -57
  42. package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +7 -20
  43. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +4 -6
  44. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -2
  45. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +15 -30
  46. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +1 -2
  47. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +2 -25
  48. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +1 -2
  49. package/dist/esm/pm-plugins/main.js +4 -4
  50. package/dist/esm/showDiffPlugin.js +4 -5
  51. package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +115 -0
  52. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +135 -0
  53. package/dist/esm/ui/ContributorTag/contributorTagController.js +493 -0
  54. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +40 -0
  55. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +3 -3
  56. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +2 -2
  57. package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +1 -1
  58. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +13 -16
  59. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +11 -9
  60. package/dist/types/pm-plugins/main.d.ts +1 -2
  61. package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +25 -0
  62. package/dist/types/ui/ContributorTag/contributorAvatarRenderer.d.ts +22 -0
  63. package/dist/types/ui/ContributorTag/contributorTagController.d.ts +85 -0
  64. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +12 -0
  65. package/package.json +9 -13
  66. package/dist/cjs/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  67. package/dist/cjs/ui/ContributorTag/ContributorTag.js +0 -383
  68. package/dist/cjs/ui/ContributorTag/ContributorTagWidget.js +0 -50
  69. package/dist/es2019/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  70. package/dist/es2019/ui/ContributorTag/ContributorTag.js +0 -312
  71. package/dist/es2019/ui/ContributorTag/ContributorTagWidget.js +0 -43
  72. package/dist/esm/ui/ContributorTag/ContributorTag.compiled.css +0 -29
  73. package/dist/esm/ui/ContributorTag/ContributorTag.js +0 -375
  74. package/dist/esm/ui/ContributorTag/ContributorTagWidget.js +0 -43
  75. package/dist/types/ui/ContributorTag/ContributorTag.d.ts +0 -37
  76. package/dist/types/ui/ContributorTag/ContributorTagWidget.d.ts +0 -22
@@ -1,7 +1,4 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
- import { participantColors } from '@atlaskit/editor-shared-styles/constants';
3
- import { PARTICIPANT_COLOR_SCHEMES } from './types';
4
-
5
2
  // token() is a build-time transform needing static literals, so pre-compute every colour's
6
3
  // tokens here and index by name at runtime.
7
4
 
@@ -77,69 +74,23 @@ const textAccentMap = {
77
74
  gray: "var(--ds-text-accent-gray, #505258)",
78
75
  lime: "var(--ds-text-accent-lime, #4C6B1F)"
79
76
  };
80
-
81
- /** Pressed counterpart of each participant's diff-safe SVG background tint. */
82
- const participantPressedBackgroundMap = {
83
- 'red-bolder': bgSubtlerPressedMap.red,
84
- 'blue-bolder': bgSubtlerPressedMap.blue,
85
- 'green-bolder': bgSubtlerPressedMap.green,
86
- 'yellow-bolder': bgSubtlerPressedMap.yellow,
87
- 'purple-bolder': bgSubtlerPressedMap.purple,
88
- 'magenta-bolder': bgSubtlerPressedMap.magenta,
89
- 'teal-bolder': bgSubtlerPressedMap.teal,
90
- 'orange-bolder': bgSubtlerPressedMap.orange,
91
- 'lime-bolder': bgSubtlerPressedMap.lime,
92
- 'gray-bolder': bgSubtlerPressedMap.gray,
93
- 'blue-subtle': bgSubtlestPressedMap.blue,
94
- 'red-subtle': bgSubtlestPressedMap.red,
95
- 'orange-subtle': bgSubtlestPressedMap.orange,
96
- 'yellow-subtle': bgSubtlestPressedMap.yellow,
97
- 'green-subtle': bgSubtlestPressedMap.green,
98
- 'teal-subtle': bgSubtlestPressedMap.teal,
99
- 'purple-subtle': bgSubtlestPressedMap.purple,
100
- 'magenta-subtle': bgSubtlestPressedMap.magenta
101
- };
102
- /**
103
- * Telepointers can put inverse text directly on their bolder colour. Diff colours sit behind
104
- * arbitrary document content, so use the participant SVG tint and its matching pressed token for
105
- * diff backgrounds. Borders and rings use the exact participant colour, preserving all 18
106
- * bolder/subtle slots while keeping every value in the shared ADS participant palette.
107
- */
108
- const participantColorTokenSets = Object.fromEntries(PARTICIPANT_COLOR_SCHEMES.map((scheme, index) => {
109
- const participantColor = participantColors[index];
110
- if (!participantColor) {
111
- throw new Error(`Missing shared participant colour for show-diff slot ${index}`);
112
- }
113
- return [scheme, {
114
- backgroundSubtlest: participantColor.svgBackgroundColor,
115
- backgroundSubtlestPressed: participantPressedBackgroundMap[scheme],
116
- backgroundSubtler: participantColor.svgBackgroundColor,
117
- backgroundSubtlerPressed: participantPressedBackgroundMap[scheme],
118
- borderAccent: participantColor.backgroundColor,
119
- // The first ten bolder colours are safe as accents on the editor surface. The
120
- // remaining subtle colours use their paired dark participant text colour.
121
- textAccent: index < 10 ? participantColor.backgroundColor : participantColor.textColor
122
- }];
123
- }));
124
- const participantColorSchemeSet = new Set(PARTICIPANT_COLOR_SCHEMES);
125
- const isParticipantColorScheme = color => participantColorSchemeSet.has(color);
126
77
  function bgSubtlest(color) {
127
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlest : bgSubtlestMap[color];
78
+ return bgSubtlestMap[color];
128
79
  }
129
80
  function bgSubtlestPressed(color) {
130
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlestPressed : bgSubtlestPressedMap[color];
81
+ return bgSubtlestPressedMap[color];
131
82
  }
132
83
  function bgSubtler(color) {
133
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtler : bgSubtlerMap[color];
84
+ return bgSubtlerMap[color];
134
85
  }
135
86
  function bgSubtlerPressed(color) {
136
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].backgroundSubtlerPressed : bgSubtlerPressedMap[color];
87
+ return bgSubtlerPressedMap[color];
137
88
  }
138
89
  function borderAccent(color) {
139
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].borderAccent : borderAccentMap[color];
90
+ return borderAccentMap[color];
140
91
  }
141
92
  function textAccent(color) {
142
- return isParticipantColorScheme(color) ? participantColorTokenSets[color].textAccent : textAccentMap[color];
93
+ return textAccentMap[color];
143
94
  }
144
95
 
145
96
  /** Presentation tokens for one diff colour, shared by highlights and contributor tags. */
@@ -1,3 +1,5 @@
1
+ import { PARTICIPANT_COLOR_SCHEMES } from './types';
2
+
1
3
  /** Green insertions, red deletions. */
2
4
  export const traditionalScheme = {
3
5
  insertColor: 'green',
@@ -52,35 +54,21 @@ export const standardScheme = {
52
54
  deletedQuoteNodeActiveTone: 'borderAccent',
53
55
  strikesDeletedEmbedCard: false
54
56
  };
55
- const createAttributionScheme = color => ({
56
- ...standardScheme,
57
- insertColor: color,
58
- insertActiveColor: color,
59
- deleteColor: color,
60
- deleteActiveColor: color,
61
- deletedCellColor: color
62
- });
57
+ const createAttributionScheme = color => {
58
+ return {
59
+ ...standardScheme,
60
+ insertColor: color,
61
+ insertActiveColor: color,
62
+ deleteColor: color,
63
+ deleteActiveColor: color,
64
+ deletedCellColor: color
65
+ };
66
+ };
67
+ const attributionColorSchemes = Object.fromEntries(PARTICIPANT_COLOR_SCHEMES.map(color => [color, createAttributionScheme(color)]));
63
68
 
64
69
  /** Maps public schemes and attribution-only participant slots to their data objects. */
65
70
  export const colorSchemeRegistry = {
66
- 'red-bolder': createAttributionScheme('red-bolder'),
67
- 'blue-bolder': createAttributionScheme('blue-bolder'),
68
- 'green-bolder': createAttributionScheme('green-bolder'),
69
- 'yellow-bolder': createAttributionScheme('yellow-bolder'),
70
- 'purple-bolder': createAttributionScheme('purple-bolder'),
71
- 'magenta-bolder': createAttributionScheme('magenta-bolder'),
72
- 'teal-bolder': createAttributionScheme('teal-bolder'),
73
- 'orange-bolder': createAttributionScheme('orange-bolder'),
74
- 'lime-bolder': createAttributionScheme('lime-bolder'),
75
- 'gray-bolder': createAttributionScheme('gray-bolder'),
76
- 'blue-subtle': createAttributionScheme('blue-subtle'),
77
- 'red-subtle': createAttributionScheme('red-subtle'),
78
- 'orange-subtle': createAttributionScheme('orange-subtle'),
79
- 'yellow-subtle': createAttributionScheme('yellow-subtle'),
80
- 'green-subtle': createAttributionScheme('green-subtle'),
81
- 'teal-subtle': createAttributionScheme('teal-subtle'),
82
- 'purple-subtle': createAttributionScheme('purple-subtle'),
83
- 'magenta-subtle': createAttributionScheme('magenta-subtle'),
71
+ ...attributionColorSchemes,
84
72
  traditional: traditionalScheme,
85
73
  standard: standardScheme
86
74
  };
@@ -1,16 +1,14 @@
1
1
  /** Schemes consumers may explicitly request through the show-diff API. */
2
2
 
3
3
  /** Attribution-only participant slots selected by the plugin, never by consumers. */
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'];
4
+ export const PARTICIPANT_COLOR_SCHEMES = ['red', 'blue', 'green', 'yellow', 'purple', 'magenta', 'teal', 'orange', 'lime', 'gray'];
5
5
 
6
6
  /** ADS accent hues used by the public and participant colour schemes. */
7
7
 
8
8
  /** A public scheme or an attribution participant slot resolved internally for one change. */
9
9
 
10
- /** A colour value that the factory expands into a complete set of diff-safe ADS tokens. */
11
-
12
10
  /**
13
- * A show-diff colour scheme. Colour fields are ADS hues or shared participant slots that
14
- * `factory.ts` expands into token paths; the rest encode structural differences no colour can
15
- * express. Ring widths, padding, radius and pointer-events are fixed in the factory, not per scheme.
11
+ * A show-diff colour scheme. Colour fields are ADS hues that `factory.ts` expands into token paths;
12
+ * the rest encode structural differences no colour can express. Ring widths, padding, radius and
13
+ * pointer-events are fixed in the factory, not per scheme.
16
14
  */
@@ -11,7 +11,6 @@ import { resolveCellOverlayStyleLegacy, resolveDeletedRowStyleLegacy } from './c
11
11
  import { buildDiffDecorationSpec } from './decorationKeys';
12
12
  import { findSafeInsertPos } from './utils/findSafeInsertPos';
13
13
  import { applyCellEdgeAttrs, getRowCellEdgeAttrs, getTableTopAndBottomCellEdgeAttrs } from './utils/tableCellEdgeAttrs';
14
- const DEFAULT_COLOR_SCHEME = 'standard';
15
14
  /**
16
15
  * Extracts information about deleted table rows from a change
17
16
  */
@@ -116,7 +115,7 @@ const isEmptyRow = rowNode => {
116
115
  */
117
116
  const createChangedRowDOM = (rowNode, cellEdgeAttrs, nodeViewSerializer, colorScheme, isInserted, diffType) => {
118
117
  const tr = document.createElement('tr');
119
- const colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
118
+ const colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
120
119
 
121
120
  // Inserted rows keep their natural styling; the row strikethrough is deletions only.
122
121
  if (!isExtendedEnabled(diffType) || !isInserted) {
@@ -1,9 +1,8 @@
1
- import { createElement } from 'react';
2
1
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
2
  import { fg } from '@atlaskit/platform-feature-flags/fg';
4
- import { ContributorTagWidget } from '../../ui/ContributorTag/ContributorTagWidget';
3
+ import { ContributorTagController } from '../../ui/ContributorTag/contributorTagController';
5
4
  import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
6
- import { buildContributorTagDecorationSpec, DecorationFamily } from './decorationKeys';
5
+ import { buildContributorTagDecorationSpec } from './decorationKeys';
7
6
 
8
7
  /** Marks the host element of one tag, so a tag can be found in the document by its own diff. */
9
8
  export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
@@ -12,23 +11,19 @@ export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
12
11
  const isContributorTagWidgetEnabled = () => fg('confluence_ncs_step_diffing_version_history');
13
12
 
14
13
  /**
15
- * Everything a tag needs to mount itself into the document, plumbed down from the plugin. Absent in
14
+ * Everything a tag needs to draw itself into the document, plumbed down from the plugin. Absent in
16
15
  * unit tests that build decorations directly: the hosts are still emitted, they just stay empty.
17
16
  */
18
17
 
19
18
  /** A mounted tag, so whoever emitted the host can take it down again. */
20
19
 
21
20
  /**
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.
21
+ * Draws the tag into `host`. The tag resolves its own model from plugin state, so a redraw of the
22
+ * host re-parents the tag instead of stranding it, and `isActive` changes arrive as an update.
23
+ *
24
+ * Each mount owns its own controller and DOM, so when ProseMirror draws a replacement widget before
25
+ * destroying the one it replaces, the outgoing `destroy` cannot take the incoming tag down with it
26
+ * (EDITOR-8702).
32
27
  */
33
28
  export const mountContributorTag = ({
34
29
  diffId,
@@ -38,24 +33,15 @@ export const mountContributorTag = ({
38
33
  if (!mountContext) {
39
34
  return undefined;
40
35
  }
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
- };
36
+ const controller = new ContributorTagController({
37
+ ...mountContext,
38
+ diffId
39
+ });
40
+ controller.mount(host);
41
+ return controller;
56
42
  };
57
43
  export const unmountContributorTag = mount => {
58
- mount === null || mount === void 0 ? void 0 : mount.portalProviderAPI.remove(mount.key);
44
+ mount === null || mount === void 0 ? void 0 : mount.destroy();
59
45
  };
60
46
 
61
47
  /**
@@ -11,8 +11,7 @@ import { buildDiffDecorationSpec } from './decorationKeys';
11
11
  const displayNoneStyle = convertToInlineCss({
12
12
  display: 'none'
13
13
  });
14
- const DEFAULT_COLOR_SCHEME = 'standard';
15
- const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
14
+ const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
16
15
 
17
16
  /**
18
17
  * Class names for an atomic inline node decoration (date, emoji, mention, status). Scheme-agnostic
@@ -1,11 +1,9 @@
1
1
  import isEqual from 'lodash/isEqual';
2
2
  import omit from 'lodash/omit';
3
3
  import { SetAttrsStep } from '@atlaskit/adf-schema/steps/set-attrs';
4
- import { isExperimentEnabled } from '@atlaskit/editor-common/deprecated-platform-feature-experiments';
5
4
  import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
6
5
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
7
6
  import { fg } from '@atlaskit/platform-feature-flags/fg';
8
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
7
  import { getRequiredIncludedDiffableAttrs, isDiffableAttr } from './diffableAttrs';
10
8
  const filterUndefined = x => !!x;
11
9
 
@@ -68,20 +66,9 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
68
66
  const nodeAtPos = doc.nodeAt(finalPos);
69
67
  const originalNodeAtPos = originalPos === undefined ? null : originalDoc.nodeAt(originalPos);
70
68
 
71
- // date node: timestamp attribute change — highlight the date node itself (inline)
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))) {
73
- return {
74
- fromB: finalPos,
75
- toB: finalPos + nodeAtPos.nodeSize,
76
- isInline: true,
77
- inlineNodeName: 'date'
78
- };
79
- }
80
-
81
- // The following inline node attr changes are gated behind platform_editor_improve_inline_diffs.
82
69
  // The changeset path (createDecorationsForChange) handles the deletion widget via
83
70
  // prosemirror-changeset; we only need to add the inline insertion highlight here.
84
- if (isExperimentEnabled('platform_editor_improve_inline_diffs', () => expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) && nodeAtPos) {
71
+ if (nodeAtPos) {
85
72
  const nodeName = nodeAtPos.type.name;
86
73
  if (isInlineAttrChangeNodeName(nodeName)) {
87
74
  const watchedAttrs = inlineNodeAttrMap[nodeName];
@@ -150,9 +137,7 @@ export const getAttrChangeRanges = (doc, attrStepContexts, originalDoc) => {
150
137
  // Deduplicate by node position: multiple AttrSteps on the same node
151
138
  // (e.g. setNodeAttribute(pos, 'text', ...) + setNodeAttribute(pos, 'color', ...))
152
139
  // should produce only one decoration, not one per step.
153
- // Gated behind the same experiment as the inline node attr changes that introduced
154
- // the possibility of multiple ranges for the same node position.
155
- .filter((range, i, arr) => !isExperimentEnabled('platform_editor_improve_inline_diffs', () => expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) || arr.findIndex(r => r.fromB === range.fromB) === i);
140
+ .filter((range, i, arr) => arr.findIndex(r => r.fromB === range.fromB) === i);
156
141
  };
157
142
 
158
143
  /**
@@ -16,8 +16,7 @@ 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
17
  import { colorSchemeRegistry, getLegacyColorScheme, standardScheme } from '../colorSchemes/schemes';
18
18
  import { getChangedContentStyleLegacy, getChangedNodeStyleLegacy, getDeletedContentStyleLegacy, getDeletedContentStyleUnboundedLegacy, getInsertedContentStyleLegacy, hasRestingDeletedRingLegacy, resolveCellOverlayStyleLegacy, resolveNestedInsertedNodeStyleLegacy, resolveRemovedLozengeStyleLegacy } from './wrapBlockNodeViewStyles.legacy';
19
- const DEFAULT_COLOR_SCHEME = 'standard';
20
- const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
19
+ const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
21
20
 
22
21
  /**
23
22
  * Inserted content inside a multi-container or list node always uses the standard scheme, so a
@@ -29,7 +29,7 @@ const dropOrphanedContributorTags = (contributorTags, decorations) => {
29
29
  }) => liveDiffIds.has(diffId));
30
30
  return liveTags.length === contributorTags.length ? contributorTags : liveTags;
31
31
  };
32
- export const createPlugin = (config, getIntl, api, portalProviderAPI, onEditorView) => {
32
+ export const createPlugin = (config, getIntl, api, onEditorView) => {
33
33
  enforceCustomStepRegisters();
34
34
  const nodeViewSerializer = new NodeViewSerializer({});
35
35
  const setNodeViewSerializer = editorView => {
@@ -46,14 +46,14 @@ export const createPlugin = (config, getIntl, api, portalProviderAPI, onEditorVi
46
46
  * lifecycle, which decorations are calculated independently of.
47
47
  */
48
48
  let currentEditorView;
49
- const tagMountContext = portalProviderAPI ? {
49
+ const tagMountContext = {
50
50
  api,
51
51
  getEditorRoot: () => {
52
52
  var _currentEditorView$do, _currentEditorView;
53
53
  return (_currentEditorView$do = (_currentEditorView = currentEditorView) === null || _currentEditorView === void 0 ? void 0 : _currentEditorView.dom) !== null && _currentEditorView$do !== void 0 ? _currentEditorView$do : null;
54
54
  },
55
- portalProviderAPI
56
- } : undefined;
55
+ getIntl
56
+ };
57
57
  return new SafePlugin({
58
58
  key: showDiffPluginKey,
59
59
  state: {
@@ -97,12 +97,11 @@ export const showDiffPlugin = ({
97
97
  pmPlugins() {
98
98
  return [{
99
99
  name: 'showDiffPlugin',
100
- // Contributor tags mount into their own decoration widgets through the portal
101
- // provider, the way the rest of the editor renders React into decorations.
100
+ // Contributor tags draw themselves into their own decoration widgets — plain DOM, so
101
+ // no portal provider is needed here.
102
102
  plugin: ({
103
- getIntl,
104
- nodeViewPortalProviderAPI
105
- }) => createPlugin(config, getIntl, api, nodeViewPortalProviderAPI, setEditorView)
103
+ getIntl
104
+ }) => createPlugin(config, getIntl, api, setEditorView)
106
105
  }];
107
106
  },
108
107
  contentComponent: () => {
@@ -0,0 +1,110 @@
1
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
+ export const CONTRIBUTOR_TAG_TESTID = 'diff-contributor-tag';
3
+ export const CONTRIBUTOR_TAG_NAME_TESTID = 'diff-contributor-tag-name';
4
+
5
+ /**
6
+ * Floor for the tag width (~one 16px avatar plus padding), so the highlight-width clamp cannot
7
+ * collapse the box on a very short highlight.
8
+ */
9
+ export const MIN_TAG_WIDTH = '28px';
10
+
11
+ // Positioned against the host widget its decoration renders on the change's first character:
12
+ // `bottom: 100%` lifts the tag onto the line above, `inset-inline-start` starts it on that
13
+ // character.
14
+ const constraintStyle = convertToInlineCss({
15
+ display: 'block',
16
+ position: 'absolute',
17
+ bottom: '100%',
18
+ insetInlineStart: 0,
19
+ minWidth: 0,
20
+ // The tag may cover surrounding text, so it has to paint over it. Matches `akEditorUnitZIndex`.
21
+ zIndex: 1,
22
+ // It renders inside the document and it is not content: dragging a selection across the change
23
+ // must not select it.
24
+ userSelect: 'none'
25
+ });
26
+
27
+ // `backgroundColor`, `borderBottomColor` and the visibility come from the model, and are set by the
28
+ // controller.
29
+ const tagStyle = convertToInlineCss({
30
+ display: 'inline-flex',
31
+ alignItems: 'center',
32
+ gap: "var(--ds-space-050, 4px)",
33
+ // Never wider than the highlight; overflow is ellipsised on the name below.
34
+ maxWidth: '100%',
35
+ minWidth: 0,
36
+ overflow: 'hidden',
37
+ paddingInline: "var(--ds-space-050, 4px)",
38
+ paddingBlock: "var(--ds-space-025, 2px)",
39
+ borderRadius: "var(--ds-radius-small, 4px)",
40
+ borderBottomStyle: 'solid',
41
+ borderBottomWidth: "var(--ds-border-width-selected, 2px)",
42
+ boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)",
43
+ font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
44
+ whiteSpace: 'nowrap',
45
+ boxSizing: 'border-box'
46
+ });
47
+ const avatarsStyle = convertToInlineCss({
48
+ display: 'inline-flex',
49
+ alignItems: 'center',
50
+ flexShrink: 0
51
+ });
52
+ const nameStyle = convertToInlineCss({
53
+ display: 'block',
54
+ minWidth: 0,
55
+ overflow: 'hidden',
56
+ textOverflow: 'ellipsis',
57
+ whiteSpace: 'nowrap'
58
+ });
59
+
60
+ /** `@atlaskit/visually-hidden`, inlined. */
61
+ const srLabelStyle = convertToInlineCss({
62
+ width: '1px',
63
+ height: '1px',
64
+ padding: 0,
65
+ position: 'absolute',
66
+ border: 0,
67
+ clip: 'rect(1px, 1px, 1px, 1px)',
68
+ overflow: 'hidden',
69
+ userSelect: 'none',
70
+ whiteSpace: 'nowrap'
71
+ });
72
+ const createSpan = (doc, style, attributes = {}) => {
73
+ const span = doc.createElement('span');
74
+ span.setAttribute('style', style);
75
+ for (const [name, value] of Object.entries(attributes)) {
76
+ span.setAttribute(name, value);
77
+ }
78
+ return span;
79
+ };
80
+ /**
81
+ * The tag's markup, with no model applied — see `ContributorTagController`.
82
+ *
83
+ * Built element by element rather than through `DOMSerializer.renderSpec`, which returns `Node` and
84
+ * would need a cast per reference below.
85
+ */
86
+ export const buildContributorTagDom = doc => {
87
+ const root = createSpan(doc, constraintStyle);
88
+ const tag = createSpan(doc, tagStyle, {
89
+ 'data-testid': CONTRIBUTOR_TAG_TESTID,
90
+ // Deliberately no `aria-label`: it would be announced ahead of the element's contents, but
91
+ // design requires the contributor *after* the change — hence the visually hidden label.
92
+ role: 'note',
93
+ // Focusable so keyboard and screen-reader users can reach the tag and its tooltip.
94
+ tabindex: '0'
95
+ });
96
+ const avatars = createSpan(doc, avatarsStyle);
97
+ const name = createSpan(doc, nameStyle, {
98
+ 'data-testid': CONTRIBUTOR_TAG_NAME_TESTID
99
+ });
100
+ const srLabel = createSpan(doc, srLabelStyle);
101
+ tag.append(avatars, name, srLabel);
102
+ root.appendChild(tag);
103
+ return {
104
+ avatars,
105
+ name,
106
+ root,
107
+ srLabel,
108
+ tag
109
+ };
110
+ };
@@ -0,0 +1,136 @@
1
+ import { bind } from 'bind-event-listener';
2
+ import { AVATAR_SIZES } from '@atlaskit/avatar/avatar-sizes';
3
+ import { BORDER_WIDTH } from '@atlaskit/avatar/constants/default';
4
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
5
+ import { getContributorTagIcon } from './contributorTagIcons';
6
+
7
+ /** The size `@atlaskit/avatar`'s `xxsmall` rendered at. */
8
+ const AVATAR_SIZE = AVATAR_SIZES.xxsmall;
9
+ /** `xxsmall` also carried a `space.025` margin — the same 2px — which is where the ring is painted. */
10
+ const AVATAR_RING_WIDTH = BORDER_WIDTH;
11
+ const AVATAR_BOX_SIZE = AVATAR_SIZE + AVATAR_RING_WIDTH * 2;
12
+
13
+ /** Reused from `@atlaskit/avatar`'s `avatar-content`, so the two shapes cannot drift. */
14
+ const HEXAGON_CLIP_PATH = 'polygon(45% 1.33975%, 46.5798% 0.60307%, 48.26352% 0.15192%, 50% 0%, 51.73648% 0.15192%, 53.4202% 0.60307%, 55% 1.33975%, 89.64102% 21.33975%, 91.06889% 22.33956%, 92.30146% 23.57212%, 93.30127% 25%, 94.03794% 26.5798%, 94.48909% 28.26352%, 94.64102% 30%, 94.64102% 70%, 94.48909% 71.73648%, 94.03794% 73.4202%, 93.30127% 75%, 92.30146% 76.42788%, 91.06889% 77.66044%, 89.64102% 78.66025%, 55% 98.66025%, 53.4202% 99.39693%, 51.73648% 99.84808%, 50% 100%, 48.26352% 99.84808%, 46.5798% 99.39693%, 45% 98.66025%, 10.35898% 78.66025%, 8.93111% 77.66044%, 7.69854% 76.42788%, 6.69873% 75%, 5.96206% 73.4202%, 5.51091% 71.73648%, 5.35898% 70%, 5.35898% 30%, 5.51091% 28.26352%, 5.96206% 26.5798%, 6.69873% 25%, 7.69854% 23.57212%, 8.93111% 22.33956%, 10.35898% 21.33975%)';
15
+ const boxStyle = convertToInlineCss({
16
+ display: 'inline-flex',
17
+ alignItems: 'center',
18
+ justifyContent: 'center',
19
+ flexShrink: 0,
20
+ boxSizing: 'border-box',
21
+ width: `${AVATAR_BOX_SIZE}px`,
22
+ height: `${AVATAR_BOX_SIZE}px`
23
+ });
24
+ const shapeStyle = convertToInlineCss({
25
+ display: 'inline-flex',
26
+ alignItems: 'center',
27
+ justifyContent: 'center',
28
+ boxSizing: 'border-box',
29
+ width: `${AVATAR_SIZE}px`,
30
+ height: `${AVATAR_SIZE}px`,
31
+ overflow: 'hidden'
32
+ });
33
+ const imageStyle = convertToInlineCss({
34
+ display: 'block',
35
+ width: '100%',
36
+ height: '100%',
37
+ objectFit: 'cover'
38
+ });
39
+
40
+ /** The `@atlaskit/avatar` image fallback: a subtle glyph on a tinted box. */
41
+ const personFallbackStyle = convertToInlineCss({
42
+ width: '100%',
43
+ height: '100%',
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ backgroundColor: "var(--ds-background-accent-gray-subtler, #DDDEE1)",
48
+ color: "var(--ds-icon-subtle, #505258)"
49
+ });
50
+ /**
51
+ * One 16px avatar for a contributor: a circle for a person, a hexagon for an agent.
52
+ *
53
+ * Mirrors `mentionAvatarRenderer` — a bare `<img>` with a glyph fallback bound to `error`, and the
54
+ * hexagon cut with `clip-path`. The ring `@atlaskit/avatar` drew from `borderColor` is a
55
+ * `box-shadow` on the circle and a second clipped hexagon behind the agent, since `box-shadow` is
56
+ * cut away by `clip-path`.
57
+ */
58
+ export const contributorAvatarRenderer = ({
59
+ contributor,
60
+ doc,
61
+ ringColor,
62
+ stackIndex
63
+ }) => {
64
+ const isAgent = contributor.kind === 'agent';
65
+ const element = doc.createElement('span');
66
+ element.setAttribute('style', boxStyle);
67
+ // Decorative: the tag names its contributors in the visually hidden label its screen-reader
68
+ // announcement comes from.
69
+ element.setAttribute('aria-hidden', 'true');
70
+ if (stackIndex !== undefined) {
71
+ element.style.setProperty('z-index', String(stackIndex));
72
+ }
73
+ const shape = doc.createElement('span');
74
+ shape.setAttribute('style', shapeStyle);
75
+ element.appendChild(shape);
76
+ if (isAgent) {
77
+ element.style.setProperty('clip-path', HEXAGON_CLIP_PATH);
78
+ shape.style.setProperty('clip-path', HEXAGON_CLIP_PATH);
79
+ } else {
80
+ shape.style.setProperty('border-radius', "var(--ds-radius-full, 9999px)");
81
+ }
82
+ let image;
83
+ let unbindImageError;
84
+ const showFallback = () => {
85
+ var _unbindImageError, _image;
86
+ (_unbindImageError = unbindImageError) === null || _unbindImageError === void 0 ? void 0 : _unbindImageError();
87
+ unbindImageError = undefined;
88
+ (_image = image) === null || _image === void 0 ? void 0 : _image.remove();
89
+ image = undefined;
90
+ if (!isAgent) {
91
+ const fallback = doc.createElement('span');
92
+ fallback.setAttribute('style', personFallbackStyle);
93
+ fallback.appendChild(getContributorTagIcon('person', doc));
94
+ shape.appendChild(fallback);
95
+ return;
96
+ }
97
+ shape.appendChild(getContributorTagIcon(contributor.agentKind === 'rovo' ? 'rovoHex' : 'aiBot', doc));
98
+ };
99
+ if (contributor.avatarUrl) {
100
+ image = doc.createElement('img');
101
+ image.alt = '';
102
+ image.decoding = 'async';
103
+ image.draggable = false;
104
+ image.loading = 'lazy';
105
+ image.setAttribute('style', imageStyle);
106
+ unbindImageError = bind(image, {
107
+ type: 'error',
108
+ listener: showFallback
109
+ });
110
+ shape.appendChild(image);
111
+ image.src = contributor.avatarUrl;
112
+ } else {
113
+ showFallback();
114
+ }
115
+ const setRingColor = nextRingColor => {
116
+ if (isAgent) {
117
+ element.style.setProperty('background', nextRingColor);
118
+ // Shows through wherever the artwork is transparent, as the avatar background did.
119
+ shape.style.setProperty('background', nextRingColor);
120
+ return;
121
+ }
122
+ shape.style.setProperty('box-shadow', `0 0 0 ${AVATAR_RING_WIDTH}px ${nextRingColor}`);
123
+ shape.style.setProperty('background', nextRingColor);
124
+ };
125
+ setRingColor(ringColor);
126
+ return {
127
+ destroy: () => {
128
+ var _unbindImageError2;
129
+ (_unbindImageError2 = unbindImageError) === null || _unbindImageError2 === void 0 ? void 0 : _unbindImageError2();
130
+ unbindImageError = undefined;
131
+ element.remove();
132
+ },
133
+ element,
134
+ setRingColor
135
+ };
136
+ };