@atlaskit/editor-plugin-show-diff 16.1.0 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/afm-products/tsconfig.json +3 -0
  3. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +67 -22
  4. package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +242 -26
  5. package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +71 -37
  6. package/dist/cjs/pm-plugins/decorations/colorSchemes/schemes.js +5 -1
  7. package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +0 -2
  8. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +21 -14
  9. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +83 -3
  10. package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +123 -43
  11. package/dist/cjs/pm-plugins/decorations/revealStyles.js +5 -1
  12. package/dist/cjs/pm-plugins/decorations/utils/absorbFirstChildMarginReset.js +33 -0
  13. package/dist/cjs/pm-plugins/decorations/utils/createMarginAbsorber.js +43 -0
  14. package/dist/cjs/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +76 -0
  15. package/dist/cjs/pm-plugins/decorations/utils/safeResolve.js +24 -0
  16. package/dist/cjs/pm-plugins/getScrollableDecorations.js +48 -19
  17. package/dist/cjs/pm-plugins/utils/baseNodeName.js +25 -0
  18. package/dist/cjs/pm-plugins/utils/taggableBlockNodes.js +37 -0
  19. package/dist/cjs/ui/ContributorTag/contributorTagController.js +4 -4
  20. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +39 -2
  21. package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +196 -10
  22. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +51 -19
  23. package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +3 -0
  24. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +0 -2
  25. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +23 -14
  26. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +83 -3
  27. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +60 -7
  28. package/dist/es2019/pm-plugins/decorations/revealStyles.js +5 -1
  29. package/dist/es2019/pm-plugins/decorations/utils/absorbFirstChildMarginReset.js +29 -0
  30. package/dist/es2019/pm-plugins/decorations/utils/createMarginAbsorber.js +39 -0
  31. package/dist/es2019/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +69 -0
  32. package/dist/es2019/pm-plugins/decorations/utils/safeResolve.js +18 -0
  33. package/dist/es2019/pm-plugins/getScrollableDecorations.js +44 -19
  34. package/dist/es2019/pm-plugins/utils/baseNodeName.js +18 -0
  35. package/dist/es2019/pm-plugins/utils/taggableBlockNodes.js +28 -0
  36. package/dist/es2019/ui/ContributorTag/contributorTagController.js +4 -4
  37. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +67 -22
  38. package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +243 -26
  39. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +71 -37
  40. package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +5 -1
  41. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +0 -2
  42. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +21 -14
  43. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +83 -3
  44. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +123 -43
  45. package/dist/esm/pm-plugins/decorations/revealStyles.js +5 -1
  46. package/dist/esm/pm-plugins/decorations/utils/absorbFirstChildMarginReset.js +28 -0
  47. package/dist/esm/pm-plugins/decorations/utils/createMarginAbsorber.js +37 -0
  48. package/dist/esm/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +70 -0
  49. package/dist/esm/pm-plugins/decorations/utils/safeResolve.js +18 -0
  50. package/dist/esm/pm-plugins/getScrollableDecorations.js +48 -19
  51. package/dist/esm/pm-plugins/utils/baseNodeName.js +20 -0
  52. package/dist/esm/pm-plugins/utils/taggableBlockNodes.js +32 -0
  53. package/dist/esm/ui/ContributorTag/contributorTagController.js +4 -4
  54. package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +20 -1
  55. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +4 -11
  56. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +9 -8
  57. package/dist/types/pm-plugins/decorations/utils/absorbFirstChildMarginReset.d.ts +23 -0
  58. package/dist/types/pm-plugins/decorations/utils/createMarginAbsorber.d.ts +23 -0
  59. package/dist/types/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.d.ts +26 -0
  60. package/dist/types/pm-plugins/decorations/utils/safeResolve.d.ts +13 -0
  61. package/dist/types/pm-plugins/getScrollableDecorations.d.ts +2 -2
  62. package/dist/types/pm-plugins/utils/baseNodeName.d.ts +16 -0
  63. package/dist/types/pm-plugins/utils/taggableBlockNodes.d.ts +14 -0
  64. package/package.json +14 -5
