@atlaskit/editor-plugin-show-diff 16.1.1 → 17.0.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 (42) hide show
  1. package/CHANGELOG.md +23 -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/extractContributorTags.js +123 -43
  10. package/dist/cjs/pm-plugins/decorations/revealStyles.js +5 -1
  11. package/dist/cjs/pm-plugins/getScrollableDecorations.js +48 -19
  12. package/dist/cjs/pm-plugins/utils/baseNodeName.js +25 -0
  13. package/dist/cjs/pm-plugins/utils/taggableBlockNodes.js +37 -0
  14. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +39 -2
  15. package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +196 -10
  16. package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +51 -19
  17. package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +3 -0
  18. package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +0 -2
  19. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +23 -14
  20. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +60 -7
  21. package/dist/es2019/pm-plugins/decorations/revealStyles.js +5 -1
  22. package/dist/es2019/pm-plugins/getScrollableDecorations.js +44 -19
  23. package/dist/es2019/pm-plugins/utils/baseNodeName.js +18 -0
  24. package/dist/es2019/pm-plugins/utils/taggableBlockNodes.js +28 -0
  25. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +67 -22
  26. package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +243 -26
  27. package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +71 -37
  28. package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +5 -1
  29. package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +0 -2
  30. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +21 -14
  31. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +123 -43
  32. package/dist/esm/pm-plugins/decorations/revealStyles.js +5 -1
  33. package/dist/esm/pm-plugins/getScrollableDecorations.js +48 -19
  34. package/dist/esm/pm-plugins/utils/baseNodeName.js +20 -0
  35. package/dist/esm/pm-plugins/utils/taggableBlockNodes.js +32 -0
  36. package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +20 -1
  37. package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +4 -11
  38. package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +9 -8
  39. package/dist/types/pm-plugins/getScrollableDecorations.d.ts +2 -2
  40. package/dist/types/pm-plugins/utils/baseNodeName.d.ts +16 -0
  41. package/dist/types/pm-plugins/utils/taggableBlockNodes.d.ts +14 -0
  42. package/package.json +12 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 17.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 16.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`b2a2413928cac`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b2a2413928cac) -
