@atlaskit/editor-plugin-show-diff 14.2.6 → 14.2.8
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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +63 -38
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
- package/dist/cjs/pm-plugins/getScrollableDecorations.js +75 -26
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +27 -5
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
- package/dist/es2019/pm-plugins/getScrollableDecorations.js +68 -24
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +63 -38
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +1 -1
- package/dist/esm/pm-plugins/getScrollableDecorations.js +75 -26
- package/dist/types/pm-plugins/getScrollableDecorations.d.ts +6 -4
- package/package.json +18 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-show-diff
|
|
2
2
|
|
|
3
|
+
## 14.2.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 14.2.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`68281638de8fc`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/68281638de8fc) -
|
|
14
|
+
Group overlapping diff decorations into a single customer-facing edit and highlight the full edit
|
|
15
|
+
when navigating changes, gated by `confluence_ncs_step_diffing_version_history`.
|
|
16
|
+
|
|
3
17
|
## 14.2.6
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -131,6 +131,20 @@ var leafTextblockRanges = function leafTextblockRanges(doc, from, to) {
|
|
|
131
131
|
// once it has more leaf textblocks (~cells) than this threshold. Small tables
|
|
132
132
|
// keep the precise per-cell path.
|
|
133
133
|
var LARGE_TABLE_LEAF_THRESHOLD = 40;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* An active scrollable change may contain several underlying diff changes. Treat ranges that
|
|
137
|
+
* overlap or touch the active range as part of the same active customer-facing edit.
|
|
138
|
+
*/
|
|
139
|
+
var isRangeActive = function isRangeActive(activeIndexPos, from, to) {
|
|
140
|
+
if (activeIndexPos === undefined) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (!(0, _fg.fg)('confluence_ncs_step_diffing_version_history')) {
|
|
144
|
+
return from === activeIndexPos.from && to === activeIndexPos.to;
|
|
145
|
+
}
|
|
146
|
+
return from <= activeIndexPos.to && activeIndexPos.from <= to;
|
|
147
|
+
};
|
|
134
148
|
var isLargeInsertedTableRange = function isLargeInsertedTableRange(doc, from, to) {
|
|
135
149
|
var containsTable = false;
|
|
136
150
|
var leafCount = 0;
|
|
@@ -198,7 +212,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
|
|
|
198
212
|
}
|
|
199
213
|
if (node.isBlock && (!(0, _isExtendedEnabled.isExtendedEnabled)(diffType) || pos + node.nodeSize <= to)) {
|
|
200
214
|
var nodeEnd = pos + node.nodeSize;
|
|
201
|
-
var isActive = activeIndexPos
|
|
215
|
+
var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
|
|
202
216
|
decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
|
|
203
217
|
change: {
|
|
204
218
|
from: pos,
|
|
@@ -431,7 +445,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
431
445
|
var isInserted = !isInverted;
|
|
432
446
|
var createDecorationsForChange = function createDecorationsForChange(change) {
|
|
433
447
|
var changeColorScheme = attributionColors ? (0, _attributions.getColorSchemeForChange)(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
|
|
434
|
-
var isActive = activeIndexPos
|
|
448
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
435
449
|
|
|
436
450
|
// Hoisted because it decides BOTH where the deleted widget is anchored and — since the
|
|
437
451
|
// widget pins whichever end of the range it sits at — how the indicator anchors below are
|
|
@@ -559,11 +573,22 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
559
573
|
changes.forEach(function (change) {
|
|
560
574
|
createDecorationsForChange(change);
|
|
561
575
|
});
|
|
562
|
-
|
|
563
|
-
|
|
576
|
+
// The attribute-aware token encoder already reports mark-only changes as regular changes in the
|
|
577
|
+
// smart path. Only add the legacy step-range decoration when no equivalent changeset range was
|
|
578
|
+
// produced; otherwise the same content receives two inline decorations (and two navigation
|
|
579
|
+
// changes), usually one attribution colour and one default purple decoration.
|
|
580
|
+
(0, _getMarkChangeRanges.getMarkChangeRanges)(steps).filter(function (_ref10) {
|
|
581
|
+
var fromB = _ref10.fromB,
|
|
582
|
+
toB = _ref10.toB;
|
|
583
|
+
return !(0, _fg.fg)('confluence_ncs_step_diffing_version_history') || !changes.some(function (change) {
|
|
584
|
+
return change.fromB === fromB && change.toB === toB;
|
|
585
|
+
});
|
|
586
|
+
}).forEach(function (change) {
|
|
587
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
564
588
|
decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
|
|
565
589
|
change: change,
|
|
566
590
|
colorScheme: colorScheme,
|
|
591
|
+
diffType: diffType,
|
|
567
592
|
isActive: isActive,
|
|
568
593
|
isInserted: true
|
|
569
594
|
})));
|
|
@@ -571,7 +596,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
571
596
|
(0, _getAttrChangeRanges.getAttrChangeRanges)(tr.doc, attrStepContexts, originalDoc).forEach(function (change) {
|
|
572
597
|
if (change.isInline) {
|
|
573
598
|
// Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
|
|
574
|
-
var isActive = activeIndexPos
|
|
599
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
575
600
|
var isAtomicInlineNodeFlag = (0, _deprecatedPlatformFeatureExperiments.isExperimentEnabled)('platform_editor_improve_inline_diffs', function () {
|
|
576
601
|
return (0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true);
|
|
577
602
|
});
|
|
@@ -658,42 +683,42 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
658
683
|
};
|
|
659
684
|
var calculateDiffDecorations = exports.calculateDiffDecorations = (0, _memoizeOne.default)(calculateDiffDecorationsInner,
|
|
660
685
|
// Cache results unless relevant inputs change
|
|
661
|
-
function (
|
|
662
|
-
var
|
|
663
|
-
var _ref12 = (0, _slicedToArray2.default)(_ref10, 1),
|
|
664
|
-
_ref12$ = _ref12[0],
|
|
665
|
-
pluginState = _ref12$.pluginState,
|
|
666
|
-
state = _ref12$.state,
|
|
667
|
-
colorScheme = _ref12$.colorScheme,
|
|
668
|
-
intl = _ref12$.intl,
|
|
669
|
-
activeIndexPos = _ref12$.activeIndexPos,
|
|
670
|
-
isInverted = _ref12$.isInverted,
|
|
671
|
-
diffType = _ref12$.diffType,
|
|
672
|
-
hideDeletedDiffs = _ref12$.hideDeletedDiffs,
|
|
673
|
-
hideAddedDiffsUnderline = _ref12$.hideAddedDiffsUnderline,
|
|
674
|
-
showIndicators = _ref12$.showIndicators,
|
|
675
|
-
smartThresholds = _ref12$.smartThresholds,
|
|
676
|
-
deletedDiffPlacement = _ref12$.deletedDiffPlacement,
|
|
677
|
-
inlineDeletedDiffPlacement = _ref12$.inlineDeletedDiffPlacement;
|
|
686
|
+
function (_ref11, _ref12) {
|
|
687
|
+
var _ref16;
|
|
678
688
|
var _ref13 = (0, _slicedToArray2.default)(_ref11, 1),
|
|
679
689
|
_ref13$ = _ref13[0],
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
690
|
+
pluginState = _ref13$.pluginState,
|
|
691
|
+
state = _ref13$.state,
|
|
692
|
+
colorScheme = _ref13$.colorScheme,
|
|
693
|
+
intl = _ref13$.intl,
|
|
694
|
+
activeIndexPos = _ref13$.activeIndexPos,
|
|
695
|
+
isInverted = _ref13$.isInverted,
|
|
696
|
+
diffType = _ref13$.diffType,
|
|
697
|
+
hideDeletedDiffs = _ref13$.hideDeletedDiffs,
|
|
698
|
+
hideAddedDiffsUnderline = _ref13$.hideAddedDiffsUnderline,
|
|
699
|
+
showIndicators = _ref13$.showIndicators,
|
|
700
|
+
smartThresholds = _ref13$.smartThresholds,
|
|
701
|
+
deletedDiffPlacement = _ref13$.deletedDiffPlacement,
|
|
702
|
+
inlineDeletedDiffPlacement = _ref13$.inlineDeletedDiffPlacement;
|
|
703
|
+
var _ref14 = (0, _slicedToArray2.default)(_ref12, 1),
|
|
704
|
+
_ref14$ = _ref14[0],
|
|
705
|
+
lastPluginState = _ref14$.pluginState,
|
|
706
|
+
lastState = _ref14$.state,
|
|
707
|
+
lastColorScheme = _ref14$.colorScheme,
|
|
708
|
+
lastIntl = _ref14$.intl,
|
|
709
|
+
lastActiveIndexPos = _ref14$.activeIndexPos,
|
|
710
|
+
lastIsInverted = _ref14$.isInverted,
|
|
711
|
+
lastDiffType = _ref14$.diffType,
|
|
712
|
+
lastHideDeletedDiffs = _ref14$.hideDeletedDiffs,
|
|
713
|
+
lastHideAddedDiffsUnderline = _ref14$.hideAddedDiffsUnderline,
|
|
714
|
+
lastShowIndicators = _ref14$.showIndicators,
|
|
715
|
+
lastSmartThresholds = _ref14$.smartThresholds,
|
|
716
|
+
lastDeletedDiffPlacement = _ref14$.deletedDiffPlacement,
|
|
717
|
+
lastInlineDeletedDiffPlacement = _ref14$.inlineDeletedDiffPlacement;
|
|
693
718
|
var originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
|
|
694
719
|
if ((0, _isExtendedEnabled.isExtendedEnabled)(diffType)) {
|
|
695
|
-
var
|
|
696
|
-
return (
|
|
720
|
+
var _ref15;
|
|
721
|
+
return (_ref15 = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && (0, _isEqual.default)(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && (0, _isEqual.default)(pluginState.steps, lastPluginState.steps) && (0, _isEqual.default)(pluginState.stepAttributions, lastPluginState.stepAttributions) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && (0, _isEqual.default)(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement) !== null && _ref15 !== void 0 ? _ref15 : false;
|
|
697
722
|
}
|
|
698
|
-
return (
|
|
723
|
+
return (_ref16 = originalDocIsSame && (0, _isEqual.default)(pluginState.steps, lastPluginState.steps) && (0, _isEqual.default)(pluginState.stepAttributions, lastPluginState.stepAttributions) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && (0, _isEqual.default)(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref16 !== void 0 ? _ref16 : false;
|
|
699
724
|
});
|
|
@@ -122,7 +122,7 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
|
|
|
122
122
|
// node renders beneath its replacement; otherwise anchor at the start (change.fromB).
|
|
123
123
|
var anchorPos = placeBelow ? change.toB : change.fromB;
|
|
124
124
|
var safeInsertPos = (0, _findSafeInsertPos.findSafeInsertPos)(newDoc, anchorPos, slice);
|
|
125
|
-
var isActive = activeIndexPos && safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to;
|
|
125
|
+
var isActive = activeIndexPos && ((0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
|
|
126
126
|
if (slice.content.content.length === 0 || shouldSkipDeletedEmptyParagraphDecoration) {
|
|
127
127
|
return [];
|
|
128
128
|
}
|
|
@@ -8,6 +8,8 @@ exports.getScrollableDecorations = void 0;
|
|
|
8
8
|
exports.isInlineDiffDecorationRenderableInDoc = isInlineDiffDecorationRenderableInDoc;
|
|
9
9
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
10
|
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
11
|
+
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
12
|
+
var _fg = require("@atlaskit/platform-feature-flags/fg");
|
|
11
13
|
var _decorationKeys = require("./decorations/decorationKeys");
|
|
12
14
|
var _isExtendedEnabled = require("./isExtendedEnabled");
|
|
13
15
|
/**
|
|
@@ -40,10 +42,6 @@ function isInlineDiffDecorationRenderableInDoc(doc, from, to) {
|
|
|
40
42
|
return false;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Checks if range1 is fully contained within range2
|
|
46
|
-
*/
|
|
47
45
|
function isRangeFullyInside(range1Start, range1End, range2Start, range2End) {
|
|
48
46
|
return range2Start <= range1Start && range1End <= range2End;
|
|
49
47
|
}
|
|
@@ -51,6 +49,61 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
51
49
|
return Boolean(spec && (0, _typeof2.default)(spec) === 'object' && 'key' in spec && typeof spec.key === 'string' && spec.key.startsWith(keyPrefix));
|
|
52
50
|
}
|
|
53
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Collapses decorations that represent one continuous customer-facing edit.
|
|
54
|
+
*
|
|
55
|
+
* Decorations are kept separate in the DecorationSet so each one can retain its own visual
|
|
56
|
+
* styling. This list is only used for change counting and navigation, where an insertion and a
|
|
57
|
+
* deletion at the same location should be treated as one replacement. Zero-width decorations
|
|
58
|
+
* (normally deleted-content widgets) can join a group, but cannot extend its range and therefore
|
|
59
|
+
* cannot bridge two otherwise separate edits.
|
|
60
|
+
*/
|
|
61
|
+
function groupTouchingDecorations(decorations, isInlineDecoration) {
|
|
62
|
+
if (decorations.length < 2) {
|
|
63
|
+
return decorations;
|
|
64
|
+
}
|
|
65
|
+
var groups = [];
|
|
66
|
+
var currentGroup = [];
|
|
67
|
+
var currentGroupFrom = 0;
|
|
68
|
+
var currentGroupTo = 0;
|
|
69
|
+
var sortedDecorations = (0, _toConsumableArray2.default)(decorations).sort(function (a, b) {
|
|
70
|
+
return a.from === b.from ? a.to - b.to : a.from - b.from;
|
|
71
|
+
});
|
|
72
|
+
sortedDecorations.forEach(function (decoration) {
|
|
73
|
+
if (currentGroup.length === 0 || decoration.from > currentGroupTo) {
|
|
74
|
+
currentGroup = [decoration];
|
|
75
|
+
currentGroupFrom = decoration.from;
|
|
76
|
+
currentGroupTo = decoration.to;
|
|
77
|
+
groups.push({
|
|
78
|
+
decorations: currentGroup,
|
|
79
|
+
from: currentGroupFrom,
|
|
80
|
+
to: currentGroupTo
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
currentGroup.push(decoration);
|
|
85
|
+
// A zero-width decoration must not extend the group and bridge a gap.
|
|
86
|
+
currentGroupTo = Math.max(currentGroupTo, decoration.to);
|
|
87
|
+
groups[groups.length - 1].to = currentGroupTo;
|
|
88
|
+
});
|
|
89
|
+
return groups.map(function (_ref) {
|
|
90
|
+
var _ref2, _group$find;
|
|
91
|
+
var group = _ref.decorations,
|
|
92
|
+
from = _ref.from,
|
|
93
|
+
to = _ref.to;
|
|
94
|
+
var representative = (_ref2 = (_group$find = group.find(isInlineDecoration)) !== null && _group$find !== void 0 ? _group$find : group.find(function (decoration) {
|
|
95
|
+
return decoration.from !== decoration.to;
|
|
96
|
+
})) !== null && _ref2 !== void 0 ? _ref2 : group[0];
|
|
97
|
+
if (!representative || representative.from === from && representative.to === to) {
|
|
98
|
+
return representative;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// This decoration is only used for navigation and active-range calculation. The actual
|
|
102
|
+
// visual decorations remain in the DecorationSet with their original ranges and styles.
|
|
103
|
+
return isInlineDecoration(representative) ? _view.Decoration.inline(from, to, {}, representative.spec) : _view.Decoration.node(from, to, {}, representative.spec);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
/**
|
|
55
108
|
* Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations
|
|
56
109
|
* and applying various rules for diff visualization.
|
|
@@ -61,9 +114,11 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
61
114
|
* 3. Deduplicates diff-block decorations with same from, to and nodeName
|
|
62
115
|
* 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
|
|
63
116
|
* (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
|
|
64
|
-
* 5.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
117
|
+
* 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
|
|
118
|
+
* directly touching ranges across decoration types into one result, using the union of all
|
|
119
|
+
* grouped ranges
|
|
120
|
+
* (zero-width widgets can join a group without extending it)
|
|
121
|
+
* 6. Results are sorted by from position, then by to position
|
|
67
122
|
*
|
|
68
123
|
* @param set - The DecorationSet to extract scrollable decorations from
|
|
69
124
|
* @param doc - Current document; when set, diff-inline ranges are validated against this doc
|
|
@@ -114,25 +169,19 @@ var getScrollableDecorations = exports.getScrollableDecorations = function getSc
|
|
|
114
169
|
return isInlineDiffDecorationRenderableInDoc(doc, d.from, d.to);
|
|
115
170
|
}) : rawInlines;
|
|
116
171
|
var widgets = filtered.filter(isWidgetDecoration);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
} // inline inside block
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Combine all non-overlapping decorations
|
|
135
|
-
var result = [].concat((0, _toConsumableArray2.default)(nonOverlappingBlocks), (0, _toConsumableArray2.default)(inlines), (0, _toConsumableArray2.default)(widgets));
|
|
172
|
+
var result;
|
|
173
|
+
if ((0, _fg.fg)('confluence_ncs_step_diffing_version_history')) {
|
|
174
|
+
// Group overlapping or directly touching ranges into one customer-facing edit.
|
|
175
|
+
result = groupTouchingDecorations([].concat((0, _toConsumableArray2.default)(blocks), (0, _toConsumableArray2.default)(inlines), (0, _toConsumableArray2.default)(widgets)), isInlineDecoration);
|
|
176
|
+
} else {
|
|
177
|
+
// Legacy behavior: exclude blocks that contain or are contained by an inline decoration.
|
|
178
|
+
var nonOverlappingBlocks = blocks.filter(function (block) {
|
|
179
|
+
return !inlines.some(function (inline) {
|
|
180
|
+
return isRangeFullyInside(block.from, block.to, inline.from, inline.to) || isRangeFullyInside(inline.from, inline.to, block.from, block.to);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
result = [].concat((0, _toConsumableArray2.default)(nonOverlappingBlocks), (0, _toConsumableArray2.default)(inlines), (0, _toConsumableArray2.default)(widgets));
|
|
184
|
+
}
|
|
136
185
|
|
|
137
186
|
// Sort by from position, then by to position
|
|
138
187
|
result.sort(function (a, b) {
|
|
@@ -118,6 +118,20 @@ const leafTextblockRanges = (doc, from, to) => {
|
|
|
118
118
|
// once it has more leaf textblocks (~cells) than this threshold. Small tables
|
|
119
119
|
// keep the precise per-cell path.
|
|
120
120
|
const LARGE_TABLE_LEAF_THRESHOLD = 40;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* An active scrollable change may contain several underlying diff changes. Treat ranges that
|
|
124
|
+
* overlap or touch the active range as part of the same active customer-facing edit.
|
|
125
|
+
*/
|
|
126
|
+
const isRangeActive = (activeIndexPos, from, to) => {
|
|
127
|
+
if (activeIndexPos === undefined) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
if (!fg('confluence_ncs_step_diffing_version_history')) {
|
|
131
|
+
return from === activeIndexPos.from && to === activeIndexPos.to;
|
|
132
|
+
}
|
|
133
|
+
return from <= activeIndexPos.to && activeIndexPos.from <= to;
|
|
134
|
+
};
|
|
121
135
|
const isLargeInsertedTableRange = (doc, from, to) => {
|
|
122
136
|
let containsTable = false;
|
|
123
137
|
let leafCount = 0;
|
|
@@ -175,7 +189,7 @@ const calculateNodesForBlockDecoration = ({
|
|
|
175
189
|
}
|
|
176
190
|
if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
|
|
177
191
|
const nodeEnd = pos + node.nodeSize;
|
|
178
|
-
const isActive = activeIndexPos
|
|
192
|
+
const isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
|
|
179
193
|
decorations.push(...createBlockChangedDecoration({
|
|
180
194
|
change: {
|
|
181
195
|
from: pos,
|
|
@@ -393,7 +407,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
393
407
|
const isInserted = !isInverted;
|
|
394
408
|
const createDecorationsForChange = change => {
|
|
395
409
|
const changeColorScheme = attributionColors ? getColorSchemeForChange(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
|
|
396
|
-
const isActive = activeIndexPos
|
|
410
|
+
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
397
411
|
|
|
398
412
|
// Hoisted because it decides BOTH where the deleted widget is anchored and — since the
|
|
399
413
|
// widget pins whichever end of the range it sits at — how the indicator anchors below are
|
|
@@ -512,11 +526,19 @@ const calculateDiffDecorationsInner = ({
|
|
|
512
526
|
changes.forEach(change => {
|
|
513
527
|
createDecorationsForChange(change);
|
|
514
528
|
});
|
|
515
|
-
|
|
516
|
-
|
|
529
|
+
// The attribute-aware token encoder already reports mark-only changes as regular changes in the
|
|
530
|
+
// smart path. Only add the legacy step-range decoration when no equivalent changeset range was
|
|
531
|
+
// produced; otherwise the same content receives two inline decorations (and two navigation
|
|
532
|
+
// changes), usually one attribution colour and one default purple decoration.
|
|
533
|
+
getMarkChangeRanges(steps).filter(({
|
|
534
|
+
fromB,
|
|
535
|
+
toB
|
|
536
|
+
}) => !fg('confluence_ncs_step_diffing_version_history') || !changes.some(change => change.fromB === fromB && change.toB === toB)).forEach(change => {
|
|
537
|
+
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
517
538
|
decorations.push(...createInlineChangedDecoration({
|
|
518
539
|
change,
|
|
519
540
|
colorScheme,
|
|
541
|
+
diffType,
|
|
520
542
|
isActive,
|
|
521
543
|
isInserted: true
|
|
522
544
|
}));
|
|
@@ -524,7 +546,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
524
546
|
getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).forEach(change => {
|
|
525
547
|
if (change.isInline) {
|
|
526
548
|
// Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
|
|
527
|
-
const isActive = activeIndexPos
|
|
549
|
+
const isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
528
550
|
const isAtomicInlineNodeFlag = isExperimentEnabled('platform_editor_improve_inline_diffs', () => expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true));
|
|
529
551
|
decorations.push(...createInlineChangedDecoration({
|
|
530
552
|
change,
|
|
@@ -115,7 +115,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
115
115
|
// node renders beneath its replacement; otherwise anchor at the start (change.fromB).
|
|
116
116
|
const anchorPos = placeBelow ? change.toB : change.fromB;
|
|
117
117
|
const safeInsertPos = findSafeInsertPos(newDoc, anchorPos, slice);
|
|
118
|
-
const isActive = activeIndexPos && safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to;
|
|
118
|
+
const isActive = activeIndexPos && (fg('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
|
|
119
119
|
if (slice.content.content.length === 0 || shouldSkipDeletedEmptyParagraphDecoration) {
|
|
120
120
|
return [];
|
|
121
121
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
1
3
|
import { DiffDecorationKey, isDiffDecoration, isDiffDecorationSpec } from './decorations/decorationKeys';
|
|
2
4
|
import { isExtendedEnabled } from './isExtendedEnabled';
|
|
3
5
|
|
|
@@ -31,10 +33,6 @@ export function isInlineDiffDecorationRenderableInDoc(doc, from, to) {
|
|
|
31
33
|
return false;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if range1 is fully contained within range2
|
|
37
|
-
*/
|
|
38
36
|
function isRangeFullyInside(range1Start, range1End, range2Start, range2End) {
|
|
39
37
|
return range2Start <= range1Start && range1End <= range2End;
|
|
40
38
|
}
|
|
@@ -42,6 +40,58 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
42
40
|
return Boolean(spec && typeof spec === 'object' && 'key' in spec && typeof spec.key === 'string' && spec.key.startsWith(keyPrefix));
|
|
43
41
|
}
|
|
44
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Collapses decorations that represent one continuous customer-facing edit.
|
|
45
|
+
*
|
|
46
|
+
* Decorations are kept separate in the DecorationSet so each one can retain its own visual
|
|
47
|
+
* styling. This list is only used for change counting and navigation, where an insertion and a
|
|
48
|
+
* deletion at the same location should be treated as one replacement. Zero-width decorations
|
|
49
|
+
* (normally deleted-content widgets) can join a group, but cannot extend its range and therefore
|
|
50
|
+
* cannot bridge two otherwise separate edits.
|
|
51
|
+
*/
|
|
52
|
+
function groupTouchingDecorations(decorations, isInlineDecoration) {
|
|
53
|
+
if (decorations.length < 2) {
|
|
54
|
+
return decorations;
|
|
55
|
+
}
|
|
56
|
+
const groups = [];
|
|
57
|
+
let currentGroup = [];
|
|
58
|
+
let currentGroupFrom = 0;
|
|
59
|
+
let currentGroupTo = 0;
|
|
60
|
+
const sortedDecorations = [...decorations].sort((a, b) => a.from === b.from ? a.to - b.to : a.from - b.from);
|
|
61
|
+
sortedDecorations.forEach(decoration => {
|
|
62
|
+
if (currentGroup.length === 0 || decoration.from > currentGroupTo) {
|
|
63
|
+
currentGroup = [decoration];
|
|
64
|
+
currentGroupFrom = decoration.from;
|
|
65
|
+
currentGroupTo = decoration.to;
|
|
66
|
+
groups.push({
|
|
67
|
+
decorations: currentGroup,
|
|
68
|
+
from: currentGroupFrom,
|
|
69
|
+
to: currentGroupTo
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
currentGroup.push(decoration);
|
|
74
|
+
// A zero-width decoration must not extend the group and bridge a gap.
|
|
75
|
+
currentGroupTo = Math.max(currentGroupTo, decoration.to);
|
|
76
|
+
groups[groups.length - 1].to = currentGroupTo;
|
|
77
|
+
});
|
|
78
|
+
return groups.map(({
|
|
79
|
+
decorations: group,
|
|
80
|
+
from,
|
|
81
|
+
to
|
|
82
|
+
}) => {
|
|
83
|
+
var _ref, _group$find;
|
|
84
|
+
const representative = (_ref = (_group$find = group.find(isInlineDecoration)) !== null && _group$find !== void 0 ? _group$find : group.find(decoration => decoration.from !== decoration.to)) !== null && _ref !== void 0 ? _ref : group[0];
|
|
85
|
+
if (!representative || representative.from === from && representative.to === to) {
|
|
86
|
+
return representative;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// This decoration is only used for navigation and active-range calculation. The actual
|
|
90
|
+
// visual decorations remain in the DecorationSet with their original ranges and styles.
|
|
91
|
+
return isInlineDecoration(representative) ? Decoration.inline(from, to, {}, representative.spec) : Decoration.node(from, to, {}, representative.spec);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
45
95
|
/**
|
|
46
96
|
* Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations
|
|
47
97
|
* and applying various rules for diff visualization.
|
|
@@ -52,9 +102,11 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
52
102
|
* 3. Deduplicates diff-block decorations with same from, to and nodeName
|
|
53
103
|
* 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
|
|
54
104
|
* (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
|
|
55
|
-
* 5.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
105
|
+
* 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
|
|
106
|
+
* directly touching ranges across decoration types into one result, using the union of all
|
|
107
|
+
* grouped ranges
|
|
108
|
+
* (zero-width widgets can join a group without extending it)
|
|
109
|
+
* 6. Results are sorted by from position, then by to position
|
|
58
110
|
*
|
|
59
111
|
* @param set - The DecorationSet to extract scrollable decorations from
|
|
60
112
|
* @param doc - Current document; when set, diff-inline ranges are validated against this doc
|
|
@@ -101,23 +153,15 @@ export const getScrollableDecorations = (set, doc, diffType) => {
|
|
|
101
153
|
const rawInlines = filtered.filter(isInlineDecoration);
|
|
102
154
|
const inlines = doc !== undefined ? rawInlines.filter(d => isInlineDiffDecorationRenderableInDoc(doc, d.from, d.to)) : rawInlines;
|
|
103
155
|
const widgets = filtered.filter(isWidgetDecoration);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return !inlines.some(inline => isRangeFullyInside(block.from, block.to, inline.from, inline.to) ||
|
|
114
|
-
// block inside inline
|
|
115
|
-
isRangeFullyInside(inline.from, inline.to, block.from, block.to) // inline inside block
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Combine all non-overlapping decorations
|
|
120
|
-
const result = [...nonOverlappingBlocks, ...inlines, ...widgets];
|
|
156
|
+
let result;
|
|
157
|
+
if (fg('confluence_ncs_step_diffing_version_history')) {
|
|
158
|
+
// Group overlapping or directly touching ranges into one customer-facing edit.
|
|
159
|
+
result = groupTouchingDecorations([...blocks, ...inlines, ...widgets], isInlineDecoration);
|
|
160
|
+
} else {
|
|
161
|
+
// Legacy behavior: exclude blocks that contain or are contained by an inline decoration.
|
|
162
|
+
const nonOverlappingBlocks = blocks.filter(block => !inlines.some(inline => isRangeFullyInside(block.from, block.to, inline.from, inline.to) || isRangeFullyInside(inline.from, inline.to, block.from, block.to)));
|
|
163
|
+
result = [...nonOverlappingBlocks, ...inlines, ...widgets];
|
|
164
|
+
}
|
|
121
165
|
|
|
122
166
|
// Sort by from position, then by to position
|
|
123
167
|
result.sort((a, b) => a.from === b.from ? a.to - b.to : a.from - b.from);
|
|
@@ -124,6 +124,20 @@ var leafTextblockRanges = function leafTextblockRanges(doc, from, to) {
|
|
|
124
124
|
// once it has more leaf textblocks (~cells) than this threshold. Small tables
|
|
125
125
|
// keep the precise per-cell path.
|
|
126
126
|
var LARGE_TABLE_LEAF_THRESHOLD = 40;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* An active scrollable change may contain several underlying diff changes. Treat ranges that
|
|
130
|
+
* overlap or touch the active range as part of the same active customer-facing edit.
|
|
131
|
+
*/
|
|
132
|
+
var isRangeActive = function isRangeActive(activeIndexPos, from, to) {
|
|
133
|
+
if (activeIndexPos === undefined) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
if (!fg('confluence_ncs_step_diffing_version_history')) {
|
|
137
|
+
return from === activeIndexPos.from && to === activeIndexPos.to;
|
|
138
|
+
}
|
|
139
|
+
return from <= activeIndexPos.to && activeIndexPos.from <= to;
|
|
140
|
+
};
|
|
127
141
|
var isLargeInsertedTableRange = function isLargeInsertedTableRange(doc, from, to) {
|
|
128
142
|
var containsTable = false;
|
|
129
143
|
var leafCount = 0;
|
|
@@ -191,7 +205,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
|
|
|
191
205
|
}
|
|
192
206
|
if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
|
|
193
207
|
var nodeEnd = pos + node.nodeSize;
|
|
194
|
-
var isActive = activeIndexPos
|
|
208
|
+
var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
|
|
195
209
|
decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
|
|
196
210
|
change: {
|
|
197
211
|
from: pos,
|
|
@@ -424,7 +438,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
424
438
|
var isInserted = !isInverted;
|
|
425
439
|
var createDecorationsForChange = function createDecorationsForChange(change) {
|
|
426
440
|
var changeColorScheme = attributionColors ? getColorSchemeForChange(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
|
|
427
|
-
var isActive = activeIndexPos
|
|
441
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
428
442
|
|
|
429
443
|
// Hoisted because it decides BOTH where the deleted widget is anchored and — since the
|
|
430
444
|
// widget pins whichever end of the range it sits at — how the indicator anchors below are
|
|
@@ -552,11 +566,22 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
552
566
|
changes.forEach(function (change) {
|
|
553
567
|
createDecorationsForChange(change);
|
|
554
568
|
});
|
|
555
|
-
|
|
556
|
-
|
|
569
|
+
// The attribute-aware token encoder already reports mark-only changes as regular changes in the
|
|
570
|
+
// smart path. Only add the legacy step-range decoration when no equivalent changeset range was
|
|
571
|
+
// produced; otherwise the same content receives two inline decorations (and two navigation
|
|
572
|
+
// changes), usually one attribution colour and one default purple decoration.
|
|
573
|
+
getMarkChangeRanges(steps).filter(function (_ref10) {
|
|
574
|
+
var fromB = _ref10.fromB,
|
|
575
|
+
toB = _ref10.toB;
|
|
576
|
+
return !fg('confluence_ncs_step_diffing_version_history') || !changes.some(function (change) {
|
|
577
|
+
return change.fromB === fromB && change.toB === toB;
|
|
578
|
+
});
|
|
579
|
+
}).forEach(function (change) {
|
|
580
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
557
581
|
decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration({
|
|
558
582
|
change: change,
|
|
559
583
|
colorScheme: colorScheme,
|
|
584
|
+
diffType: diffType,
|
|
560
585
|
isActive: isActive,
|
|
561
586
|
isInserted: true
|
|
562
587
|
})));
|
|
@@ -564,7 +589,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
564
589
|
getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).forEach(function (change) {
|
|
565
590
|
if (change.isInline) {
|
|
566
591
|
// Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
|
|
567
|
-
var isActive = activeIndexPos
|
|
592
|
+
var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
|
|
568
593
|
var isAtomicInlineNodeFlag = isExperimentEnabled('platform_editor_improve_inline_diffs', function () {
|
|
569
594
|
return expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true);
|
|
570
595
|
});
|
|
@@ -651,42 +676,42 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref7
|
|
|
651
676
|
};
|
|
652
677
|
export var calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner,
|
|
653
678
|
// Cache results unless relevant inputs change
|
|
654
|
-
function (
|
|
655
|
-
var
|
|
656
|
-
var _ref12 = _slicedToArray(_ref10, 1),
|
|
657
|
-
_ref12$ = _ref12[0],
|
|
658
|
-
pluginState = _ref12$.pluginState,
|
|
659
|
-
state = _ref12$.state,
|
|
660
|
-
colorScheme = _ref12$.colorScheme,
|
|
661
|
-
intl = _ref12$.intl,
|
|
662
|
-
activeIndexPos = _ref12$.activeIndexPos,
|
|
663
|
-
isInverted = _ref12$.isInverted,
|
|
664
|
-
diffType = _ref12$.diffType,
|
|
665
|
-
hideDeletedDiffs = _ref12$.hideDeletedDiffs,
|
|
666
|
-
hideAddedDiffsUnderline = _ref12$.hideAddedDiffsUnderline,
|
|
667
|
-
showIndicators = _ref12$.showIndicators,
|
|
668
|
-
smartThresholds = _ref12$.smartThresholds,
|
|
669
|
-
deletedDiffPlacement = _ref12$.deletedDiffPlacement,
|
|
670
|
-
inlineDeletedDiffPlacement = _ref12$.inlineDeletedDiffPlacement;
|
|
679
|
+
function (_ref11, _ref12) {
|
|
680
|
+
var _ref16;
|
|
671
681
|
var _ref13 = _slicedToArray(_ref11, 1),
|
|
672
682
|
_ref13$ = _ref13[0],
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
683
|
+
pluginState = _ref13$.pluginState,
|
|
684
|
+
state = _ref13$.state,
|
|
685
|
+
colorScheme = _ref13$.colorScheme,
|
|
686
|
+
intl = _ref13$.intl,
|
|
687
|
+
activeIndexPos = _ref13$.activeIndexPos,
|
|
688
|
+
isInverted = _ref13$.isInverted,
|
|
689
|
+
diffType = _ref13$.diffType,
|
|
690
|
+
hideDeletedDiffs = _ref13$.hideDeletedDiffs,
|
|
691
|
+
hideAddedDiffsUnderline = _ref13$.hideAddedDiffsUnderline,
|
|
692
|
+
showIndicators = _ref13$.showIndicators,
|
|
693
|
+
smartThresholds = _ref13$.smartThresholds,
|
|
694
|
+
deletedDiffPlacement = _ref13$.deletedDiffPlacement,
|
|
695
|
+
inlineDeletedDiffPlacement = _ref13$.inlineDeletedDiffPlacement;
|
|
696
|
+
var _ref14 = _slicedToArray(_ref12, 1),
|
|
697
|
+
_ref14$ = _ref14[0],
|
|
698
|
+
lastPluginState = _ref14$.pluginState,
|
|
699
|
+
lastState = _ref14$.state,
|
|
700
|
+
lastColorScheme = _ref14$.colorScheme,
|
|
701
|
+
lastIntl = _ref14$.intl,
|
|
702
|
+
lastActiveIndexPos = _ref14$.activeIndexPos,
|
|
703
|
+
lastIsInverted = _ref14$.isInverted,
|
|
704
|
+
lastDiffType = _ref14$.diffType,
|
|
705
|
+
lastHideDeletedDiffs = _ref14$.hideDeletedDiffs,
|
|
706
|
+
lastHideAddedDiffsUnderline = _ref14$.hideAddedDiffsUnderline,
|
|
707
|
+
lastShowIndicators = _ref14$.showIndicators,
|
|
708
|
+
lastSmartThresholds = _ref14$.smartThresholds,
|
|
709
|
+
lastDeletedDiffPlacement = _ref14$.deletedDiffPlacement,
|
|
710
|
+
lastInlineDeletedDiffPlacement = _ref14$.inlineDeletedDiffPlacement;
|
|
686
711
|
var originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
|
|
687
712
|
if (isExtendedEnabled(diffType)) {
|
|
688
|
-
var
|
|
689
|
-
return (
|
|
713
|
+
var _ref15;
|
|
714
|
+
return (_ref15 = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement) !== null && _ref15 !== void 0 ? _ref15 : false;
|
|
690
715
|
}
|
|
691
|
-
return (
|
|
716
|
+
return (_ref16 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref16 !== void 0 ? _ref16 : false;
|
|
692
717
|
});
|
|
@@ -115,7 +115,7 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
|
|
|
115
115
|
// node renders beneath its replacement; otherwise anchor at the start (change.fromB).
|
|
116
116
|
var anchorPos = placeBelow ? change.toB : change.fromB;
|
|
117
117
|
var safeInsertPos = findSafeInsertPos(newDoc, anchorPos, slice);
|
|
118
|
-
var isActive = activeIndexPos && safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to;
|
|
118
|
+
var isActive = activeIndexPos && (fg('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
|
|
119
119
|
if (slice.content.content.length === 0 || shouldSkipDeletedEmptyParagraphDecoration) {
|
|
120
120
|
return [];
|
|
121
121
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
2
|
import _typeof from "@babel/runtime/helpers/typeof";
|
|
3
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
5
|
import { DiffDecorationKey, isDiffDecoration, isDiffDecorationSpec } from './decorations/decorationKeys';
|
|
4
6
|
import { isExtendedEnabled } from './isExtendedEnabled';
|
|
5
7
|
|
|
@@ -33,10 +35,6 @@ export function isInlineDiffDecorationRenderableInDoc(doc, from, to) {
|
|
|
33
35
|
return false;
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Checks if range1 is fully contained within range2
|
|
39
|
-
*/
|
|
40
38
|
function isRangeFullyInside(range1Start, range1End, range2Start, range2End) {
|
|
41
39
|
return range2Start <= range1Start && range1End <= range2End;
|
|
42
40
|
}
|
|
@@ -44,6 +42,61 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
44
42
|
return Boolean(spec && _typeof(spec) === 'object' && 'key' in spec && typeof spec.key === 'string' && spec.key.startsWith(keyPrefix));
|
|
45
43
|
}
|
|
46
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Collapses decorations that represent one continuous customer-facing edit.
|
|
47
|
+
*
|
|
48
|
+
* Decorations are kept separate in the DecorationSet so each one can retain its own visual
|
|
49
|
+
* styling. This list is only used for change counting and navigation, where an insertion and a
|
|
50
|
+
* deletion at the same location should be treated as one replacement. Zero-width decorations
|
|
51
|
+
* (normally deleted-content widgets) can join a group, but cannot extend its range and therefore
|
|
52
|
+
* cannot bridge two otherwise separate edits.
|
|
53
|
+
*/
|
|
54
|
+
function groupTouchingDecorations(decorations, isInlineDecoration) {
|
|
55
|
+
if (decorations.length < 2) {
|
|
56
|
+
return decorations;
|
|
57
|
+
}
|
|
58
|
+
var groups = [];
|
|
59
|
+
var currentGroup = [];
|
|
60
|
+
var currentGroupFrom = 0;
|
|
61
|
+
var currentGroupTo = 0;
|
|
62
|
+
var sortedDecorations = _toConsumableArray(decorations).sort(function (a, b) {
|
|
63
|
+
return a.from === b.from ? a.to - b.to : a.from - b.from;
|
|
64
|
+
});
|
|
65
|
+
sortedDecorations.forEach(function (decoration) {
|
|
66
|
+
if (currentGroup.length === 0 || decoration.from > currentGroupTo) {
|
|
67
|
+
currentGroup = [decoration];
|
|
68
|
+
currentGroupFrom = decoration.from;
|
|
69
|
+
currentGroupTo = decoration.to;
|
|
70
|
+
groups.push({
|
|
71
|
+
decorations: currentGroup,
|
|
72
|
+
from: currentGroupFrom,
|
|
73
|
+
to: currentGroupTo
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
currentGroup.push(decoration);
|
|
78
|
+
// A zero-width decoration must not extend the group and bridge a gap.
|
|
79
|
+
currentGroupTo = Math.max(currentGroupTo, decoration.to);
|
|
80
|
+
groups[groups.length - 1].to = currentGroupTo;
|
|
81
|
+
});
|
|
82
|
+
return groups.map(function (_ref) {
|
|
83
|
+
var _ref2, _group$find;
|
|
84
|
+
var group = _ref.decorations,
|
|
85
|
+
from = _ref.from,
|
|
86
|
+
to = _ref.to;
|
|
87
|
+
var representative = (_ref2 = (_group$find = group.find(isInlineDecoration)) !== null && _group$find !== void 0 ? _group$find : group.find(function (decoration) {
|
|
88
|
+
return decoration.from !== decoration.to;
|
|
89
|
+
})) !== null && _ref2 !== void 0 ? _ref2 : group[0];
|
|
90
|
+
if (!representative || representative.from === from && representative.to === to) {
|
|
91
|
+
return representative;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// This decoration is only used for navigation and active-range calculation. The actual
|
|
95
|
+
// visual decorations remain in the DecorationSet with their original ranges and styles.
|
|
96
|
+
return isInlineDecoration(representative) ? Decoration.inline(from, to, {}, representative.spec) : Decoration.node(from, to, {}, representative.spec);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
47
100
|
/**
|
|
48
101
|
* Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations
|
|
49
102
|
* and applying various rules for diff visualization.
|
|
@@ -54,9 +107,11 @@ function specHasDiffKeyPrefix(spec, keyPrefix) {
|
|
|
54
107
|
* 3. Deduplicates diff-block decorations with same from, to and nodeName
|
|
55
108
|
* 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
|
|
56
109
|
* (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
|
|
57
|
-
* 5.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
110
|
+
* 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
|
|
111
|
+
* directly touching ranges across decoration types into one result, using the union of all
|
|
112
|
+
* grouped ranges
|
|
113
|
+
* (zero-width widgets can join a group without extending it)
|
|
114
|
+
* 6. Results are sorted by from position, then by to position
|
|
60
115
|
*
|
|
61
116
|
* @param set - The DecorationSet to extract scrollable decorations from
|
|
62
117
|
* @param doc - Current document; when set, diff-inline ranges are validated against this doc
|
|
@@ -107,25 +162,19 @@ export var getScrollableDecorations = function getScrollableDecorations(set, doc
|
|
|
107
162
|
return isInlineDiffDecorationRenderableInDoc(doc, d.from, d.to);
|
|
108
163
|
}) : rawInlines;
|
|
109
164
|
var widgets = filtered.filter(isWidgetDecoration);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} // inline inside block
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Combine all non-overlapping decorations
|
|
128
|
-
var result = [].concat(_toConsumableArray(nonOverlappingBlocks), _toConsumableArray(inlines), _toConsumableArray(widgets));
|
|
165
|
+
var result;
|
|
166
|
+
if (fg('confluence_ncs_step_diffing_version_history')) {
|
|
167
|
+
// Group overlapping or directly touching ranges into one customer-facing edit.
|
|
168
|
+
result = groupTouchingDecorations([].concat(_toConsumableArray(blocks), _toConsumableArray(inlines), _toConsumableArray(widgets)), isInlineDecoration);
|
|
169
|
+
} else {
|
|
170
|
+
// Legacy behavior: exclude blocks that contain or are contained by an inline decoration.
|
|
171
|
+
var nonOverlappingBlocks = blocks.filter(function (block) {
|
|
172
|
+
return !inlines.some(function (inline) {
|
|
173
|
+
return isRangeFullyInside(block.from, block.to, inline.from, inline.to) || isRangeFullyInside(inline.from, inline.to, block.from, block.to);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
result = [].concat(_toConsumableArray(nonOverlappingBlocks), _toConsumableArray(inlines), _toConsumableArray(widgets));
|
|
177
|
+
}
|
|
129
178
|
|
|
130
179
|
// Sort by from position, then by to position
|
|
131
180
|
result.sort(function (a, b) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
-
import
|
|
2
|
+
import { Decoration, type DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
3
3
|
import type { DiffType } from '../showDiffPluginType';
|
|
4
4
|
/**
|
|
5
5
|
* Returns true when an inline decoration's [from, to) range can actually show in the document:
|
|
@@ -17,9 +17,11 @@ export declare function isInlineDiffDecorationRenderableInDoc(doc: PMNode, from:
|
|
|
17
17
|
* 3. Deduplicates diff-block decorations with same from, to and nodeName
|
|
18
18
|
* 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
|
|
19
19
|
* (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
|
|
20
|
-
* 5.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
|
|
21
|
+
* directly touching ranges across decoration types into one result, using the union of all
|
|
22
|
+
* grouped ranges
|
|
23
|
+
* (zero-width widgets can join a group without extending it)
|
|
24
|
+
* 6. Results are sorted by from position, then by to position
|
|
23
25
|
*
|
|
24
26
|
* @param set - The DecorationSet to extract scrollable decorations from
|
|
25
27
|
* @param doc - Current document; when set, diff-inline ranges are validated against this doc
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-show-diff",
|
|
3
|
-
"version": "14.2.
|
|
3
|
+
"version": "14.2.8",
|
|
4
4
|
"description": "ShowDiff plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"atlaskit:src": "src/index.ts",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@atlaskit/adf-schema": "^57.2.0",
|
|
24
|
-
"@atlaskit/custom-steps": "^1.
|
|
25
|
-
"@atlaskit/editor-plugin-analytics": "^16.
|
|
24
|
+
"@atlaskit/custom-steps": "^1.1.0",
|
|
25
|
+
"@atlaskit/editor-plugin-analytics": "^16.1.0",
|
|
26
26
|
"@atlaskit/editor-plugin-user-intent": "^14.0.0",
|
|
27
27
|
"@atlaskit/editor-prosemirror": "^8.0.0",
|
|
28
|
-
"@atlaskit/editor-tables": "^3.
|
|
28
|
+
"@atlaskit/editor-tables": "^3.1.0",
|
|
29
29
|
"@atlaskit/platform-feature-experiments": "^0.3.0",
|
|
30
|
-
"@atlaskit/platform-feature-flags": "^2.
|
|
31
|
-
"@atlaskit/tmp-editor-statsig": "^
|
|
32
|
-
"@atlaskit/tokens": "^16.
|
|
30
|
+
"@atlaskit/platform-feature-flags": "^2.2.0",
|
|
31
|
+
"@atlaskit/tmp-editor-statsig": "^169.0.0",
|
|
32
|
+
"@atlaskit/tokens": "^16.10.0",
|
|
33
33
|
"@babel/runtime": "^7.0.0",
|
|
34
34
|
"@compiled/react": "^1.0.2",
|
|
35
35
|
"lodash": "^4.17.21",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"prosemirror-changeset": "^2.3.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@atlaskit/adf-utils": "^20.
|
|
41
|
-
"@atlaskit/button": "^25.
|
|
40
|
+
"@atlaskit/adf-utils": "^20.8.0",
|
|
41
|
+
"@atlaskit/button": "^25.3.0",
|
|
42
42
|
"@atlaskit/css": "^1.1.0",
|
|
43
|
-
"@atlaskit/dropdown-menu": "^18.
|
|
44
|
-
"@atlaskit/editor-core": "^226.
|
|
45
|
-
"@atlaskit/editor-json-transformer": "^9.
|
|
43
|
+
"@atlaskit/dropdown-menu": "^18.3.0",
|
|
44
|
+
"@atlaskit/editor-core": "^226.3.0",
|
|
45
|
+
"@atlaskit/editor-json-transformer": "^9.8.0",
|
|
46
46
|
"@atlaskit/editor-plugins": "^16.1.0",
|
|
47
|
-
"@atlaskit/form": "^17.
|
|
48
|
-
"@atlaskit/primitives": "^22.
|
|
49
|
-
"@atlaskit/section-message": "^10.
|
|
50
|
-
"@atlaskit/textarea": "^10.
|
|
51
|
-
"@atlaskit/textfield": "^10.
|
|
47
|
+
"@atlaskit/form": "^17.2.0",
|
|
48
|
+
"@atlaskit/primitives": "^22.5.0",
|
|
49
|
+
"@atlaskit/section-message": "^10.2.0",
|
|
50
|
+
"@atlaskit/textarea": "^10.2.0",
|
|
51
|
+
"@atlaskit/textfield": "^10.2.0",
|
|
52
52
|
"@atlaskit/util-data-test": "^19.1.0",
|
|
53
53
|
"@atlassian/confluence-presets": "workspace:^",
|
|
54
54
|
"@atlassian/content-reconciliation": "^0.1.3506",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"react-intl": "^7.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@atlaskit/editor-common": "^120.
|
|
62
|
+
"@atlaskit/editor-common": "^120.10.0",
|
|
63
63
|
"react": "^18.2.0 || ^19.2.0"
|
|
64
64
|
},
|
|
65
65
|
"techstack": {
|