@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
@@ -1,6 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
4
5
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
5
6
  import { getStandardDeletedTextDecorationStyle } from './getStandardDeletedTextDecorationStyle';
6
7
 
@@ -118,40 +119,48 @@ var textAccentMap = {
118
119
  gray: "var(--ds-text-accent-gray, #505258)",
119
120
  lime: "var(--ds-text-accent-lime, #4C6B1F)"
120
121
  };
122
+ var getBrand = function getBrand(color) {
123
+ return Object.prototype.hasOwnProperty.call(agentBrandColorSchemes, color) ? agentBrandColorSchemes[color] : undefined;
124
+ };
121
125
  function bgSubtlest(color) {
122
- return bgSubtlestMap[color];
126
+ var _getBrand$background, _getBrand;
127
+ return (_getBrand$background = (_getBrand = getBrand(color)) === null || _getBrand === void 0 ? void 0 : _getBrand.background) !== null && _getBrand$background !== void 0 ? _getBrand$background : bgSubtlestMap[color];
123
128
  }
124
129
  function bgSubtlestPressed(color) {
125
- return bgSubtlestPressedMap[color];
130
+ var _getBrand$background2, _getBrand2;
131
+ return (_getBrand$background2 = (_getBrand2 = getBrand(color)) === null || _getBrand2 === void 0 ? void 0 : _getBrand2.background) !== null && _getBrand$background2 !== void 0 ? _getBrand$background2 : bgSubtlestPressedMap[color];
126
132
  }
127
133
  function bgSubtler(color) {
128
- return bgSubtlerMap[color];
134
+ var _getBrand$border, _getBrand3;
135
+ return (_getBrand$border = (_getBrand3 = getBrand(color)) === null || _getBrand3 === void 0 ? void 0 : _getBrand3.border) !== null && _getBrand$border !== void 0 ? _getBrand$border : bgSubtlerMap[color];
129
136
  }
130
137
  function bgBolder(color) {
131
- return bgBolderMap[color];
138
+ var _getBrand$bold, _getBrand4;
139
+ return (_getBrand$bold = (_getBrand4 = getBrand(color)) === null || _getBrand4 === void 0 ? void 0 : _getBrand4.bold) !== null && _getBrand$bold !== void 0 ? _getBrand$bold : bgBolderMap[color];
132
140
  }
133
141
  function bgSubtlerPressed(color) {
134
- return bgSubtlerPressedMap[color];
142
+ var _getBrand$background3, _getBrand5;
143
+ return (_getBrand$background3 = (_getBrand5 = getBrand(color)) === null || _getBrand5 === void 0 ? void 0 : _getBrand5.background) !== null && _getBrand$background3 !== void 0 ? _getBrand$background3 : bgSubtlerPressedMap[color];
135
144
  }
136
145
  function borderAccent(color) {
137
- return borderAccentMap[color];
146
+ var _getBrand$border2, _getBrand6;
147
+ return (_getBrand$border2 = (_getBrand6 = getBrand(color)) === null || _getBrand6 === void 0 ? void 0 : _getBrand6.border) !== null && _getBrand$border2 !== void 0 ? _getBrand$border2 : borderAccentMap[color];
138
148
  }
139
149
  function textAccent(color) {
140
- return textAccentMap[color];
150
+ var _getBrand$accentText, _getBrand7;
151
+ return (_getBrand$accentText = (_getBrand7 = getBrand(color)) === null || _getBrand7 === void 0 ? void 0 : _getBrand7.accentText) !== null && _getBrand$accentText !== void 0 ? _getBrand$accentText : textAccentMap[color];
152
+ }
153
+ function deletedBackground(color) {
154
+ var _getBrand$emphasisBac, _getBrand8;
155
+ return (_getBrand$emphasisBac = (_getBrand8 = getBrand(color)) === null || _getBrand8 === void 0 ? void 0 : _getBrand8.emphasisBackground) !== null && _getBrand$emphasisBac !== void 0 ? _getBrand$emphasisBac : bgSubtlest(color);
141
156
  }
