@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
@@ -190,8 +190,51 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
190
190
  diffType = _ref5.diffType,
191
191
  _ref5$coarseTableCell = _ref5.coarseTableCellsOnly,
192
192
  coarseTableCellsOnly = _ref5$coarseTableCell === void 0 ? false : _ref5$coarseTableCell,
193
- tagMountContext = _ref5.tagMountContext;
193
+ tagMountContext = _ref5.tagMountContext,
194
+ nodeRanges = _ref5.nodeRanges;
194
195
  var decorations = [];
196
+
197
+ // Exact-nodes path: only reached for a split change (EDITOR-8932), which names the nodes it
198
+ // owns. Deliberately kept separate from the walk below rather than sharing it.
199
+ if (nodeRanges) {
200
+ var _iterator = _createForOfIteratorHelper(nodeRanges),
201
+ _step;
202
+ try {
203
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
204
+ var _step$value = _step.value,
205
+ pos = _step$value.from,
206
+ nodeEnd = _step$value.to;
207
+ var node = doc.nodeAt(pos);
208
+ if (!(node !== null && node !== void 0 && node.isBlock)) {
209
+ continue;
210
+ }
211
+ var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
212
+ decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
213
+ attributionKey: attributionKey,
214
+ change: {
215
+ from: pos,
216
+ to: nodeEnd,
217
+ name: node.type.name
218
+ },
219
+ colorScheme: colorScheme,
220
+ isInserted: isInserted,
221
+ isActive: isActive,
222
+ shouldHideDeleted: shouldHideDeleted,
223
+ showContributorTags: showContributorTags,
224
+ showIndicators: showIndicators,
225
+ doc: doc,
226
+ diffType: diffType,
227
+ tagMountContext: tagMountContext
228
+ })));
229
+ }
230
+ } catch (err) {
231
+ _iterator.e(err);
232
+ } finally {
233
+ _iterator.f();
234
+ }
235
+ return decorations;
236
+ }
237
+
195
238
  // Coarse path: inside the large table, only cell/header overlays are kept — the
196
239
  // table/row/paragraph decorations there are redundant and cause expensive per-cell
197
240
  // re-render. Blocks outside the table keep their normal decorations.
@@ -205,18 +248,18 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
205
248
  }
206
249
  }
207
250
  if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
208
- var nodeEnd = pos + node.nodeSize;
209
- var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
251
+ var _nodeEnd = pos + node.nodeSize;
252
+ var _isActive = isRangeActive(activeIndexPos, pos, _nodeEnd);
210
253
  decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
211
254
  attributionKey: attributionKey,
212
255
  change: {
213
256
  from: pos,
214
- to: nodeEnd,
257
+ to: _nodeEnd,
215
258
  name: node.type.name
216
259
  },
217
260
  colorScheme: colorScheme,
218
261
  isInserted: isInserted,
219
- isActive: isActive,
262
+ isActive: _isActive,
220
263
  shouldHideDeleted: shouldHideDeleted,
221
264
  showContributorTags: showContributorTags,
222
265
  showIndicators: showIndicators,
@@ -339,13 +382,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
339
382
  }
340
383
  var pendingAttrSteps = [];
341
384
  var stepMaps = [];