14
+ Match agent highlights, cursors and review diffs to shared Studio and third-party brand colours
15
+ under the existing telepointer redesign. Use the existing purple attribution treatment for Rovo
16
+ review diffs and preserve square Rovo telepointer avatars.
17
+
18
+ ### Patch Changes
19
+
20
+ - [`8c53dda37401d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8c53dda37401d) -
21
+ Fix contributor tags on block nodes, behind `confluence_ncs_step_diffing_version_history`. Changes
22
+ are now split per contributor instead of being credited to whoever wrote last, so each
23
+ contributor's change hosts its own tag and is its own navigation stop.
24
+ - Updated dependencies
25
+
3
26
  ## 16.1.1
4
27
 
5
28
  ### Patch Changes
@@ -20,6 +20,9 @@
20
20
  {
21
21
  "path": "../../adf-schema/afm-products/tsconfig.json"
22
22
  },
23
+ {
24
+ "path": "../../../ai-mate/agent-color/afm-products/tsconfig.json"
25
+ },
23
26
  {
24
27
  "path": "../../../design-system/avatar/afm-products/tsconfig.json"
25
28
  },
@@ -197,8 +197,51 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
197
197
  diffType = _ref5.diffType,
198
198
  _ref5$coarseTableCell = _ref5.coarseTableCellsOnly,
199
199
  coarseTableCellsOnly = _ref5$coarseTableCell === void 0 ? false : _ref5$coarseTableCell,
200
- tagMountContext = _ref5.tagMountContext;
200
+ tagMountContext = _ref5.tagMountContext,
201
+ nodeRanges = _ref5.nodeRanges;
201
202
  var decorations = [];
203
+
204
+ // Exact-nodes path: only reached for a split change (EDITOR-8932), which names the nodes it
205
+ // owns. Deliberately kept separate from the walk below rather than sharing it.
206
+ if (nodeRanges) {
207
+ var _iterator = _createForOfIteratorHelper(nodeRanges),
208
+ _step;
209
+ try {
210
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
211
+ var _step$value = _step.value,
212
+ pos = _step$value.from,
213
+ nodeEnd = _step$value.to;
214
+ var node = doc.nodeAt(pos);
215
+ if (!(node !== null && node !== void 0 && node.isBlock)) {
216
+ continue;
217
+ }
218
+ var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
219
+ decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
220
+ attributionKey: attributionKey,
221
+ change: {
222
+ from: pos,
223
+ to: nodeEnd,
224
+ name: node.type.name
225
+ },
226
+ colorScheme: colorScheme,
227
+ isInserted: isInserted,
228
+ isActive: isActive,
229
+ shouldHideDeleted: shouldHideDeleted,
230
+ showContributorTags: showContributorTags,
231
+ showIndicators: showIndicators,
232
+ doc: doc,
233
+ diffType: diffType,
234
+ tagMountContext: tagMountContext
235
+ })));
236
+ }
237
+ } catch (err) {
238
+ _iterator.e(err);
239
+ } finally {
240
+ _iterator.f();
241
+ }
242
+ return decorations;
243
+ }
244
+
202
245
  // Coarse path: inside the large table, only cell/header overlays are kept — the
203
246
  // table/row/paragraph decorations there are redundant and cause expensive per-cell
204
247
  // re-render. Blocks outside the table keep their normal decorations.
@@ -212,18 +255,18 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
212
255
  }
213
256
  }
214
257
  if (node.isBlock && (!(0, _isExtendedEnabled.isExtendedEnabled)(diffType) || pos + node.nodeSize <= to)) {
215
- var nodeEnd = pos + node.nodeSize;
216
- var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
258
+ var _nodeEnd = pos + node.nodeSize;
259
+ var _isActive = isRangeActive(activeIndexPos, pos, _nodeEnd);
217
260
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
218
261
  attributionKey: attributionKey,
219
262
  change: {
220
263
  from: pos,
221
- to: nodeEnd,
264
+ to: _nodeEnd,
222
265
  name: node.type.name
223
266
  },
224
267
  colorScheme: colorScheme,
225
268
  isInserted: isInserted,
226
- isActive: isActive,
269
+ isActive: _isActive,
227
270
  shouldHideDeleted: shouldHideDeleted,
228
271
  showContributorTags: showContributorTags,
229
272
  showIndicators: showIndicators,
@@ -346,13 +389,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
346
389
  }
347
390
  var pendingAttrSteps = [];
348
391
  var stepMaps = [];
349
- var _iterator = _createForOfIteratorHelper(simplifiedSteps.entries()),
350
- _step;
392
+ var _iterator2 = _createForOfIteratorHelper(simplifiedSteps.entries()),
393
+ _step2;
351
394
  try {
352
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
353
- var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
354
- stepIndex = _step$value[0],
355
- step = _step$value[1];
395
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
396
+ var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
397
+ stepIndex = _step2$value[0],
398
+ step = _step2$value[1];
356
399
  var result = step.apply(steppedDoc);
357
400
  if (result.failed === null && result.doc) {
358
401
  if ((0, _getAttrChangeRanges.stepIsValidAttrChange)(step, steppedDoc, result.doc)) {
@@ -371,9 +414,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
371
414
  // Gate the complete attribution fix: mapped positions and step-time nodes are used together.
372
415
  // The fallback deliberately preserves the legacy raw-position lookups for rollout safety.
373
416
  } catch (err) {
374
- _iterator.e(err);
417
+ _iterator2.e(err);
375
418
  } finally {
376
- _iterator.f();
419
+ _iterator2.f();
377
420
  }
378
421
  var attrStepContexts = (0, _fg.fg)('platform_editor_reduce_diff_attr_sensitivity') ? pendingAttrSteps.flatMap(function (_ref1) {
379
422
  var afterNode = _ref1.afterNode,
@@ -461,7 +504,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
461
504
  tr: tr,
462
505
  attributedChanges: attributedChanges
463
506
  });
464
- changes = (0, _simplifyChangesWithAttribution.collapseOverlappingChanges)(uncollapsedChanges);
507
+ changes = (0, _simplifyChangesWithAttribution.collapseOverlappingChanges)(uncollapsedChanges, tr.doc);
465
508
 
466
509
  // Still colours-only: a tags-only diff keeps the plain colour scheme.
467
510
  if (attributionColoringEnabled) {
@@ -558,13 +601,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
558
601
  // table (the cell overlays added below provide the highlight without the
559
602
  // expensive content re-render); leaf blocks outside the table still get theirs.
560
603
  var coarseTables = useCoarseTableDecoration ? tableRanges(tr.doc, change.fromB, change.toB) : [];
561
- var _iterator2 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
562
- _step2;
604
+ var _iterator3 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
605
+ _step3;
563
606
  try {
564
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
565
- var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
566
- from = _step2$value[0],
567
- to = _step2$value[1];
607
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
608
+ var _step3$value = (0, _slicedToArray2.default)(_step3.value, 2),
609
+ from = _step3$value[0],
610
+ to = _step3$value[1];
568
611
  if (useCoarseTableDecoration && isInsideTable(coarseTables, from)) {
569
612
  continue;
570
613
  }
@@ -591,9 +634,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
591
634
  }))));
592
635
  }
593
636
  } catch (err) {
594
- _iterator2.e(err);
637
+ _iterator3.e(err);
595
638
  } finally {
596
- _iterator2.f();
639
+ _iterator3.f();
597
640
  }
598
641
  } else {
599
642
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)(_objectSpread({
@@ -620,6 +663,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
620
663
  doc: tr.doc,
621
664
  from: change.fromB,
622
665
  to: change.toB,
666
+ // A split piece's own range no longer covers the container it opened (EDITOR-8932).
667
+ nodeRanges: change.blockNodeRangesB,
623
668
  colorScheme: changeColorScheme
624
669
  }, (0, _isExtendedEnabled.isExtendedEnabled)(diffType) && {
625
670
  isInserted: isInserted,
@@ -10,11 +10,17 @@ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers
10
10
  var _prosemirrorChangeset = require("prosemirror-changeset");
11
11
  var _attributions = require("../decorations/colorSchemes/attributions");
12
12
  var _optimizeChanges = require("./optimizeChanges");
13
- 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; }
14
- 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) { (0, _defineProperty2.default)(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; }
15
13
  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; } } }; }
16
14
  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; } }
17
15
  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; }
16
+ 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; }
17
+ 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) { (0, _defineProperty2.default)(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; }
18
+ /**
19
+ * A change, plus the block nodes it owns when it is one piece of a split — a piece's own range no
20
+ * longer covers the container it opened. Absent on an unsplit change, which decorates the blocks
21
+ * inside its own range as before.
22
+ */
23
+
18
24
  var getAttributionIdentity = function getAttributionIdentity(change) {
19
25
  var identities = new Set();
20
26
  for (var _i = 0, _arr = [].concat((0, _toConsumableArray2.default)(change.deleted), (0, _toConsumableArray2.default)(change.inserted)); _i < _arr.length; _i++) {
@@ -36,55 +42,265 @@ var byNewDocRange = function byNewDocRange(left, right) {
36
42
  return left.fromB - right.fromB || left.toB - right.toB;
37
43
  };
38
44
 
45
+ /** Union of the given ranges, with overlapping and touching ones merged. */
46
+ var mergeRanges = function mergeRanges(ranges) {
47
+ return (0, _toConsumableArray2.default)(ranges).sort(function (left, right) {
48
+ return left.from - right.from;
49
+ }).reduce(function (merged, range) {
50
+ var last = merged[merged.length - 1];
51
+ if (last && range.from <= last.to) {
52
+ last.to = Math.max(last.to, range.to);
53
+ return merged;
54
+ }
55
+ return [].concat((0, _toConsumableArray2.default)(merged), [_objectSpread({}, range)]);
56
+ }, []);
57
+ };
58
+
59
+ /** Whether `inner` sits wholly inside `outer` and covers less of the document. */
60
+ var isContained = function isContained(outer, inner) {
61
+ return inner.toB > inner.fromB && outer.fromB <= inner.fromB && inner.toB <= outer.toB && inner.toB - inner.fromB < outer.toB - outer.fromB;
62
+ };
63
+
39
64
  /**
40
- * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
41
- * independently, so runs either side of an unattributed step can come back covering the same
42
- * characters which stacks two inline decorations and renders one contributor tag per copy.
43
- *
44
- * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
45
- * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
46
- * is not credited to it.
65
+ * The block nodes in `change` whose opening position `ownsPosition` claims. Ownership is by opening
66
+ * position so a block another contributor added inside a container is not claimed by whoever opened
67
+ * the container. `undefined` for a range this document cannot resolve, which falls the piece back
68
+ * to decorating its own range.
47
69
  */
48
- var collapseOverlappingChanges = exports.collapseOverlappingChanges = function collapseOverlappingChanges(changes) {
49
- if (changes.length <= 1) {
50
- return changes;
70
+ var blockNodeRanges = function blockNodeRanges(doc, change, ownsPosition) {
71
+ if (!doc || change.from >= change.to || change.to > doc.content.size) {
72
+ return undefined;
51
73
  }
74
+ var ranges = [];
75
+ doc.nodesBetween(change.from, change.to, function (node, pos) {
76
+ if (node.isBlock && ownsPosition(pos) && pos + node.nodeSize <= change.to) {
77
+ ranges.push({
78
+ from: pos,
79
+ to: pos + node.nodeSize
80
+ });
81
+ }
82
+ return true;
83
+ });
84
+ return ranges;
85
+ };
86
+
87
+ /**
88
+ * Splits a change whose spans name more than one contributor into one change per run of consecutive
89
+ * spans sharing a key.
90
+ *
91
+ * `prosemirror-changeset` merges contiguous inserted content into one change while keeping per-span
92
+ * data, so a panel inserted by one contributor and edited inside by another arrives as a single
93
+ * change holding every span — and the latest-writer rule credited all of it, panel included, to
94
+ * whoever wrote last (EDITOR-8932).
95
+ */
96
+ var splitMixedIdentityChanges = function splitMixedIdentityChanges(changes, doc) {
97
+ return changes.flatMap(function (change) {
98
+ if (getAttributionIdentity(change).mergeable) {
99
+ return [change];
100
+ }
101
+ var runs = [];
102
+ var offset = change.fromB;
103
+ var _iterator = _createForOfIteratorHelper(change.inserted),
104
+ _step;
105
+ try {
106
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
107
+ var span = _step.value;
108
+ var key = (0, _attributions.getAttributionKey)(span.data);
109
+ var last = runs[runs.length - 1];
110
+ if (last && last.key === key) {
111
+ last.to = offset + span.length;
112
+ last.spans.push(span);
113
+ } else {
114
+ runs.push({
115
+ key: key,
116
+ from: offset,
117
+ to: offset + span.length,
118
+ spans: [span]
119
+ });
120
+ }
121
+ offset += span.length;
122
+ }
52
123
 
124
+ // The offsets above are only positions if the spans partition the inserted range.
125
+ } catch (err) {
126
+ _iterator.e(err);
127
+ } finally {
128
+ _iterator.f();
129
+ }
130
+ if (runs.length < 2 || offset !== change.toB) {
131
+ return [change];
132
+ }
133
+ return runs.map(function (run, index) {
134
+ var owned = blockNodeRanges(doc, {
135
+ from: change.fromB,
136
+ to: change.toB
137
+ }, function (pos) {
138
+ return pos >= run.from && pos < run.to;
139
+ });
140
+ return _objectSpread(_objectSpread({}, change), {}, {
141
+ fromA: index === 0 ? change.fromA : change.toA,
142
+ fromB: run.from,
143
+ toB: run.to,
144
+ deleted: index === 0 ? change.deleted : [],
145
+ inserted: run.spans
146
+ }, owned ? {
147
+ blockNodeRangesB: owned
148
+ } : {});
149
+ });
150
+ });
151
+ };
152
+
153
+ /**
154
+ * Subtracts every other author's contained change from a change's range, so both contributions
155
+ * survive. Applies when the contributors arrive as separate changes rather than as spans of one —
156
+ * the `step` diff type. A contained change by the same author, and one that only partially
157
+ * overlaps, are left for the collapse below.
158
+ *
159
+ * The leading fragment keeps the deleted side, so one deletion is not drawn once per fragment.
160
+ */
161
+ var splitContainedChanges = function splitContainedChanges(changes, doc) {
162
+ return changes.flatMap(function (change) {
163
+ var _getAttributionIdenti = getAttributionIdentity(change),
164
+ key = _getAttributionIdenti.key;
165
+ var inner = mergeRanges(changes.filter(function (other) {
166
+ var otherKey = getAttributionIdentity(other).key;
167
+ return other !== change && key !== undefined && otherKey !== undefined && otherKey !== key && isContained(change, other);
168
+ }).map(function (_ref) {
169
+ var fromB = _ref.fromB,
170
+ toB = _ref.toB;
171
+ return {
172
+ from: fromB,
173
+ to: toB
174
+ };
175
+ }));
176
+ if (inner.length === 0) {
177
+ return [change];
178
+ }
179
+ var gaps = [];
180
+ var cursor = change.fromB;
181
+ var _iterator2 = _createForOfIteratorHelper(inner),
182
+ _step2;
183
+ try {
184
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
185
+ var _step2$value = _step2.value,
186
+ from = _step2$value.from,
187
+ to = _step2$value.to;
188
+ if (from > cursor) {
189
+ gaps.push({
190
+ from: cursor,
191
+ to: from
192
+ });
193
+ }
194
+ cursor = Math.max(cursor, to);
195
+ }
196
+ } catch (err) {
197
+ _iterator2.e(err);
198
+ } finally {
199
+ _iterator2.f();
200
+ }
201
+ if (change.toB > cursor) {
202
+ gaps.push({
203
+ from: cursor,
204
+ to: change.toB
205
+ });
206
+ }
207
+
208
+ // Subtracted end to end, so nothing is left to highlight — but the blocks are still owned.
209
+ var fragments = gaps.length > 0 ? gaps : [{
210
+ from: change.toB,
211
+ to: change.toB
212
+ }];
213
+ // The outer author's: every block in their range that no inner change opened.
214
+ var owned = blockNodeRanges(doc, {
215
+ from: change.fromB,
216
+ to: change.toB
217
+ }, function (pos) {
218
+ return !inner.some(function (_ref2) {
219
+ var from = _ref2.from,
220
+ to = _ref2.to;
221
+ return pos >= from && pos < to;
222
+ });
223
+ });
224
+ return fragments.map(function (fragment, index) {
225
+ return _objectSpread(_objectSpread({}, change), {}, {
226
+ fromA: index === 0 ? change.fromA : change.toA,
227
+ fromB: fragment.from,
228
+ toB: fragment.to,
229
+ deleted: index === 0 ? change.deleted : []
230
+ }, owned ? {
231
+ blockNodeRangesB: index === 0 ? owned : []
232
+ } : {});
233
+ });
234
+ });
235
+ };
236
+
237
+ /**
238
+ * The blocks a merged change owns. Both sides must name theirs, or the merge falls back to its own
239
+ * range — narrowing it could drop a block altogether.
240
+ */
241
+ var mergeBlockNodeRanges = function mergeBlockNodeRanges(left, right) {
242
+ return left.blockNodeRangesB && right.blockNodeRangesB ? [].concat((0, _toConsumableArray2.default)(left.blockNodeRangesB), (0, _toConsumableArray2.default)(right.blockNodeRangesB)) : undefined;
243
+ };
244
+
245
+ /** Unions the changes whose new-document ranges strictly overlap, leaving touching ones alone. */
246
+ var collapse = function collapse(changes) {
53
247
  // A zero-length new-document range paints no inline decoration, so it is kept aside rather than
54
248
  // folded into a neighbouring insertion.
55
249
  var emptyRanges = changes.filter(function (change) {
56
250
  return change.toB <= change.fromB;
57
251
  });
58
252
  var collapsed = [];
59
- var _iterator = _createForOfIteratorHelper(changes.filter(function (change) {
253
+ var _iterator3 = _createForOfIteratorHelper(changes.filter(function (change) {
60
254
  return change.toB > change.fromB;
61
255
  }).sort(byNewDocRange)),
62
- _step;
256
+ _step3;
63
257
  try {
64
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
65
- var change = _step.value;
258
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
259
+ var change = _step3.value;
66
260
  var current = collapsed[collapsed.length - 1];
67
261
  if (current && change.fromB < current.toB) {
68
- collapsed[collapsed.length - 1] = {
262
+ var blockNodeRangesB = mergeBlockNodeRanges(current, change);
263
+ collapsed[collapsed.length - 1] = _objectSpread({
69
264
  fromA: Math.min(current.fromA, change.fromA),
70
265
  toA: Math.max(current.toA, change.toA),
71
266
  fromB: Math.min(current.fromB, change.fromB),
72
267
  toB: Math.max(current.toB, change.toB),
73
268
  deleted: [].concat((0, _toConsumableArray2.default)(current.deleted), (0, _toConsumableArray2.default)(change.deleted)),
74
269
  inserted: [].concat((0, _toConsumableArray2.default)(current.inserted), (0, _toConsumableArray2.default)(change.inserted))
75
- };
270
+ }, blockNodeRangesB ? {
271
+ blockNodeRangesB: blockNodeRangesB
272
+ } : {});
76
273
  } else {
77
274
  collapsed.push(_objectSpread({}, change));
78
275
  }
79
276
  }
80
277
  } catch (err) {
81
- _iterator.e(err);
278
+ _iterator3.e(err);
82
279
  } finally {
83
- _iterator.f();
280
+ _iterator3.f();
84
281
  }
85
282
  return [].concat(collapsed, (0, _toConsumableArray2.default)(emptyRanges)).sort(byNewDocRange);
86
283
  };
87
284
 
285
+ /**
286
+ * Collapses changes whose new-document ranges strictly overlap. Attribution runs are simplified
287
+ * independently, so runs either side of an unattributed step can come back covering the same
288
+ * characters — which stacks two inline decorations and renders one contributor tag per copy.
289
+ *
290
+ * Merging keeps every span from both sides, so the latest-writer rule still decides whose tag is
291
+ * shown. Ranges that merely touch are left alone, so an unattributed edit next to an attributed one
292
+ * is not credited to it.
293
+ *
294
+ * Contributors are separated first, by `splitMixedIdentityChanges` and `splitContainedChanges`.
295
+ * Both leave ranges that only touch, so they survive the collapse.
296
+ *
297
+ * Carries no gate of its own: the only caller reaches it through the attributed changeset, which
298
+ * already requires `confluence_ncs_step_diffing_version_history`.
299
+ */
300
+ var collapseOverlappingChanges = exports.collapseOverlappingChanges = function collapseOverlappingChanges(changes, doc) {
301
+ return collapse(splitContainedChanges(splitMixedIdentityChanges(changes, doc), doc));
302
+ };
303
+
88
304
  /**
89
305
  * Simplifies and optimizes consecutive changes without crossing an attribution boundary.
90
306
  * A change which already contains more than one identity is deliberately kept isolated.
@@ -114,12 +330,12 @@ var simplifyChangesWithAttribution = exports.simplifyChangesWithAttribution = fu
114
330
  run = [];
115
331
  runIdentity = undefined;
116
332
  };
117
- var _iterator2 = _createForOfIteratorHelper(changes),
118
- _step2;
333
+ var _iterator4 = _createForOfIteratorHelper(changes),
334
+ _step4;
119
335
  try {
120
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
336
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
121
337
  var _runIdentity;
122
- var change = _step2.value;
338
+ var change = _step4.value;
123
339
  var identity = getAttributionIdentity(change);
124
340
  var canJoinRun = ((_runIdentity = runIdentity) === null || _runIdentity === void 0 ? void 0 : _runIdentity.mergeable) === true && identity.mergeable && runIdentity.key === identity.key;
125
341
  if (run.length > 0 && !canJoinRun) {
@@ -132,9 +348,9 @@ var simplifyChangesWithAttribution = exports.simplifyChangesWithAttribution = fu
132
348
  }
133
349
  }
134
350
  } catch (err) {
135
- _iterator2.e(err);
351
+ _iterator4.e(err);
136
352
  } finally {
137
- _iterator2.f();
353
+ _iterator4.f();
138
354
  }
139
355
  flush();
140
356
  return result;