142
157
 
143
- /**
144
- * Presentation tokens for a contributor tag in one diff colour.
145
- *
146
- * A `bolder` fill rather than the `subtlest` tint the highlight uses: the tag is a label on the
147
- * change, and at tag size a tint reads as a second, weaker highlight sitting above the real one.
148
- * `inverse` is the only text tone that reads on a bolder fill, and it needs no per-colour map —
149
- * which is also why the tag carries no border: the fill alone bounds it.
150
- */
158
+ /** Contributor tags use a bold fill with a contrasting foreground, including branded schemes. */
151
159
  export function getAccentTokens(color) {
160
+ var _getBrand$boldText, _getBrand9;
152
161
  return {
153
162
  background: bgBolder(color),
154
- text: "var(--ds-text-inverse, #FFFFFF)"
163
+ 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)"
155
164
  };
156
165
  }
157
166
 
@@ -173,18 +182,23 @@ function deletedCellOutline(colors) {
173
182
  // --- Inserted content styles ---
174
183
 
175
184
  function insertedInlineBorderColor(colors) {
176
- return colors.insertedInlineBorderTone === 'backgroundHovered' ? bgSubtlestHoveredMap[colors.insertColor] : borderAccent(colors.insertColor);
185
+ var _getBrand$border3, _getBrand0;
186
+ 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);
177
187
  }
178
188
 
179
189
  /** Inline inserted content — default state. */
180
190
  export function buildInsertStyle(colors) {
181
- return convertToInlineCss({
182
- background: bgSubtlest(colors.insertColor),
191
+ var brand = getBrand(colors.insertColor);
192
+ return convertToInlineCss(_objectSpread(_objectSpread({
193
+ background: bgSubtlest(colors.insertColor)
194
+ }, brand ? {
195
+ color: brand.text
196
+ } : {}), {}, {
183
197
  textDecoration: 'underline',
184
198
  textDecorationStyle: colors.insertUnderlineStyle,
185
199
  textDecorationThickness: "var(--ds-space-025, 2px)",
186
200
  textDecorationColor: borderAccent(colors.insertColor)
187
- });
201
+ }));
188
202
  }
189
203
 
190
204
  /** Inline insert style for content within a block node (no background, underline only). */