342
- var _iterator = _createForOfIteratorHelper(simplifiedSteps.entries()),
343
- _step;
385
+ var _iterator2 = _createForOfIteratorHelper(simplifiedSteps.entries()),
386
+ _step2;
344
387
  try {
345
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
346
- var _step$value = _slicedToArray(_step.value, 2),
347
- stepIndex = _step$value[0],
348
- step = _step$value[1];
388
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
389
+ var _step2$value = _slicedToArray(_step2.value, 2),
390
+ stepIndex = _step2$value[0],
391
+ step = _step2$value[1];
349
392
  var result = step.apply(steppedDoc);
350
393
  if (result.failed === null && result.doc) {
351
394
  if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
@@ -364,9 +407,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
364
407
  // Gate the complete attribution fix: mapped positions and step-time nodes are used together.
365
408
  // The fallback deliberately preserves the legacy raw-position lookups for rollout safety.
366
409
  } catch (err) {
367
- _iterator.e(err);
410
+ _iterator2.e(err);
368
411
  } finally {
369
- _iterator.f();
412
+ _iterator2.f();
370
413
  }
371
414
  var attrStepContexts = fg('platform_editor_reduce_diff_attr_sensitivity') ? pendingAttrSteps.flatMap(function (_ref1) {
372
415
  var afterNode = _ref1.afterNode,
@@ -454,7 +497,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
454
497
  tr: tr,
455
498
  attributedChanges: attributedChanges
456
499
  });
457
- changes = collapseOverlappingChanges(uncollapsedChanges);
500
+ changes = collapseOverlappingChanges(uncollapsedChanges, tr.doc);
458
501
 
459
502
  // Still colours-only: a tags-only diff keeps the plain colour scheme.
460
503
  if (attributionColoringEnabled) {
@@ -551,13 +594,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
551
594
  // table (the cell overlays added below provide the highlight without the
552
595
  // expensive content re-render); leaf blocks outside the table still get theirs.
553
596
  var coarseTables = useCoarseTableDecoration ? tableRanges(tr.doc, change.fromB, change.toB) : [];
554
- var _iterator2 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
555
- _step2;
597
+ var _iterator3 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
598
+ _step3;
556
599
  try {
557
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
558
- var _step2$value = _slicedToArray(_step2.value, 2),
559
- from = _step2$value[0],
560
- to = _step2$value[1];
600
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
601
+ var _step3$value = _slicedToArray(_step3.value, 2),
602
+ from = _step3$value[0],
603
+ to = _step3$value[1];
561
604
  if (useCoarseTableDecoration && isInsideTable(coarseTables, from)) {
562
605
  continue;
563
606
  }
@@ -584,9 +627,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
584
627
  }))));
585
628
  }
586
629
  } catch (err) {
587
- _iterator2.e(err);
630
+ _iterator3.e(err);
588
631
  } finally {
589
- _iterator2.f();
632
+ _iterator3.f();
590
633
  }
591
634
  } else {
592
635
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration(_objectSpread({
@@ -613,6 +656,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
613
656
  doc: tr.doc,
614
657
  from: change.fromB,
615
658
  to: change.toB,
659
+ // A split piece's own range no longer covers the container it opened (EDITOR-8932).
660
+ nodeRanges: change.blockNodeRangesB,
616
661
  colorScheme: changeColorScheme
617
662
  }, isExtendedEnabled(diffType) && {
618
663
  isInserted: isInserted,
@@ -1,13 +1,20 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
- 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; }
4
- 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; }
5
3
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
6
4
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
7
5
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ 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; }
7
+ 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; }
8
8
  import { simplifyChanges } from 'prosemirror-changeset';
9
9
  import { getAttributionKey } from '../decorations/colorSchemes/attributions';
10
10
  import { optimizeChanges } from './optimizeChanges';
