@atlaskit/editor-plugin-show-diff 16.1.1 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +67 -22
- package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +242 -26
- package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +71 -37
- package/dist/cjs/pm-plugins/decorations/colorSchemes/schemes.js +5 -1
- package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +0 -2
- package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +21 -14
- package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +123 -43
- package/dist/cjs/pm-plugins/decorations/revealStyles.js +5 -1
- package/dist/cjs/pm-plugins/getScrollableDecorations.js +48 -19
- package/dist/cjs/pm-plugins/utils/baseNodeName.js +25 -0
- package/dist/cjs/pm-plugins/utils/taggableBlockNodes.js +37 -0
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +39 -2
- package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +196 -10
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +51 -19
- package/dist/es2019/pm-plugins/decorations/colorSchemes/schemes.js +3 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +0 -2
- package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +23 -14
- package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +60 -7
- package/dist/es2019/pm-plugins/decorations/revealStyles.js +5 -1
- package/dist/es2019/pm-plugins/getScrollableDecorations.js +44 -19
- package/dist/es2019/pm-plugins/utils/baseNodeName.js +18 -0
- package/dist/es2019/pm-plugins/utils/taggableBlockNodes.js +28 -0
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +67 -22
- package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +243 -26
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +71 -37
- package/dist/esm/pm-plugins/decorations/colorSchemes/schemes.js +5 -1
- package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +0 -2
- package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +21 -14
- package/dist/esm/pm-plugins/decorations/extractContributorTags.js +123 -43
- package/dist/esm/pm-plugins/decorations/revealStyles.js +5 -1
- package/dist/esm/pm-plugins/getScrollableDecorations.js +48 -19
- package/dist/esm/pm-plugins/utils/baseNodeName.js +20 -0
- package/dist/esm/pm-plugins/utils/taggableBlockNodes.js +32 -0
- package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +20 -1
- package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +4 -11
- package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +9 -8
- package/dist/types/pm-plugins/getScrollableDecorations.d.ts +2 -2
- package/dist/types/pm-plugins/utils/baseNodeName.d.ts +16 -0
- package/dist/types/pm-plugins/utils/taggableBlockNodes.d.ts +14 -0
- package/package.json +5 -4
|
@@ -4,6 +4,7 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
|
4
4
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
5
5
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
6
6
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
7
|
+
import { isTaggableBlockNode } from '../utils/taggableBlockNodes';
|
|
7
8
|
import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
|
|
8
9
|
import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
|
|
9
10
|
import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
|
|
@@ -23,23 +24,28 @@ const UNSTYLED_NODES = ['mediaSingle', 'mediaGroup', 'table',
|
|
|
23
24
|
'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'];
|
|
24
25
|
const CELL_NODES = ['tableCell', 'tableHeader'];
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
* Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
|
|
28
|
-
* Lists and tables are excluded: they decorate per item and per cell, with no one-tag-per-block
|
|
29
|
-
* design yet. `blockCard` covers the datasource variant, which is the same node type.
|
|
30
|
-
*/
|
|
31
|
-
const TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule']);
|
|
32
|
-
|
|
33
27
|
/** Panels, rules and media can also sit in a table cell, which is out of scope for block tags. */
|
|
34
28
|
const isInsideTable = (doc, pos) => findParentNodeClosestToPos(doc.resolve(pos), node => node.type.name === 'table') !== undefined;
|
|
35
29
|
|
|
36
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Whether this block can host a contributor tag of its own.
|
|
32
|
+
*
|
|
33
|
+
* The node type is resolved from the document rather than matched on `change.name`, so a schema
|
|
34
|
+
* variant is recognised as the type it varies — see `isTaggableBlockNode`. `from` is the node's own
|
|
35
|
+
* position, so `nodeAt` returns exactly the node being decorated.
|
|
36
|
+
*/
|
|
37
37
|
const canTagBlock = ({
|
|
38
38
|
diffType,
|
|
39
39
|
doc,
|
|
40
|
-
from
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
from
|
|
41
|
+
}) => {
|
|
42
|
+
var _doc$nodeAt;
|
|
43
|
+
if (doc === undefined || !isExtendedEnabled(diffType)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const nodeType = (_doc$nodeAt = doc.nodeAt(from)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type;
|
|
47
|
+
return nodeType !== undefined && isTaggableBlockNode(nodeType) && !isInsideTable(doc, from);
|
|
48
|
+
};
|
|
43
49
|
|
|
44
50
|
/** Positioning context for the cell overlay widget decorations. */
|
|
45
51
|
const cellPositionStyle = convertToInlineCss({
|
|
@@ -176,8 +182,7 @@ export const createBlockChangedDecoration = ({
|
|
|
176
182
|
const shouldTagBlock = showContributorTags && canTagBlock({
|
|
177
183
|
diffType,
|
|
178
184
|
doc,
|
|
179
|
-
from: change.from
|
|
180
|
-
name: change.name
|
|
185
|
+
from: change.from
|
|
181
186
|
});
|
|
182
187
|
// Named so this block's own tag can position against it; see `createContributorTagWidget`.
|
|
183
188
|
const tagAnchorName = shouldTagBlock && isContributorTagWidgetEnabled() ? buildAnchorDecorationKey({
|
|
@@ -221,7 +226,9 @@ export const createBlockChangedDecoration = ({
|
|
|
221
226
|
key: 'cell-overlay-decoration'
|
|
222
227
|
}));
|
|
223
228
|
}
|
|
224
|
-
// isInserted is only read under the extended experience, so pass it unconditionally.
|
|
229
|
+
// isInserted is only read under the extended experience, so pass it unconditionally. `change.name`
|
|
230
|
+
// verbatim: base-name resolution stays in `canTagBlock`, which needs tags on, so a schema
|
|
231
|
+
// variant's legacy style is untouched — see `resolveBaseNodeName`.
|
|
225
232
|
const nodeStyle = getBlockNodeStyle({
|
|
226
233
|
nodeName: change.name,
|
|
227
234
|
colorScheme,
|
|
@@ -254,6 +261,8 @@ export const createBlockChangedDecoration = ({
|
|
|
254
261
|
})));
|
|
255
262
|
}
|
|
256
263
|
if (decorations.length === 0) {
|
|
264
|
+
// An unstyled node type (paragraph, heading, list, layout, …) emits no decoration at all, so
|
|
265
|
+
// there is nothing for a tag to hang off even when the node type would otherwise be taggable.
|
|
257
266
|
return decorations;
|
|
258
267
|
}
|
|
259
268
|
if (showIndicators && doc && isExtendedEnabled(diffType)) {
|
|
@@ -34,6 +34,12 @@ const leadsReplacement = widget => fg('confluence_ncs_step_diffing_version_histo
|
|
|
34
34
|
*/
|
|
35
35
|
const isSameBlockChange = (inner, block) => inner.spec.attributionKey === block.spec.attributionKey && (block.from <= inner.from && inner.to <= block.to || inner.from <= block.from && block.to <= inner.to);
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Whether `outer` is the box `inner` sits in, for one contributor. Nested blocks they inserted read
|
|
39
|
+
* as one change, so only the outermost keeps a tag (EDITOR-8932).
|
|
40
|
+
*/
|
|
41
|
+
const containsBlock = (outer, inner) => outer.spec.attributionKey === inner.spec.attributionKey && outer.from <= inner.from && inner.to <= outer.to && outer.to - outer.from > inner.to - inner.from;
|
|
42
|
+
|
|
37
43
|
/**
|
|
38
44
|
* Which of two changes in the same range leads, and so is the one a tag captions. Position first,
|
|
39
45
|
* then `side`, since deleted content shares its `from` with the content that replaced it and paints
|
|
@@ -52,6 +58,15 @@ const containedBy = (targets, {
|
|
|
52
58
|
decoration
|
|
53
59
|
}) => from <= decoration.from && decoration.to <= to).sort(byLeadingPosition);
|
|
54
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The stop a target is stepped to by: the narrowest one that wholly covers it. A stop's range is
|
|
63
|
+
* the union of the decorations grouped into it, so one inserted run's inline highlight stretches
|
|
64
|
+
* its stop over every block the run spans — including blocks that are a stop of their own. Picking
|
|
65
|
+
* the narrowest keeps each of those blocks in the stop that actually reaches it, rather than
|
|
66
|
+
* letting the widest stop claim them all and fold away every tag but the first (EDITOR-8932).
|
|
67
|
+
*/
|
|
68
|
+
const owningStop = (target, stops) => stops.reduce((narrowest, stop) => stop.from <= target.decoration.from && target.decoration.to <= stop.to && (narrowest === undefined || stop.to - stop.from < narrowest.to - narrowest.from) ? stop : narrowest, undefined);
|
|
69
|
+
|
|
55
70
|
/**
|
|
56
71
|
* The one tag a navigation step reveals. `spec.isActive` is no use here: it covers everything the
|
|
57
72
|
* group's union range touches, so several tags revealed over each other (EDITOR-8971). Hence
|
|
@@ -108,6 +123,10 @@ export const extractContributorTags = (decorations, contributors, activeIndexPos
|
|
|
108
123
|
}) => decoration.spec.decorationType === 'widget');
|
|
109
124
|
const linkedDiffIds = new Map();
|
|
110
125
|
const folded = new Set();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A folded target keeps no tag of its own — it becomes a hover target of the host instead.
|
|
129
|
+
*/
|
|
111
130
|
const fold = (host, target) => {
|
|
112
131
|
var _linkedDiffIds$get, _linkedDiffIds$get2;
|
|
113
132
|
folded.add(target);
|
|
@@ -158,20 +177,54 @@ export const extractContributorTags = (decorations, contributors, activeIndexPos
|
|
|
158
177
|
}
|
|
159
178
|
}
|
|
160
179
|
|
|
180
|
+
// A nested block folds into the outermost one of the same contributor, so nesting adds no tags.
|
|
181
|
+
if (fg('confluence_ncs_step_diffing_version_history')) {
|
|
182
|
+
const blocksByContributor = new Map();
|
|
183
|
+
for (const target of blockTargets) {
|
|
184
|
+
var _target$decoration$sp, _blocksByContributor$;
|
|
185
|
+
const key = (_target$decoration$sp = target.decoration.spec.attributionKey) !== null && _target$decoration$sp !== void 0 ? _target$decoration$sp : '';
|
|
186
|
+
blocksByContributor.set(key, [...((_blocksByContributor$ = blocksByContributor.get(key)) !== null && _blocksByContributor$ !== void 0 ? _blocksByContributor$ : []), target]);
|
|
187
|
+
}
|
|
188
|
+
for (const contributorBlocks of blocksByContributor.values()) {
|
|
189
|
+
// Block ranges nest rather than partially overlap, so ascending `from` then descending `to`
|
|
190
|
+
// reaches every block after the ones containing it. A stack of the blocks still open at
|
|
191
|
+
// this position then yields each block's container in one pass, rather than rescanning
|
|
192
|
+
// every block for the widest that contains it.
|
|
193
|
+
const nested = [...contributorBlocks].sort((left, right) => left.decoration.from - right.decoration.from || right.decoration.to - left.decoration.to);
|
|
194
|
+
const open = [];
|
|
195
|
+
for (const target of nested) {
|
|
196
|
+
while (open.length > 0 && !containsBlock(open[open.length - 1].decoration, target.decoration)) {
|
|
197
|
+
open.pop();
|
|
198
|
+
}
|
|
199
|
+
// Outermost first, so the bottom of the stack is the block the tag survives on.
|
|
200
|
+
if (open.length > 0) {
|
|
201
|
+
fold(open[0], target);
|
|
202
|
+
}
|
|
203
|
+
open.push(target);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
161
208
|
// One tag per contributor per navigation stop. The folds above only catch a replacement's two
|
|
162
209
|
// halves and a block's own highlights; two of one contributor's changes that merely touch are a
|
|
163
210
|
// single stop (`groupTouchingDecorations`) yet kept a tag each, so the stop's trailing tag was
|
|
164
211
|
// drawn but could never be stepped to (EDITOR-8971). It folds into the leading tag of its
|
|
165
212
|
// contributor, staying a hover target. Contributors are kept apart: a stop spanning two of them
|
|
166
213
|
// still captions each, rather than crediting one for the other's change.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
214
|
+
// Each target is counted against one stop only — see `owningStop`.
|
|
215
|
+
if (stops !== null && stops !== void 0 && stops.length) {
|
|
216
|
+
const byStopAndContributor = new Map();
|
|
217
|
+
for (const target of targets.filter(target => !folded.has(target))) {
|
|
218
|
+
var _target$decoration$sp2, _byStopAndContributor;
|
|
219
|
+
const stop = owningStop(target, stops);
|
|
220
|
+
if (stop === undefined) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const key = `${stop.from}-${stop.to}|${(_target$decoration$sp2 = target.decoration.spec.attributionKey) !== null && _target$decoration$sp2 !== void 0 ? _target$decoration$sp2 : ''}`;
|
|
224
|
+
byStopAndContributor.set(key, [...((_byStopAndContributor = byStopAndContributor.get(key)) !== null && _byStopAndContributor !== void 0 ? _byStopAndContributor : []), target]);
|
|
173
225
|
}
|
|
174
|
-
for (const
|
|
226
|
+
for (const members of byStopAndContributor.values()) {
|
|
227
|
+
const [leader, ...trailing] = [...members].sort(byLeadingPosition);
|
|
175
228
|
trailing.forEach(target => fold(leader, target));
|
|
176
229
|
}
|
|
177
230
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
|
|
1
2
|
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
3
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
3
4
|
import { buildDeletedInlineStyleStandard, getDeletedInlineRevealColors, getInsertedInlineRevealColors } from './colorSchemes/factory';
|
|
@@ -96,6 +97,7 @@ export const resolveRevealStyle = ({
|
|
|
96
97
|
return undefined;
|
|
97
98
|
}
|
|
98
99
|
const colors = getColorScheme(colorScheme);
|
|
100
|
+
const brand = isInserted && Object.prototype.hasOwnProperty.call(agentBrandColorSchemes, colors.insertColor) ? agentBrandColorSchemes[colors.insertColor] : undefined;
|
|
99
101
|
const {
|
|
100
102
|
background,
|
|
101
103
|
border
|
|
@@ -106,7 +108,9 @@ export const resolveRevealStyle = ({
|
|
|
106
108
|
});
|
|
107
109
|
return {
|
|
108
110
|
role: isInserted ? 'added' : 'deleted',
|
|
109
|
-
style: (
|
|
111
|
+
style: (brand ? convertToInlineCss({
|
|
112
|
+
color: brand.text
|
|
113
|
+
}) : '') + (isInserted || !includeDeletedTextStyle ? '' : deletedTextOnlyStyle(colors, isActive)) + revealBaseStyle + buildWipeableBackground(background) + buildRevealVariables({
|
|
110
114
|
background,
|
|
111
115
|
border
|
|
112
116
|
})
|
|
@@ -49,6 +49,17 @@ function decorationSide(decoration) {
|
|
|
49
49
|
return isDiffDecoration(decoration) && decoration.spec.side || 0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Whose change a decoration is part of, or `''` on an unattributed diff — where every decoration
|
|
54
|
+
* reads as one contributor and the grouping below is unchanged.
|
|
55
|
+
*
|
|
56
|
+
* Contributors are kept apart so one stop never covers two of them, since only the tag on the
|
|
57
|
+
* change stepped to reveals (EDITOR-8932).
|
|
58
|
+
*/
|
|
59
|
+
function contributorOf(decoration) {
|
|
60
|
+
return isDiffDecoration(decoration) && decoration.spec.attributionKey || '';
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
/**
|
|
53
64
|
* Collapses decorations that represent one continuous customer-facing edit.
|
|
54
65
|
*
|
|
@@ -57,32 +68,46 @@ function decorationSide(decoration) {
|
|
|
57
68
|
* deletion at the same location should be treated as one replacement. Zero-width decorations
|
|
58
69
|
* (normally deleted-content widgets) can join a group, but cannot extend its range and therefore
|
|
59
70
|
* cannot bridge two otherwise separate edits.
|
|
71
|
+
*
|
|
72
|
+
* One group per contributor within a run of touching ranges — see `contributorOf`.
|
|
60
73
|
*/
|
|
61
74
|
function groupTouchingDecorations(decorations, isInlineDecoration) {
|
|
62
75
|
if (decorations.length < 2) {
|
|
63
76
|
return decorations;
|
|
64
77
|
}
|
|
65
78
|
const groups = [];
|
|
66
|
-
|
|
67
|
-
let
|
|
68
|
-
let
|
|
79
|
+
// The groups a following decoration can still join — one per contributor, dropped at every gap.
|
|
80
|
+
let openGroups = [];
|
|
81
|
+
let clusterTo = 0;
|
|
69
82
|
const sortedDecorations = [...decorations].sort((a, b) => a.from === b.from ? a.to - b.to : a.from - b.from);
|
|
70
83
|
sortedDecorations.forEach(decoration => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
groups.push({
|
|
76
|
-
decorations: currentGroup,
|
|
77
|
-
from: currentGroupFrom,
|
|
78
|
-
to: currentGroupTo
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
84
|
+
var _openGroups$find;
|
|
85
|
+
// A gap ends the run: nothing before it can be joined.
|
|
86
|
+
if (decoration.from > clusterTo) {
|
|
87
|
+
openGroups = [];
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
const contributor = contributorOf(decoration);
|
|
90
|
+
// An unattributed decoration names nobody to keep apart, so it joins whatever is open — a
|
|
91
|
+
// decision item's own highlight must not split the stop from the tag on its list.
|
|
92
|
+
const openGroup = (_openGroups$find = openGroups.find(group => group.contributor === contributor)) !== null && _openGroups$find !== void 0 ? _openGroups$find : contributor === '' ? openGroups[0] : openGroups.find(group => group.contributor === '');
|
|
93
|
+
if (openGroup === undefined) {
|
|
94
|
+
const group = {
|
|
95
|
+
contributor,
|
|
96
|
+
decorations: [decoration],
|
|
97
|
+
from: decoration.from,
|
|
98
|
+
to: decoration.to
|
|
99
|
+
};
|
|
100
|
+
groups.push(group);
|
|
101
|
+
openGroups = [...openGroups, group];
|
|
102
|
+
} else {
|
|
103
|
+
// The first contributor to join claims the group, so the next one still starts its own.
|
|
104
|
+
openGroup.contributor = openGroup.contributor || contributor;
|
|
105
|
+
openGroup.decorations.push(decoration);
|
|
106
|
+
openGroup.to = Math.max(openGroup.to, decoration.to);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A zero-width decoration must not extend the run and bridge a gap.
|
|
110
|
+
clusterTo = Math.max(clusterTo, decoration.to);
|
|
86
111
|
});
|
|
87
112
|
return groups.map(({
|
|
88
113
|
decorations: group,
|
|
@@ -127,8 +152,8 @@ function groupTouchingDecorations(decorations, isInlineDecoration) {
|
|
|
127
152
|
* 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content
|
|
128
153
|
* (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks)
|
|
129
154
|
* 5. When `confluence_ncs_step_diffing_version_history` is enabled, groups overlapping or
|
|
130
|
-
* directly touching ranges across decoration types into one result, using the
|
|
131
|
-
* grouped ranges
|
|
155
|
+
* directly touching ranges across decoration types into one result per contributor, using the
|
|
156
|
+
* union of all grouped ranges
|
|
132
157
|
* (zero-width widgets can join a group without extending it). Under
|
|
133
158
|
* `platform_editor_ai_show_diff_patch_1`, a group that starts with content painting above
|
|
134
159
|
* the content that replaced it — reports that widget as its `scrollTarget` spec,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A node type's base name, with a schema variant's suffix stripped: `panel`, `panel_c1` and
|
|
5
|
+
* `panel_c1_root_only` all resolve to `panel`.
|
|
6
|
+
*
|
|
7
|
+
* Package-local rather than `getBaseNodeTypeName` from editor-common, which enumerates the variants
|
|
8
|
+
* it happens to know (`panel_c1` alone) and is shared with features outside this one. Derived
|
|
9
|
+
* instead of listed, so a variant added later needs no change here: the schema generator names a
|
|
10
|
+
* variant `<type>_<variant>`, and every other node name in the ADF schema is camelCase, so the
|
|
11
|
+
* first `_` is the boundary.
|
|
12
|
+
*
|
|
13
|
+
* Gated, so the diff behaves exactly as before for anyone outside the rollout.
|
|
14
|
+
*
|
|
15
|
+
* For taggability only — this gate is weaker than the ones attribution needs, so resolving a variant
|
|
16
|
+
* in the styling decisions would move the legacy diff for readers who see no tags.
|
|
17
|
+
*/
|
|
18
|
+
export const resolveBaseNodeName = name => fg('confluence_ncs_step_diffing_version_history') ? name.split('_')[0] : name;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolveBaseNodeName } from './baseNodeName';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
|
|
5
|
+
* Everything that draws a box whatever it holds, plus the two lists that are inserted whole: without
|
|
6
|
+
* them the tag fell through to the text inside the first item (EDITOR-8932). Bullet and ordered lists
|
|
7
|
+
* are left out, since their items accrue one at a time. Tables and list items decorate per cell and
|
|
8
|
+
* per item, with no one-tag-per-block design yet. `blockCard` covers the datasource variant, which is
|
|
9
|
+
* the same node type.
|
|
10
|
+
*
|
|
11
|
+
* Base names only — match with the predicates below, never against `type.name` directly.
|
|
12
|
+
*/
|
|
13
|
+
const TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'decisionList', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule', 'taskList']);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether this node hosts a contributor tag of its own. Navigation grouping does not consult this:
|
|
17
|
+
* a run of these one contributor inserted is a single stop, tagged on the leading block.
|
|
18
|
+
*
|
|
19
|
+
* Resolved through `resolveBaseNodeName` so a schema variant counts as the type it is a variant of:
|
|
20
|
+
* `panel_c1` is the panel the table-in-panel schema inserts, and matching on `type.name` left it
|
|
21
|
+
* untagged (EDITOR-8932).
|
|
22
|
+
*
|
|
23
|
+
* By name, because the decoration spec carries `nodeName` rather than the type it came from.
|
|
24
|
+
*/
|
|
25
|
+
export const isTaggableBlockNodeName = name => name !== undefined && TAGGABLE_BLOCK_NODES.has(resolveBaseNodeName(name));
|
|
26
|
+
|
|
27
|
+
/** As above, from the node type itself. */
|
|
28
|
+
export const isTaggableBlockNode = nodeType => isTaggableBlockNodeName(nodeType.name);
|
|
@@ -190,8 +190,51 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
|
|
|
190
190
|
diffType = _ref5.diffType,
|
|
191
191
|
_ref5$coarseTableCell = _ref5.coarseTableCellsOnly,
|
|
192
192
|
coarseTableCellsOnly = _ref5$coarseTableCell === void 0 ? false : _ref5$coarseTableCell,
|
|
193
|
-
tagMountContext = _ref5.tagMountContext
|
|
193
|
+
tagMountContext = _ref5.tagMountContext,
|
|
194
|
+
nodeRanges = _ref5.nodeRanges;
|
|
194
195
|
var decorations = [];
|
|
196
|
+
|
|
197
|
+
// Exact-nodes path: only reached for a split change (EDITOR-8932), which names the nodes it
|
|
198
|
+
// owns. Deliberately kept separate from the walk below rather than sharing it.
|
|
199
|
+
if (nodeRanges) {
|
|
200
|
+
var _iterator = _createForOfIteratorHelper(nodeRanges),
|
|
201
|
+
_step;
|
|
202
|
+
try {
|
|
203
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
204
|
+
var _step$value = _step.value,
|
|
205
|
+
pos = _step$value.from,
|
|
206
|
+
nodeEnd = _step$value.to;
|
|
207
|
+
var node = doc.nodeAt(pos);
|
|
208
|
+
if (!(node !== null && node !== void 0 && node.isBlock)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
var isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
|
|
212
|
+
decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
|
|
213
|
+
attributionKey: attributionKey,
|
|
214
|
+
change: {
|
|
215
|
+
from: pos,
|
|
216
|
+
to: nodeEnd,
|
|
217
|
+
name: node.type.name
|
|
218
|
+
},
|
|
219
|
+
colorScheme: colorScheme,
|
|
220
|
+
isInserted: isInserted,
|
|
221
|
+
isActive: isActive,
|
|
222
|
+
shouldHideDeleted: shouldHideDeleted,
|
|
223
|
+
showContributorTags: showContributorTags,
|
|
224
|
+
showIndicators: showIndicators,
|
|
225
|
+
doc: doc,
|
|
226
|
+
diffType: diffType,
|
|
227
|
+
tagMountContext: tagMountContext
|
|
228
|
+
})));
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
_iterator.e(err);
|
|
232
|
+
} finally {
|
|
233
|
+
_iterator.f();
|
|
234
|
+
}
|
|
235
|
+
return decorations;
|
|
236
|
+
}
|
|
237
|
+
|
|
195
238
|
// Coarse path: inside the large table, only cell/header overlays are kept — the
|
|
196
239
|
// table/row/paragraph decorations there are redundant and cause expensive per-cell
|
|
197
240
|
// re-render. Blocks outside the table keep their normal decorations.
|
|
@@ -205,18 +248,18 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
|
|
|
205
248
|
}
|
|
206
249
|
}
|
|
207
250
|
if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
|
|
208
|
-
var
|
|
209
|
-
var
|
|
251
|
+
var _nodeEnd = pos + node.nodeSize;
|
|
252
|
+
var _isActive = isRangeActive(activeIndexPos, pos, _nodeEnd);
|
|
210
253
|
decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
|
|
211
254
|
attributionKey: attributionKey,
|
|
212
255
|
change: {
|
|
213
256
|
from: pos,
|
|
214
|
-
to:
|
|
257
|
+
to: _nodeEnd,
|
|
215
258
|
name: node.type.name
|
|
216
259
|
},
|
|
217
260
|
colorScheme: colorScheme,
|
|
218
261
|
isInserted: isInserted,
|
|
219
|
-
isActive:
|
|
262
|
+
isActive: _isActive,
|
|
220
263
|
shouldHideDeleted: shouldHideDeleted,
|
|
221
264
|
showContributorTags: showContributorTags,
|
|
222
265
|
showIndicators: showIndicators,
|
|
@@ -339,13 +382,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
339
382
|
}
|
|
340
383
|
var pendingAttrSteps = [];
|
|
341
384
|
var stepMaps = [];
|
|
342
|
-
var
|
|
343
|
-
|
|
385
|
+
var _iterator2 = _createForOfIteratorHelper(simplifiedSteps.entries()),
|
|
386
|
+
_step2;
|
|
344
387
|
try {
|
|
345
|
-
for (
|
|
346
|
-
var
|
|
347
|
-
stepIndex =
|
|
348
|
-
step =
|
|
388
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
389
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
390
|
+
stepIndex = _step2$value[0],
|
|
391
|
+
step = _step2$value[1];
|
|
349
392
|
var result = step.apply(steppedDoc);
|
|
350
393
|
if (result.failed === null && result.doc) {
|
|
351
394
|
if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
|
|
@@ -364,9 +407,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
364
407
|
// Gate the complete attribution fix: mapped positions and step-time nodes are used together.
|
|
365
408
|
// The fallback deliberately preserves the legacy raw-position lookups for rollout safety.
|
|
366
409
|
} catch (err) {
|
|
367
|
-
|
|
410
|
+
_iterator2.e(err);
|
|
368
411
|
} finally {
|
|
369
|
-
|
|
412
|
+
_iterator2.f();
|
|
370
413
|
}
|
|
371
414
|
var attrStepContexts = fg('platform_editor_reduce_diff_attr_sensitivity') ? pendingAttrSteps.flatMap(function (_ref1) {
|
|
372
415
|
var afterNode = _ref1.afterNode,
|
|
@@ -454,7 +497,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
454
497
|
tr: tr,
|
|
455
498
|
attributedChanges: attributedChanges
|
|
456
499
|
});
|
|
457
|
-
changes = collapseOverlappingChanges(uncollapsedChanges);
|
|
500
|
+
changes = collapseOverlappingChanges(uncollapsedChanges, tr.doc);
|
|
458
501
|
|
|
459
502
|
// Still colours-only: a tags-only diff keeps the plain colour scheme.
|
|
460
503
|
if (attributionColoringEnabled) {
|
|
@@ -551,13 +594,13 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
551
594
|
// table (the cell overlays added below provide the highlight without the
|
|
552
595
|
// expensive content re-render); leaf blocks outside the table still get theirs.
|
|
553
596
|
var coarseTables = useCoarseTableDecoration ? tableRanges(tr.doc, change.fromB, change.toB) : [];
|
|
554
|
-
var
|
|
555
|
-
|
|
597
|
+
var _iterator3 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
|
|
598
|
+
_step3;
|
|
556
599
|
try {
|
|
557
|
-
for (
|
|
558
|
-
var
|
|
559
|
-
from =
|
|
560
|
-
to =
|
|
600
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
601
|
+
var _step3$value = _slicedToArray(_step3.value, 2),
|
|
602
|
+
from = _step3$value[0],
|
|
603
|
+
to = _step3$value[1];
|
|
561
604
|
if (useCoarseTableDecoration && isInsideTable(coarseTables, from)) {
|
|
562
605
|
continue;
|
|
563
606
|
}
|
|
@@ -584,9 +627,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
584
627
|
}))));
|
|
585
628
|
}
|
|
586
629
|
} catch (err) {
|
|
587
|
-
|
|
630
|
+
_iterator3.e(err);
|
|
588
631
|
} finally {
|
|
589
|
-
|
|
632
|
+
_iterator3.f();
|
|
590
633
|
}
|
|
591
634
|
} else {
|
|
592
635
|
decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration(_objectSpread({
|
|
@@ -613,6 +656,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
613
656
|
doc: tr.doc,
|
|
614
657
|
from: change.fromB,
|
|
615
658
|
to: change.toB,
|
|
659
|
+
// A split piece's own range no longer covers the container it opened (EDITOR-8932).
|
|
660
|
+
nodeRanges: change.blockNodeRangesB,
|
|
616
661
|
colorScheme: changeColorScheme
|
|
617
662
|
}, isExtendedEnabled(diffType) && {
|
|
618
663
|
isInserted: isInserted,
|