@@ -199,11 +213,15 @@ export function buildInsertStyleInBlock(colors) {
199
213
 
200
214
  /** Extended insert style — background + border-bottom underline. */
201
215
  export function buildInsertStyleExtended(colors) {
202
- return convertToInlineCss({
203
- background: bgSubtlest(colors.insertColor),
216
+ var brand = getBrand(colors.insertColor);
217
+ return convertToInlineCss(_objectSpread(_objectSpread({
218
+ background: bgSubtlest(colors.insertColor)
219
+ }, brand ? {
220
+ color: brand.text
221
+ } : {}), {}, {
204
222
  borderBottom: "2px solid ".concat(insertedInlineBorderColor(colors)),
205
223
  padding: "1px 0 2px"
206
- });
224
+ }));
207
225
  }
208
226
 
209
227
  /** Extended insert style for content within a block node (border-bottom only, no background). */
@@ -216,27 +234,39 @@ export function buildInsertStyleInBlockExtended(colors) {
216
234
 
217
235
  /** Active state of the extended insert style. */
218
236
  export function buildInsertStyleExtendedActive(colors) {
219
- return convertToInlineCss({
220
- background: bgSubtlerPressed(colors.insertActiveColor),
237
+ var brand = getBrand(colors.insertColor);
238
+ return convertToInlineCss(_objectSpread(_objectSpread({
239
+ background: bgSubtlerPressed(colors.insertActiveColor)
240
+ }, brand ? {
241
+ color: brand.text
242
+ } : {}), {}, {
221
243
  borderBottom: "2px solid ".concat(borderAccent(colors.insertColor)),
222
244
  padding: "1px 0 2px"
223
- });
245
+ }));
224
246
  }
225
247
 
226
248
  /** Extended insert style without underline — background + padding only. */
227
249
  export function buildInsertStyleExtendedNoUnderline(colors) {
228
- return convertToInlineCss({
229
- background: bgSubtlest(colors.insertColor),
250
+ var brand = getBrand(colors.insertColor);
251
+ return convertToInlineCss(_objectSpread(_objectSpread({
252
+ background: bgSubtlest(colors.insertColor)
253
+ }, brand ? {
254
+ color: brand.text
255
+ } : {}), {}, {
230
256
  padding: "1px 0 2px"
231
- });
257
+ }));
232
258
  }
233
259
 
234
260
  /** Active state of the no-underline extended insert style. */
235
261
  export function buildInsertStyleExtendedNoUnderlineActive(colors) {
236
- return convertToInlineCss({
237
- background: bgSubtlerPressed(colors.insertActiveColor),
262
+ var brand = getBrand(colors.insertColor);
263
+ return convertToInlineCss(_objectSpread(_objectSpread({
264
+ background: bgSubtlerPressed(colors.insertActiveColor)
265
+ }, brand ? {
266
+ color: brand.text
267
+ } : {}), {}, {
238
268
  padding: "1px 0 2px"
239
- });
269
+ }));
240
270
  }
241
271
 
242
272
  /** No-underline extended insert style for block content (padding only). */
@@ -248,13 +278,17 @@ export function buildInsertStyleInBlockExtendedNoUnderline(_colors) {
248
278
 
249
279
  /** Inline inserted content — active/focused state. */
250
280
  export function buildInsertStyleActive(colors) {
251
- return convertToInlineCss({
252
- background: bgSubtlerPressed(colors.insertActiveColor),
281
+ var brand = getBrand(colors.insertColor);
282
+ return convertToInlineCss(_objectSpread(_objectSpread({
283
+ background: bgSubtlerPressed(colors.insertActiveColor)
284
+ }, brand ? {
285
+ color: brand.text
286
+ } : {}), {}, {
253
287
  textDecoration: 'underline',
254
288
  textDecorationStyle: colors.insertUnderlineStyle,
255
289
  textDecorationThickness: "var(--ds-space-025, 2px)",
256
290
  textDecorationColor: textAccent(colors.insertActiveColor)
257
- });
291
+ }));
258
292
  }
259
293
 
260
294
  // --- Deleted inline content styles ---
@@ -612,7 +646,7 @@ export function buildDeletedInlineContentStyleExtended(colors) {
612
646
  // Under 'underline' emphasis the highlight stays on its resting tint — the same one hover paints —
613
647
  // so only the border carries the emphasis.
614
648
  var isUnderlineEmphasis = isActive && usesUnderlineEmphasis(colors);
615
- var backgroundColor = isActive && !isUnderlineEmphasis && colors.deletedInlineBorderTone === 'background' ? bgSubtlerPressed(colors.deleteActiveColor) : bgSubtlest(colors.deleteColor);
649
+ var backgroundColor = isActive && !isUnderlineEmphasis && colors.deletedInlineBorderTone === 'background' ? bgSubtlerPressed(colors.deleteActiveColor) : deletedBackground(colors.deleteColor);
616
650
  var borderColor = isUnderlineEmphasis ? borderAccent(colors.deleteActiveColor) : colors.deletedInlineBorderTone === 'background' ? backgroundColor : borderAccent(colors.deleteColor);
617
651
  return convertToInlineCss({
618
652
  backgroundColor: backgroundColor,
@@ -822,7 +856,7 @@ export function getDeletedInlineRevealColors(colors) {
822
856
  * paints at rest, so hovering restores the appearance rather than introducing a third one.
823
857
  */
824
858
  export function getDeletedHighlightHoverColor(colors) {
825
- return bgSubtlest(colors.deleteColor);
859
+ return deletedBackground(colors.deleteColor);
826
860
  }
827
861
 
828
862
  /**
@@ -1,6 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
4
5
  import { PARTICIPANT_COLOR_SCHEMES } from './types';
5
6
 
6
7
  /** Green insertions, red deletions. */
@@ -78,9 +79,12 @@ var createAttributionScheme = function createAttributionScheme(color) {
78
79
  var attributionColorSchemes = Object.fromEntries(PARTICIPANT_COLOR_SCHEMES.map(function (color) {
79
80
  return [color, createAttributionScheme(color)];
80
81
  }));
82
+ var brandSchemes = Object.fromEntries(Object.keys(agentBrandColorSchemes).map(function (scheme) {
83
+ return [scheme, createAttributionScheme(scheme)];
84
+ }));
81
85
 
82
86
  /** Maps public schemes and attribution-only participant slots to their data objects. */
83
- export var colorSchemeRegistry = _objectSpread(_objectSpread({}, attributionColorSchemes), {}, {
87
+ export var colorSchemeRegistry = _objectSpread(_objectSpread(_objectSpread({}, attributionColorSchemes), brandSchemes), {}, {
84
88
  traditional: traditionalScheme,
85
89
  standard: standardScheme
86
90
  });
@@ -5,8 +5,6 @@ export var PARTICIPANT_COLOR_SCHEMES = ['red', 'blue', 'green', 'yellow', 'purpl
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
@@ -8,6 +8,7 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
8
8
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
9
9
  import { fg } from '@atlaskit/platform-feature-flags/fg';
10
10
  import { isExtendedEnabled } from '../isExtendedEnabled';
11
+ import { isTaggableBlockNode } from '../utils/taggableBlockNodes';
11
12
  import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
12
13
  import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
13
14
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
@@ -27,13 +28,6 @@ var UNSTYLED_NODES = ['mediaSingle', 'mediaGroup', 'table',
27
28
  'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'];
28
29
  var CELL_NODES = ['tableCell', 'tableHeader'];
29
30
 
30
- /**
31
- * Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
32
- * Lists and tables are excluded: they decorate per item and per cell, with no one-tag-per-block
33
- * design yet. `blockCard` covers the datasource variant, which is the same node type.
34
- */
35
- var TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule']);
36
-
37
31
  /** Panels, rules and media can also sit in a table cell, which is out of scope for block tags. */
38
32
  var isInsideTable = function isInsideTable(doc, pos) {
39
33
  return findParentNodeClosestToPos(doc.resolve(pos), function (node) {
@@ -41,13 +35,23 @@ var isInsideTable = function isInsideTable(doc, pos) {
41
35
  }) !== undefined;
42
36
  };
43
37
 
44
- /** Whether this block can host a contributor tag of its own. */
38
+ /**
39
+ * Whether this block can host a contributor tag of its own.
40
+ *
41
+ * The node type is resolved from the document rather than matched on `change.name`, so a schema
42
+ * variant is recognised as the type it varies — see `isTaggableBlockNode`. `from` is the node's own
43
+ * position, so `nodeAt` returns exactly the node being decorated.
44
+ */
45
45
  var canTagBlock = function canTagBlock(_ref) {
46
+ var _doc$nodeAt;
46
47
  var diffType = _ref.diffType,
47
48
  doc = _ref.doc,
48
- from = _ref.from,
49
- name = _ref.name;
50
- return doc !== undefined && isExtendedEnabled(diffType) && TAGGABLE_BLOCK_NODES.has(name) && !isInsideTable(doc, from);
49
+ from = _ref.from;
50
+ if (doc === undefined || !isExtendedEnabled(diffType)) {
51
+ return false;
52
+ }
53
+ var nodeType = (_doc$nodeAt = doc.nodeAt(from)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type;
54
+ return nodeType !== undefined && isTaggableBlockNode(nodeType) && !isInsideTable(doc, from);
51
55
  };
52
56
 
53
57
  /** Positioning context for the cell overlay widget decorations. */
@@ -189,8 +193,7 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
189
193
  var shouldTagBlock = showContributorTags && canTagBlock({
190
194
  diffType: diffType,
191
195
  doc: doc,
192
- from: change.from,
193
- name: change.name
196
+ from: change.from
194
197
  });
195
198
  // Named so this block's own tag can position against it; see `createContributorTagWidget`.
196
199
  var tagAnchorName = shouldTagBlock && isContributorTagWidgetEnabled() ? buildAnchorDecorationKey({
@@ -234,7 +237,9 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
234
237
  key: 'cell-overlay-decoration'
235
238
  }));
236
239
  }
237
- // isInserted is only read under the extended experience, so pass it unconditionally.
240
+ // isInserted is only read under the extended experience, so pass it unconditionally. `change.name`
241
+ // verbatim: base-name resolution stays in `canTagBlock`, which needs tags on, so a schema
242
+ // variant's legacy style is untouched — see `resolveBaseNodeName`.
238
243
  var nodeStyle = getBlockNodeStyle({
239
244
  nodeName: change.name,
240
245
  colorScheme: colorScheme,
@@ -265,6 +270,8 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
265
270
  })));
266
271
  }
267
272
  if (decorations.length === 0) {
273
+ // An unstyled node type (paragraph, heading, list, layout, …) emits no decoration at all, so
274
+ // there is nothing for a tag to hang off even when the node type would otherwise be taggable.
268
275
  return decorations;
269
276
  }
270
277
  if (showIndicators && doc && isExtendedEnabled(diffType)) {
@@ -9,7 +9,11 @@ import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
9
9
  import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
10
10
  import { createContributorTagHost, unmountContributorTag } from './createContributorTagWidget';
11
11
  import { buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
12
+ import { absorbFirstChildMarginReset } from './utils/absorbFirstChildMarginReset';
13
+ import { createMarginAbsorber } from './utils/createMarginAbsorber';
14
+ import { createNodeShapedMarginSpacer } from './utils/createNodeShapedMarginSpacer';
12
15
  import { findSafeInsertPos } from './utils/findSafeInsertPos';
16
+ import { safeResolve } from './utils/safeResolve';
13
17
  import { wrapBlockNodeView, injectInnerWrapper, createContentWrapper } from './utils/wrapBlockNodeView';
14
18
  var isHeadingLevel = function isHeadingLevel(level) {
15
19
  return typeof level === 'number' && level >= 1 && level <= 6;
@@ -183,6 +187,14 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
183
187
  // For non-table content, use the existing span wrapper approach
184
188
  var dom = document.createElement('span');
185
189
  var $safeInsertPos = newDoc.resolve(safeInsertPos);
190
+
191
+ // Whether the widget renders above the very start of its parent's content — the document, a
192
+ // layout column, a table cell, a panel.
193
+ //
194
+ // `parentOffset === 0` is the whole test on the position side: prosemirror-view paints every
195
+ // widget at a position before the node starting there, so a leading widget at the parent's start
196
+ // always renders above its content.
197
+ var isVisuallyFirstInParent = !placeBelow && $safeInsertPos.parentOffset === 0;
186
198
  var isTopLevelInsert = $safeInsertPos.depth === 0;
187
199
  var hasPreviousBlock = ((_$safeInsertPos$nodeB = $safeInsertPos.nodeBefore) === null || _$safeInsertPos$nodeB === void 0 ? void 0 : _$safeInsertPos$nodeB.isBlock) === true;
188
200
  var isFirstDocHeadingReplacement = isExtendedEnabled(diffType) && !placeBelow && change.fromB === 0 && ((_slice$content$firstC = slice.content.firstChild) === null || _slice$content$firstC === void 0 ? void 0 : _slice$content$firstC.type.name) === 'heading' && ((_newDoc$firstChild = newDoc.firstChild) === null || _newDoc$firstChild === void 0 ? void 0 : _newDoc$firstChild.type.name) === 'heading';
@@ -234,6 +246,20 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
234
246
  var firstReplacedNode = slice.content.firstChild;
235
247
  var lastReplacedNode = slice.content.lastChild;
236
248
  var showDiffPatch1 = fg('platform_editor_ai_show_diff_patch_1');
249
+ // Whether the slice's outermost textblocks still hold all of their original content.
250
+ //
251
+ // `openStart`/`openEnd` say whether the cut landed inside a block, but not whether it took
252
+ // any text with it — a deletion stopping exactly at a block's content boundary is still
253
+ // reported as open. Resolving the change's own ends separates the two, so a block whose text
254
+ // survived intact can render as a block even though its boundary was open.
255
+ //
256
+ // Both resolves sit behind the gate that consumes them, so this adds no position arithmetic to
257
+ // the ungated path, and an unresolvable position degrades to the `openStart`/`openEnd` test on
258
+ // its own — the block is then treated as partial, which is what it rendered as before the gate.
259
+ var $changeFromA = fg('platform_editor_ai_show_diff_patch_2') ? safeResolve(doc, change.fromA) : null;
260
+ var $changeToA = fg('platform_editor_ai_show_diff_patch_2') ? safeResolve(doc, change.toA) : null;
261
+ var isFirstNodeContentComplete = slice.openStart === 0 || ($changeFromA === null || $changeFromA === void 0 ? void 0 : $changeFromA.parentOffset) === 0;
262
+ var isLastNodeContentComplete = slice.openEnd === 0 || $changeToA !== null && $changeToA.parentOffset === $changeToA.parent.content.size;
237
263
  var isCompleteSameTypeReplacement = slice.content.childCount === 1 && firstReplacedNode !== null && replacementNode !== null && firstReplacedNode.type === replacementNode.type && replacementNode.nodeSize === change.toB - change.fromB;
238
264
 
239
265
  /*
@@ -246,10 +272,16 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
246
272
  var isLast = lastReplacedNode === node;
247
273
  var isOpenAtSliceBoundary = isFirst && slice.openStart > 0 || isLast && slice.openEnd > 0;
248
274
  var shouldPreserveCompleteMultiInlineBlock = showDiffPatch1 && node.isBlock && node.type.inlineContent && node.content.childCount > 1 && !isOpenAtSliceBoundary && isCompleteSameTypeReplacement;
275
+ // A textblock is otherwise serialized as its inline content only, with no `<p>`/`<h2>` wrapper.
276
+ // That is right when the diff cuts into an existing block — the deleted text belongs on the same
277
+ // line as the text that replaced it — but when the whole block went away it drops the very
278
+ // element that carries the block's margin, so the deleted block renders flush against its
279
+ // neighbours. Only the first and last children can be partial; middle ones are always complete.
280
+ var shouldRenderAsBlockNode = fg('platform_editor_ai_show_diff_patch_2') && node.isTextblock && (!isFirst || isFirstNodeContentComplete) && (!isLast || isLastNodeContentComplete);
249
281
 
250
282
  // Helper function to handle multiple child nodes
251
283
  var handleMultipleChildNodes = function handleMultipleChildNodes(node) {
252
- if (!shouldPreserveCompleteMultiInlineBlock && node.content.childCount > 1 && node.type.inlineContent) {
284
+ if (!shouldPreserveCompleteMultiInlineBlock && !shouldRenderAsBlockNode && node.content.childCount > 1 && node.type.inlineContent) {
253
285
  node.content.forEach(function (childNode) {
254
286
  var childNodeView = serializer.tryCreateNodeView(childNode);
255
287
  if (childNodeView) {
@@ -279,7 +311,7 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
279
311
  if (handleMultipleChildNodes(node)) {
280
312
  return;
281
313
  }
282
- if (shouldPreserveCompleteMultiInlineBlock) {
314
+ if (shouldPreserveCompleteMultiInlineBlock || shouldRenderAsBlockNode) {
283
315
  fallbackSerialization = function fallbackSerialization() {
284
316
  return serializer.serializeNode(node);
285
317
  };
@@ -356,6 +388,21 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
356
388
  dom.style.setProperty('scroll-margin-top', scrollMarginTopValue);
357
389
  }
358
390
 
391
+ // A block node serialized into the widget is the widget's first child, and the editor's
392
+ // first-child reset is written against the parent element rather than the document position — so
393
+ // it zeroes the margin the block wrapper was kept for in the first place.
394
+ //
395
+ // Only away from the parent's start. There the reset is the correct outcome: the widget is the
396
+ // first thing in the document, column, cell or panel, and a leading gap above it would be wrong.
397
+ // The margin that matters at that position belongs to the node *below* the widget, which the
398
+ // shaped spacer pair supplies instead.
399
+ if (fg('platform_editor_ai_show_diff_patch_2') && !isVisuallyFirstInParent) {
400
+ absorbFirstChildMarginReset({
401
+ dom: dom,
402
+ testId: 'show-diff-widget-margin-absorber'
403
+ });
404
+ }
405
+
359
406
  // Needed even when the indicator bar is off, because a contributor tag also anchors against the
360
407
  // widget.
361
408
  if ((showIndicators || showContributorTags) && isExtendedEnabled(diffType)) {
@@ -418,7 +465,40 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
418
465
  var isPureDeletion = change.fromB === change.toB;
419
466
  var isSingleBlock = slice.content.childCount === 1 && ((_slice$content$firstC2 = slice.content.firstChild) === null || _slice$content$firstC2 === void 0 ? void 0 : _slice$content$firstC2.isBlock);
420
467
  var isDiffWidgetAtStartOfDoc = $safeInsertPos.depth === 0 && $safeInsertPos.index(0) === 0;
421
- if (isDiffWidgetAtStartOfDoc && isSingleBlock && isPureDeletion && isExtendedEnabled(diffType)) {
468
+
469
+ // A node at the start of its parent has its top margin reset, so a diff widget rendered above
470
+ // it sits flush against it. This applies wherever that parent is — the document, a layout
471
+ // column, a table cell — so it is decided from the anchor rather than from the document root
472
+ // (see `isVisuallyFirstInParent` above).
473
+ //
474
+ // Away from the parent's start the node below keeps its own margin, and it is the block *inside*
475
+ // the widget that loses one — handled by `absorbFirstChildMarginReset` above, not here.
476
+ var nodeAfterWidget = $safeInsertPos.nodeAfter;
477
+ if (fg('platform_editor_ai_show_diff_patch_2') && isExtendedEnabled(diffType) && isVisuallyFirstInParent && nodeAfterWidget) {
478
+ var shapedSpacer = createNodeShapedMarginSpacer({
479
+ node: nodeAfterWidget,
480
+ serializer: serializer,
481
+ testId: 'show-diff-shaped-margin-spacer'
482
+ });
483
+ if (shapedSpacer) {
484
+ // Two elements, because the two families of reset have to be handled differently. The
485
+ // absorber takes the document-level adjacent-sibling reset for a leading widget, whose
486
+ // `!important` would otherwise zero the shaped spacer's margin; the shaped spacer, now one
487
+ // position further along, is out of that rule's reach and keeps the margin it supplies.
488
+ // Inside containers the counting selectors skip both, so only the shaped one has an effect.
489
+ //
490
+ // Sides order the widgets against each other only — both still paint before the real node.
491
+ decorations.push(Decoration.widget(safeInsertPos, createMarginAbsorber({
492
+ testId: 'show-diff-margin-absorber'
493
+ }), {
494
+ side: 0
495
+ }));
496
+ decorations.push(Decoration.widget(safeInsertPos, shapedSpacer, {
497
+ side: 1
498
+ }));
499
+ }
500
+ }
501
+ if (!fg('platform_editor_ai_show_diff_patch_2') && isDiffWidgetAtStartOfDoc && isSingleBlock && isPureDeletion && isExtendedEnabled(diffType)) {
422
502
  var followingNode = $safeInsertPos.nodeAfter;
423
503
  var headingLevel = (followingNode === null || followingNode === void 0 ? void 0 : followingNode.type.name) === 'heading' ? followingNode.attrs.level : undefined;
424
504
  if (isHeadingLevel(headingLevel)) {