@atlaskit/editor-plugin-show-diff 15.1.6 → 15.1.8

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 (37) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  3. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +32 -7
  4. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  5. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  6. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  7. package/dist/cjs/pm-plugins/resolveDiffContributors.js +58 -7
  8. package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +70 -14
  9. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  10. package/dist/cjs/ui/ContributorTag/contributorTagController.js +169 -150
  11. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +5 -1
  12. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  13. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +17 -0
  14. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  15. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  16. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  17. package/dist/es2019/pm-plugins/resolveDiffContributors.js +57 -4
  18. package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +69 -13
  19. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  20. package/dist/es2019/ui/ContributorTag/contributorTagController.js +164 -104
  21. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +5 -1
  22. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
  23. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +32 -7
  24. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +25 -4
  25. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
  26. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
  27. package/dist/esm/pm-plugins/resolveDiffContributors.js +58 -7
  28. package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +69 -13
  29. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +8 -2
  30. package/dist/esm/ui/ContributorTag/contributorTagController.js +171 -152
  31. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +5 -1
  32. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +8 -2
  33. package/dist/types/showDiffPluginType.d.ts +2 -2
  34. package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +32 -3
  35. package/dist/types/ui/ContributorTag/contributorTagController.d.ts +32 -12
  36. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +2 -2
  37. package/package.json +5 -5
@@ -1,6 +1,7 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
3
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
4
+ import { CONTRIBUTOR_TAG_Z_INDEX } from '../../ui/ContributorTag/buildContributorTagDom';
4
5
  import { isExtendedEnabled } from '../isExtendedEnabled';
5
6
  import { hasVisibleContent } from '../utils/hasVisibleContent';
6
7
  import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
@@ -13,6 +14,25 @@ import { REVEAL_ATTR, resolveRevealStyle } from './revealStyles';
13
14
  const displayNoneStyle = convertToInlineCss({
14
15
  display: 'none'
15
16
  });
17
+
18
+ /**
19
+ * Stacks the change's highlight below its contributor tag, so no highlight can slice through a tag:
20
+ * a tag overlays the line *above* its own change, and a highlight already on that line — the tail of
21
+ * this change, or another change entirely — would otherwise paint straight over it.
22
+ *
23
+ * The tag still reads as attached to the change rather than laid on top of it, because it cannot
24
+ * paint over this highlight at all: its bottom edge is pinned to the highlight's top edge and
25
+ * clipped there. See `CONTRIBUTOR_TAG_Z_INDEX`.
26
+ *
27
+ * prosemirror-view places the tag's host widget as a *sibling* of this decoration's span, so
28
+ * painting order is decided by stacking level — and the host is absolutely positioned at
29
+ * `CONTRIBUTOR_TAG_Z_INDEX`. `position: relative` is what makes `z-index` apply to an otherwise
30
+ * static inline box; it adds no offset.
31
+ */
32
+ const stackBelowContributorTagStyle = convertToInlineCss({
33
+ position: 'relative',
34
+ zIndex: CONTRIBUTOR_TAG_Z_INDEX - 1
35
+ });
16
36
  const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
17
37
 