@@ -172,9 +172,44 @@ const calculateNodesForBlockDecoration = ({
172
172
  showIndicators = false,
173
173
  diffType,
174
174
  coarseTableCellsOnly = false,
175
- tagMountContext
175
+ tagMountContext,
176
+ nodeRanges
176
177
  }) => {
177
178
  const decorations = [];
179
+
180
+ // Exact-nodes path: only reached for a split change (EDITOR-8932), which names the nodes it
181
+ // owns. Deliberately kept separate from the walk below rather than sharing it.
182
+ if (nodeRanges) {
183
+ for (const {
184
+ from: pos,
185
+ to: nodeEnd
186
+ } of nodeRanges) {
187
+ const node = doc.nodeAt(pos);
188
+ if (!(node !== null && node !== void 0 && node.isBlock)) {
189
+ continue;
190
+ }
191
+ const isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
192
+ decorations.push(...createBlockChangedDecoration({
193
+ attributionKey,
194
+ change: {
195
+ from: pos,
196
+ to: nodeEnd,
197
+ name: node.type.name
198
+ },
199
+ colorScheme,
200
+ isInserted,
201
+ isActive,
202
+ shouldHideDeleted,
203
+ showContributorTags,
204
+ showIndicators,
205
+ doc,
206
+ diffType,
207
+ tagMountContext
208
+ }));
209
+ }
210
+ return decorations;
211
+ }
212
+
178
213
  // Coarse path: inside the large table, only cell/header overlays are kept — the
179
214
  // table/row/paragraph decorations there are redundant and cause expensive per-cell
180
215
  // re-render. Blocks outside the table keep their normal decorations.
@@ -420,7 +455,7 @@ const calculateDiffDecorationsInner = ({
420
455
  tr,
421
456
  attributedChanges
422
457
  });
423
- changes = collapseOverlappingChanges(uncollapsedChanges);
458
+ changes = collapseOverlappingChanges(uncollapsedChanges, tr.doc);
424
459
 
425
460
  // Still colours-only: a tags-only diff keeps the plain colour scheme.
426
461
  if (attributionColoringEnabled) {
@@ -570,6 +605,8 @@ const calculateDiffDecorationsInner = ({
570
605
  doc: tr.doc,
571
606
  from: change.fromB,
572
607
  to: change.toB,
608
+ // A split piece's own range no longer covers the container it opened (EDITOR-8932).
609
+ nodeRanges: change.blockNodeRangesB,
573
610
  colorScheme: changeColorScheme,
574
611
  ...(isExtendedEnabled(diffType) && {
575
612
  isInserted,
@@ -1,6 +1,13 @@
1
1
  import { simplifyChanges } from 'prosemirror-changeset';
2
2
  import { getAttributionKey } from '../decorations/colorSchemes/attributions';
3
3
  import { optimizeChanges } from './optimizeChanges';
4
+
5
+ /**
6
+ * A change, plus the block nodes it owns when it is one piece of a split — a piece's own range no
7
+ * longer covers the container it opened. Absent on an unsplit change, which decorates the blocks
8
+ * inside its own range as before.
9
+ */
10
+
4
11
  const getAttributionIdentity = change => {
5
12
  const identities = new Set();
6
13
  for (const span of [...change.deleted, ...change.inserted]) {
@@ -19,20 +26,178 @@ const getAttributionIdentity = change => {
19
26
  };
20
27
  const byNewDocRange = (left, right) => left.fromB - right.fromB || left.toB - right.toB;
21
28
 
29
+ /** Union of the given ranges, with overlapping and touching ones merged. */
30
+ const mergeRanges = ranges => [...ranges].sort((left, right) => left.from - right.from).reduce((merged, range) => {
31
+ const last = merged[merged.length - 1];
32
+ if (last && range.from <= last.to) {
33
+ last.to = Math.max(last.to, range.to);
34
+ return merged;
35
+ }
36
+ return [...merged, {
37
+ ...range
38
+ }];
39
+ }, []);
40
+
41
+ /** Whether `inner` sits wholly inside `outer` and covers less of the document. */
42
+ const isContained = (outer, inner) => inner.toB > inner.fromB && outer.fromB <= inner.fromB && inner.toB <= outer.toB && inner.toB - inner.fromB < outer.toB - outer.fromB;
43
+
22
44
  /**
23
- * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
24
- * independently, so runs either side of an unattributed step can come back covering the same
25
- * characters which stacks two inline decorations and renders one contributor tag per copy.
45
+ * The block nodes in `change` whose opening position `ownsPosition` claims. Ownership is by opening
46
+ * position so a block another contributor added inside a container is not claimed by whoever opened
47
+ * the container. `undefined` for a range this document cannot resolve, which falls the piece back
48
+ * to decorating its own range.
49
+ */
50
+ const blockNodeRanges = (doc, change, ownsPosition) => {
51
+ if (!doc || change.from >= change.to || change.to > doc.content.size) {
52
+ return undefined;
53
+ }
54
+ const ranges = [];
55
+ doc.nodesBetween(change.from, change.to, (node, pos) => {
56
+ if (node.isBlock && ownsPosition(pos) && pos + node.nodeSize <= change.to) {
57
+ ranges.push({
58
+ from: pos,
59
+ to: pos + node.nodeSize
60
+ });
61
+ }
62
+ return true;
63
+ });
64
+ return ranges;
65
+ };
66
+
67
+ /**
68
+ * Splits a change whose spans name more than one contributor into one change per run of consecutive
69
+ * spans sharing a key.
26
70
  *
27
- * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
28
- * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
29
- * is not credited to it.
71
+ * `prosemirror-changeset` merges contiguous inserted content into one change while keeping per-span
72
+ * data, so a panel inserted by one contributor and edited inside by another arrives as a single
73
+ * change holding every span — and the latest-writer rule credited all of it, panel included, to
74
+ * whoever wrote last (EDITOR-8932).
30
75
  */
31
- export const collapseOverlappingChanges = changes => {
32
- if (changes.length <= 1) {
33
- return changes;
76
+ const splitMixedIdentityChanges = (changes, doc) => changes.flatMap(change => {
77
+ if (getAttributionIdentity(change).mergeable) {
78
+ return [change];
79
+ }
80
+ const runs = [];
81
+ let offset = change.fromB;
82
+ for (const span of change.inserted) {
83
+ const key = getAttributionKey(span.data);
84
+ const last = runs[runs.length - 1];
85
+ if (last && last.key === key) {
86
+ last.to = offset + span.length;
87
+ last.spans.push(span);
88
+ } else {
89
+ runs.push({
90
+ key,
91
+ from: offset,
92
+ to: offset + span.length,
93
+ spans: [span]
94
+ });
95
+ }
96
+ offset += span.length;
34
97
  }
35
98
 
99
+ // The offsets above are only positions if the spans partition the inserted range.
100
+ if (runs.length < 2 || offset !== change.toB) {
101
+ return [change];
102
+ }
103
+ return runs.map((run, index) => {
104
+ const owned = blockNodeRanges(doc, {
105
+ from: change.fromB,
106
+ to: change.toB
107
+ }, pos => pos >= run.from && pos < run.to);
108
+ return {
109
+ ...change,
110
+ fromA: index === 0 ? change.fromA : change.toA,
111
+ fromB: run.from,
112
+ toB: run.to,
113
+ deleted: index === 0 ? change.deleted : [],
114
+ inserted: run.spans,
115
+ ...(owned ? {
116
+ blockNodeRangesB: owned
117
+ } : {})
118
+ };
119
+ });
120
+ });
121
+
122
+ /**
123
+ * Subtracts every other author's contained change from a change's range, so both contributions
124
+ * survive. Applies when the contributors arrive as separate changes rather than as spans of one —
125
+ * the `step` diff type. A contained change by the same author, and one that only partially
126
+ * overlaps, are left for the collapse below.
127
+ *
128
+ * The leading fragment keeps the deleted side, so one deletion is not drawn once per fragment.
129
+ */
130
+ const splitContainedChanges = (changes, doc) => changes.flatMap(change => {
131
+ const {
132
+ key
133
+ } = getAttributionIdentity(change);
134
+ const inner = mergeRanges(changes.filter(other => {
135
+ const otherKey = getAttributionIdentity(other).key;
136
+ return other !== change && key !== undefined && otherKey !== undefined && otherKey !== key && isContained(change, other);
137
+ }).map(({
138
+ fromB,
139
+ toB
140
+ }) => ({
141
+ from: fromB,
142
+ to: toB
143
+ })));
144
+ if (inner.length === 0) {
145
+ return [change];
146
+ }
147
+ const gaps = [];
148
+ let cursor = change.fromB;
149
+ for (const {
150
+ from,
151
+ to
152
+ } of inner) {
153
+ if (from > cursor) {
154
+ gaps.push({
155
+ from: cursor,
156
+ to: from
157
+ });
158
+ }
159
+ cursor = Math.max(cursor, to);
160
+ }
161
+ if (change.toB > cursor) {
162
+ gaps.push({
163
+ from: cursor,
164
+ to: change.toB
165
+ });
166
+ }
167
+
168
+ // Subtracted end to end, so nothing is left to highlight — but the blocks are still owned.
169
+ const fragments = gaps.length > 0 ? gaps : [{
170
+ from: change.toB,
171
+ to: change.toB
172
+ }];
173
+ // The outer author's: every block in their range that no inner change opened.
174
+ const owned = blockNodeRanges(doc, {
175
+ from: change.fromB,
176
+ to: change.toB
177
+ }, pos => !inner.some(({
178
+ from,
179
+ to
180
+ }) => pos >= from && pos < to));
181
+ return fragments.map((fragment, index) => ({
182
+ ...change,
183
+ fromA: index === 0 ? change.fromA : change.toA,
184
+ fromB: fragment.from,
185
+ toB: fragment.to,
186
+ deleted: index === 0 ? change.deleted : [],
187
+ ...(owned ? {
188
+ blockNodeRangesB: index === 0 ? owned : []
189
+ } : {})
190
+ }));
191
+ });
192
+
193
+ /**
194
+ * The blocks a merged change owns. Both sides must name theirs, or the merge falls back to its own
195
+ * range — narrowing it could drop a block altogether.
196
+ */
197
+ const mergeBlockNodeRanges = (left, right) => left.blockNodeRangesB && right.blockNodeRangesB ? [...left.blockNodeRangesB, ...right.blockNodeRangesB] : undefined;
198
+
199
+ /** Unions the changes whose new-document ranges strictly overlap, leaving touching ones alone. */
200
+ const collapse = changes => {
36
201
  // A zero-length new-document range paints no inline decoration, so it is kept aside rather than
37
202
  // folded into a neighbouring insertion.
38
203
  const emptyRanges = changes.filter(change => change.toB <= change.fromB);
@@ -40,13 +205,17 @@ export const collapseOverlappingChanges = changes => {
40
205
  for (const change of changes.filter(change => change.toB > change.fromB).sort(byNewDocRange)) {
41
206
  const current = collapsed[collapsed.length - 1];
42
207
  if (current && change.fromB < current.toB) {
208
+ const blockNodeRangesB = mergeBlockNodeRanges(current, change);
43
209
  collapsed[collapsed.length - 1] = {
44
210
  fromA: Math.min(current.fromA, change.fromA),
45
211
  toA: Math.max(current.toA, change.toA),
46
212
  fromB: Math.min(current.fromB, change.fromB),
47
213
  toB: Math.max(current.toB, change.toB),
48
214
  deleted: [...current.deleted, ...change.deleted],
49
- inserted: [...current.inserted, ...change.inserted]
215
+ inserted: [...current.inserted, ...change.inserted],
216
+ ...(blockNodeRangesB ? {
217
+ blockNodeRangesB
218
+ } : {})
50
219
  };
51
220
  } else {
52
221
  collapsed.push({
@@ -57,6 +226,23 @@ export const collapseOverlappingChanges = changes => {
57
226
  return [...collapsed, ...emptyRanges].sort(byNewDocRange);
58
227
  };
59
228
 
229
+ /**
230
+ * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
231
+ * independently, so runs either side of an unattributed step can come back covering the same
232
+ * characters — which stacks two inline decorations and renders one contributor tag per copy.
233
+ *
234
+ * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
235
+ * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
236
+ * is not credited to it.
237
+ *
238
+ * Contributors are separated first, by `splitMixedIdentityChanges` and `splitContainedChanges`.
239
+ * Both leave ranges that only touch, so they survive the collapse.
240
+ *
241
+ * Carries no gate of its own: the only caller reaches it through the attributed changeset, which
242
+ * already requires `confluence_ncs_step_diffing_version_history`.
243
+ */
244
+ export const collapseOverlappingChanges = (changes, doc) => collapse(splitContainedChanges(splitMixedIdentityChanges(changes, doc), doc));
245
+
60
246
  /**
61
247
  * Simplifies and optimizes consecutive changes without crossing an attribution boundary.
62
248
  * A change which already contains more than one identity is deliberately kept isolated.
@@ -1,3 +1,4 @@
1
+ import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
1
2
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
3
  import { getStandardDeletedTextDecorationStyle } from './getStandardDeletedTextDecorationStyle';
3
4
 
@@ -115,40 +116,46 @@ const textAccentMap = {
115
116
  gray: "var(--ds-text-accent-gray, #505258)",
116
117
  lime: "var(--ds-text-accent-lime, #4C6B1F)"
117
118
  };
119
+ const getBrand = color => Object.prototype.hasOwnProperty.call(agentBrandColorSchemes, color) ? agentBrandColorSchemes[color] : undefined;
118
120
  function bgSubtlest(color) {
119
- return bgSubtlestMap[color];
121
+ var _getBrand$background, _getBrand;
122
+ return (_getBrand$background = (_getBrand = getBrand(color)) === null || _getBrand === void 0 ? void 0 : _getBrand.background) !== null && _getBrand$background !== void 0 ? _getBrand$background : bgSubtlestMap[color];
120
123
  }
121
124
  function bgSubtlestPressed(color) {
122
- return bgSubtlestPressedMap[color];
125
+ var _getBrand$background2, _getBrand2;
126
+ return (_getBrand$background2 = (_getBrand2 = getBrand(color)) === null || _getBrand2 === void 0 ? void 0 : _getBrand2.background) !== null && _getBrand$background2 !== void 0 ? _getBrand$background2 : bgSubtlestPressedMap[color];
123
127
  }
124
128
  function bgSubtler(color) {
125
- return bgSubtlerMap[color];
129
+ var _getBrand$border, _getBrand3;
130
+ return (_getBrand$border = (_getBrand3 = getBrand(color)) === null || _getBrand3 === void 0 ? void 0 : _getBrand3.border) !== null && _getBrand$border !== void 0 ? _getBrand$border : bgSubtlerMap[color];
126
131
  }
127
132
  function bgBolder(color) {
128
- return bgBolderMap[color];
133
+ var _getBrand$bold, _getBrand4;
134
+ return (_getBrand$bold = (_getBrand4 = getBrand(color)) === null || _getBrand4 === void 0 ? void 0 : _getBrand4.bold) !== null && _getBrand$bold !== void 0 ? _getBrand$bold : bgBolderMap[color];
129
135
  }
130
136
  function bgSubtlerPressed(color) {
131
- return bgSubtlerPressedMap[color];
137
+ var _getBrand$background3, _getBrand5;
138
+ return (_getBrand$background3 = (_getBrand5 = getBrand(color)) === null || _getBrand5 === void 0 ? void 0 : _getBrand5.background) !== null && _getBrand$background3 !== void 0 ? _getBrand$background3 : bgSubtlerPressedMap[color];
132
139
  }
133
140
  function borderAccent(color) {
134
- return borderAccentMap[color];
141
+ var _getBrand$border2, _getBrand6;
142
+ return (_getBrand$border2 = (_getBrand6 = getBrand(color)) === null || _getBrand6 === void 0 ? void 0 : _getBrand6.border) !== null && _getBrand$border2 !== void 0 ? _getBrand$border2 : borderAccentMap[color];
135
143
  }
136
144
  function textAccent(color) {
137
- return textAccentMap[color];
145
+ var _getBrand$accentText, _getBrand7;
146
+ return (_getBrand$accentText = (_getBrand7 = getBrand(color)) === null || _getBrand7 === void 0 ? void 0 : _getBrand7.accentText) !== null && _getBrand$accentText !== void 0 ? _getBrand$accentText : textAccentMap[color];
147
+ }
148
+ function deletedBackground(color) {
149
+ var _getBrand$emphasisBac, _getBrand8;
150
+ return (_getBrand$emphasisBac = (_getBrand8 = getBrand(color)) === null || _getBrand8 === void 0 ? void 0 : _getBrand8.emphasisBackground) !== null && _getBrand$emphasisBac !== void 0 ? _getBrand$emphasisBac : bgSubtlest(color);
138
151
  }
139
152
 
140
- /**
141
- * Presentation tokens for a contributor tag in one diff colour.
142
- *
143
- * A `bolder` fill rather than the `subtlest` tint the highlight uses: the tag is a label on the
144
- * change, and at tag size a tint reads as a second, weaker highlight sitting above the real one.
145
- * `inverse` is the only text tone that reads on a bolder fill, and it needs no per-colour map —
146
- * which is also why the tag carries no border: the fill alone bounds it.
147
- */
153
+ /** Contributor tags use a bold fill with a contrasting foreground, including branded schemes. */
148
154
  export function getAccentTokens(color) {
155
+ var _getBrand$boldText, _getBrand9;
149
156
  return {
150
157
  background: bgBolder(color),
151
- text: "var(--ds-text-inverse, #FFFFFF)"
158
+ text: (_getBrand$boldText = (_getBrand9 = getBrand(color)) === null || _getBrand9 === void 0 ? void 0 : _getBrand9.boldText) !== null && _getBrand$boldText !== void 0 ? _getBrand$boldText : "var(--ds-text-inverse, #FFFFFF)"
152
159
  };
153
160
  }
154
161
 
@@ -170,13 +177,18 @@ function deletedCellOutline(colors) {
170
177
  // --- Inserted content styles ---
171
178
 
172
179
  function insertedInlineBorderColor(colors) {
173
- return colors.insertedInlineBorderTone === 'backgroundHovered' ? bgSubtlestHoveredMap[colors.insertColor] : borderAccent(colors.insertColor);
180
+ var _getBrand$border3, _getBrand0;
181
+ return colors.insertedInlineBorderTone === 'backgroundHovered' ? (_getBrand$border3 = (_getBrand0 = getBrand(colors.insertColor)) === null || _getBrand0 === void 0 ? void 0 : _getBrand0.border) !== null && _getBrand$border3 !== void 0 ? _getBrand$border3 : bgSubtlestHoveredMap[colors.insertColor] : borderAccent(colors.insertColor);
174
182
  }
175
183
 
176
184
  /** Inline inserted content — default state. */
177
185
  export function buildInsertStyle(colors) {
186
+ const brand = getBrand(colors.insertColor);
178
187
  return convertToInlineCss({
179
188
  background: bgSubtlest(colors.insertColor),
189
+ ...(brand ? {
190
+ color: brand.text
191
+ } : {}),
180
192
  textDecoration: 'underline',
181
193
  textDecorationStyle: colors.insertUnderlineStyle,
182
194
  textDecorationThickness: "var(--ds-space-025, 2px)",
@@ -196,8 +208,12 @@ export function buildInsertStyleInBlock(colors) {
196
208
 
197
209
  /** Extended insert style — background + border-bottom underline. */
198
210
  export function buildInsertStyleExtended(colors) {
211
+ const brand = getBrand(colors.insertColor);
199
212
  return convertToInlineCss({
200
213
  background: bgSubtlest(colors.insertColor),
214
+ ...(brand ? {
215
+ color: brand.text
216
+ } : {}),
201
217
  borderBottom: `2px solid ${insertedInlineBorderColor(colors)}`,
202
218
  padding: `1px 0 2px`
203
219
  });
@@ -213,8 +229,12 @@ export function buildInsertStyleInBlockExtended(colors) {
213
229
 
214
230
  /** Active state of the extended insert style. */
215
231
  export function buildInsertStyleExtendedActive(colors) {
232
+ const brand = getBrand(colors.insertColor);
216
233
  return convertToInlineCss({
217
234
  background: bgSubtlerPressed(colors.insertActiveColor),
235
+ ...(brand ? {
236
+ color: brand.text
237
+ } : {}),
218
238
  borderBottom: `2px solid ${borderAccent(colors.insertColor)}`,
219
239
  padding: `1px 0 2px`
220
240
  });
@@ -222,16 +242,24 @@ export function buildInsertStyleExtendedActive(colors) {
222
242
 
223
243
  /** Extended insert style without underline — background + padding only. */
224
244
  export function buildInsertStyleExtendedNoUnderline(colors) {
245
+ const brand = getBrand(colors.insertColor);
225
246
  return convertToInlineCss({
226
247
  background: bgSubtlest(colors.insertColor),
248
+ ...(brand ? {
249
+ color: brand.text
250
+ } : {}),
227
251
  padding: `1px 0 2px`
228
252
  });
229
253
  }
230
254
 
231
255
  /** Active state of the no-underline extended insert style. */
232
256
  export function buildInsertStyleExtendedNoUnderlineActive(colors) {
257
+ const brand = getBrand(colors.insertColor);
233
258
  return convertToInlineCss({
234
259
  background: bgSubtlerPressed(colors.insertActiveColor),
260
+ ...(brand ? {
261
+ color: brand.text
262
+ } : {}),
235
263
  padding: `1px 0 2px`
236
264
  });
237
265
  }
@@ -245,8 +273,12 @@ export function buildInsertStyleInBlockExtendedNoUnderline(_colors) {
245
273
 
246
274
  /** Inline inserted content — active/focused state. */
247
275
  export function buildInsertStyleActive(colors) {
276
+ const brand = getBrand(colors.insertColor);
248
277
  return convertToInlineCss({
249
278
  background: bgSubtlerPressed(colors.insertActiveColor),
279
+ ...(brand ? {
280
+ color: brand.text
281
+ } : {}),
250
282
  textDecoration: 'underline',
251
283
  textDecorationStyle: colors.insertUnderlineStyle,
252
284
  textDecorationThickness: "var(--ds-space-025, 2px)",
@@ -610,7 +642,7 @@ export function buildDeletedInlineContentStyleExtended(colors, isActive = false)
610
642
  // Under 'underline' emphasis the highlight stays on its resting tint — the same one hover paints —
611
643
  // so only the border carries the emphasis.
612
644
  const isUnderlineEmphasis = isActive && usesUnderlineEmphasis(colors);
613
- const backgroundColor = isActive && !isUnderlineEmphasis && colors.deletedInlineBorderTone === 'background' ? bgSubtlerPressed(colors.deleteActiveColor) : bgSubtlest(colors.deleteColor);
645
+ const backgroundColor = isActive && !isUnderlineEmphasis && colors.deletedInlineBorderTone === 'background' ? bgSubtlerPressed(colors.deleteActiveColor) : deletedBackground(colors.deleteColor);
614
646
  const borderColor = isUnderlineEmphasis ? borderAccent(colors.deleteActiveColor) : colors.deletedInlineBorderTone === 'background' ? backgroundColor : borderAccent(colors.deleteColor);
615
647
  return convertToInlineCss({
616
648
  backgroundColor,
@@ -822,7 +854,7 @@ export function getDeletedInlineRevealColors(colors) {
822
854
  * paints at rest, so hovering restores the appearance rather than introducing a third one.
823
855
  */
824
856
  export function getDeletedHighlightHoverColor(colors) {
825
- return bgSubtlest(colors.deleteColor);
857
+ return deletedBackground(colors.deleteColor);
826
858
  }
827
859
 
828
860
  /**
@@ -1,3 +1,4 @@
1
+ import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
1
2
  import { PARTICIPANT_COLOR_SCHEMES } from './types';
2
3
 
3
4
  /** Green insertions, red deletions. */
@@ -74,10 +75,12 @@ const createAttributionScheme = color => {
74
75
  };
75
76
  };
76
77
  const attributionColorSchemes = Object.fromEntries(PARTICIPANT_COLOR_SCHEMES.map(color => [color, createAttributionScheme(color)]));
78
+ const brandSchemes = Object.fromEntries(Object.keys(agentBrandColorSchemes).map(scheme => [scheme, createAttributionScheme(scheme)]));
77
79
 
78
80
  /** Maps public schemes and attribution-only participant slots to their data objects. */
79
81
  export const colorSchemeRegistry = {
80
82
  ...attributionColorSchemes,
83
+ ...brandSchemes,
81
84
  traditional: traditionalScheme,
82
85
  standard: standardScheme
83
86
  };
@@ -5,8 +5,6 @@ export const PARTICIPANT_COLOR_SCHEMES = ['red', 'blue', 'green', 'yellow', 'pur
5
5
 
6
6
  /** ADS accent hues used by the public and participant colour schemes. */
7
7
 
8
- /** A public scheme or an attribution participant slot resolved internally for one change. */
9
-
10
8
  /**
11
9
  * A show-diff colour scheme. Colour fields are ADS hues that `factory.ts` expands into token paths;
12
10
  * the rest encode structural differences no colour can express. Ring widths, padding, radius and
@@ -4,6 +4,7 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
5
  import { fg } from '@atlaskit/platform-feature-flags/fg';
6
6
  import { isExtendedEnabled } from '../isExtendedEnabled';
7
+ import { isTaggableBlockNode } from '../utils/taggableBlockNodes';
7
8
  import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
8
9
  import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
9
10
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
@@ -23,23 +24,28 @@ const UNSTYLED_NODES = ['mediaSingle', 'mediaGroup', 'table',
23
24
  'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'];
24
25
  const CELL_NODES = ['tableCell', 'tableHeader'];
25
26
 
26
- /**
27
- * Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
28
- * Lists and tables are excluded: they decorate per item and per cell, with no one-tag-per-block
29
- * design yet. `blockCard` covers the datasource variant, which is the same node type.
30
- */
31
- const TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule']);
32
-
33
27
  /** Panels, rules and media can also sit in a table cell, which is out of scope for block tags. */
34
28
  const isInsideTable = (doc, pos) => findParentNodeClosestToPos(doc.resolve(pos), node => node.type.name === 'table') !== undefined;
35
29
 
36
- /** Whether this block can host a contributor tag of its own. */
30
+ /**
31
+ * Whether this block can host a contributor tag of its own.
32
+ *
33
+ * The node type is resolved from the document rather than matched on `change.name`, so a schema
34
+ * variant is recognised as the type it varies — see `isTaggableBlockNode`. `from` is the node's own
35
+ * position, so `nodeAt` returns exactly the node being decorated.
36
+ */
37
37
  const canTagBlock = ({
38
38
  diffType,
39
39
  doc,
40
- from,
41
- name
42
- }) => doc !== undefined && isExtendedEnabled(diffType) && TAGGABLE_BLOCK_NODES.has(name) && !isInsideTable(doc, from);
40
+ from
41
+ }) => {
42
+ var _doc$nodeAt;
43
+ if (doc === undefined || !isExtendedEnabled(diffType)) {
44
+ return false;
45
+ }
46
+ const nodeType = (_doc$nodeAt = doc.nodeAt(from)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type;
47
+ return nodeType !== undefined && isTaggableBlockNode(nodeType) && !isInsideTable(doc, from);
48
+ };
43
49
 
44
50
  /** Positioning context for the cell overlay widget decorations. */
45
51
  const cellPositionStyle = convertToInlineCss({
@@ -176,8 +182,7 @@ export const createBlockChangedDecoration = ({
176
182
  const shouldTagBlock = showContributorTags && canTagBlock({
177
183
  diffType,
178
184
  doc,
179
- from: change.from,
180
- name: change.name
185
+ from: change.from
181
186
  });
182
187
  // Named so this block's own tag can position against it; see `createContributorTagWidget`.
183
188
  const tagAnchorName = shouldTagBlock && isContributorTagWidgetEnabled() ? buildAnchorDecorationKey({
@@ -221,7 +226,9 @@ export const createBlockChangedDecoration = ({
221
226
  key: 'cell-overlay-decoration'
222
227
  }));
223
228
  }
224
- // isInserted is only read under the extended experience, so pass it unconditionally.
229
+ // isInserted is only read under the extended experience, so pass it unconditionally. `change.name`
230
+ // verbatim: base-name resolution stays in `canTagBlock`, which needs tags on, so a schema
231
+ // variant's legacy style is untouched — see `resolveBaseNodeName`.
225
232
  const nodeStyle = getBlockNodeStyle({
226
233
  nodeName: change.name,
227
234
  colorScheme,
@@ -254,6 +261,8 @@ export const createBlockChangedDecoration = ({
254
261
  })));
255
262
  }
256
263
  if (decorations.length === 0) {
264
+ // An unstyled node type (paragraph, heading, list, layout, …) emits no decoration at all, so
265
+ // there is nothing for a tag to hang off even when the node type would otherwise be taggable.
257
266
  return decorations;
258
267
  }
259
268
  if (showIndicators && doc && isExtendedEnabled(diffType)) {