@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
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.extractContributorTags = void 0;
8
- var _toArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toArray"));
9
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _toArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toArray"));
10
10
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
11
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
12
12
  var _fg = require("@atlaskit/platform-feature-flags/fg");
@@ -57,6 +57,14 @@ var isSameBlockChange = function isSameBlockChange(inner, block) {
57
57
  return inner.spec.attributionKey === block.spec.attributionKey && (block.from <= inner.from && inner.to <= block.to || inner.from <= block.from && block.to <= inner.to);
58
58
  };
59
59
 
60
+ /**
61
+ * Whether `outer` is the box `inner` sits in, for one contributor. Nested blocks they inserted read
62
+ * as one change, so only the outermost keeps a tag (EDITOR-8932).
63
+ */
64
+ var containsBlock = function containsBlock(outer, inner) {
65
+ return outer.spec.attributionKey === inner.spec.attributionKey && outer.from <= inner.from && inner.to <= outer.to && outer.to - outer.from > inner.to - inner.from;
66
+ };
67
+
60
68
  /**
61
69
  * Which of two changes in the same range leads, and so is the one a tag captions. Position first,
62
70
  * then `side`, since deleted content shares its `from` with the content that replaced it and paints
@@ -77,6 +85,19 @@ var containedBy = function containedBy(targets, _ref2) {
77
85
  }).sort(byLeadingPosition);
78
86
  };
79
87
 
88
+ /**
89
+ * The stop a target is stepped to by: the narrowest one that wholly covers it. A stop's range is
90
+ * the union of the decorations grouped into it, so one inserted run's inline highlight stretches
91
+ * its stop over every block the run spans — including blocks that are a stop of their own. Picking
92
+ * the narrowest keeps each of those blocks in the stop that actually reaches it, rather than
93
+ * letting the widest stop claim them all and fold away every tag but the first (EDITOR-8932).
94
+ */
95
+ var owningStop = function owningStop(target, stops) {
96
+ return stops.reduce(function (narrowest, stop) {
97
+ return stop.from <= target.decoration.from && target.decoration.to <= stop.to && (narrowest === undefined || stop.to - stop.from < narrowest.to - narrowest.from) ? stop : narrowest;
98
+ }, undefined);
99
+ };
100
+
80
101
  /**
81
102
  * The one tag a navigation step reveals. `spec.isActive` is no use here: it covers everything the
82
103
  * group's union range touches, so several tags revealed over each other (EDITOR-8971). Hence
@@ -145,6 +166,10 @@ var extractContributorTags = exports.extractContributorTags = function extractCo
145
166
  });
146
167
  var linkedDiffIds = new Map();
147
168
  var folded = new Set();
169
+
170
+ /**
171
+ * A folded target keeps no tag of its own — it becomes a hover target of the host instead.
172
+ */
148
173
  var fold = function fold(host, target) {
149
174
  var _linkedDiffIds$get, _linkedDiffIds$get2;
150
175
  folded.add(target);
@@ -202,58 +227,113 @@ var extractContributorTags = exports.extractContributorTags = function extractCo
202
227
  if (_ret === 0) continue;
203
228
  }
204
229
 