18
38
  /**
@@ -158,8 +178,23 @@ export const createInlineChangedDecoration = ({
158
178
  });
159
179
  const isAtomicInlineInsertion = isAtomicInlineNode && isInserted;
160
180
  const atomicInlineAttrs = isAtomicInlineInsertion ? resolveAtomicInlineAttrs(inlineNodeName, colorScheme, colors) : undefined;
181
+
182
+ // Built from the raw change range, not the indicator anchors resolved below: a tag lines up with
183
+ // the changed text, while the bar's anchors are held on a block boundary when a deleted widget
184
+ // renders beside them.
185
+ //
186
+ // Built up here, ahead of where it is pushed, so the highlight's stacking below keys off whether a
187
+ // tag exists rather than re-deriving the condition and risking disagreement.
188
+ const tagWidget = canTagChange && doc ? createContributorTagWidget({
189
+ doc,
190
+ from: change.fromB,
191
+ to: change.toB,
192
+ diffId,
193
+ mountContext: tagMountContext
194
+ }) : undefined;
195
+ const inlineStyle = [style, atomicInlineAttrs === null || atomicInlineAttrs === void 0 ? void 0 : atomicInlineAttrs.styleSuffix, tagWidget ? stackBelowContributorTagStyle : undefined].filter(Boolean).join('');
161
196
  const decorations = [Decoration.inline(change.fromB, change.toB, {
162
- style: atomicInlineAttrs ? `${style}${atomicInlineAttrs.styleSuffix}` : style,
197
+ style: inlineStyle,
163
198
  ...(atomicInlineAttrs && {
164
199
  class: atomicInlineAttrs.className
165
200
  }),
@@ -215,19 +250,10 @@ export const createInlineChangedDecoration = ({
215
250
  }));
216
251
  }
217
252
 
218
- // Built from the raw change range, not the anchors above: a tag lines up with the changed text,
219
- // while the bar's anchors are held on a block boundary when a deleted widget renders beside them.
220
- if (canTagChange && doc) {
221
- const tagWidget = createContributorTagWidget({
222
- doc,
223
- from: change.fromB,
224
- to: change.toB,
225
- diffId,
226
- mountContext: tagMountContext
227
- });
228
- if (tagWidget) {
229
- decorations.push(tagWidget);
230
- }
253
+ // Pushed after the indicator anchors so the decoration order is unchanged — see `tagWidget` above
254
+ // for why it is built before them.
255
+ if (tagWidget) {
256
+ decorations.push(tagWidget);
231
257
  }
232
258
  return decorations;
233
259
  };
@@ -2,6 +2,7 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { trackChangesMessages } from '@atlaskit/editor-common/messages';
3
3
  import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
4
4
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
5
6
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
7
  import { isExtendedEnabled } from '../../isExtendedEnabled';
7
8
  import { applyRevealToElement } from '../revealStyles';
@@ -276,6 +277,16 @@ const applyInlineLeafNodeStyles = ({
276
277
  });
277
278
  });
278
279
  };
280
+
281
+ /**
282
+ * Editor CSS makes `blockquote` `inline-block` for its block formatting context. Inside a widget
283
+ * that collapses the indicator bar: the container is an inline `span`, so its anchor rect comes from
284
+ * font metrics, not from an inline-level child. `flow-root` keeps the formatting context but is
285
+ * block-level — unlike `block`, which lets the child paragraph's top margin collapse out.
286
+ */
287
+ const quoteBlockLevelStyle = convertToInlineCss({
288
+ display: 'flow-root'
289
+ });
279
290
  const applyTextLikeBlockNodeStyles = ({
280
291
  element,
281
292
  targetNode,
@@ -533,6 +544,9 @@ export const wrapBlockNodeView = ({
533
544
  hideAddedDiffsUnderline,
534
545
  highlightInlineLeafNodes
535
546
  });
547
+ if (targetNode.type.name === 'blockquote' && fg('platform_editor_ai_show_diff_patch_1')) {
548
+ appendStyleToElement(nodeView, quoteBlockLevelStyle);
549
+ }
536
550
  dom.append(nodeView);
537
551
  return;
538
552
  }
@@ -5,6 +5,49 @@ const ROVO_AGENT_NAME = 'Rovo';
5
5
 
6
6
  /** `agentType` values that render as "Rovo" rather than as a generic external agent. */
7
7
  const ROVO_AGENT_TYPES = new Set(['convo-ai', 'rovo_chat']);
8
+ /** Known external agent types emitted by Confluence's agent identification service. */
9
+ const EXTERNAL_AGENT_PRESENTATIONS = {
10
+ claude: {
11
+ agentKind: 'claude',
12
+ name: 'Claude'
13
+ },
14
+ codex: {
15
+ agentKind: 'external',
16
+ name: 'Codex'
17
+ },
18
+ vscode: {
19
+ agentKind: 'external',
20
+ name: 'VS Code'
21
+ },
22
+ hermes: {
23
+ agentKind: 'external',
24
+ name: 'Hermes'
25
+ },
26
+ runlayer: {
27
+ agentKind: 'external',
28
+ name: 'Runlayer'
29
+ },
30
+ zed: {
31
+ agentKind: 'external',
32
+ name: 'Zed'
33
+ },
34
+ ampcode: {
35
+ agentKind: 'external',
36
+ name: 'Ampcode'
37
+ },
38
+ antigravity: {
39
+ agentKind: 'external',
40
+ name: 'Antigravity'
41
+ },
42
+ devin_cli: {
43
+ agentKind: 'external',
44
+ name: 'Devin CLI'
45
+ },
46
+ pi_agent: {
47
+ agentKind: 'external',
48
+ name: 'Pi Agent'
49
+ }
50
+ };
8
51
  const hasAgentIdentity = attribution => {
9
52
  var _attribution$agentId, _attribution$agentTyp;
10
53
  return Boolean(((_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim()) || ((_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim()));
@@ -29,9 +72,17 @@ const resolveUser = (userId, profilesByAccountId) => {
29
72
  } : undefined;
30
73
  };
31
74
 
32
- /** An agent always resolves: its profile, else "Rovo" for a first-party type, else external. */
75
+ /** Resolve branding or a profile first, then a known type name or the external-agent fallback. */
33
76
  const resolveAgent = (attribution, profilesByAccountId) => {
34
- var _attribution$agentId2, _attribution$agentTyp2;
77
+ var _attribution$agentTyp2, _attribution$agentId2;
78
+ const agentType = (_attribution$agentTyp2 = attribution.agentType) === null || _attribution$agentTyp2 === void 0 ? void 0 : _attribution$agentTyp2.trim().toLowerCase();
79
+ const agentPresentation = agentType ? EXTERNAL_AGENT_PRESENTATIONS[agentType] : undefined;
80
+ if (agentPresentation && agentPresentation.agentKind !== 'external') {
81
+ return {
82
+ ...agentPresentation,
83
+ kind: 'agent'
84
+ };
85
+ }
35
86
  const agentId = (_attribution$agentId2 = attribution.agentId) === null || _attribution$agentId2 === void 0 ? void 0 : _attribution$agentId2.trim();
36
87
  const agentProfile = agentId ? profilesByAccountId.get(agentId) : undefined;
37
88
  if (agentProfile) {
@@ -42,13 +93,15 @@ const resolveAgent = (attribution, profilesByAccountId) => {
42
93
  name: agentProfile.name
43
94
  };
44
95
  }
45
- const agentType = (_attribution$agentTyp2 = attribution.agentType) === null || _attribution$agentTyp2 === void 0 ? void 0 : _attribution$agentTyp2.trim().toLowerCase();
46
96
 
47
- // An empty name lets the tag fall back to its own localised "External agent" label.
97
+ // Unknown types keep an empty name for the tag's localised "External agent" label.
48
98
  return agentType && ROVO_AGENT_TYPES.has(agentType) ? {
49
99
  agentKind: 'rovo',
50
100
  kind: 'agent',
51
101
  name: ROVO_AGENT_NAME
102
+ } : agentPresentation ? {
103
+ ...agentPresentation,
104
+ kind: 'agent'
52
105
  } : {
53
106
  agentKind: 'external',
54
107
  kind: 'agent',
@@ -3,43 +3,98 @@ export const CONTRIBUTOR_TAG_TESTID = 'diff-contributor-tag';
3
3
  export const CONTRIBUTOR_TAG_NAME_TESTID = 'diff-contributor-tag-name';
4
4
 
5
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.
6
+ * Ceiling for the tag width. A constant rather than the width of the change: a change is routinely a
7
+ * word or two, and clamping to it ellipsised the contributor's name — the one thing the tag exists to
8
+ * show. Longer names are ellipsised into the tooltip.
8
9
  */
9
- export const MIN_TAG_WIDTH = '28px';
10
+ export const MAX_TAG_WIDTH = '200px';
11
+
12
+ /**
13
+ * The tag sits on the line above the change, so it has to paint over that line to be readable —
14
+ * including any diff highlight already on it, which would otherwise slice straight through the tag.
15
+ * Hence one level above `akEditorUnitZIndex`, where the highlights sit; exported because the
16
+ * change's own highlight is stacked one level below it — see `stackBelowContributorTagStyle` in
17
+ * `createInlineChangedDecoration`.
18
+ *
19
+ * Painting above its own highlight is only safe because the tag can never reach it: `bottom: 100%`
20
+ * puts the root's bottom edge exactly on the highlight's top edge, and the root's `overflow: clip`
21
+ * keeps every pixel the tag paints inside it.
22
+ */
23
+ export const CONTRIBUTOR_TAG_Z_INDEX = 2;
24
+
25
+ /**
26
+ * The plugin's whole side of the tag's motion: the fade and the hidden state are
27
+ * `contributorTagStyles` in both EditorContentContainer stylesheets, keyed on this literal and the
28
+ * one below — rename in step. This package cannot import `@atlaskit/editor-core` at runtime, so the
29
+ * plugin emits the hook and editor-core owns the rule, as `VanillaTooltip` does with
30
+ * `VANILLA_TOOLTIP_DEFAULT_CLASS`.
31
+ */
32
+ export const CONTRIBUTOR_TAG_CLASS = 'ak-editor-diff-contributor-tag';
33
+
34
+ /** Toggled by the controller to move the tag between the fade's two ends. */
35
+ export const CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = 'data-revealed';
36
+
37
+ /**
38
+ * Backstop for removing a fading-out tag: a cancelled transition fires no `transitionend`. Must stay
39
+ * above the fade's 600ms, which lives in another package's stylesheet and cannot be read back as a
40
+ * number — hence the wide margin. Raise in step with that duration.
41
+ */
42
+ export const TAG_EXIT_FALLBACK_MS = 1200;
10
43
 
11
44
  // Positioned against the host widget its decoration renders on the change's first character:
12
45
  // `bottom: 100%` lifts the tag onto the line above, `inset-inline-start` starts it on that
13
46
  // character.
14
47
  const constraintStyle = convertToInlineCss({
15
- display: 'block',
48
+ // `flex`, not `block`: a block box lays the tag on a line box whose strut is the *paragraph's*
49
+ // line-height (~24px) against the tag's ~20px, so `bottom: 100%` pins that leftover strut to the
50
+ // change and the tag floats above it. A flex container has no line box.
51
+ display: 'flex',
16
52
  position: 'absolute',
17
53
  bottom: '100%',
18
54
  insetInlineStart: 0,
55
+ // On the root, since that is the absolutely positioned box; the tag below fills it.
56
+ maxWidth: MAX_TAG_WIDTH,
19
57
  minWidth: 0,
20
- // The tag may cover surrounding text, so it has to paint over it. Matches `akEditorUnitZIndex`.
21
- zIndex: 1,
58
+ // This box's bottom edge is the join with the highlight, and the tag paints *above* that
59
+ // highlight — so anything spilling past this edge would read as the tag laid on top of the change
60
+ // rather than attached to it. The reveal's `translateY` is exactly that spill: it starts the tag
61
+ // 4px low, and clipping the part that hangs over the highlight turns the rise into an unfurl out
62
+ // of the join. `clip`, not `hidden`: `hidden` would make this a scroll container, and focusing the
63
+ // tag mid-reveal would scroll it 4px out of alignment for good.
64
+ //
65
+ // The tooltip is unaffected — it is a `popover`, so it paints in the top layer, which no
66
+ // ancestor's clip reaches.
67
+ overflow: 'clip',
68
+ zIndex: CONTRIBUTOR_TAG_Z_INDEX,
22
69
  // It renders inside the document and it is not content: dragging a selection across the change
23
70
  // must not select it.
24
71
  userSelect: 'none'
25
72
  });
26
73
 
27
- // `backgroundColor`, `borderBottomColor` and the visibility come from the model, and are set by the
28
- // controller.
74
+ // `backgroundColor` comes from the model, and is set by the controller. `opacity`, `pointer-events`
75
+ // and the transition between their two states are in `contributorTagStyles` — see
76
+ // `CONTRIBUTOR_TAG_CLASS`.
29
77
  const tagStyle = convertToInlineCss({
30
78
  display: 'inline-flex',
31
79
  alignItems: 'center',
32
80
  gap: "var(--ds-space-050, 4px)",
33
- // Never wider than the highlight; overflow is ellipsised on the name below.
81
+ // Never wider than `MAX_TAG_WIDTH` on the root; overflow is ellipsised on the name below.
34
82
  maxWidth: '100%',
35
83
  minWidth: 0,
36
84
  overflow: 'hidden',
37
85
  paddingInline: "var(--ds-space-050, 4px)",
38
86
  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)",
87
+ // Top corners only: the bottom edge butts onto the change, and a rounded corner there would let
88
+ // the page background through at the join.
89
+ borderTopLeftRadius: "var(--ds-radius-small, 4px)",
90
+ borderTopRightRadius: "var(--ds-radius-small, 4px)",
91
+ borderBottomLeftRadius: 0,
92
+ borderBottomRightRadius: 0,
93
+ // Deliberately no bottom border: the change already carries the accent as its own underline, and
94
+ // a second rule directly above it reads as two objects rather than one label on one change.
95
+ //
96
+ // Deliberately no `elevation.shadow.overlay`: its `0 8px 12px` offset casts downwards onto the
97
+ // change, which reads as a gap between the tag and the content it captions.
43
98
  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
99
  whiteSpace: 'nowrap',
45
100
  boxSizing: 'border-box'
@@ -86,6 +141,7 @@ const createSpan = (doc, style, attributes = {}) => {
86
141
  export const buildContributorTagDom = doc => {
87
142
  const root = createSpan(doc, constraintStyle);
88
143
  const tag = createSpan(doc, tagStyle, {
144
+ class: CONTRIBUTOR_TAG_CLASS,
89
145
  'data-testid': CONTRIBUTOR_TAG_TESTID,
90
146
  // Deliberately no `aria-label`: it would be announced ahead of the element's contents, but
91
147
  // design requires the contributor *after* the change — hence the visually hidden label.
@@ -4,6 +4,12 @@ import { BORDER_WIDTH } from '@atlaskit/avatar/constants/default';
4
4
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
5
5
  import { getContributorTagIcon } from './contributorTagIcons';
6
6
 
7
+ /** Agent kinds with a custom contributor-tag icon. */
8
+ const AGENT_KIND_ICONS = {
9
+ claude: 'claude',
10
+ rovo: 'rovoHex'
11
+ };
12
+
7
13
  /** The size `@atlaskit/avatar`'s `xxsmall` rendered at. */
8
14
  const AVATAR_SIZE = AVATAR_SIZES.xxsmall;
9
15
  /** `xxsmall` also carried a `space.025` margin — the same 2px — which is where the ring is painted. */
@@ -82,7 +88,7 @@ export const contributorAvatarRenderer = ({
82
88
  let image;
83
89
  let unbindImageError;
84
90
  const showFallback = () => {
85
- var _unbindImageError, _image;
91
+ var _unbindImageError, _image, _AGENT_KIND_ICONS, _contributor$agentKin;
86
92
  (_unbindImageError = unbindImageError) === null || _unbindImageError === void 0 ? void 0 : _unbindImageError();
87
93
  unbindImageError = undefined;
88
94
  (_image = image) === null || _image === void 0 ? void 0 : _image.remove();
@@ -94,7 +100,7 @@ export const contributorAvatarRenderer = ({
94
100
  shape.appendChild(fallback);
95
101
  return;
96
102
  }
97
- shape.appendChild(getContributorTagIcon(contributor.agentKind === 'rovo' ? 'rovoHex' : 'aiBot', doc));
103
+ shape.appendChild(getContributorTagIcon((_AGENT_KIND_ICONS = AGENT_KIND_ICONS[(_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external']) !== null && _AGENT_KIND_ICONS !== void 0 ? _AGENT_KIND_ICONS : 'aiBot', doc));
98
104
  };
99
105
  if (contributor.avatarUrl) {
100
106
  image = doc.createElement('img');