@atlaskit/editor-plugin-show-diff 20.0.0 → 20.0.2

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 (35) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +9 -10
  3. package/dist/cjs/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +92 -26
  4. package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +46 -22
  5. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +4 -5
  6. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +4 -1
  7. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
  8. package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +3 -1
  9. package/dist/cjs/pm-plugins/getScrollableDecorations.js +1 -8
  10. package/dist/cjs/pm-plugins/main.js +5 -2
  11. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +9 -10
  12. package/dist/es2019/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +85 -14
  13. package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +42 -22
  14. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +4 -5
  15. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +4 -1
  16. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
  17. package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +1 -1
  18. package/dist/es2019/pm-plugins/getScrollableDecorations.js +1 -6
  19. package/dist/es2019/pm-plugins/main.js +5 -1
  20. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +9 -10
  21. package/dist/esm/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +92 -26
  22. package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +46 -22
  23. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +4 -5
  24. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +4 -1
  25. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
  26. package/dist/esm/pm-plugins/decorations/extractContributorTags.js +3 -1
  27. package/dist/esm/pm-plugins/getScrollableDecorations.js +1 -8
  28. package/dist/esm/pm-plugins/main.js +5 -2
  29. package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +6 -4
  30. package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +2 -2
  31. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +2 -3
  32. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +1 -0
  33. package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +1 -0
  34. package/dist/types/pm-plugins/main.d.ts +1 -0
  35. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 20.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4d3e3d0afb998`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4d3e3d0afb998) -
8
+ Fix "Removed" labels on table diffs:
9
+ - Keep the label on deleted table rows attached to its row when the page scrolls.
10
+ - Identify deleted table columns by cell `localId` when a diff replaces the whole table, so
11
+ columns with matching content, such as empty columns, are labelled correctly. Tables without
12
+ usable cell ids fall back to comparing whole columns, and only label columns when that
13
+ comparison has one answer.
14
+
15
+ - [`b8c8e84ed75bd`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b8c8e84ed75bd) -
16
+ Keep unattributed diff decorations separate from attributed changes in navigation and active
17
+ highlighting under `confluence_ncs_step_diffing_version_history`.
18
+ - Updated dependencies
19
+
20
+ ## 20.0.1
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies
25
+
3
26
  ## 20.0.0
4
27
 
5
28
  ### Patch Changes
@@ -127,19 +127,18 @@ var leafTextblockRanges = function leafTextblockRanges(doc, from, to) {
127
127
  // once it has more leaf textblocks (~cells) than this threshold. Small tables
128
128
  // keep the precise per-cell path.
129
129
  var LARGE_TABLE_LEAF_THRESHOLD = 40;
130
-
131
130
  /**
132
- * An active scrollable change may contain several underlying diff changes. Treat ranges that
133
- * overlap or touch the active range as part of the same active customer-facing edit.
131
+ * An active scrollable change may contain several underlying diff changes. With attribution, only
132
+ * ranges belonging to the selected contributor (including the anonymous contributor) are active.
134
133
  */
135
- var isRangeActive = function isRangeActive(activeIndexPos, from, to) {
134
+ var isRangeActive = function isRangeActive(activeIndexPos, from, to, attributionKey) {
136
135
  if (activeIndexPos === undefined) {
137
136
  return undefined;
138
137
  }
139
138
  if (!(0, _fg.fg)('confluence_ncs_step_diffing_version_history')) {
140
139
  return from === activeIndexPos.from && to === activeIndexPos.to;
141
140
  }
142
- return from <= activeIndexPos.to && activeIndexPos.from <= to;
141
+ return from <= activeIndexPos.to && activeIndexPos.from <= to && (!('attributionKey' in activeIndexPos) || activeIndexPos.attributionKey === attributionKey);
143
142
  };
144
143
  var isLargeInsertedTableRange = function isLargeInsertedTableRange(doc, from, to) {
145
144
  var containsTable = false;
@@ -214,7 +213,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
214
213
  if (!(node !== null && node !== void 0 && node.isBlock)) {
215
214
  continue;
216
215
  }
217
- var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
216
+ var isActive = isRangeActive(activeIndexPos, pos, nodeEnd, attributionKey);
218
217
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
219
218
  attributionKey: attributionKey,
220
219
  change: {
@@ -258,7 +257,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
258
257
  }
259
258
  if (node.isBlock && (!requireContainedBlocks || pos >= from) && pos + node.nodeSize <= to) {
260
259
  var _nodeEnd = pos + node.nodeSize;
261
- var _isActive = isRangeActive(activeIndexPos, pos, _nodeEnd);
260
+ var _isActive = isRangeActive(activeIndexPos, pos, _nodeEnd, attributionKey);
262
261
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
263
262
  attributionKey: attributionKey,
264
263
  change: {
@@ -542,7 +541,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
542
541
  var leftAnchorId = (0, _fg.fg)('platform_editor_ai_show_diff_patch_2') ? "replacement-".concat(change.fromA, "-").concat(change.toA, "-").concat(change.fromB, "-").concat(change.toB) : undefined;
543
542
  var changeColorScheme = attributionColors ? (0, _attributions.getColorSchemeForChange)(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
544
543
  var attributionKey = showContributorTags ? (0, _attributions.getAttributionKeyForChange)(change, attributedChanges) : undefined;
545
- var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
544
+ var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
546
545
  var deletedColumns = change.deletedColumns;
547
546
  // On an inverted diff the table that lost columns is the one in `tr.doc`, so the column
548
547
  // indices address it directly and each label anchors inside its own cell.
@@ -779,7 +778,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
779
778
  var _attributionColors$ge, _attributionColors;
780
779
  var changeColorScheme = change.attributionKey ? (_attributionColors$ge = (_attributionColors = attributionColors) === null || _attributionColors === void 0 ? void 0 : _attributionColors.get(change.attributionKey)) !== null && _attributionColors$ge !== void 0 ? _attributionColors$ge : colorScheme : colorScheme;
781
780
  var attributionKey = showContributorTags ? change.attributionKey : undefined;
782
- var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
781
+ var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
783
782
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
784
783
  attributionKey: attributionKey,
785
784
  change: change,
@@ -808,7 +807,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
808
807
  var attributionKey = showContributorTags ? change.attributionKey : undefined;
809
808
  if (change.isInline) {
810
809
  // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
811
- var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
810
+ var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
812
811
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
813
812
  attributionKey: attributionKey,
814
813
  change: change,
@@ -13,7 +13,6 @@ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol
13
13
  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; } }
14
14
  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; }
15
15
  var CELL_CONTENT_OFFSET = exports.CELL_CONTENT_OFFSET = 2;
16
- var IGNORED_CELL_ATTRS = ['localId', 'colwidth'];
17
16
  var tablesIn = function tablesIn(doc) {
18
17
  var tables = [];
19
18
  doc.descendants(function (node, pos) {
@@ -63,41 +62,107 @@ var positionMatchedColumns = function positionMatchedColumns(_ref) {
63
62
  }
64
63
  return surviving;
65
64
  };
66
- var addContentMatchedColumns = function addContentMatchedColumns(surviving, before, after) {
67
- var _before$node$firstChi, _before$node$firstChi2, _after$node$firstChil, _after$node$firstChil2;
68
- var beforeCells = (_before$node$firstChi = (_before$node$firstChi2 = before.node.firstChild) === null || _before$node$firstChi2 === void 0 ? void 0 : _before$node$firstChi2.children) !== null && _before$node$firstChi !== void 0 ? _before$node$firstChi : [];
69
- var afterCells = (_after$node$firstChil = (_after$node$firstChil2 = after.node.firstChild) === null || _after$node$firstChil2 === void 0 ? void 0 : _after$node$firstChil2.children) !== null && _after$node$firstChil !== void 0 ? _after$node$firstChil : [];
70
- var _iterator = _createForOfIteratorHelper(afterCells),
65
+ var headerLocalIds = function headerLocalIds(table) {
66
+ var _table$node$firstChil, _table$node$firstChil2;
67
+ return ((_table$node$firstChil = (_table$node$firstChil2 = table.node.firstChild) === null || _table$node$firstChil2 === void 0 ? void 0 : _table$node$firstChil2.children) !== null && _table$node$firstChil !== void 0 ? _table$node$firstChil : []).map(function (cell) {
68
+ var _cell$attrs$localId;
69
+ return (_cell$attrs$localId = cell.attrs.localId) !== null && _cell$attrs$localId !== void 0 ? _cell$attrs$localId : undefined;
70
+ });
71
+ };
72
+
73
+ /**
74
+ * Cells keep their `localId` when a column is deleted, so the first row's ids name each remaining
75
+ * column even when the steps replaced the whole table. Missing or repeated ids, such as in content
76
+ * pasted from elsewhere, cannot name a column.
77
+ */
78
+ var localIdMatchedColumns = function localIdMatchedColumns(before, after) {
79
+ var beforeIds = headerLocalIds(before);
80
+ if (beforeIds.some(function (id) {
81
+ return !id;
82
+ }) || new Set(beforeIds).size !== beforeIds.length) {
83
+ return undefined;
84
+ }
85
+ var surviving = new Set();
86
+ var previousColumn = -1;
87
+ var _iterator = _createForOfIteratorHelper(headerLocalIds(after)),
71
88
  _step;
72
89
  try {
73
- var _loop = function _loop() {
74
- var afterCell = _step.value;
75
- var matches = beforeCells.flatMap(function (beforeCell, column) {
76
- return (0, _document.areNodesEqualIgnoreAttrs)(beforeCell, afterCell, IGNORED_CELL_ATTRS) ? [column] : [];
77
- });
78
- if (matches.length !== 1) {
79
- return {
80
- v: false
81
- };
82
- }
83
- surviving.add(matches[0]);
84
- },
85
- _ret;
86
90
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
87
- _ret = _loop();
88
- if (_ret) return _ret.v;
91
+ var id = _step.value;
92
+ var column = id ? beforeIds.indexOf(id) : -1;
93
+ if (column <= previousColumn) {
94
+ return undefined;
95
+ }
96
+ surviving.add(column);
97
+ previousColumn = column;
89
98
  }
90
99
  } catch (err) {
91
100
  _iterator.e(err);
92
101
  } finally {
93
102
  _iterator.f();
94
103
  }
95
- var ordered = (0, _toConsumableArray2.default)(surviving);
96
- return !ordered.some(function (column, index) {
97
- return index > 0 && column <= ordered[index - 1];
104
+ return surviving;
105
+ };
106
+ var IGNORED_CELL_ATTRS = ['localId', 'colwidth'];
107
+
108
+ /** Rows may also have been deleted, so the remaining cells only need to appear in order. */
109
+ var isColumnKept = function isColumnKept(before, beforeColumn, after, afterColumn) {
110
+ var beforeRow = 0;
111
+ return after.node.children.every(function (afterRow) {
112
+ var afterCell = afterRow.maybeChild(afterColumn);
113
+ while (beforeRow < before.node.childCount) {
114
+ var beforeCell = before.node.child(beforeRow++).maybeChild(beforeColumn);
115
+ if (afterCell && beforeCell && (0, _document.areNodesEqualIgnoreAttrs)(beforeCell, afterCell, IGNORED_CELL_ATTRS)) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
98
120
  });
99
121
  };
122
+
123
+ /**
124
+ * Pairs each remaining column with an equal original column, keeping their order, and takes each
125
+ * pair from the `direction` end first. Returns the original column of each remaining column.
126
+ */
127
+ var pairColumnsInOrder = function pairColumnsInOrder(beforeWidth, afterWidth, isMatch, direction) {
128
+ var step = direction === 'left' ? 1 : -1;
129
+ var pairing = [];
130
+ var beforeColumn = direction === 'left' ? 0 : beforeWidth - 1;
131
+ var afterColumn = direction === 'left' ? 0 : afterWidth - 1;
132
+ for (; afterColumn >= 0 && afterColumn < afterWidth; afterColumn += step) {
133
+ while (beforeColumn >= 0 && beforeColumn < beforeWidth && !isMatch(beforeColumn, afterColumn)) {
134
+ beforeColumn += step;
135
+ }
136
+ if (beforeColumn < 0 || beforeColumn >= beforeWidth) {
137
+ return undefined;
138
+ }
139
+ pairing.push(beforeColumn);
140
+ beforeColumn += step;
141
+ }
142
+ return direction === 'left' ? pairing : pairing.reverse();
143
+ };
144
+
145
+ /**
146
+ * For tables whose cells have no usable ids, such as content created before cells had ids. Every
147
+ * valid pairing of remaining columns to original ones lies between the leftmost and the rightmost
148
+ * pairing, so the columns are only reported when those two agree. Otherwise, such as when several
149
+ * original columns are identical, which of them were removed cannot be known.
150
+ */
151
+ var uniquelyContentMatchedColumns = function uniquelyContentMatchedColumns(before, after, beforeWidth, afterWidth) {
152
+ var isMatch = function isMatch(beforeColumn, afterColumn) {
153
+ return isColumnKept(before, beforeColumn, after, afterColumn);
154
+ };
155
+ var leftmost = pairColumnsInOrder(beforeWidth, afterWidth, isMatch, 'left');
156
+ var rightmost = pairColumnsInOrder(beforeWidth, afterWidth, isMatch, 'right');
157
+ if (!leftmost || !rightmost || leftmost.some(function (column, index) {
158
+ return column !== rightmost[index];
159
+ })) {
160
+ return undefined;
161
+ }
162
+ return new Set(leftmost);
163
+ };
100
164
  var findDeletedColumns = function findDeletedColumns(before, after, afterToBefore) {
165
+ var _localIdMatchedColumn;
101
166
  var beforeMap = _tableMap.TableMap.get(before.node);
102
167
  var afterMap = _tableMap.TableMap.get(after.node);
103
168
  // Merged or malformed tables do not have reliable column indices.
@@ -105,13 +170,14 @@ var findDeletedColumns = function findDeletedColumns(before, after, afterToBefor
105
170
  return [];
106
171
  }
107
172
  var beforeOffsets = columnOffsets(beforeMap, before.node);
108
- var surviving = positionMatchedColumns({
173
+ var positionMatched = positionMatchedColumns({
109
174
  before: before,
110
175
  after: after,
111
176
  afterToBefore: afterToBefore,
112
177
  beforeOffsets: beforeOffsets
113
178
  });
114
- if (surviving.size !== afterMap.width && !addContentMatchedColumns(surviving, before, after)) {
179
+ var surviving = positionMatched.size === afterMap.width ? positionMatched : (_localIdMatchedColumn = localIdMatchedColumns(before, after)) !== null && _localIdMatchedColumn !== void 0 ? _localIdMatchedColumn : uniquelyContentMatchedColumns(before, after, beforeMap.width, afterMap.width);
180
+ if (!surviving) {
115
181
  return [];
116
182
  }
117
183
  return beforeOffsets.flatMap(function (_, column) {
@@ -38,6 +38,11 @@ var getAttributionIdentity = function getAttributionIdentity(change) {
38
38
  mergeable: true
39
39
  };
40
40
  };
41
+ var hasContributor = function hasContributor(change) {
42
+ return [].concat((0, _toConsumableArray2.default)(change.deleted), (0, _toConsumableArray2.default)(change.inserted)).some(function (span) {
43
+ return (0, _attributions.getAttributionKey)(span.data) !== undefined;
44
+ });
45
+ };
41
46
  var byNewDocRange = function byNewDocRange(left, right) {
42
47
  return left.fromB - right.fromB || left.toB - right.toB;
43
48
  };
@@ -95,12 +100,28 @@ var blockNodeRanges = function blockNodeRanges(doc, change, ownsPosition) {
95
100
  */
96
101
  var splitMixedIdentityChanges = function splitMixedIdentityChanges(changes, doc) {
97
102
  return changes.flatMap(function (change) {
98
- if (getAttributionIdentity(change).mergeable) {
99
- return [change];
103
+ var _change$inserted$;
104
+ // A replacement can carry an anonymous deletion and an authored insertion. The deleted side
105
+ // needs its own zero-width change or the insertion's actor claims the deletion too.
106
+ var deletedKeys = new Set(change.deleted.map(function (span) {
107
+ return (0, _attributions.getAttributionKey)(span.data);
108
+ }));
109
+ var firstInsertedKey = (0, _attributions.getAttributionKey)((_change$inserted$ = change.inserted[0]) === null || _change$inserted$ === void 0 ? void 0 : _change$inserted$.data);
110
+ var detachDeleted = change.deleted.length > 0 && change.inserted.length > 0 && deletedKeys.size === 1 && !deletedKeys.has(firstInsertedKey) && (deletedKeys.has(undefined) || firstInsertedKey === undefined);
111
+ var deletedChange = detachDeleted ? [_objectSpread(_objectSpread({}, change), {}, {
112
+ toB: change.fromB,
113
+ inserted: []
114
+ })] : [];
115
+ var contentChange = detachDeleted ? _objectSpread(_objectSpread({}, change), {}, {
116
+ fromA: change.toA,
117
+ deleted: []
118
+ }) : change;
119
+ if (getAttributionIdentity(contentChange).mergeable) {
120
+ return [].concat(deletedChange, [contentChange]);
100
121
  }
101
122
  var runs = [];
102
- var offset = change.fromB;
103
- var _iterator = _createForOfIteratorHelper(change.inserted),
123
+ var offset = contentChange.fromB;
124
+ var _iterator = _createForOfIteratorHelper(contentChange.inserted),
104
125
  _step;
105
126
  try {
106
127
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -127,32 +148,32 @@ var splitMixedIdentityChanges = function splitMixedIdentityChanges(changes, doc)
127
148
  } finally {
128
149
  _iterator.f();
129
150
  }
130
- if (runs.length < 2 || offset !== change.toB) {
131
- return [change];
151
+ if (runs.length < 2 || offset !== contentChange.toB) {
152
+ return [].concat(deletedChange, [contentChange]);
132
153
  }
133
- return runs.map(function (run, index) {
154
+ return [].concat(deletedChange, (0, _toConsumableArray2.default)(runs.map(function (run, index) {
134
155
  var owned = blockNodeRanges(doc, {
135
- from: change.fromB,
136
- to: change.toB
156
+ from: contentChange.fromB,
157
+ to: contentChange.toB
137
158
  }, function (pos) {
138
159
  return pos >= run.from && pos < run.to;
139
160
  });
140
- return _objectSpread(_objectSpread({}, change), {}, {
141
- fromA: index === 0 ? change.fromA : change.toA,
161
+ return _objectSpread(_objectSpread({}, contentChange), {}, {
162
+ fromA: index === 0 ? contentChange.fromA : contentChange.toA,
142
163
  fromB: run.from,
143
164
  toB: run.to,
144
- deleted: index === 0 ? change.deleted : [],
165
+ deleted: index === 0 ? contentChange.deleted : [],
145
166
  inserted: run.spans
146
167
  }, owned ? {
147
168
  blockNodeRangesB: owned
148
169
  } : {});
149
- });
170
+ })));
150
171
  });
151
172
  };
152
173
 
153
174
  /**
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 —
175
+ * Subtracts every differently attributed contained change from a change's range, including when
176
+ * one has no contributor. Applies when the changes arrive separately rather than as spans of one —
156
177
  * the `step` diff type. A contained change by the same author, and one that only partially
157
178
  * overlaps, are left for the collapse below.
158
179
  *
@@ -161,10 +182,11 @@ var splitMixedIdentityChanges = function splitMixedIdentityChanges(changes, doc)
161
182
  var splitContainedChanges = function splitContainedChanges(changes, doc) {
162
183
  return changes.flatMap(function (change) {
163
184
  var _getAttributionIdenti = getAttributionIdentity(change),
164
- key = _getAttributionIdenti.key;
185
+ key = _getAttributionIdenti.key,
186
+ mergeable = _getAttributionIdenti.mergeable;
165
187
  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);
188
+ var otherIdentity = getAttributionIdentity(other);
189
+ return other !== change && mergeable && otherIdentity.mergeable && otherIdentity.key !== key && isContained(change, other);
168
190
  }).map(function (_ref) {
169
191
  var fromB = _ref.fromB,
170
192
  toB = _ref.toB;
@@ -242,7 +264,7 @@ var mergeBlockNodeRanges = function mergeBlockNodeRanges(left, right) {
242
264
  return left.blockNodeRangesB && right.blockNodeRangesB ? [].concat((0, _toConsumableArray2.default)(left.blockNodeRangesB), (0, _toConsumableArray2.default)(right.blockNodeRangesB)) : undefined;
243
265
  };
244
266
 
245
- /** Unions the changes whose new-document ranges strictly overlap, leaving touching ones alone. */
267
+ /** Unions overlapping changes, except when only one side names a contributor. */
246
268
  var collapse = function collapse(changes) {
247
269
  // A zero-length new-document range paints no inline decoration, so it is kept aside rather than
248
270
  // folded into a neighbouring insertion.
@@ -258,7 +280,9 @@ var collapse = function collapse(changes) {
258
280
  for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
259
281
  var change = _step3.value;
260
282
  var current = collapsed[collapsed.length - 1];
261
- if (current && change.fromB < current.toB) {
283
+ var currentHasContributor = current && hasContributor(current);
284
+ var changeHasContributor = hasContributor(change);
285
+ if (current && change.fromB < current.toB && currentHasContributor === changeHasContributor) {
262
286
  var blockNodeRangesB = mergeBlockNodeRanges(current, change);
263
287
  collapsed[collapsed.length - 1] = _objectSpread({
264
288
  fromA: Math.min(current.fromA, change.fromA),
@@ -288,8 +312,8 @@ var collapse = function collapse(changes) {
288
312
  * characters — which stacks two inline decorations and renders one contributor tag per copy.
289
313
  *
290
314
  * 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.
315
+ * shown among attributed edits. An unattributed change never merges with an attributed one.
316
+ * Ranges that merely touch are left alone.
293
317
  *
294
318
  * Contributors are separated first, by `splitMixedIdentityChanges` and `splitContainedChanges`.
295
319
  * Both leave ranges that only touch, so they survive the collapse.
@@ -244,13 +244,12 @@ var getAttributionKeyForChanges = function getAttributionKeyForChanges(changes)
244
244
  };
245
245
 
246
246
  /**
247
- * The attribution key owning a change. Inline and step changes retain direct attribution spans, so
248
- * resolve those first. Grouped and smart-classified changes can lose that data; for those, fall
249
- * back to the latest overlapping attributed step.
247
+ * Preserve overlap attribution outside Confluence version history. Within version history, a
248
+ * recovery step's keyless spans must not inherit an overlapping NCS step's actor.
250
249
  */
251
250
  var getAttributionKeyForChange = exports.getAttributionKeyForChange = function getAttributionKeyForChange(change, attributedChanges) {
252
- var _getAttributionKeyFor;
253
- return (_getAttributionKeyFor = getAttributionKeyForChanges([change])) !== null && _getAttributionKeyFor !== void 0 ? _getAttributionKeyFor : getAttributionKeyForChanges(attributedChanges.filter(function (attributedChange) {
251
+ var directKey = getAttributionKeyForChanges([change]);
252
+ return directKey !== null && directKey !== void 0 ? directKey : (0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? undefined : getAttributionKeyForChanges(attributedChanges.filter(function (attributedChange) {
254
253
  return changesOverlap(change, attributedChange);
255
254
  }));
256
255
  };
@@ -222,7 +222,7 @@ var createChangedRowDOM = function createChangedRowDOM(_ref3) {
222
222
  return tr;
223
223
  };
224
224
  var supportsAnchorPositioning = function supportsAnchorPositioning() {
225
- return typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('top', 'anchor(--a top)');
225
+ return typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('top', 'anchor(--a top)') && CSS.supports('position-anchor', '--a');
226
226
  };
227
227
  var createAnchoredRemovedLozengeWidget = function createAnchoredRemovedLozengeWidget(_ref4) {
228
228
  var anchorName = _ref4.anchorName,
@@ -233,6 +233,9 @@ var createAnchoredRemovedLozengeWidget = function createAnchoredRemovedLozengeWi
233
233
  var lozenge = (0, _wrapBlockNodeView.createRemovedLozenge)(intl, isActive, colorScheme, true);
234
234
  var inset = lozenge.style.top;
235
235
  lozenge.style.setProperty('position', 'fixed');
236
+ // Fixed labels only follow their default anchor on scroll, and escape scroll-container clipping.
237
+ lozenge.style.setProperty('position-anchor', "--".concat(anchorName));
238
+ lozenge.style.setProperty('position-visibility', 'anchors-visible');
236
239
  lozenge.style.setProperty('top', "calc(anchor(--".concat(anchorName, " top) + ").concat(inset, ")"));
237
240
  lozenge.style.setProperty('left', "calc(anchor(--".concat(anchorName, " right) - ").concat(inset, ")"));
238
241
  lozenge.style.setProperty('right', 'auto');
@@ -148,7 +148,7 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
148
148
  // node renders beneath its replacement; otherwise anchor at the start (change.fromB).
149
149
  var anchorPos = placeBelow ? change.toB : change.fromB;
150
150
  var safeInsertPos = (0, _findSafeInsertPos.findSafeInsertPos)(newDoc, anchorPos, slice);
151
- var isActive = activeIndexPos && ((0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
151
+ var isActive = activeIndexPos && ((0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to && (!('attributionKey' in activeIndexPos) || activeIndexPos.attributionKey === attributionKey) : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
152
152
  if (deletedBlankLineCount > 0) {
153
153
  // The blocks have no content to serialize, so the deletion is shown as one struck-through
154
154
  // return glyph per line the author removed. A single widget, not one per line: the lines are
@@ -104,7 +104,9 @@ var owningStop = function owningStop(target, stops) {
104
104
  * containment, and the leading change of what it covers.
105
105
  */
106
106
  var resolveActiveTarget = function resolveActiveTarget(surviving, activeIndexPos) {
107
- return containedBy(surviving, activeIndexPos)[0];
107
+ return containedBy(surviving, activeIndexPos).find(function (target) {
108
+ return !(0, _fg.fg)('confluence_ncs_step_diffing_version_history') || !('attributionKey' in activeIndexPos) || target.decoration.spec.attributionKey === activeIndexPos.attributionKey;
109
+ });
108
110
  };
109
111
 
110
112
  /**
@@ -90,18 +90,13 @@ function groupTouchingDecorations(decorations, isInlineDecoration) {
90
90
  return a.from === b.from ? a.to - b.to : a.from - b.from;
91
91
  });
92
92
  sortedDecorations.forEach(function (decoration) {
93
- var _openGroups$find;
94
93
  // A gap ends the run: nothing before it can be joined.
95
94
  if (decoration.from > clusterTo) {
96
95
  openGroups = [];
97
96
  }
98
97
  var contributor = contributorOf(decoration);
99
- // An unattributed decoration names nobody to keep apart, so it joins whatever is open — a
100
- // decision item's own highlight must not split the stop from the tag on its list.
101
- var openGroup = (_openGroups$find = openGroups.find(function (group) {
98
+ var openGroup = openGroups.find(function (group) {
102
99
  return group.contributor === contributor;
103
- })) !== null && _openGroups$find !== void 0 ? _openGroups$find : contributor === '' ? openGroups[0] : openGroups.find(function (group) {
104
- return group.contributor === '';
105
100
  });
106
101
  if (openGroup === undefined) {
107
102
  var group = {
@@ -113,8 +108,6 @@ function groupTouchingDecorations(decorations, isInlineDecoration) {
113
108
  groups.push(group);
114
109
  openGroups = [].concat((0, _toConsumableArray2.default)(openGroups), [group]);
115
110
  } else {
116
- // The first contributor to join claims the group, so the next one still starts its own.
117
- openGroup.contributor = openGroup.contributor || contributor;
118
111
  openGroup.decorations.push(decoration);
119
112
  openGroup.to = Math.max(openGroup.to, decoration.to);
120
113
  }
@@ -12,6 +12,7 @@ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
12
12
  var _state2 = require("@atlaskit/editor-prosemirror/state");
13
13
  var _transformOverride = require("@atlaskit/editor-prosemirror/transform-override");
14
14
  var _view = require("@atlaskit/editor-prosemirror/view");
15
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
15
16
  var _announceActiveDiff = require("../ui/DiffNavigation/announceActiveDiff");
16
17
  var _calculateDiffDecorations = require("./calculateDiff/calculateDiffDecorations");
17
18
  var _decorationKeys = require("./decorations/decorationKeys");
@@ -177,10 +178,12 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
177
178
  var activeDecoration = _decorations[nextIndex];
178
179
  newPluginState = _objectSpread(_objectSpread({}, currentPluginState), {}, {
179
180
  activeIndex: nextIndex,
180
- activeIndexPos: activeDecoration ? {
181
+ activeIndexPos: activeDecoration ? _objectSpread({
181
182
  from: activeDecoration.from,
182
183
  to: activeDecoration.to
183
- } : undefined
184
+ }, (0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? {
185
+ attributionKey: (0, _decorationKeys.isDiffDecoration)(activeDecoration) ? activeDecoration.spec.attributionKey : undefined
186
+ } : {}) : undefined
184
187
  });
185
188
  // Recalculate decorations with the new active index
186
189
  var _calculateDiffDecorat2 = (0, _calculateDiffDecorations.calculateDiffDecorations)({
@@ -114,19 +114,18 @@ const leafTextblockRanges = (doc, from, to) => {
114
114
  // once it has more leaf textblocks (~cells) than this threshold. Small tables
115
115
  // keep the precise per-cell path.
116
116
  const LARGE_TABLE_LEAF_THRESHOLD = 40;
117
-
118
117
  /**
119
- * An active scrollable change may contain several underlying diff changes. Treat ranges that
120
- * overlap or touch the active range as part of the same active customer-facing edit.
118
+ * An active scrollable change may contain several underlying diff changes. With attribution, only
119
+ * ranges belonging to the selected contributor (including the anonymous contributor) are active.
121
120
  */
122
- const isRangeActive = (activeIndexPos, from, to) => {
121
+ const isRangeActive = (activeIndexPos, from, to, attributionKey) => {
123
122
  if (activeIndexPos === undefined) {
124
123
  return undefined;
125
124
  }
126
125
  if (!fg('confluence_ncs_step_diffing_version_history')) {
127
126
  return from === activeIndexPos.from && to === activeIndexPos.to;
128
127
  }
129
- return from <= activeIndexPos.to && activeIndexPos.from <= to;
128
+ return from <= activeIndexPos.to && activeIndexPos.from <= to && (!('attributionKey' in activeIndexPos) || activeIndexPos.attributionKey === attributionKey);
130
129
  };
131
130
  const isLargeInsertedTableRange = (doc, from, to) => {
132
131
  let containsTable = false;
@@ -187,7 +186,7 @@ const calculateNodesForBlockDecoration = ({
187
186
  if (!(node !== null && node !== void 0 && node.isBlock)) {
188
187
  continue;
189
188
  }
190
- const isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
189
+ const isActive = isRangeActive(activeIndexPos, pos, nodeEnd, attributionKey);
191
190
  decorations.push(...createBlockChangedDecoration({
192
191
  attributionKey,
193
192
  change: {
@@ -226,7 +225,7 @@ const calculateNodesForBlockDecoration = ({
226
225
  }
227
226
  if (node.isBlock && (!requireContainedBlocks || pos >= from) && pos + node.nodeSize <= to) {
228
227
  const nodeEnd = pos + node.nodeSize;
229
- const isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
228
+ const isActive = isRangeActive(activeIndexPos, pos, nodeEnd, attributionKey);
230
229
  decorations.push(...createBlockChangedDecoration({
231
230
  attributionKey,
232
231
  change: {
@@ -488,7 +487,7 @@ const calculateDiffDecorationsInner = ({
488
487
  const leftAnchorId = fg('platform_editor_ai_show_diff_patch_2') ? `replacement-${change.fromA}-${change.toA}-${change.fromB}-${change.toB}` : undefined;
489
488
  const changeColorScheme = attributionColors ? getColorSchemeForChange(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
490
489
  const attributionKey = showContributorTags ? getAttributionKeyForChange(change, attributedChanges) : undefined;
491
- const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
490
+ const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
492
491
  const {
493
492
  deletedColumns
494
493
  } = change;
@@ -701,7 +700,7 @@ const calculateDiffDecorationsInner = ({
701
700
  var _attributionColors$ge, _attributionColors;
702
701
  const changeColorScheme = change.attributionKey ? (_attributionColors$ge = (_attributionColors = attributionColors) === null || _attributionColors === void 0 ? void 0 : _attributionColors.get(change.attributionKey)) !== null && _attributionColors$ge !== void 0 ? _attributionColors$ge : colorScheme : colorScheme;
703
702
  const attributionKey = showContributorTags ? change.attributionKey : undefined;
704
- const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
703
+ const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
705
704
  decorations.push(...createInlineChangedDecoration({
706
705
  attributionKey,
707
706
  change,
@@ -726,7 +725,7 @@ const calculateDiffDecorationsInner = ({
726
725
  const attributionKey = showContributorTags ? change.attributionKey : undefined;
727
726
  if (change.isInline) {
728
727
  // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
729
- const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
728
+ const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB, attributionKey);
730
729
  decorations.push(...createInlineChangedDecoration({
731
730
  attributionKey,
732
731
  change,