230
+ // A nested block folds into the outermost one of the same contributor, so nesting adds no tags.
231
+ if ((0, _fg.fg)('confluence_ncs_step_diffing_version_history')) {
232
+ var blocksByContributor = new Map();
233
+ var _iterator2 = _createForOfIteratorHelper(blockTargets),
234
+ _step2;
235
+ try {
236
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
237
+ var _target$decoration$sp, _blocksByContributor$;
238
+ var target = _step2.value;
239
+ var key = (_target$decoration$sp = target.decoration.spec.attributionKey) !== null && _target$decoration$sp !== void 0 ? _target$decoration$sp : '';
240
+ blocksByContributor.set(key, [].concat((0, _toConsumableArray2.default)((_blocksByContributor$ = blocksByContributor.get(key)) !== null && _blocksByContributor$ !== void 0 ? _blocksByContributor$ : []), [target]));
241
+ }
242
+ } catch (err) {
243
+ _iterator2.e(err);
244
+ } finally {
245
+ _iterator2.f();
246
+ }
247
+ var _iterator3 = _createForOfIteratorHelper(blocksByContributor.values()),
248
+ _step3;
249
+ try {
250
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
251
+ var contributorBlocks = _step3.value;
252
+ // Block ranges nest rather than partially overlap, so ascending `from` then descending `to`
253
+ // reaches every block after the ones containing it. A stack of the blocks still open at
254
+ // this position then yields each block's container in one pass, rather than rescanning
255
+ // every block for the widest that contains it.
256
+ var nested = (0, _toConsumableArray2.default)(contributorBlocks).sort(function (left, right) {
257
+ return left.decoration.from - right.decoration.from || right.decoration.to - left.decoration.to;
258
+ });
259
+ var open = [];
260
+ var _iterator4 = _createForOfIteratorHelper(nested),
261
+ _step4;
262
+ try {
263
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
264
+ var _target = _step4.value;
265
+ while (open.length > 0 && !containsBlock(open[open.length - 1].decoration, _target.decoration)) {
266
+ open.pop();
267
+ }
268
+ // Outermost first, so the bottom of the stack is the block the tag survives on.
269
+ if (open.length > 0) {
270
+ fold(open[0], _target);
271
+ }
272
+ open.push(_target);
273
+ }
274
+ } catch (err) {
275
+ _iterator4.e(err);
276
+ } finally {
277
+ _iterator4.f();
278
+ }
279
+ }
280
+ } catch (err) {
281
+ _iterator3.e(err);
282
+ } finally {
283
+ _iterator3.f();
284
+ }
285
+ }
286
+
205
287
  // One tag per contributor per navigation stop. The folds above only catch a replacement's two
206
288
  // halves and a block's own highlights; two of one contributor's changes that merely touch are a
207
289
  // single stop (`groupTouchingDecorations`) yet kept a tag each, so the stop's trailing tag was
208
290
  // drawn but could never be stepped to (EDITOR-8971). It folds into the leading tag of its
209
291
  // contributor, staying a hover target. Contributors are kept apart: a stop spanning two of them
210
292
  // still captions each, rather than crediting one for the other's change.
211
- var _iterator2 = _createForOfIteratorHelper(stops !== null && stops !== void 0 ? stops : []),
212
- _step2;
213
- try {
214
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
215
- var stop = _step2.value;
216
- var byContributor = new Map();
217
- var _iterator3 = _createForOfIteratorHelper(containedBy(targets.filter(function (target) {
218
- return !folded.has(target);
219
- }), stop)),
220
- _step3;
221
- try {
222
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
223
- var _target$decoration$sp, _byContributor$get;
224
- var target = _step3.value;
225
- var key = (_target$decoration$sp = target.decoration.spec.attributionKey) !== null && _target$decoration$sp !== void 0 ? _target$decoration$sp : '';
226
- byContributor.set(key, [].concat((0, _toConsumableArray2.default)((_byContributor$get = byContributor.get(key)) !== null && _byContributor$get !== void 0 ? _byContributor$get : []), [target]));
293
+ // Each target is counted against one stop only see `owningStop`.
294
+ if (stops !== null && stops !== void 0 && stops.length) {
295
+ var byStopAndContributor = new Map();
296
+ var _iterator5 = _createForOfIteratorHelper(targets.filter(function (target) {
297
+ return !folded.has(target);
298
+ })),
299
+ _step5;
300
+ try {
301
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
302
+ var _target2$decoration$s, _byStopAndContributor;
303
+ var _target2 = _step5.value;
304
+ var stop = owningStop(_target2, stops);
305
+ if (stop === undefined) {
306
+ continue;
227
307
  }
228
- } catch (err) {
229
- _iterator3.e(err);
230
- } finally {
231
- _iterator3.f();
308
+ var _key = "".concat(stop.from, "-").concat(stop.to, "|").concat((_target2$decoration$s = _target2.decoration.spec.attributionKey) !== null && _target2$decoration$s !== void 0 ? _target2$decoration$s : '');
309
+ byStopAndContributor.set(_key, [].concat((0, _toConsumableArray2.default)((_byStopAndContributor = byStopAndContributor.get(_key)) !== null && _byStopAndContributor !== void 0 ? _byStopAndContributor : []), [_target2]));
232
310
  }
233
- var _iterator4 = _createForOfIteratorHelper(byContributor.values()),
234
- _step4;
235
- try {
236
- var _loop2 = function _loop2() {
237
- var _step4$value = (0, _toArray2.default)(_step4.value),
238
- leader = _step4$value[0],
239
- trailing = _arrayLikeToArray(_step4$value).slice(1);
240
- trailing.forEach(function (target) {
241
- return fold(leader, target);
242
- });
243
- };
244
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
245
- _loop2();
246
- }
247
- } catch (err) {
248
- _iterator4.e(err);
249
- } finally {
250
- _iterator4.f();
311
+ } catch (err) {
312
+ _iterator5.e(err);
313
+ } finally {
314
+ _iterator5.f();
315
+ }
316
+ var _iterator6 = _createForOfIteratorHelper(byStopAndContributor.values()),
317
+ _step6;
318
+ try {
319
+ var _loop2 = function _loop2() {
320
+ var members = _step6.value;
321
+ var _sort = (0, _toConsumableArray2.default)(members).sort(byLeadingPosition),
322
+ _sort2 = (0, _toArray2.default)(_sort),
323
+ leader = _sort2[0],
324
+ trailing = _arrayLikeToArray(_sort2).slice(1);
325
+ trailing.forEach(function (target) {
326
+ return fold(leader, target);
327
+ });
328
+ };
329
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
330
+ _loop2();
251
331
  }
332
+ } catch (err) {
333
+ _iterator6.e(err);
334
+ } finally {
335
+ _iterator6.f();
252
336
  }
253
- } catch (err) {
254
- _iterator2.e(err);
255
- } finally {
256
- _iterator2.f();
257
337
  }
