@atlaskit/editor-plugin-show-diff 6.2.5 → 6.2.6

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 (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +1 -2
  3. package/dist/cjs/pm-plugins/decorations/colorSchemes/standard.js +19 -1
  4. package/dist/cjs/pm-plugins/decorations/colorSchemes/traditional.js +18 -2
  5. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +13 -10
  6. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +83 -14
  7. package/dist/cjs/pm-plugins/getScrollableDecorations.js +132 -0
  8. package/dist/cjs/pm-plugins/main.js +12 -25
  9. package/dist/cjs/pm-plugins/scrollToActiveDecoration.js +50 -9
  10. package/dist/cjs/showDiffPlugin.js +2 -1
  11. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +1 -2
  12. package/dist/es2019/pm-plugins/decorations/colorSchemes/standard.js +18 -0
  13. package/dist/es2019/pm-plugins/decorations/colorSchemes/traditional.js +17 -1
  14. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +13 -9
  15. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +80 -18
  16. package/dist/es2019/pm-plugins/getScrollableDecorations.js +117 -0
  17. package/dist/es2019/pm-plugins/main.js +11 -22
  18. package/dist/es2019/pm-plugins/scrollToActiveDecoration.js +50 -10
  19. package/dist/es2019/showDiffPlugin.js +3 -2
  20. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +1 -2
  21. package/dist/esm/pm-plugins/decorations/colorSchemes/standard.js +18 -0
  22. package/dist/esm/pm-plugins/decorations/colorSchemes/traditional.js +17 -1
  23. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +13 -10
  24. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +85 -16
  25. package/dist/esm/pm-plugins/getScrollableDecorations.js +124 -0
  26. package/dist/esm/pm-plugins/main.js +11 -24
  27. package/dist/esm/pm-plugins/scrollToActiveDecoration.js +50 -9
  28. package/dist/esm/showDiffPlugin.js +3 -2
  29. package/dist/types/pm-plugins/decorations/colorSchemes/standard.d.ts +4 -0
  30. package/dist/types/pm-plugins/decorations/colorSchemes/traditional.d.ts +3 -0
  31. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +5 -2
  32. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +2 -1
  33. package/dist/types/pm-plugins/getScrollableDecorations.d.ts +27 -0
  34. package/dist/types/pm-plugins/main.d.ts +0 -2
  35. package/dist/types/pm-plugins/scrollToActiveDecoration.d.ts +4 -2
  36. package/dist/types-ts4.5/pm-plugins/decorations/colorSchemes/standard.d.ts +4 -0
  37. package/dist/types-ts4.5/pm-plugins/decorations/colorSchemes/traditional.d.ts +3 -0
  38. package/dist/types-ts4.5/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +5 -2
  39. package/dist/types-ts4.5/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +2 -1
  40. package/dist/types-ts4.5/pm-plugins/getScrollableDecorations.d.ts +27 -0
  41. package/dist/types-ts4.5/pm-plugins/main.d.ts +0 -2
  42. package/dist/types-ts4.5/pm-plugins/scrollToActiveDecoration.d.ts +4 -2
  43. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { trackChangesMessages } from '@atlaskit/editor-common/messages';
3
3
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
- import { deletedBlockOutline, deletedBlockOutlineRounded, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleNewActive, deletedStyleQuoteNodeWithLozenge, editingStyle, editingStyleActive, editingStyleNode, addedCellOverlayStyle, deletedCellOverlayStyle } from '../colorSchemes/standard';
5
- import { deletedTraditionalBlockOutline, deletedTraditionalBlockOutlineRounded, deletedTraditionalContentStyle, deletedTraditionalStyleQuoteNode, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from '../colorSchemes/traditional';
4
+ import { deletedBlockOutline, deletedBlockOutlineActive, deletedBlockOutlineRounded, deletedBlockOutlineRoundedActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleNewActive, deletedStyleQuoteNodeWithLozenge, deletedStyleQuoteNodeWithLozengeActive, editingStyle, editingStyleActive, editingStyleNode, addedCellOverlayStyle, deletedCellOverlayStyle } from '../colorSchemes/standard';
5
+ import { deletedTraditionalBlockOutline, deletedTraditionalBlockOutlineActive, deletedTraditionalBlockOutlineRounded, deletedTraditionalBlockOutlineRoundedActive, deletedTraditionalContentStyle, deletedTraditionalContentStyleActive, deletedTraditionalStyleQuoteNode, deletedTraditionalStyleQuoteNodeActive, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from '../colorSchemes/traditional';
6
6
  var lozengeStyle = convertToInlineCss({
7
7
  display: 'inline-flex',
8
8
  boxSizing: 'border-box',
@@ -19,6 +19,38 @@ var lozengeStyle = convertToInlineCss({
19
19
  whiteSpace: 'nowrap',
20
20
  color: "var(--ds-text-warning-inverse, #292A2E)"
21
21
  });
22
+ var lozengeStyleActiveStandard = convertToInlineCss({
23
+ display: 'inline-flex',
24
+ boxSizing: 'border-box',
25
+ position: 'static',
26
+ blockSize: 'min-content',
27
+ borderRadius: "var(--ds-radius-small, 4px)",
28
+ overflow: 'hidden',
29
+ paddingInlineStart: "var(--ds-space-050, 4px)",
30
+ paddingInlineEnd: "var(--ds-space-050, 4px)",
31
+ backgroundColor: "var(--ds-background-accent-red-subtler-pressed, #FD9891)",
32
+ 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)",
33
+ fontWeight: "var(--ds-font-weight-bold, 653)",
34
+ textOverflow: 'ellipsis',
35
+ whiteSpace: 'nowrap',
36
+ color: "var(--ds-text-warning-inverse, #292A2E)"
37
+ });
38
+ var lozengeStyleActiveTraditional = convertToInlineCss({
39
+ display: 'inline-flex',
40
+ boxSizing: 'border-box',
41
+ position: 'static',
42
+ blockSize: 'min-content',
43
+ borderRadius: "var(--ds-radius-small, 4px)",
44
+ overflow: 'hidden',
45
+ paddingInlineStart: "var(--ds-space-050, 4px)",
46
+ paddingInlineEnd: "var(--ds-space-050, 4px)",
47
+ backgroundColor: "var(--ds-background-accent-red-subtler-pressed, #FD9891)",
48
+ 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)",
49
+ fontWeight: "var(--ds-font-weight-bold, 653)",
50
+ textOverflow: 'ellipsis',
51
+ whiteSpace: 'nowrap',
52
+ color: "var(--ds-text-warning-inverse, #292A2E)"
53
+ });
22
54
  var getChangedContentStyle = function getChangedContentStyle(colorScheme) {
23
55
  var isActive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
24
56
  var isInserted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
@@ -29,7 +61,7 @@ var getChangedContentStyle = function getChangedContentStyle(colorScheme) {
29
61
  return isActive ? editingStyleActive : editingStyle;
30
62
  }
31
63
  if (colorScheme === 'traditional') {
32
- return deletedTraditionalContentStyle;
64
+ return isActive ? deletedTraditionalContentStyleActive : deletedTraditionalContentStyle;
33
65
  }
34
66
  if (isActive) {
35
67
  return expValEquals('platform_editor_enghealth_a11y_jan_fixes', 'isEnabled', true) ? deletedContentStyleNewActive : deletedContentStyleActive;
@@ -38,6 +70,7 @@ var getChangedContentStyle = function getChangedContentStyle(colorScheme) {
38
70
  };
39
71
  var getChangedNodeStyle = function getChangedNodeStyle(nodeName, colorScheme) {
40
72
  var isInserted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
73
+ var isActive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
41
74
  var isTraditional = colorScheme === 'traditional';
42
75
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && isInserted) {
43
76
  if (shouldApplyStylesDirectly(nodeName)) {
@@ -50,13 +83,22 @@ var getChangedNodeStyle = function getChangedNodeStyle(nodeName, colorScheme) {
50
83
  }
51
84
  switch (nodeName) {
52
85
  case 'blockquote':
53
- return isTraditional ? deletedTraditionalStyleQuoteNode : deletedStyleQuoteNodeWithLozenge;
86
+ if (isTraditional) {
87
+ return isActive ? deletedTraditionalStyleQuoteNodeActive : deletedTraditionalStyleQuoteNode;
88
+ }
89
+ return isActive ? deletedStyleQuoteNodeWithLozengeActive : deletedStyleQuoteNodeWithLozenge;
54
90
  case 'expand':
55
91
  case 'decisionList':
56
- return isTraditional ? deletedTraditionalBlockOutline : deletedBlockOutline;
92
+ if (isTraditional) {
93
+ return isActive ? deletedTraditionalBlockOutlineActive : deletedTraditionalBlockOutline;
94
+ }
95
+ return isActive ? deletedBlockOutlineActive : deletedBlockOutline;
57
96
  case 'panel':
58
97
  case 'codeBlock':
59
- return isTraditional ? deletedTraditionalBlockOutlineRounded : deletedBlockOutlineRounded;
98
+ if (isTraditional) {
99
+ return isActive ? deletedTraditionalBlockOutlineRoundedActive : deletedTraditionalBlockOutlineRounded;
100
+ }
101
+ return isActive ? deletedBlockOutlineRoundedActive : deletedBlockOutlineRounded;
60
102
  default:
61
103
  return undefined;
62
104
  }
@@ -109,6 +151,8 @@ var applyCellOverlayStyles = function applyCellOverlayStyles(_ref) {
109
151
  * Creates a "Removed" lozenge to be displayed at the top right corner of deleted block nodes
110
152
  */
111
153
  var createRemovedLozenge = function createRemovedLozenge(intl) {
154
+ var isActive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
155
+ var colorScheme = arguments.length > 2 ? arguments[2] : undefined;
112
156
  var container = document.createElement('span');
113
157
  var containerStyle = convertToInlineCss({
114
158
  position: 'absolute',
@@ -123,7 +167,8 @@ var createRemovedLozenge = function createRemovedLozenge(intl) {
123
167
 
124
168
  // Create vanilla HTML lozenge element with Atlaskit Lozenge styling (visual refresh)
125
169
  var lozengeElement = document.createElement('span');
126
- lozengeElement.setAttribute('style', lozengeStyle);
170
+ var lozengeInnerStyle = isActive && colorScheme === 'traditional' ? lozengeStyleActiveTraditional : isActive ? lozengeStyleActiveStandard : lozengeStyle;
171
+ lozengeElement.setAttribute('style', lozengeInnerStyle);
127
172
  lozengeElement.textContent = intl.formatMessage(trackChangesMessages.removed).toUpperCase();
128
173
  container.appendChild(lozengeElement);
129
174
  return container;
@@ -150,10 +195,11 @@ var applyStylesToElement = function applyStylesToElement(_ref2) {
150
195
  var element = _ref2.element,
151
196
  targetNode = _ref2.targetNode,
152
197
  colorScheme = _ref2.colorScheme,
198
+ isActive = _ref2.isActive,
153
199
  isInserted = _ref2.isInserted;
154
200
  var currentStyle = element.getAttribute('style') || '';
155
- var contentStyle = getChangedContentStyle(colorScheme, false, isInserted);
156
- var nodeSpecificStyle = getChangedNodeStyle(targetNode.type.name, colorScheme, isInserted) || '';
201
+ var contentStyle = getChangedContentStyle(colorScheme, isActive, isInserted);
202
+ var nodeSpecificStyle = getChangedNodeStyle(targetNode.type.name, colorScheme, isInserted, isActive) || '';
157
203
  element.setAttribute('style', "".concat(currentStyle).concat(contentStyle).concat(nodeSpecificStyle));
158
204
  };
159
205
 
@@ -164,10 +210,11 @@ var createBlockNodeContentWrapper = function createBlockNodeContentWrapper(_ref3
164
210
  var nodeView = _ref3.nodeView,
165
211
  targetNode = _ref3.targetNode,
166
212
  colorScheme = _ref3.colorScheme,
213
+ isActive = _ref3.isActive,
167
214
  isInserted = _ref3.isInserted;
168
215
  var contentWrapper = document.createElement('div');
169
- var nodeStyle = getChangedNodeStyle(targetNode.type.name, colorScheme, isInserted);
170
- contentWrapper.setAttribute('style', "".concat(getChangedContentStyle(colorScheme, false, isInserted)).concat(nodeStyle || ''));
216
+ var nodeStyle = getChangedNodeStyle(targetNode.type.name, colorScheme, isInserted, isActive);
217
+ contentWrapper.setAttribute('style', "".concat(getChangedContentStyle(colorScheme, isActive, isInserted)).concat(nodeStyle || ''));
171
218
  contentWrapper.append(nodeView);
172
219
  return contentWrapper;
173
220
  };
@@ -183,7 +230,9 @@ var handleEmbedCardWithLozenge = function handleEmbedCardWithLozenge(_ref4) {
183
230
  nodeView = _ref4.nodeView,
184
231
  targetNode = _ref4.targetNode,
185
232
  lozenge = _ref4.lozenge,
186
- colorScheme = _ref4.colorScheme;
233
+ colorScheme = _ref4.colorScheme,
234
+ _ref4$isActive = _ref4.isActive,
235
+ isActive = _ref4$isActive === void 0 ? false : _ref4$isActive;
187
236
  if (targetNode.type.name !== 'embedCard' || !(nodeView instanceof HTMLElement)) {
188
237
  return false;
189
238
  }
@@ -206,6 +255,9 @@ var handleEmbedCardWithLozenge = function handleEmbedCardWithLozenge(_ref4) {
206
255
  if (shouldAddShowDiffDeletedNodeClass(targetNode.type.name)) {
207
256
  var showDiffDeletedNodeClass = colorScheme === 'traditional' ? 'show-diff-deleted-node-traditional' : 'show-diff-deleted-node';
208
257
  nodeView.classList.add(showDiffDeletedNodeClass);
258
+ if (isActive) {
259
+ nodeView.classList.add('show-diff-deleted-active');
260
+ }
209
261
  }
210
262
  dom.append(nodeView);
211
263
  return true;
@@ -220,7 +272,9 @@ var handleMediaSingleWithLozenge = function handleMediaSingleWithLozenge(_ref5)
220
272
  nodeView = _ref5.nodeView,
221
273
  targetNode = _ref5.targetNode,
222
274
  lozenge = _ref5.lozenge,
223
- colorScheme = _ref5.colorScheme;
275
+ colorScheme = _ref5.colorScheme,
276
+ _ref5$isActive = _ref5.isActive,
277
+ isActive = _ref5$isActive === void 0 ? false : _ref5$isActive;
224
278
  if (targetNode.type.name !== 'mediaSingle' || !(nodeView instanceof HTMLElement)) {
225
279
  return false;
226
280
  }
@@ -241,6 +295,9 @@ var handleMediaSingleWithLozenge = function handleMediaSingleWithLozenge(_ref5)
241
295
  if (shouldAddShowDiffDeletedNodeClass(targetNode.type.name)) {
242
296
  var showDiffDeletedNodeClass = colorScheme === 'traditional' ? 'show-diff-deleted-node-traditional' : 'show-diff-deleted-node';
243
297
  nodeView.classList.add(showDiffDeletedNodeClass);
298
+ if (isActive) {
299
+ nodeView.classList.add('show-diff-deleted-active');
300
+ }
244
301
  }
245
302
  dom.append(nodeView);
246
303
  return true;
@@ -255,17 +312,20 @@ var wrapBlockNode = function wrapBlockNode(_ref6) {
255
312
  targetNode = _ref6.targetNode,
256
313
  colorScheme = _ref6.colorScheme,
257
314
  intl = _ref6.intl,
315
+ _ref6$isActive = _ref6.isActive,
316
+ isActive = _ref6$isActive === void 0 ? false : _ref6$isActive,
258
317
  _ref6$isInserted = _ref6.isInserted,
259
318
  isInserted = _ref6$isInserted === void 0 ? false : _ref6$isInserted;
260
319
  var blockWrapper = createBlockNodeWrapper();
261
320
  if (shouldShowRemovedLozenge(targetNode.type.name) && (!expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || !isInserted)) {
262
- var lozenge = createRemovedLozenge(intl);
321
+ var lozenge = createRemovedLozenge(intl, isActive, colorScheme);
263
322
  if (handleEmbedCardWithLozenge({
264
323
  dom: dom,
265
324
  nodeView: nodeView,
266
325
  targetNode: targetNode,
267
326
  lozenge: lozenge,
268
- colorScheme: colorScheme
327
+ colorScheme: colorScheme,
328
+ isActive: isActive
269
329
  })) {
270
330
  return;
271
331
  }
@@ -274,7 +334,8 @@ var wrapBlockNode = function wrapBlockNode(_ref6) {
274
334
  nodeView: nodeView,
275
335
  targetNode: targetNode,
276
336
  lozenge: lozenge,
277
- colorScheme: colorScheme
337
+ colorScheme: colorScheme,
338
+ isActive: isActive
278
339
  })) {
279
340
  return;
280
341
  }
@@ -284,12 +345,16 @@ var wrapBlockNode = function wrapBlockNode(_ref6) {
284
345
  nodeView: nodeView,
285
346
  targetNode: targetNode,
286
347
  colorScheme: colorScheme,
348
+ isActive: isActive,
287
349
  isInserted: isInserted
288
350
  });
289
351
  blockWrapper.append(contentWrapper);
290
352
  if (nodeView instanceof HTMLElement && shouldAddShowDiffDeletedNodeClass(targetNode.type.name)) {
291
353
  var showDiffDeletedNodeClass = colorScheme === 'traditional' ? 'show-diff-deleted-node-traditional' : 'show-diff-deleted-node';
292
354
  nodeView.classList.add(showDiffDeletedNodeClass);
355
+ if (isActive) {
356
+ nodeView.classList.add('show-diff-deleted-active');
357
+ }
293
358
  }
294
359
  dom.append(blockWrapper);
295
360
  };
@@ -305,6 +370,8 @@ export var wrapBlockNodeView = function wrapBlockNodeView(_ref7) {
305
370
  targetNode = _ref7.targetNode,
306
371
  colorScheme = _ref7.colorScheme,
307
372
  intl = _ref7.intl,
373
+ _ref7$isActive = _ref7.isActive,
374
+ isActive = _ref7$isActive === void 0 ? false : _ref7$isActive,
308
375
  _ref7$isInserted = _ref7.isInserted,
309
376
  isInserted = _ref7$isInserted === void 0 ? false : _ref7$isInserted;
310
377
  if (shouldApplyStylesDirectly(targetNode.type.name) && nodeView instanceof HTMLElement) {
@@ -313,6 +380,7 @@ export var wrapBlockNodeView = function wrapBlockNodeView(_ref7) {
313
380
  element: nodeView,
314
381
  targetNode: targetNode,
315
382
  colorScheme: colorScheme,
383
+ isActive: isActive,
316
384
  isInserted: isInserted
317
385
  });
318
386
  dom.append(nodeView);
@@ -331,6 +399,7 @@ export var wrapBlockNodeView = function wrapBlockNodeView(_ref7) {
331
399
  targetNode: targetNode,
332
400
  colorScheme: colorScheme,
333
401
  intl: intl,
402
+ isActive: isActive,
334
403
  isInserted: isInserted
335
404
  });
336
405
  }
@@ -0,0 +1,124 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ /**
3
+ * True if `fragment` contains at least one inline node (text, hardBreak, emoji, mention, etc.).
4
+ * Block-only subtrees (e.g. empty paragraphs, block cards with no inline children) return false.
5
+ */
6
+ function fragmentContainsInlineContent(fragment) {
7
+ for (var i = 0; i < fragment.childCount; i++) {
8
+ var node = fragment.child(i);
9
+ if (node.isInline) {
10
+ return true;
11
+ }
12
+ if (node.content.size > 0 && fragmentContainsInlineContent(node.content)) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+
19
+ /**
20
+ * Returns true when an inline decoration's [from, to) range can actually show in the document:
21
+ * positions are valid, and the slice contains at least one inline node ProseMirror would paint
22
+ * (not only empty block wrappers or block-only structure).
23
+ */
24
+ export function isInlineDiffDecorationRenderableInDoc(doc, from, to) {
25
+ try {
26
+ var slice = doc.slice(from, to);
27
+ return fragmentContainsInlineContent(slice.content);
28
+ } catch (_unused) {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Checks if range1 is fully contained within range2
35
+ */
36
+ function isRangeFullyInside(range1Start, range1End, range2Start, range2End) {
37
+ return range2Start <= range1Start && range1End <= range2End;
38
+ }
39
+
40
+ /**
41
+ * Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations
42
+ * and applying various rules for diff visualization.
43
+ *
44
+ * Rules:
45
+ * 1. Only includes diff-inline, diff-widget-* and diff-block decorations
46
+ * 2. Excludes listItem diff-block decorations (never scrollable)
47
+ * 3. Deduplicates diff-block decorations with same from, to and nodeName
48
+ * 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
49
+ * (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
50
+ * 5. Excludes diff-inline decorations that are fully contained within a diff-block
51
+ * 6. Excludes diff-block decorations that are fully contained within a diff-inline
52
+ * 7. Results are sorted by from position, then by to position
53
+ *
54
+ * @param set - The DecorationSet to extract scrollable decorations from
55
+ * @param doc - Current document; when set, diff-inline ranges are validated against this doc
56
+ * @returns Array of scrollable decorations, sorted and deduplicated
57
+ */
58
+ export var getScrollableDecorations = function getScrollableDecorations(set, doc) {
59
+ if (!set) {
60
+ return [];
61
+ }
62
+ var seenBlockKeys = new Set();
63
+ var allDecorations = set.find(undefined, undefined, function (spec) {
64
+ var _spec$key;
65
+ return spec.key === 'diff-inline' || ((_spec$key = spec.key) === null || _spec$key === void 0 ? void 0 : _spec$key.startsWith('diff-widget')) || spec.key === 'diff-block';
66
+ });
67
+
68
+ // First pass: filter out listItem blocks and deduplicates blocks
69
+ var filtered = allDecorations.filter(function (dec) {
70
+ var _dec$spec, _dec$spec$nodeName, _dec$spec3;
71
+ if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.key) === 'diff-block') {
72
+ var _dec$spec2;
73
+ // Skip listItem blocks as they are not scrollable
74
+ if (((_dec$spec2 = dec.spec) === null || _dec$spec2 === void 0 ? void 0 : _dec$spec2.nodeName) === 'listItem') return false;
75
+ }
76
+ var key = "".concat(dec.from, "-").concat(dec.to, "-").concat((_dec$spec$nodeName = (_dec$spec3 = dec.spec) === null || _dec$spec3 === void 0 ? void 0 : _dec$spec3.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : '');
77
+ // Skip blocks that have already been seen
78
+ if (seenBlockKeys.has(key)) return false;
79
+ seenBlockKeys.add(key);
80
+ return true;
81
+ });
82
+
83
+ // Separate decorations by type for easier processing
84
+ var blocks = filtered.filter(function (d) {
85
+ var _d$spec;
86
+ return ((_d$spec = d.spec) === null || _d$spec === void 0 ? void 0 : _d$spec.key) === 'diff-block';
87
+ });
88
+ var rawInlines = filtered.filter(function (d) {
89
+ var _d$spec2;
90
+ return ((_d$spec2 = d.spec) === null || _d$spec2 === void 0 ? void 0 : _d$spec2.key) === 'diff-inline';
91
+ });
92
+ var inlines = doc !== undefined ? rawInlines.filter(function (d) {
93
+ return isInlineDiffDecorationRenderableInDoc(doc, d.from, d.to);
94
+ }) : rawInlines;
95
+ var widgets = filtered.filter(function (d) {
96
+ var _d$spec3;
97
+ return (_d$spec3 = d.spec) === null || _d$spec3 === void 0 || (_d$spec3 = _d$spec3.key) === null || _d$spec3 === void 0 ? void 0 : _d$spec3.startsWith('diff-widget');
98
+ });
99
+
100
+ // Second pass: exclude overlapping decorations
101
+ // Rules:
102
+ // - If an inline is fully inside a block, exclude the block (inline takes priority)
103
+ // - If a block is fully inside an inline, exclude the block (inline takes priority)
104
+ var nonOverlappingBlocks = blocks.filter(function (block) {
105
+ // Exclude block if:
106
+ // 1. It's fully contained within any inline, OR
107
+ // 2. It fully contains any inline
108
+ return !inlines.some(function (inline) {
109
+ return isRangeFullyInside(block.from, block.to, inline.from, inline.to) ||
110
+ // block inside inline
111
+ isRangeFullyInside(inline.from, inline.to, block.from, block.to);
112
+ } // inline inside block
113
+ );
114
+ });
115
+
116
+ // Combine all non-overlapping decorations
117
+ var result = [].concat(_toConsumableArray(nonOverlappingBlocks), _toConsumableArray(inlines), _toConsumableArray(widgets));
118
+
119
+ // Sort by from position, then by to position
120
+ result.sort(function (a, b) {
121
+ return a.from === b.from ? a.to - b.to : a.from - b.from;
122
+ });
123
+ return result;
124
+ };
@@ -10,31 +10,10 @@ import { fg } from '@atlaskit/platform-feature-flags';
10
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
11
11
  import { calculateDiffDecorations } from './calculateDiff/calculateDiffDecorations';
12
12
  import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
13
+ import { getScrollableDecorations } from './getScrollableDecorations';
13
14
  import { NodeViewSerializer } from './NodeViewSerializer';
14
15
  import { scrollToActiveDecoration } from './scrollToActiveDecoration';
15
16
  export var showDiffPluginKey = new PluginKey('showDiffPlugin');
16
- export var getScrollableDecorations = function getScrollableDecorations(set) {
17
- var _set$find;
18
- var seenBlockKeys = new Set();
19
- return ((_set$find = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, function (spec) {
20
- var _spec$key;
21
- return spec.key === 'diff-inline' || ((_spec$key = spec.key) === null || _spec$key === void 0 ? void 0 : _spec$key.startsWith('diff-widget')) || spec.key === 'diff-block';
22
- })) !== null && _set$find !== void 0 ? _set$find : []).filter(function (dec) {
23
- var _dec$spec;
24
- if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.key) === 'diff-block') {
25
- var _dec$spec2, _dec$spec$nodeName, _dec$spec3;
26
- // Skip listItem blocks as they are not scrollable
27
- if (((_dec$spec2 = dec.spec) === null || _dec$spec2 === void 0 ? void 0 : _dec$spec2.nodeName) === 'listItem') return false;
28
- var key = "".concat(dec.from, "-").concat(dec.to, "-").concat((_dec$spec$nodeName = (_dec$spec3 = dec.spec) === null || _dec$spec3 === void 0 ? void 0 : _dec$spec3.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : '');
29
- // Skip blocks that have already been seen
30
- if (seenBlockKeys.has(key)) return false;
31
- seenBlockKeys.add(key);
32
- }
33
- return true;
34
- }).sort(function (a, b) {
35
- return a.from === b.from ? a.to - b.to : a.from - b.from;
36
- });
37
- };
38
17
  export var createPlugin = function createPlugin(config, getIntl, api) {
39
18
  if (fg('platform_editor_show_diff_equality_fallback')) {
40
19
  enforceCustomStepRegisters();
@@ -97,7 +76,7 @@ export var createPlugin = function createPlugin(config, getIntl, api) {
97
76
  } : {});
98
77
  } else if (((meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_NEXT' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_PREVIOUS') && fg('platform_editor_show_diff_scroll_navigation')) {
99
78
  // Update the active index in plugin state and recalculate decorations
100
- var _decorations = getScrollableDecorations(currentPluginState.decorations);
79
+ var _decorations = getScrollableDecorations(currentPluginState.decorations, newState.doc);
101
80
  if (_decorations.length > 0) {
102
81
  var _currentPluginState$a;
103
82
  // Initialize to -1 if undefined so that the first "next" scroll takes us to index 0 (first change).
@@ -149,6 +128,7 @@ export var createPlugin = function createPlugin(config, getIntl, api) {
149
128
  setNodeViewSerializer(editorView);
150
129
  var isFirst = true;
151
130
  var previousActiveIndex;
131
+ var cancelPendingScrollToDecoration = null;
152
132
  return {
153
133
  update: function update(view) {
154
134
  // If we're using configuration to show diffs we initialise here once we setup the editor view
@@ -169,9 +149,16 @@ export var createPlugin = function createPlugin(config, getIntl, api) {
169
149
  var activeIndexChanged = (pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && pluginState.activeIndex !== previousActiveIndex;
170
150
  previousActiveIndex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex;
171
151
  if ((pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && activeIndexChanged) {
172
- scrollToActiveDecoration(view, getScrollableDecorations(pluginState.decorations), pluginState.activeIndex);
152
+ var _cancelPendingScrollT;
153
+ (_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 || _cancelPendingScrollT();
154
+ cancelPendingScrollToDecoration = scrollToActiveDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc), pluginState.activeIndex);
173
155
  }
174
156
  }
157
+ },
158
+ destroy: function destroy() {
159
+ var _cancelPendingScrollT2;
160
+ (_cancelPendingScrollT2 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT2 === void 0 || _cancelPendingScrollT2();
161
+ cancelPendingScrollToDecoration = null;
175
162
  }
176
163
  };
177
164
  },
@@ -1,25 +1,66 @@
1
+ /**
2
+ * Extra space above the scrolled-to element so it does not sit flush under the
3
+ * viewport edge (helps with sticky table headers, toolbars, etc.).
4
+ *
5
+ * Implemented with `scroll-margin-top` so we still use the browser’s native
6
+ * `scrollIntoView`, which scrolls every relevant scrollport (nested containers
7
+ * and the window). A single manual `scrollTop` on one ancestor often misses
8
+ * outer scroll or mis-identifies the active scroll container.
9
+ */
10
+ var SCROLL_TOP_MARGIN_PX = 100;
11
+
1
12
  /**
2
13
  * Scrolls to the current position/selection of the document. It does the same as scrollIntoView()
3
14
  * but without requiring the focus on the editor, thus it can be called at any time.
4
15
  */
5
- function scrollToSelection(view, position) {
6
- var _view$domAtPos = view.domAtPos(position),
7
- node = _view$domAtPos.node;
8
- if (node instanceof Element) {
9
- node.scrollIntoView({
16
+ function scrollToSelection(node) {
17
+ var element = node instanceof Element ? node : (node === null || node === void 0 ? void 0 : node.parentElement) instanceof Element ? node.parentElement : null;
18
+ if (!(element instanceof HTMLElement)) {
19
+ return;
20
+ }
21
+
22
+ // scroll-margin is included in scroll-into-view math; it does not change layout.
23
+ var previousScrollMarginTop = element.style.scrollMarginTop;
24
+ element.style.scrollMarginTop = "".concat(SCROLL_TOP_MARGIN_PX, "px");
25
+ try {
26
+ element.scrollIntoView({
10
27
  behavior: 'smooth',
11
- block: 'center'
28
+ block: 'start'
12
29
  });
30
+ } finally {
31
+ element.style.scrollMarginTop = previousScrollMarginTop;
13
32
  }
14
33
  }
15
34
 
16
35
  /**
17
- * Scrolls the editor view to the decoration at the given index.
36
+ * Schedules scrolling to the decoration at the given index after the next frame.
37
+ *
38
+ * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
18
39
  */
19
40
  export var scrollToActiveDecoration = function scrollToActiveDecoration(view, decorations, activeIndex) {
20
41
  var decoration = decorations[activeIndex];
21
42
  if (!decoration) {
22
- return;
43
+ return function () {};
23
44
  }
24
- scrollToSelection(view, decoration === null || decoration === void 0 ? void 0 : decoration.from);
45
+ var rafId = requestAnimationFrame(function () {
46
+ var _decoration$spec;
47
+ rafId = null;
48
+ if (((_decoration$spec = decoration.spec) === null || _decoration$spec === void 0 ? void 0 : _decoration$spec.key) === 'diff-widget-active') {
49
+ var _decoration$type;
50
+ // @ts-expect-error - decoration.type is not typed public API
51
+ var widgetDom = decoration === null || decoration === void 0 || (_decoration$type = decoration.type) === null || _decoration$type === void 0 ? void 0 : _decoration$type.toDOM;
52
+ scrollToSelection(widgetDom);
53
+ } else {
54
+ var _view$domAtPos;
55
+ var targetNode = view.nodeDOM(decoration === null || decoration === void 0 ? void 0 : decoration.from);
56
+ var node = targetNode instanceof Element ? targetNode : (_view$domAtPos = view.domAtPos(decoration === null || decoration === void 0 ? void 0 : decoration.from)) === null || _view$domAtPos === void 0 ? void 0 : _view$domAtPos.node;
57
+ scrollToSelection(node);
58
+ }
59
+ });
60
+ return function () {
61
+ if (rafId !== null) {
62
+ cancelAnimationFrame(rafId);
63
+ rafId = null;
64
+ }
65
+ };
25
66
  };
@@ -2,7 +2,8 @@ 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
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
- import { createPlugin, showDiffPluginKey, getScrollableDecorations } from './pm-plugins/main';
5
+ import { getScrollableDecorations } from './pm-plugins/getScrollableDecorations';
6
+ import { createPlugin, showDiffPluginKey } from './pm-plugins/main';
6
7
  export var showDiffPlugin = function showDiffPlugin(_ref) {
7
8
  var api = _ref.api,
8
9
  config = _ref.config;
@@ -55,7 +56,7 @@ export var showDiffPlugin = function showDiffPlugin(_ref) {
55
56
  };
56
57
  }
57
58
  var pluginState = showDiffPluginKey.getState(editorState);
58
- var decorationCount = fg('platform_editor_show_diff_scroll_navigation') ? getScrollableDecorations(pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations) : (pluginState === null || pluginState === void 0 || (_pluginState$decorati = pluginState.decorations) === null || _pluginState$decorati === void 0 ? void 0 : _pluginState$decorati.find()) || [];
59
+ var decorationCount = fg('platform_editor_show_diff_scroll_navigation') ? getScrollableDecorations(pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations, editorState.doc) : (pluginState === null || pluginState === void 0 || (_pluginState$decorati = pluginState.decorations) === null || _pluginState$decorati === void 0 ? void 0 : _pluginState$decorati.find()) || [];
59
60
  return {
60
61
  isDisplayingChanges: decorationCount.length > 0,
61
62
  activeIndex: pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex,
@@ -7,8 +7,12 @@ export declare const deletedContentStyleNewActive: string;
7
7
  export declare const deletedContentStyleUnbounded: string;
8
8
  export declare const deletedStyleQuoteNode: string;
9
9
  export declare const deletedStyleQuoteNodeWithLozenge: string;
10
+ /** Stronger outline when this deleted block decoration is the active scroll target */
11
+ export declare const deletedStyleQuoteNodeWithLozengeActive: string;
10
12
  export declare const deletedBlockOutline: string;
13
+ export declare const deletedBlockOutlineActive: string;
11
14
  export declare const deletedBlockOutlineRounded: string;
15
+ export declare const deletedBlockOutlineRoundedActive: string;
12
16
  export declare const deletedRowStyle: string;
13
17
  export declare const editingStyleQuoteNode: string;
14
18
  export declare const editingStyleRuleNode: string;
@@ -7,8 +7,11 @@ export declare const deletedTraditionalContentStyleUnbounded: string;
7
7
  /** Emphasised (pressed) strikethrough line for traditional when active */
8
8
  export declare const deletedTraditionalContentStyleUnboundedActive: string;
9
9
  export declare const deletedTraditionalStyleQuoteNode: string;
10
+ export declare const deletedTraditionalStyleQuoteNodeActive: string;
10
11
  export declare const deletedTraditionalBlockOutline: string;
12
+ export declare const deletedTraditionalBlockOutlineActive: string;
11
13
  export declare const deletedTraditionalBlockOutlineRounded: string;
14
+ export declare const deletedTraditionalBlockOutlineRoundedActive: string;
12
15
  export declare const deletedTraditionalRowStyle: string;
13
16
  export declare const traditionalStyleQuoteNode: string;
14
17
  export declare const traditionalStyleQuoteNodeActive: string;
@@ -8,12 +8,15 @@ import type { NodeViewSerializer } from '../NodeViewSerializer';
8
8
  * This function is used to create a decoration widget to show content
9
9
  * that is not in the current document.
10
10
  */
11
- export declare const createNodeChangedDecorationWidget: ({ change, doc, nodeViewSerializer, colorScheme, newDoc, intl, isActive, isInserted, }: {
11
+ export declare const createNodeChangedDecorationWidget: ({ change, doc, nodeViewSerializer, colorScheme, newDoc, intl, activeIndexPos, isInserted, }: {
12
+ activeIndexPos?: {
13
+ from: number;
14
+ to: number;
15
+ };
12
16
  change: Pick<Change, "fromA" | "toA" | "fromB" | "deleted" | "toB">;
13
17
  colorScheme?: ColorScheme;
14
18
  doc: PMNode;
15
19
  intl: IntlShape;
16
- isActive?: boolean;
17
20
  isInserted?: boolean;
18
21
  newDoc: PMNode;
19
22
  nodeViewSerializer: NodeViewSerializer;
@@ -6,10 +6,11 @@ import type { ColorScheme } from '../../../showDiffPluginType';
6
6
  * For heading nodes, applies styles directly to preserve natural margins.
7
7
  * For other block nodes, uses wrapper approach with optional lozenge.
8
8
  */
9
- export declare const wrapBlockNodeView: ({ dom, nodeView, targetNode, colorScheme, intl, isInserted, }: {
9
+ export declare const wrapBlockNodeView: ({ dom, nodeView, targetNode, colorScheme, intl, isActive, isInserted, }: {
10
10
  colorScheme?: ColorScheme;
11
11
  dom: HTMLElement;
12
12
  intl: IntlShape;
13
+ isActive?: boolean;
13
14
  isInserted: boolean;
14
15
  nodeView: Node;
15
16
  targetNode: PMNode;
@@ -0,0 +1,27 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ import type { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
+ /**
4
+ * Returns true when an inline decoration's [from, to) range can actually show in the document:
5
+ * positions are valid, and the slice contains at least one inline node ProseMirror would paint
6
+ * (not only empty block wrappers or block-only structure).
7
+ */
8
+ export declare function isInlineDiffDecorationRenderableInDoc(doc: PMNode, from: number, to: number): boolean;
9
+ /**
10
+ * Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations
11
+ * and applying various rules for diff visualization.
12
+ *
13
+ * Rules:
14
+ * 1. Only includes diff-inline, diff-widget-* and diff-block decorations
15
+ * 2. Excludes listItem diff-block decorations (never scrollable)
16
+ * 3. Deduplicates diff-block decorations with same from, to and nodeName
17
+ * 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
18
+ * (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
19
+ * 5. Excludes diff-inline decorations that are fully contained within a diff-block
20
+ * 6. Excludes diff-block decorations that are fully contained within a diff-inline
21
+ * 7. Results are sorted by from position, then by to position
22
+ *
23
+ * @param set - The DecorationSet to extract scrollable decorations from
24
+ * @param doc - Current document; when set, diff-inline ranges are validated against this doc
25
+ * @returns Array of scrollable decorations, sorted and deduplicated
26
+ */
27
+ export declare const getScrollableDecorations: (set: DecorationSet | undefined, doc?: PMNode) => Decoration[];
@@ -4,7 +4,6 @@ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
4
4
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
5
5
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
6
6
  import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
7
- import type { Decoration } from '@atlaskit/editor-prosemirror/view';
8
7
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
9
8
  import { type DiffParams, type DiffType, type ShowDiffPlugin } from '../showDiffPluginType';
10
9
  export declare const showDiffPluginKey: PluginKey<ShowDiffPluginState>;
@@ -21,5 +20,4 @@ export type ShowDiffPluginState = {
21
20
  originalDoc: PMNode | undefined;
22
21
  steps: ProseMirrorStep[];
23
22
  };
24
- export declare const getScrollableDecorations: (set: DecorationSet | undefined) => Decoration[];
25
23
  export declare const createPlugin: (config: DiffParams | undefined, getIntl: () => IntlShape, api: ExtractInjectionAPI<ShowDiffPlugin> | undefined) => SafePlugin<ShowDiffPluginState>;
@@ -1,5 +1,7 @@
1
1
  import type { EditorView, Decoration } from '@atlaskit/editor-prosemirror/view';
2
2
  /**
3
- * Scrolls the editor view to the decoration at the given index.
3
+ * Schedules scrolling to the decoration at the given index after the next frame.
4
+ *
5
+ * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
4
6
  */
5
- export declare const scrollToActiveDecoration: (view: EditorView, decorations: Decoration[], activeIndex: number) => void;
7
+ export declare const scrollToActiveDecoration: (view: EditorView, decorations: Decoration[], activeIndex: number) => (() => void);