11
+
12
+ /**
13
+ * A change, plus the block nodes it owns when it is one piece of a split — a piece's own range no
14
+ * longer covers the container it opened. Absent on an unsplit change, which decorates the blocks
15
+ * inside its own range as before.
16
+ */
17
+
11
18
  var getAttributionIdentity = function getAttributionIdentity(change) {
12
19
  var identities = new Set();
13
20
  for (var _i = 0, _arr = [].concat(_toConsumableArray(change.deleted), _toConsumableArray(change.inserted)); _i < _arr.length; _i++) {
@@ -29,55 +36,265 @@ var byNewDocRange = function byNewDocRange(left, right) {
29
36
  return left.fromB - right.fromB || left.toB - right.toB;
30
37
  };
31
38
 
39
+ /** Union of the given ranges, with overlapping and touching ones merged. */
40
+ var mergeRanges = function mergeRanges(ranges) {
41
+ return _toConsumableArray(ranges).sort(function (left, right) {
42
+ return left.from - right.from;
43
+ }).reduce(function (merged, range) {
44
+ var last = merged[merged.length - 1];
45
+ if (last && range.from <= last.to) {
46
+ last.to = Math.max(last.to, range.to);
47
+ return merged;
48
+ }
49
+ return [].concat(_toConsumableArray(merged), [_objectSpread({}, range)]);
50
+ }, []);
51
+ };
52
+
53
+ /** Whether `inner` sits wholly inside `outer` and covers less of the document. */
54
+ var isContained = function isContained(outer, inner) {
55
+ return inner.toB > inner.fromB && outer.fromB <= inner.fromB && inner.toB <= outer.toB && inner.toB - inner.fromB < outer.toB - outer.fromB;
56
+ };
57
+
32
58
  /**
33
- * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
34
- * independently, so runs either side of an unattributed step can come back covering the same
35
- * characters — which stacks two inline decorations and renders one contributor tag per copy.
36
- *
37
- * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
38
- * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
39
- * is not credited to it.
59
+ * The block nodes in `change` whose opening position `ownsPosition` claims. Ownership is by opening
60
+ * position so a block another contributor added inside a container is not claimed by whoever opened
61
+ * the container. `undefined` for a range this document cannot resolve, which falls the piece back
62
+ * to decorating its own range.
40
63
  */
41
- export var collapseOverlappingChanges = function collapseOverlappingChanges(changes) {
42
- if (changes.length <= 1) {
43
- return changes;
64
+ var blockNodeRanges = function blockNodeRanges(doc, change, ownsPosition) {
65
+ if (!doc || change.from >= change.to || change.to > doc.content.size) {
66
+ return undefined;
44
67
  }
68
+ var ranges = [];
69
+ doc.nodesBetween(change.from, change.to, function (node, pos) {
70
+ if (node.isBlock && ownsPosition(pos) && pos + node.nodeSize <= change.to) {
71
+ ranges.push({
72
+ from: pos,
73
+ to: pos + node.nodeSize
74
+ });
75
+ }
76
+ return true;
77
+ });
78
+ return ranges;
79
+ };
80
+
81
+ /**
82
+ * Splits a change whose spans name more than one contributor into one change per run of consecutive
83
+ * spans sharing a key.
84
+ *
85
+ * `prosemirror-changeset` merges contiguous inserted content into one change while keeping per-span
86
+ * data, so a panel inserted by one contributor and edited inside by another arrives as a single
87
+ * change holding every span — and the latest-writer rule credited all of it, panel included, to
88
+ * whoever wrote last (EDITOR-8932).
89
+ */
90
+ var splitMixedIdentityChanges = function splitMixedIdentityChanges(changes, doc) {
91
+ return changes.flatMap(function (change) {
92
+ if (getAttributionIdentity(change).mergeable) {
93
+ return [change];
94
+ }
95
+ var runs = [];
96
+ var offset = change.fromB;
97
+ var _iterator = _createForOfIteratorHelper(change.inserted),
98
+ _step;
99
+ try {
100
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
101
+ var span = _step.value;
102
+ var key = getAttributionKey(span.data);
103
+ var last = runs[runs.length - 1];
104
+ if (last && last.key === key) {
105
+ last.to = offset + span.length;
106
+ last.spans.push(span);
107
+ } else {
108
+ runs.push({
109
+ key: key,
110
+ from: offset,
111
+ to: offset + span.length,
112
+ spans: [span]
113
+ });
114
+ }
115
+ offset += span.length;
116
+ }
117
+
118
+ // The offsets above are only positions if the spans partition the inserted range.
119
+ } catch (err) {
120
+ _iterator.e(err);
121
+ } finally {
122
+ _iterator.f();
123
+ }
124
+ if (runs.length < 2 || offset !== change.toB) {
125
+ return [change];
126
+ }
127
+ return runs.map(function (run, index) {
128
+ var owned = blockNodeRanges(doc, {
129
+ from: change.fromB,
130
+ to: change.toB
131
+ }, function (pos) {
132
+ return pos >= run.from && pos < run.to;
133
+ });
134
+ return _objectSpread(_objectSpread({}, change), {}, {
135
+ fromA: index === 0 ? change.fromA : change.toA,
136
+ fromB: run.from,
137
+ toB: run.to,
138
+ deleted: index === 0 ? change.deleted : [],
139
+ inserted: run.spans
140
+ }, owned ? {
141
+ blockNodeRangesB: owned
142
+ } : {});
143
+ });
144
+ });
145
+ };
45
146
 
147
+ /**
148
+ * Subtracts every other author's contained change from a change's range, so both contributions
149
+ * survive. Applies when the contributors arrive as separate changes rather than as spans of one —
150
+ * the `step` diff type. A contained change by the same author, and one that only partially
151
+ * overlaps, are left for the collapse below.
152
+ *
153
+ * The leading fragment keeps the deleted side, so one deletion is not drawn once per fragment.
154
+ */
155
+ var splitContainedChanges = function splitContainedChanges(changes, doc) {
156
+ return changes.flatMap(function (change) {
157
+ var _getAttributionIdenti = getAttributionIdentity(change),
158
+ key = _getAttributionIdenti.key;
159
+ var inner = mergeRanges(changes.filter(function (other) {
160
+ var otherKey = getAttributionIdentity(other).key;
161
+ return other !== change && key !== undefined && otherKey !== undefined && otherKey !== key && isContained(change, other);
162
+ }).map(function (_ref) {
163
+ var fromB = _ref.fromB,
164
+ toB = _ref.toB;
165
+ return {
166
+ from: fromB,
167
+ to: toB
168
+ };
169
+ }));
170
+ if (inner.length === 0) {
171
+ return [change];
172
+ }
173
+ var gaps = [];
174
+ var cursor = change.fromB;
175
+ var _iterator2 = _createForOfIteratorHelper(inner),
176
+ _step2;
177
+ try {
178
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
179
+ var _step2$value = _step2.value,
180
+ from = _step2$value.from,
181
+ to = _step2$value.to;
182
+ if (from > cursor) {
183
+ gaps.push({
184
+ from: cursor,
185
+ to: from
186
+ });
187
+ }
188
+ cursor = Math.max(cursor, to);
189
+ }
190
+ } catch (err) {
191
+ _iterator2.e(err);
192
+ } finally {
193
+ _iterator2.f();
194
+ }
195
+ if (change.toB > cursor) {
196
+ gaps.push({
197
+ from: cursor,
198
+ to: change.toB
199
+ });
200
+ }
201
+
202
+ // Subtracted end to end, so nothing is left to highlight — but the blocks are still owned.
203
+ var fragments = gaps.length > 0 ? gaps : [{
204
+ from: change.toB,
205
+ to: change.toB
206
+ }];
207
+ // The outer author's: every block in their range that no inner change opened.
208
+ var owned = blockNodeRanges(doc, {
209
+ from: change.fromB,
210
+ to: change.toB
211
+ }, function (pos) {
212
+ return !inner.some(function (_ref2) {
213
+ var from = _ref2.from,
214
+ to = _ref2.to;
215
+ return pos >= from && pos < to;
216
+ });
217
+ });
218
+ return fragments.map(function (fragment, index) {
219
+ return _objectSpread(_objectSpread({}, change), {}, {
220
+ fromA: index === 0 ? change.fromA : change.toA,
221
+ fromB: fragment.from,
222
+ toB: fragment.to,
223
+ deleted: index === 0 ? change.deleted : []
224
+ }, owned ? {
225
+ blockNodeRangesB: index === 0 ? owned : []
226
+ } : {});
227
+ });
228
+ });
229
+ };
230
+
231
+ /**
232
+ * The blocks a merged change owns. Both sides must name theirs, or the merge falls back to its own
233
+ * range — narrowing it could drop a block altogether.
234
+ */
235
+ var mergeBlockNodeRanges = function mergeBlockNodeRanges(left, right) {
236
+ return left.blockNodeRangesB && right.blockNodeRangesB ? [].concat(_toConsumableArray(left.blockNodeRangesB), _toConsumableArray(right.blockNodeRangesB)) : undefined;
237
+ };
238
+
239
+ /** Unions the changes whose new-document ranges strictly overlap, leaving touching ones alone. */
240
+ var collapse = function collapse(changes) {
46
241
  // A zero-length new-document range paints no inline decoration, so it is kept aside rather than
47
242
  // folded into a neighbouring insertion.
48
243
  var emptyRanges = changes.filter(function (change) {
49
244
  return change.toB <= change.fromB;
50
245
  });
51
246
  var collapsed = [];
52
- var _iterator = _createForOfIteratorHelper(changes.filter(function (change) {
247
+ var _iterator3 = _createForOfIteratorHelper(changes.filter(function (change) {
53
248
  return change.toB > change.fromB;
54
249
  }).sort(byNewDocRange)),
55
- _step;
250
+ _step3;
56
251
  try {
57
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
58
- var change = _step.value;
252
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
253
+ var change = _step3.value;
59
254
  var current = collapsed[collapsed.length - 1];
60
255
  if (current && change.fromB < current.toB) {
61
- collapsed[collapsed.length - 1] = {
256
+ var blockNodeRangesB = mergeBlockNodeRanges(current, change);
257
+ collapsed[collapsed.length - 1] = _objectSpread({
62
258
  fromA: Math.min(current.fromA, change.fromA),
63
259
  toA: Math.max(current.toA, change.toA),
64
260
  fromB: Math.min(current.fromB, change.fromB),
65
261
  toB: Math.max(current.toB, change.toB),
66
262
  deleted: [].concat(_toConsumableArray(current.deleted), _toConsumableArray(change.deleted)),
67
263
  inserted: [].concat(_toConsumableArray(current.inserted), _toConsumableArray(change.inserted))
68
- };
264
+ }, blockNodeRangesB ? {
265
+ blockNodeRangesB: blockNodeRangesB
266
+ } : {});
69
267
  } else {
70
268
  collapsed.push(_objectSpread({}, change));
71
269
  }
72
270
  }
73
271
  } catch (err) {
74
- _iterator.e(err);
272
+ _iterator3.e(err);
75
273
  } finally {
76
- _iterator.f();
274
+ _iterator3.f();
77
275
  }
78
276
  return [].concat(collapsed, _toConsumableArray(emptyRanges)).sort(byNewDocRange);
79
277
  };
80
278
 
279
+ /**
280
+ * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
281
+ * independently, so runs either side of an unattributed step can come back covering the same
282
+ * characters — which stacks two inline decorations and renders one contributor tag per copy.
283
+ *
284
+ * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
285
+ * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
286
+ * is not credited to it.
287
+ *
288
+ * Contributors are separated first, by `splitMixedIdentityChanges` and `splitContainedChanges`.
289
+ * Both leave ranges that only touch, so they survive the collapse.
290
+ *
291
+ * Carries no gate of its own: the only caller reaches it through the attributed changeset, which
292
+ * already requires `confluence_ncs_step_diffing_version_history`.
293
+ */
294
+ export var collapseOverlappingChanges = function collapseOverlappingChanges(changes, doc) {
295
+ return collapse(splitContainedChanges(splitMixedIdentityChanges(changes, doc), doc));
296
+ };
297
+
81
298
  /**
82
299
  * Simplifies and optimizes consecutive changes without crossing an attribution boundary.
83
300
  * A change which already contains more than one identity is deliberately kept isolated.
@@ -107,12 +324,12 @@ export var simplifyChangesWithAttribution = function simplifyChangesWithAttribut
107
324
  run = [];
108
325
  runIdentity = undefined;
109
326
  };
110
- var _iterator2 = _createForOfIteratorHelper(changes),
111
- _step2;
327
+ var _iterator4 = _createForOfIteratorHelper(changes),
328
+ _step4;
112
329
  try {
113
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
330
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
114
331
  var _runIdentity;
115
- var change = _step2.value;
332
+ var change = _step4.value;
116
333
  var identity = getAttributionIdentity(change);
117
334
  var canJoinRun = ((_runIdentity = runIdentity) === null || _runIdentity === void 0 ? void 0 : _runIdentity.mergeable) === true && identity.mergeable && runIdentity.key === identity.key;
118
335
  if (run.length > 0 && !canJoinRun) {
@@ -125,9 +342,9 @@ export var simplifyChangesWithAttribution = function simplifyChangesWithAttribut
125
342
  }
126
343
  }
127
344
  } catch (err) {
128
- _iterator2.e(err);
345
+ _iterator4.e(err);
129
346
  } finally {
130
- _iterator2.f();
347
+ _iterator4.f();
131
348
  }
132
349
  flush();
133
350
  return result;