258
338
  var surviving = targets.filter(function (target) {
259
339
  return !folded.has(target);
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.resolveRevealStyle = exports.buildWipeableBackground = exports.applyRevealToElement = exports.REVEAL_BORDER_VAR = exports.REVEAL_BG_VAR = exports.REVEAL_ATTR = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _agentBrandColorSchemes = require("@atlaskit/agent-color/agent-brand-color-schemes");
9
10
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
10
11
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
11
12
  var _factory = require("./colorSchemes/factory");
@@ -115,6 +116,7 @@ var resolveRevealStyle = exports.resolveRevealStyle = function resolveRevealStyl
115
116
  return undefined;
116
117
  }
117
118
  var colors = getColorScheme(colorScheme);
119
+ var brand = isInserted && Object.prototype.hasOwnProperty.call(_agentBrandColorSchemes.agentBrandColorSchemes, colors.insertColor) ? _agentBrandColorSchemes.agentBrandColorSchemes[colors.insertColor] : undefined;
118
120
  var _resolveRevealColors = resolveRevealColors(colors, {
119
121
  hideAddedDiffsUnderline: hideAddedDiffsUnderline,
120
122
  isActive: isActive,
@@ -124,7 +126,9 @@ var resolveRevealStyle = exports.resolveRevealStyle = function resolveRevealStyl
124
126
  border = _resolveRevealColors.border;
125
127
  return {
126
128
  role: isInserted ? 'added' : 'deleted',
127
- style: (isInserted || !includeDeletedTextStyle ? '' : deletedTextOnlyStyle(colors, isActive)) + revealBaseStyle + buildWipeableBackground(background) + buildRevealVariables({
129
+ style: (brand ? (0, _lazyNodeView.convertToInlineCss)({
130
+ color: brand.text
131
+ }) : '') + (isInserted || !includeDeletedTextStyle ? '' : deletedTextOnlyStyle(colors, isActive)) + revealBaseStyle + buildWipeableBackground(background) + buildRevealVariables({
128
132
  background: background,
129
133
  border: border
130
134
  })
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.absorbFirstChildMarginReset = void 0;
7
+ var _createMarginAbsorber = require("./createMarginAbsorber");
8
+ /**
9
+ * Keeps a block node rendered inside a diff widget from losing its own top margin.
10
+ *
11
+ * The editor's leading-block margin reset is written against the parent element, not the document
12
+ * position, so it fires wherever a text block is a first child — including inside the widget's own
13
+ * `span`, which is what a whole-block deletion renders into. The widget then has no margin of its
14
+ * own, and the block sits flush against whatever is above it, regardless of where in the document
15
+ * the widget landed.
16
+ *
17
+ * The margin is not re-supplied; it is never taken away. Prepending an element the reset does not
18
+ * select moves the real block off the leading position, so the ordinary `.ProseMirror p`,
19
+ * `.ProseMirror h2` and similar rules go on applying to it untouched. That is why this needs no
20
+ * knowledge of which margin the block should have — unlike `createNodeShapedMarginSpacer`, which
21
+ * replicates a margin that has already been zeroed and cannot be read back.
22
+ *
23
+ * Unconditional, because the absorber generates no box: it costs nothing in a widget holding
24
+ * inline content, which the reset was never going to match anyway. Deciding here instead would mean
25
+ * restating the reset's selector list, and a copy of another package's CSS drifts silently.
26
+ */
27
+ var absorbFirstChildMarginReset = exports.absorbFirstChildMarginReset = function absorbFirstChildMarginReset(_ref) {
28
+ var dom = _ref.dom,
29
+ testId = _ref.testId;
30
+ dom.prepend((0, _createMarginAbsorber.createBoxlessMarginAbsorber)({
31
+ testId: testId
32
+ }));
33
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createMarginAbsorber = exports.createBoxlessMarginAbsorber = void 0;
7
+ /**
8
+ * An empty, invisible element whose only job is to occupy a position in the DOM.
9
+ *
10
+ * At the top of the document `firstBlockNodeStyles` zeroes the top margin of whatever element
11
+ * follows a leading `.ProseMirror-widget`, and it does so with `!important` — which no inline style
12
+ * can outrank. This takes that hit so the shaped spacer after it, one position further along, keeps
13
+ * the margin it is there to supply.
14
+ *
15
+ * It contributes no height (no content, border or padding) and margins collapse through it, so it
16
+ * cannot affect layout beyond the selector it absorbs.
17
+ */
18
+ var createMarginAbsorber = exports.createMarginAbsorber = function createMarginAbsorber(_ref) {
19
+ var testId = _ref.testId;
20
+ // Block-level: an empty inline element takes the match just as well, but collapses to a
21
+ // zero-height line box, and the following margin then lands in the wrong place.
22
+ var absorber = document.createElement('div');
23
+ absorber.dataset.testid = testId;
24
+ absorber.setAttribute('aria-hidden', 'true');
25
+ absorber.contentEditable = 'false';
26
+ return absorber;
27
+ };
28
+
29
+ /**
30
+ * An absorber that occupies a DOM position without generating a box.
31
+ *
32
+ * Not interchangeable with `createMarginAbsorber`: a rule that selects the *rendered* sibling of a
33
+ * leading widget needs an element that generates a box, and this one does not.
34
+ */
35
+ var createBoxlessMarginAbsorber = exports.createBoxlessMarginAbsorber = function createBoxlessMarginAbsorber(_ref2) {
36
+ var testId = _ref2.testId;
37
+ var absorber = document.createElement('div');
38
+ absorber.dataset.testid = testId;
39
+ absorber.setAttribute('aria-hidden', 'true');
40
+ absorber.contentEditable = 'false';
41
+ absorber.style.display = 'contents';
42
+ return absorber;
43
+ };
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createNodeShapedMarginSpacer = void 0;
7
+ /**
8
+ * Node types that can safely be reduced to an empty shell.
9
+ *
10
+ * A shell only replicates a margin if it collapses to nothing itself, which needs zero height and
11
+ * no vertical border or padding. Textblocks and lists qualify; nodes with intrinsic sizing (tables
12
+ * are `display: table`, media has a measured height) or a painted box (a zero-height panel is a
13
+ * visible stripe of background) do not, and are left alone.
14
+ */
15
+ var isShapeableNode = function isShapeableNode(node) {
16
+ return node.isTextblock || ['bulletList', 'orderedList'].includes(node.type.name);
17
+ };
18
+
19
+ /**
20
+ * An invisible element shaped like `node`, used to supply the top margin `node` no longer gets.
21
+ *
22
+ * A node at the start of its parent has its top margin reset, so a diff widget rendered above it
23
+ * sits flush against it. The reset cannot simply be taken off the node: the container variants skip
24
+ * widgets when counting — `nth-child(1 of :not(style, .ProseMirror-gapcursor, .ProseMirror-widget,
25
+ * span))` in layout columns, expands, sync blocks and table cells — so no amount of extra elements
26
+ * moves the match. That exclusion is what makes this work instead: the spacer is a
27
+ * `.ProseMirror-widget`, so it is never the counted first child and keeps its own margin, while the
28
+ * real node goes on taking the reset.
29
+ *
30
+ * The margin is not measured. Reading it off the real node is impossible — by then the reset has
31
+ * set it to `0`, and both `firstBlockNodeStyles` (`!important`) and block controls' `firstNodeDec`
32
+ * (an inline style) make it unrecoverable. Instead the spacer carries the node's own tag, classes
33
+ * and attributes, so the same rules that would have given the node its margin match the spacer
34
+ * (`.ProseMirror p`, `.ProseMirror h2`, the root-list rule, and so on).
35
+ *
36
+ * Returns `null` when the node is not safely shapeable or serialization fails.
37
+ */
38
+ var createNodeShapedMarginSpacer = exports.createNodeShapedMarginSpacer = function createNodeShapedMarginSpacer(_ref) {
39
+ var _node$type$createAndF;
40
+ var node = _ref.node,
41
+ serializer = _ref.serializer,
42
+ testId = _ref.testId;
43
+ if (!isShapeableNode(node)) {
44
+ return null;
45
+ }
46
+
47
+ // An empty node of the same type serializes to the same shell without walking real content.
48
+ var shellNode = (_node$type$createAndF = node.type.createAndFill(node.attrs)) !== null && _node$type$createAndF !== void 0 ? _node$type$createAndF : node;
49
+ var serialized = serializer.serializeNode(shellNode);
50
+ if (!(serialized instanceof HTMLElement)) {
51
+ return null;
52
+ }
53
+
54
+ // Keep the tag, classes and attributes the margin rules select on; drop anything that could
55
+ // occupy a line box.
56
+ serialized.replaceChildren();
57
+ serialized.dataset.testid = testId;
58
+ serialized.setAttribute('aria-hidden', 'true');
59
+ serialized.contentEditable = 'false';
60
+
61
+ // Zero height with no vertical border or padding keeps the spacer self-collapsing: its margins
62
+ // stay adjoining, so they collapse with the neighbours into a single margin equal to the largest
63
+ // rather than adding to them. That is what makes this safe in containers that never reset the
64
+ // first child — the spacer cannot double the gap there. Deliberately no `overflow: hidden`, which
65
+ // would open a block formatting context and stop that collapsing.
66
+ serialized.style.height = '0';
67
+ serialized.style.minHeight = '0';
68
+ serialized.style.paddingTop = '0';
69
+ serialized.style.paddingBottom = '0';
70
+ serialized.style.borderTopWidth = '0';
71
+ serialized.style.borderBottomWidth = '0';
72
+ // Only the top margin is being replicated. Left as-is, a larger bottom margin would win the
73
+ // self-collapse and overshoot the gap.
74
+ serialized.style.marginBottom = '0';
75
+ return serialized;
76
+ };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.safeResolve = void 0;
7
+ /**
8
+ * Resolves a document position, or returns `null` if it cannot be resolved.
9
+ *
10
+ * `Node.resolve` throws a `RangeError` for a position outside the document. Diff decorations are
11
+ * built from change ranges computed against a document that may since have moved on, so a position
12
+ * arriving out of range is a normal outcome rather than a defect — but an exception escaping here
13
+ * takes down the whole decoration set, leaving the diff unrendered.
14
+ *
15
+ * Callers are expected to treat `null` as "cannot tell" and fall back to behaviour that does not
16
+ * need the resolved position.
17
+ */
18
+ var safeResolve = exports.safeResolve = function safeResolve(doc, pos) {
19
+ try {
20
+ return doc.resolve(pos);
21
+ } catch (_unused) {
22
+ return null;
23
+ }
24
+ };
@@ -61,6 +61,17 @@ function decorationSide(decoration) {
61
61
  return (0, _decorationKeys.isDiffDecoration)(decoration) && decoration.spec.side || 0;
62
62
  }
63
63
 
64
+ /**
65
+ * Whose change a decoration is part of, or `''` on an unattributed diff — where every decoration
66
+ * reads as one contributor and the grouping below is unchanged.
67
+ *
68
+ * Contributors are kept apart so one stop never covers two of them, since only the tag on the
69
+ * change stepped to reveals (EDITOR-8932).
70
+ */
71
+ function contributorOf(decoration) {
72
+ return (0, _decorationKeys.isDiffDecoration)(decoration) && decoration.spec.attributionKey || '';
73
+ }
74
+
64
75
  /**
65
76
  * Collapses decorations that represent one continuous customer-facing edit.
66
77
  *
@@ -69,34 +80,52 @@ function decorationSide(decoration) {
69
80
  * deletion at the same location should be treated as one replacement. Zero-width decorations
70
81
  * (normally deleted-content widgets) can join a group, but cannot extend its range and therefore
71
82
  * cannot bridge two otherwise separate edits.
83
+ *
84
+ * One group per contributor within a run of touching ranges — see `contributorOf`.
72
85
  */
73
86
  function groupTouchingDecorations(decorations, isInlineDecoration) {
74
87
  if (decorations.length < 2) {
75
88
  return decorations;
76
89
  }
77
90
  var groups = [];
78
- var currentGroup = [];
79
- var currentGroupFrom = 0;
80
- var currentGroupTo = 0;
91
+ // The groups a following decoration can still join — one per contributor, dropped at every gap.
92
+ var openGroups = [];
93
+ var clusterTo = 0;
81
94
  var sortedDecorations = (0, _toConsumableArray2.default)(decorations).sort(function (a, b) {
82
95
  return a.from === b.from ? a.to - b.to : a.from - b.from;
83
96
  });
84
97
  sortedDecorations.forEach(function (decoration) {
85
- if (currentGroup.length === 0 || decoration.from > currentGroupTo) {
86
- currentGroup = [decoration];
87
- currentGroupFrom = decoration.from;
88
- currentGroupTo = decoration.to;
89
- groups.push({
90
- decorations: currentGroup,
91
- from: currentGroupFrom,
92
- to: currentGroupTo
93
- });
94
- return;
98
+ var _openGroups$find;
99
+ // A gap ends the run: nothing before it can be joined.
100
+ if (decoration.from > clusterTo) {
101
+ openGroups = [];
95
102
  }
96
- currentGroup.push(decoration);
97
- // A zero-width decoration must not extend the group and bridge a gap.
98
- currentGroupTo = Math.max(currentGroupTo, decoration.to);
99
- groups[groups.length - 1].to = currentGroupTo;
103
+ var contributor = contributorOf(decoration);
104
+ // An unattributed decoration names nobody to keep apart, so it joins whatever is open — a
105
+ // decision item's own highlight must not split the stop from the tag on its list.
106
+ var openGroup = (_openGroups$find = openGroups.find(function (group) {
107
+ return group.contributor === contributor;
108
+ })) !== null && _openGroups$find !== void 0 ? _openGroups$find : contributor === '' ? openGroups[0] : openGroups.find(function (group) {
109
+ return group.contributor === '';
110
+ });
111
+ if (openGroup === undefined) {
112
+ var group = {
113
+ contributor: contributor,
114
+ decorations: [decoration],
115
+ from: decoration.from,
116
+ to: decoration.to
117
+ };
118
+ groups.push(group);
119
+ openGroups = [].concat((0, _toConsumableArray2.default)(openGroups), [group]);
120
+ } else {
121
+ // The first contributor to join claims the group, so the next one still starts its own.
122
+ openGroup.contributor = openGroup.contributor || contributor;
123
+ openGroup.decorations.push(decoration);
124
+ openGroup.to = Math.max(openGroup.to, decoration.to);
125
+ }
126
+
127
+ // A zero-width decoration must not extend the run and bridge a gap.
128
+ clusterTo = Math.max(clusterTo, decoration.to);
100
129
  });
101
130
  return groups.map(function (_ref) {
102
131
  var _ref2, _group$find;
@@ -143,8 +172,8 @@ function groupTouchingDecorations(decorations, isInlineDecoration) {
143
172
  * 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
144
173
  * (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
145
174
  * 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
146
- * directly touching ranges across decoration types into one result, using the union of all
147
- * grouped ranges
175
+ * directly touching ranges across decoration types into one result per contributor, using the
176
+ * union of all grouped ranges
148
177
  * (zero-width widgets can join a group without extending it). Under
149
178
  * `platform_editor_ai_show_diff_patch_1`, a group that starts with content painting above
150
179
  * the content that replaced it — reports that widget as its `scrollTarget` spec,
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.resolveBaseNodeName = void 0;
7
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
8
+ /**
9
+ * A node type's base name, with a schema variant's suffix stripped: `panel`, `panel_c1` and
10
+ * `panel_c1_root_only` all resolve to `panel`.
11
+ *
12
+ * Package-local rather than `getBaseNodeTypeName` from editor-common, which enumerates the variants
13
+ * it happens to know (`panel_c1` alone) and is shared with features outside this one. Derived
14
+ * instead of listed, so a variant added later needs no change here: the schema generator names a
15
+ * variant `<type>_<variant>`, and every other node name in the ADF schema is camelCase, so the
16
+ * first `_` is the boundary.
17
+ *
18
+ * Gated, so the diff behaves exactly as before for anyone outside the rollout.
19
+ *
20
+ * For taggability only — this gate is weaker than the ones attribution needs, so resolving a variant
21
+ * in the styling decisions would move the legacy diff for readers who see no tags.
22
+ */
23
+ var resolveBaseNodeName = exports.resolveBaseNodeName = function resolveBaseNodeName(name) {
24
+ return (0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? name.split('_')[0] : name;
25
+ };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isTaggableBlockNodeName = exports.isTaggableBlockNode = void 0;
7
+ var _baseNodeName = require("./baseNodeName");
8
+ /**
9
+ * Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
10
+ * Everything that draws a box whatever it holds, plus the two lists that are inserted whole: without
11
+ * them the tag fell through to the text inside the first item (EDITOR-8932). Bullet and ordered lists
12
+ * are left out, since their items accrue one at a time. Tables and list items decorate per cell and
13
+ * per item, with no one-tag-per-block design yet. `blockCard` covers the datasource variant, which is
14
+ * the same node type.
15
+ *
16
+ * Base names only — match with the predicates below, never against `type.name` directly.
17
+ */
18
+ var TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'decisionList', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule', 'taskList']);
19
+
20
+ /**
21
+ * Whether this node hosts a contributor tag of its own. Navigation grouping does not consult this:
22
+ * a run of these one contributor inserted is a single stop, tagged on the leading block.
23
+ *
24
+ * Resolved through `resolveBaseNodeName` so a schema variant counts as the type it is a variant of:
25
+ * `panel_c1` is the panel the table-in-panel schema inserts, and matching on `type.name` left it
26
+ * untagged (EDITOR-8932).
27
+ *
28
+ * By name, because the decoration spec carries `nodeName` rather than the type it came from.
29
+ */
30
+ var isTaggableBlockNodeName = exports.isTaggableBlockNodeName = function isTaggableBlockNodeName(name) {
31
+ return name !== undefined && TAGGABLE_BLOCK_NODES.has((0, _baseNodeName.resolveBaseNodeName)(name));
32
+ };
33
+
34
+ /** As above, from the node type itself. */
35
+ var isTaggableBlockNode = exports.isTaggableBlockNode = function isTaggableBlockNode(nodeType) {
36
+ return isTaggableBlockNodeName(nodeType.name);
37
+ };
@@ -332,11 +332,11 @@ var ContributorTagController = exports.ContributorTagController = /*#__PURE__*/f
332
332
 
333
333
  // Hand-rolled avatar stack: `@atlaskit/avatar-group` cannot render below 24px and the tag uses
334
334
  // 16px avatars.
335
- var stack = model.connectedContributor ? [].concat((0, _toConsumableArray2.default)(user ? [{
336
- contributor: user,
337
- stackIndex: 1
338
- }] : []), (0, _toConsumableArray2.default)(agent ? [{
335
+ var stack = model.connectedContributor ? [].concat((0, _toConsumableArray2.default)(agent ? [{
339
336
  contributor: agent,
337
+ stackIndex: 1
338
+ }] : []), (0, _toConsumableArray2.default)(user ? [{
339
+ contributor: user,
340
340
  stackIndex: 0
341
341
  }] : [])) : [{
342
342
  contributor: model.contributor