@atlaskit/editor-plugin-show-diff 14.3.2 → 14.4.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 +18 -0
- package/afm-cc/tsconfig.json +18 -0
- package/afm-products/tsconfig.json +18 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +47 -26
- package/dist/cjs/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +61 -9
- package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +77 -20
- package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +10 -0
- package/dist/cjs/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/cjs/pm-plugins/decorations/createAnchorDecorationWidgets.js +52 -7
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +172 -0
- package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
- package/dist/cjs/pm-plugins/decorations/decorationKeys.js +31 -5
- package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +128 -0
- package/dist/cjs/pm-plugins/main.js +57 -6
- package/dist/cjs/pm-plugins/resolveDiffContributors.js +143 -0
- package/dist/cjs/showDiffPlugin.js +21 -6
- package/dist/cjs/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/cjs/ui/ContributorTag/ContributorTag.js +383 -0
- package/dist/cjs/ui/ContributorTag/ContributorTagWidget.js +50 -0
- package/dist/cjs/ui/ContributorTag/messages.js +24 -0
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +48 -27
- package/dist/es2019/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +39 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +53 -11
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +9 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/es2019/pm-plugins/decorations/createAnchorDecorationWidgets.js +43 -1
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +163 -0
- package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +38 -5
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +38 -6
- package/dist/es2019/pm-plugins/decorations/decorationKeys.js +30 -2
- package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +101 -0
- package/dist/es2019/pm-plugins/main.js +52 -3
- package/dist/es2019/pm-plugins/resolveDiffContributors.js +128 -0
- package/dist/es2019/showDiffPlugin.js +20 -5
- package/dist/es2019/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/es2019/ui/ContributorTag/ContributorTag.js +312 -0
- package/dist/es2019/ui/ContributorTag/ContributorTagWidget.js +43 -0
- package/dist/es2019/ui/ContributorTag/messages.js +18 -0
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +49 -28
- package/dist/esm/pm-plugins/calculateDiff/simplifyChangesWithAttribution.js +60 -8
- package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +77 -19
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +9 -0
- package/dist/esm/pm-plugins/decorations/colorSchemes/types.js +2 -0
- package/dist/esm/pm-plugins/decorations/createAnchorDecorationWidgets.js +51 -6
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +166 -0
- package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +37 -5
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +40 -7
- package/dist/esm/pm-plugins/decorations/decorationKeys.js +30 -5
- package/dist/esm/pm-plugins/decorations/extractContributorTags.js +122 -0
- package/dist/esm/pm-plugins/main.js +57 -6
- package/dist/esm/pm-plugins/resolveDiffContributors.js +137 -0
- package/dist/esm/showDiffPlugin.js +21 -6
- package/dist/esm/ui/ContributorTag/ContributorTag.compiled.css +29 -0
- package/dist/esm/ui/ContributorTag/ContributorTag.js +375 -0
- package/dist/esm/ui/ContributorTag/ContributorTagWidget.js +43 -0
- package/dist/esm/ui/ContributorTag/messages.js +18 -0
- package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
- package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +7 -1
- package/dist/types/pm-plugins/calculateDiff/simplifyChangesWithAttribution.d.ts +10 -0
- package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +24 -2
- package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +7 -1
- package/dist/types/pm-plugins/decorations/colorSchemes/schemes.d.ts +1 -2
- package/dist/types/pm-plugins/decorations/colorSchemes/types.d.ts +2 -1
- package/dist/types/pm-plugins/decorations/createAnchorDecorationWidgets.d.ts +14 -0
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +55 -0
- package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/decorationKeys.d.ts +28 -3
- package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +9 -0
- package/dist/types/pm-plugins/main.d.ts +8 -2
- package/dist/types/pm-plugins/resolveDiffContributors.d.ts +7 -0
- package/dist/types/showDiffPluginType.d.ts +65 -2
- package/dist/types/ui/ContributorTag/ContributorTag.d.ts +37 -0
- package/dist/types/ui/ContributorTag/ContributorTagWidget.d.ts +22 -0
- package/dist/types/ui/ContributorTag/messages.d.ts +6 -0
- package/package.json +16 -7
|
@@ -5,6 +5,7 @@ import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
|
5
5
|
import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
|
|
6
6
|
import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
|
|
7
7
|
import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
|
|
8
|
+
import { createContributorTagWidget } from './createContributorTagWidget';
|
|
8
9
|
import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy';
|
|
9
10
|
import { buildDiffDecorationSpec } from './decorationKeys';
|
|
10
11
|
const displayNoneStyle = convertToInlineCss({
|
|
@@ -92,28 +93,38 @@ export const getAtomicInlineChangedAttrs = (inlineNodeName, colorScheme) => reso
|
|
|
92
93
|
*/
|
|
93
94
|
|
|
94
95
|
export const createInlineChangedDecoration = ({
|
|
96
|
+
attributionKey,
|
|
95
97
|
change,
|
|
96
98
|
colorScheme,
|
|
97
99
|
isActive = false,
|
|
98
100
|
isInserted = true,
|
|
99
101
|
isAtomicInlineNode = false,
|
|
100
102
|
shouldHideDeleted = false,
|
|
103
|
+
showContributorTags = false,
|
|
101
104
|
showIndicators = false,
|
|
102
105
|
doc,
|
|
103
106
|
diffType,
|
|
104
107
|
hideAddedDiffsUnderline = false,
|
|
105
108
|
inlineNodeName,
|
|
106
109
|
hasDeletedWidget = false,
|
|
107
|
-
isDeletedWidgetBelow = false
|
|
110
|
+
isDeletedWidgetBelow = false,
|
|
111
|
+
tagMountContext
|
|
108
112
|
}) => {
|
|
109
|
-
|
|
113
|
+
// Derived from the range so it survives a recalculation: stepping to the next/previous change
|
|
114
|
+
// rebuilds decorations without touching the doc, and a fresh id would change the React key and the
|
|
115
|
+
// tag host's key, remounting every tag mid-step. Random ids are kept when tags are off.
|
|
116
|
+
const diffId = showContributorTags ? `inline-${change.fromB}-${change.toB}` : crypto.randomUUID();
|
|
110
117
|
if (shouldHideDeleted) {
|
|
111
118
|
return [Decoration.inline(change.fromB, change.toB, {
|
|
112
119
|
style: displayNoneStyle
|
|
113
120
|
}, buildDiffDecorationSpec({
|
|
121
|
+
// No attribution: this decoration paints nothing and emits no tag host, so a tag
|
|
122
|
+
// resolved from it would have nowhere to render.
|
|
123
|
+
colorScheme,
|
|
114
124
|
decorationType: 'inline',
|
|
115
125
|
diffId,
|
|
116
|
-
isActive
|
|
126
|
+
isActive,
|
|
127
|
+
isInserted
|
|
117
128
|
}))];
|
|
118
129
|
}
|
|
119
130
|
const colors = getColorScheme(colorScheme);
|
|
@@ -132,11 +143,18 @@ export const createInlineChangedDecoration = ({
|
|
|
132
143
|
...(atomicInlineAttrs && {
|
|
133
144
|
class: atomicInlineAttrs.className
|
|
134
145
|
}),
|
|
135
|
-
'data-testid': 'show-diff-changed-decoration'
|
|
146
|
+
'data-testid': 'show-diff-changed-decoration',
|
|
147
|
+
// Lets the contributor tag find its highlight on hover.
|
|
148
|
+
...(showContributorTags && {
|
|
149
|
+
'data-diff-id': diffId
|
|
150
|
+
})
|
|
136
151
|
}, buildDiffDecorationSpec({
|
|
152
|
+
attributionKey,
|
|
153
|
+
colorScheme,
|
|
137
154
|
decorationType: 'inline',
|
|
138
155
|
diffId,
|
|
139
|
-
isActive
|
|
156
|
+
isActive,
|
|
157
|
+
isInserted
|
|
140
158
|
}))];
|
|
141
159
|
if (showIndicators && doc && isExtendedEnabled(diffType)) {
|
|
142
160
|
// For paragraphs, fromB/toB land on the outer block
|
|
@@ -173,5 +191,20 @@ export const createInlineChangedDecoration = ({
|
|
|
173
191
|
diffId
|
|
174
192
|
}));
|
|
175
193
|
}
|
|
194
|
+
|
|
195
|
+
// Built from the raw change range, not the anchors above: a tag lines up with the changed text,
|
|
196
|
+
// while the bar's anchors are held on a block boundary when a deleted widget renders beside them.
|
|
197
|
+
if (showContributorTags && doc && isExtendedEnabled(diffType)) {
|
|
198
|
+
const tagWidget = createContributorTagWidget({
|
|
199
|
+
doc,
|
|
200
|
+
from: change.fromB,
|
|
201
|
+
to: change.toB,
|
|
202
|
+
diffId,
|
|
203
|
+
mountContext: tagMountContext
|
|
204
|
+
});
|
|
205
|
+
if (tagWidget) {
|
|
206
|
+
decorations.push(tagWidget);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
176
209
|
return decorations;
|
|
177
210
|
};
|
|
@@ -3,6 +3,7 @@ import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
|
3
3
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
4
4
|
import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
|
|
5
5
|
import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
|
|
6
|
+
import { createContributorTagHost, unmountContributorTag } from './createContributorTagWidget';
|
|
6
7
|
import { buildDiffDecorationSpec, buildAnchorDecorationKey } from './decorationKeys';
|
|
7
8
|
import { findSafeInsertPos } from './utils/findSafeInsertPos';
|
|
8
9
|
import { wrapBlockNodeView, injectInnerWrapper, createContentWrapper } from './utils/wrapBlockNodeView';
|
|
@@ -89,6 +90,7 @@ const createTableCellContentWidgets = ({
|
|
|
89
90
|
* that is not in the current document.
|
|
90
91
|
*/
|
|
91
92
|
export const createNodeChangedDecorationWidget = ({
|
|
93
|
+
attributionKey,
|
|
92
94
|
change,
|
|
93
95
|
doc,
|
|
94
96
|
nodeViewSerializer,
|
|
@@ -98,13 +100,15 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
98
100
|
activeIndexPos,
|
|
99
101
|
// This is false by default as this is generally used to show deleted content
|
|
100
102
|
isInserted = false,
|
|
103
|
+
showContributorTags = false,
|
|
101
104
|
showIndicators = false,
|
|
102
105
|
// When true, render the deleted content *after* (below) the new content instead of
|
|
103
106
|
// before it. Used for `smart` node-level changes so the deleted node appears beneath
|
|
104
107
|
// its replacement (gray + strikethrough).
|
|
105
108
|
placeBelow = false,
|
|
106
109
|
diffType,
|
|
107
|
-
hideAddedDiffsUnderline = false
|
|
110
|
+
hideAddedDiffsUnderline = false,
|
|
111
|
+
tagMountContext
|
|
108
112
|
}) => {
|
|
109
113
|
var _slice$content, _slice$content2, _slice$content2$first, _slice$content3, _slice$content3$first, _$safeInsertPos$nodeB, _slice$content$firstC, _newDoc$firstChild, _slice$content$firstC2;
|
|
110
114
|
const slice = doc.slice(change.fromA, change.toA);
|
|
@@ -196,7 +200,9 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
196
200
|
subtree: true
|
|
197
201
|
});
|
|
198
202
|
}
|
|
199
|
-
|
|
203
|
+
// Derived from the deleted range so it survives a recalculation — see the same reasoning in
|
|
204
|
+
// `createInlineChangedDecoration`.
|
|
205
|
+
const diffId = showContributorTags ? `widget-${change.fromA}-${change.toA}` : crypto.randomUUID();
|
|
200
206
|
const decorations = [];
|
|
201
207
|
const replacementNode = newDoc.nodeAt(change.fromB);
|
|
202
208
|
const firstReplacedNode = slice.content.firstChild;
|
|
@@ -313,24 +319,48 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
313
319
|
}
|
|
314
320
|
});
|
|
315
321
|
dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
|
|
322
|
+
|
|
323
|
+
// Needed even when the indicator bar is off, because a contributor tag also anchors against the
|
|
324
|
+
// widget.
|
|
325
|
+
if ((showIndicators || showContributorTags) && isExtendedEnabled(diffType)) {
|
|
326
|
+
dom.style.setProperty('anchor-name', `--${buildAnchorDecorationKey({
|
|
327
|
+
diffId
|
|
328
|
+
})}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Taken down in the widget's `destroy` below: `dom` is rebuilt on every recalculation, so the tag
|
|
332
|
+
// mounted into it must not outlive the copy ProseMirror is drawing.
|
|
333
|
+
let tagMount;
|
|
334
|
+
if (showContributorTags) {
|
|
335
|
+
// Lets the contributor tag find the deleted content on hover.
|
|
336
|
+
dom.setAttribute('data-diff-id', diffId);
|
|
337
|
+
|
|
338
|
+
// Hosted at the start of the deleted run rather than on `dom` itself: `dom` is inline, so once
|
|
339
|
+
// the deleted content wraps its box is the union of its line fragments.
|
|
340
|
+
const tagHost = createContributorTagHost(diffId, tagMountContext);
|
|
341
|
+
if (tagHost) {
|
|
342
|
+
dom.prepend(tagHost.host);
|
|
343
|
+
tagMount = tagHost.mount;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
316
346
|
if (showIndicators && isExtendedEnabled(diffType)) {
|
|
317
347
|
const leftAnchor = createLeftAnchorWidget({
|
|
318
348
|
doc: newDoc,
|
|
319
349
|
from: safeInsertPos,
|
|
320
350
|
diffId
|
|
321
351
|
});
|
|
322
|
-
dom.style.setProperty('anchor-name', `--${buildAnchorDecorationKey({
|
|
323
|
-
diffId
|
|
324
|
-
})}`);
|
|
325
352
|
if (leftAnchor) {
|
|
326
353
|
decorations.push(leftAnchor);
|
|
327
354
|
}
|
|
328
355
|
}
|
|
329
356
|
decorations.push(Decoration.widget(safeInsertPos, dom, {
|
|
330
357
|
...buildDiffDecorationSpec({
|
|
358
|
+
attributionKey,
|
|
359
|
+
colorScheme,
|
|
331
360
|
decorationType: 'widget',
|
|
332
361
|
diffId,
|
|
333
362
|
isActive,
|
|
363
|
+
isInserted,
|
|
334
364
|
diffType,
|
|
335
365
|
...(isExtendedEnabled(diffType) && {
|
|
336
366
|
// placeBelow anchors at the end of the new content, so render on the
|
|
@@ -347,7 +377,9 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
347
377
|
}),
|
|
348
378
|
destroy: () => {
|
|
349
379
|
var _constrainMediaObserv2;
|
|
350
|
-
|
|
380
|
+
(_constrainMediaObserv2 = constrainMediaObserver) === null || _constrainMediaObserv2 === void 0 ? void 0 : _constrainMediaObserv2.disconnect();
|
|
381
|
+
unmountContributorTag(tagMount);
|
|
382
|
+
tagMount = undefined;
|
|
351
383
|
}
|
|
352
384
|
}));
|
|
353
385
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Decoration families produced by the show-diff plugin.
|
|
5
4
|
* Each family owns its own key space and spec shape.
|
|
6
5
|
*/
|
|
7
6
|
export const DecorationFamily = {
|
|
8
7
|
diff: 'diff',
|
|
9
|
-
anchor: 'anchor'
|
|
8
|
+
anchor: 'anchor',
|
|
9
|
+
contributorTag: 'contributor-tag'
|
|
10
10
|
};
|
|
11
11
|
export const AnchorTypeKey = {
|
|
12
12
|
from: 'from',
|
|
@@ -14,6 +14,12 @@ export const AnchorTypeKey = {
|
|
|
14
14
|
left: 'left',
|
|
15
15
|
docMargin: 'doc-margin'
|
|
16
16
|
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The host widget one contributor tag renders into. Its own family, so the tag never reaches the
|
|
20
|
+
* consumers that read diff decorations — change counting, navigation and `getDeletedWidgets`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* The diff-decoration kinds produced by the show-diff plugin. Each value is
|
|
19
25
|
* the leading segment of the generated key, so a diff decoration's kind can be
|
|
@@ -49,9 +55,12 @@ export const buildDiffDecorationKey = ({
|
|
|
49
55
|
return isActive !== undefined ? `${decorationKeyPrefix}-${isActive ? 'active' : 'inactive'}` : decorationKeyPrefix;
|
|
50
56
|
};
|
|
51
57
|
export const buildDiffDecorationSpec = ({
|
|
58
|
+
attributionKey,
|
|
59
|
+
colorScheme,
|
|
52
60
|
decorationType,
|
|
53
61
|
diffId,
|
|
54
62
|
isActive,
|
|
63
|
+
isInserted,
|
|
55
64
|
nodeName,
|
|
56
65
|
side,
|
|
57
66
|
diffType
|
|
@@ -65,6 +74,19 @@ export const buildDiffDecorationSpec = ({
|
|
|
65
74
|
isActive,
|
|
66
75
|
diffType
|
|
67
76
|
}),
|
|
77
|
+
...(attributionKey ? {
|
|
78
|
+
attributionKey
|
|
79
|
+
} : {}),
|
|
80
|
+
// Only alongside an attribution key, so the spec shape is unchanged when the gate is off.
|
|
81
|
+
...(attributionKey && isActive !== undefined ? {
|
|
82
|
+
isActive
|
|
83
|
+
} : {}),
|
|
84
|
+
...(attributionKey && isInserted !== undefined ? {
|
|
85
|
+
isInserted
|
|
86
|
+
} : {}),
|
|
87
|
+
...(attributionKey && colorScheme ? {
|
|
88
|
+
colorScheme
|
|
89
|
+
} : {}),
|
|
68
90
|
...(nodeName ? {
|
|
69
91
|
nodeName
|
|
70
92
|
} : {}),
|
|
@@ -103,6 +125,12 @@ export function buildAnchorDecorationSpec({
|
|
|
103
125
|
} : {})
|
|
104
126
|
};
|
|
105
127
|
}
|
|
128
|
+
export const buildContributorTagDecorationSpec = diffId => ({
|
|
129
|
+
decorationFamily: DecorationFamily.contributorTag,
|
|
130
|
+
diffId,
|
|
131
|
+
// Stable across recalculations, so ProseMirror keeps the host element the tag is portalled into.
|
|
132
|
+
key: `${DecorationFamily.contributorTag}-${diffId}`
|
|
133
|
+
});
|
|
106
134
|
export const isDiffDecorationSpec = spec => Boolean(spec && typeof spec === 'object' && 'decorationFamily' in spec && spec.decorationFamily === DecorationFamily.diff);
|
|
107
135
|
export const isAnchorDecorationSpec = spec => Boolean(spec && typeof spec === 'object' && 'decorationFamily' in spec && spec.decorationFamily === DecorationFamily.anchor);
|
|
108
136
|
export const isDiffDecoration = decoration => isDiffDecorationSpec(decoration.spec);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isDiffDecoration, isDiffDecorationSpec } from './decorationKeys';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Diff kinds that expose an anchor a tag can be pinned to: `inline` is added/changed content,
|
|
5
|
+
* `widget` is deleted content. Block diffs carry no attribution.
|
|
6
|
+
*/
|
|
7
|
+
const TAGGABLE_DECORATION_TYPES = new Set(['inline', 'widget']);
|
|
8
|
+
|
|
9
|
+
/** The connection is an internal key, so it is dropped on the way to the tag. */
|
|
10
|
+
const toTagContributor = ({
|
|
11
|
+
connectedToKey: _connectedToKey,
|
|
12
|
+
...tagContributor
|
|
13
|
+
}) => tagContributor;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a deleted-content widget belongs to the same change as an inline decoration. A replacement
|
|
16
|
+
* renders both — the inserted text inline, the deleted text as a widget anchored to one end of that
|
|
17
|
+
* inline range — and the two must not each claim a tag.
|
|
18
|
+
*/
|
|
19
|
+
const isSameChange = (widget, inline) => widget.spec.attributionKey === inline.spec.attributionKey && widget.from >= inline.from && widget.from <= inline.to;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One model per diff decoration whose attribution resolves to a supplied contributor; anything
|
|
23
|
+
* unattributed, untaggable or unresolved is skipped. A replacement's two decorations are collapsed
|
|
24
|
+
* into the single inline tag — see `isSameChange`.
|
|
25
|
+
*/
|
|
26
|
+
export const extractContributorTags = (decorations, contributors) => {
|
|
27
|
+
if (!contributors) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const targets = [];
|
|
31
|
+
for (const decoration of decorations.find(undefined, undefined, isDiffDecorationSpec)) {
|
|
32
|
+
if (!isDiffDecoration(decoration)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const {
|
|
36
|
+
spec
|
|
37
|
+
} = decoration;
|
|
38
|
+
if (!spec.attributionKey || !TAGGABLE_DECORATION_TYPES.has(spec.decorationType)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const contributor = contributors[spec.attributionKey];
|
|
42
|
+
if (!contributor) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
targets.push({
|
|
46
|
+
contributor,
|
|
47
|
+
decoration
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The inline decoration keeps the tag: it captions content still in the document.
|
|
52
|
+
const inlineTargets = targets.filter(({
|
|
53
|
+
decoration
|
|
54
|
+
}) => decoration.spec.decorationType === 'inline');
|
|
55
|
+
const linkedDiffIds = new Map();
|
|
56
|
+
const folded = new Set();
|
|
57
|
+
for (const target of targets) {
|
|
58
|
+
var _linkedDiffIds$get;
|
|
59
|
+
if (target.decoration.spec.decorationType !== 'widget') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const host = inlineTargets.find(({
|
|
63
|
+
decoration
|
|
64
|
+
}) => isSameChange(target.decoration, decoration));
|
|
65
|
+
if (!host) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
folded.add(target);
|
|
69
|
+
const hostDiffId = host.decoration.spec.diffId;
|
|
70
|
+
linkedDiffIds.set(hostDiffId, [...((_linkedDiffIds$get = linkedDiffIds.get(hostDiffId)) !== null && _linkedDiffIds$get !== void 0 ? _linkedDiffIds$get : []), target.decoration.spec.diffId]);
|
|
71
|
+
}
|
|
72
|
+
return targets.filter(target => !folded.has(target)).map(({
|
|
73
|
+
contributor,
|
|
74
|
+
decoration: {
|
|
75
|
+
spec
|
|
76
|
+
}
|
|
77
|
+
}) => {
|
|
78
|
+
const connected = contributor.connectedToKey ? contributors[contributor.connectedToKey] : undefined;
|
|
79
|
+
const connectedContributor = connected ? toTagContributor(connected) : undefined;
|
|
80
|
+
const linked = linkedDiffIds.get(spec.diffId);
|
|
81
|
+
return {
|
|
82
|
+
contributor: toTagContributor(contributor),
|
|
83
|
+
diffId: spec.diffId,
|
|
84
|
+
...(connectedContributor ? {
|
|
85
|
+
connectedContributor
|
|
86
|
+
} : {}),
|
|
87
|
+
...(spec.colorScheme ? {
|
|
88
|
+
colorScheme: spec.colorScheme
|
|
89
|
+
} : {}),
|
|
90
|
+
...(spec.isActive !== undefined ? {
|
|
91
|
+
isActive: spec.isActive
|
|
92
|
+
} : {}),
|
|
93
|
+
...(spec.isInserted !== undefined ? {
|
|
94
|
+
isInserted: spec.isInserted
|
|
95
|
+
} : {}),
|
|
96
|
+
...(linked ? {
|
|
97
|
+
linkedDiffIds: linked
|
|
98
|
+
} : {})
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
};
|
|
@@ -5,13 +5,31 @@ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
|
5
5
|
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform-override';
|
|
6
6
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
7
7
|
import { calculateDiffDecorations } from './calculateDiff/calculateDiffDecorations';
|
|
8
|
+
import { isDiffDecoration, isDiffDecorationSpec } from './decorations/decorationKeys';
|
|
8
9
|
import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
|
|
9
10
|
import { getScrollableDecorations } from './getScrollableDecorations';
|
|
10
11
|
import { getDefaultDiffType, isExtendedEnabled } from './isExtendedEnabled';
|
|
11
12
|
import { NodeViewSerializer } from './NodeViewSerializer';
|
|
12
13
|
import { scrollToDecoration } from './scrollToDiff';
|
|
13
14
|
export const showDiffPluginKey = new PluginKey('showDiffPlugin');
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Tags whose diff decoration survived the transaction. A document change can map a decoration away
|
|
17
|
+
* without any `showDiff`/`hideDiff` following it, and a tag left behind has no host to render into.
|
|
18
|
+
* Returns the given array when every tag is still live.
|
|
19
|
+
*/
|
|
20
|
+
const dropOrphanedContributorTags = (contributorTags, decorations) => {
|
|
21
|
+
if (!(contributorTags !== null && contributorTags !== void 0 && contributorTags.length)) {
|
|
22
|
+
return contributorTags;
|
|
23
|
+
}
|
|
24
|
+
const liveDiffIds = new Set(decorations.find(undefined, undefined, isDiffDecorationSpec).filter(isDiffDecoration).map(({
|
|
25
|
+
spec
|
|
26
|
+
}) => spec.diffId));
|
|
27
|
+
const liveTags = contributorTags.filter(({
|
|
28
|
+
diffId
|
|
29
|
+
}) => liveDiffIds.has(diffId));
|
|
30
|
+
return liveTags.length === contributorTags.length ? contributorTags : liveTags;
|
|
31
|
+
};
|
|
32
|
+
export const createPlugin = (config, getIntl, api, portalProviderAPI, onEditorView) => {
|
|
15
33
|
enforceCustomStepRegisters();
|
|
16
34
|
const nodeViewSerializer = new NodeViewSerializer({});
|
|
17
35
|
const setNodeViewSerializer = editorView => {
|
|
@@ -19,6 +37,23 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
19
37
|
editorView
|
|
20
38
|
});
|
|
21
39
|
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* This editor's own content root. The contributor tags scope every DOM lookup to it, so they take
|
|
43
|
+
* it from the view the plugin was mounted on rather than resolving `.ProseMirror` by class name
|
|
44
|
+
* from a decoration — a class this package does not own, and one whose nearest match is the wrong
|
|
45
|
+
* editor whenever editors are nested. A getter because the view arrives with the ProseMirror view
|
|
46
|
+
* lifecycle, which decorations are calculated independently of.
|
|
47
|
+
*/
|
|
48
|
+
let currentEditorView;
|
|
49
|
+
const tagMountContext = portalProviderAPI ? {
|
|
50
|
+
api,
|
|
51
|
+
getEditorRoot: () => {
|
|
52
|
+
var _currentEditorView$do, _currentEditorView;
|
|
53
|
+
return (_currentEditorView$do = (_currentEditorView = currentEditorView) === null || _currentEditorView === void 0 ? void 0 : _currentEditorView.dom) !== null && _currentEditorView$do !== void 0 ? _currentEditorView$do : null;
|
|
54
|
+
},
|
|
55
|
+
portalProviderAPI
|
|
56
|
+
} : undefined;
|
|
22
57
|
return new SafePlugin({
|
|
23
58
|
key: showDiffPluginKey,
|
|
24
59
|
state: {
|
|
@@ -56,6 +91,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
56
91
|
};
|
|
57
92
|
// Calculate and store decorations in state
|
|
58
93
|
const {
|
|
94
|
+
contributorTags,
|
|
59
95
|
decorations,
|
|
60
96
|
diffDescriptors
|
|
61
97
|
} = calculateDiffDecorations({
|
|
@@ -66,6 +102,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
66
102
|
intl: getIntl(),
|
|
67
103
|
activeIndexPos: newPluginState.activeIndexPos,
|
|
68
104
|
api,
|
|
105
|
+
tagMountContext,
|
|
69
106
|
...(isExtendedEnabled((_newPluginState = newPluginState) === null || _newPluginState === void 0 ? void 0 : _newPluginState.diffType) ? {
|
|
70
107
|
isInverted: (_newPluginState2 = newPluginState) === null || _newPluginState2 === void 0 ? void 0 : _newPluginState2.isInverted,
|
|
71
108
|
diffType: (_newPluginState3 = newPluginState) === null || _newPluginState3 === void 0 ? void 0 : _newPluginState3.diffType,
|
|
@@ -79,6 +116,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
79
116
|
});
|
|
80
117
|
// Update the decorations and their ids
|
|
81
118
|
newPluginState.decorations = decorations;
|
|
119
|
+
newPluginState.contributorTags = contributorTags;
|
|
82
120
|
if (isExtendedEnabled((_newPluginState0 = newPluginState) === null || _newPluginState0 === void 0 ? void 0 : _newPluginState0.diffType)) {
|
|
83
121
|
newPluginState.diffDescriptors = diffDescriptors;
|
|
84
122
|
}
|
|
@@ -89,6 +127,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
89
127
|
decorations: DecorationSet.empty,
|
|
90
128
|
isDisplayingChanges: false,
|
|
91
129
|
activeIndex: undefined,
|
|
130
|
+
contributorTags: [],
|
|
92
131
|
/**
|
|
93
132
|
* Reset isInverted & diffType state when hiding diffs
|
|
94
133
|
* Otherwise this should persist for the diff-showing session
|
|
@@ -132,6 +171,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
132
171
|
};
|
|
133
172
|
// Recalculate decorations with the new active index
|
|
134
173
|
const {
|
|
174
|
+
contributorTags: updatedContributorTags,
|
|
135
175
|
decorations: updatedDecorations,
|
|
136
176
|
diffDescriptors: updatedDiffDescriptors
|
|
137
177
|
} = calculateDiffDecorations({
|
|
@@ -142,6 +182,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
142
182
|
intl: getIntl(),
|
|
143
183
|
activeIndexPos: newPluginState.activeIndexPos,
|
|
144
184
|
api,
|
|
185
|
+
tagMountContext,
|
|
145
186
|
...(isExtendedEnabled(newPluginState.diffType) ? {
|
|
146
187
|
isInverted: newPluginState.isInverted,
|
|
147
188
|
diffType: newPluginState.diffType,
|
|
@@ -154,6 +195,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
154
195
|
} : {})
|
|
155
196
|
});
|
|
156
197
|
newPluginState.decorations = updatedDecorations;
|
|
198
|
+
newPluginState.contributorTags = updatedContributorTags;
|
|
157
199
|
if (isExtendedEnabled(newPluginState.diffType)) {
|
|
158
200
|
newPluginState.diffDescriptors = updatedDiffDescriptors;
|
|
159
201
|
}
|
|
@@ -165,14 +207,20 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
165
207
|
};
|
|
166
208
|
}
|
|
167
209
|
}
|
|
168
|
-
|
|
210
|
+
const mappedDecorations = newPluginState.decorations.map(tr.mapping, tr.doc);
|
|
211
|
+
const nextState = {
|
|
169
212
|
...newPluginState,
|
|
170
|
-
decorations:
|
|
213
|
+
decorations: mappedDecorations
|
|
171
214
|
};
|
|
215
|
+
if (tr.docChanged) {
|
|
216
|
+
nextState.contributorTags = dropOrphanedContributorTags(newPluginState.contributorTags, mappedDecorations);
|
|
217
|
+
}
|
|
218
|
+
return nextState;
|
|
172
219
|
}
|
|
173
220
|
},
|
|
174
221
|
view(editorView) {
|
|
175
222
|
setNodeViewSerializer(editorView);
|
|
223
|
+
currentEditorView = editorView;
|
|
176
224
|
onEditorView === null || onEditorView === void 0 ? void 0 : onEditorView(editorView);
|
|
177
225
|
let isFirst = true;
|
|
178
226
|
let previousActiveIndex;
|
|
@@ -224,6 +272,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
|
|
|
224
272
|
var _cancelPendingScrollT3;
|
|
225
273
|
(_cancelPendingScrollT3 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT3 === void 0 ? void 0 : _cancelPendingScrollT3();
|
|
226
274
|
cancelPendingScrollToDecoration = null;
|
|
275
|
+
currentEditorView = undefined;
|
|
227
276
|
onEditorView === null || onEditorView === void 0 ? void 0 : onEditorView(undefined);
|
|
228
277
|
}
|
|
229
278
|
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getAttributionKey } from './decorations/colorSchemes/attributions';
|
|
2
|
+
|
|
3
|
+
/** A brand name, so a literal rather than a translated message. */
|
|
4
|
+
const ROVO_AGENT_NAME = 'Rovo';
|
|
5
|
+
|
|
6
|
+
/** `agentType` values that render as "Rovo" rather than as a generic external agent. */
|
|
7
|
+
const ROVO_AGENT_TYPES = new Set(['convo-ai', 'rovo_chat']);
|
|
8
|
+
const hasAgentIdentity = attribution => {
|
|
9
|
+
var _attribution$agentId, _attribution$agentTyp;
|
|
10
|
+
return Boolean(((_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim()) || ((_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim()));
|
|
11
|
+
};
|
|
12
|
+
const toProfilesByAccountId = profiles => {
|
|
13
|
+
const profilesByAccountId = new Map();
|
|
14
|
+
profiles === null || profiles === void 0 ? void 0 : profiles.forEach(profile => {
|
|
15
|
+
// Trimmed to match how attribution ids are keyed, so the two sides compare like for like.
|
|
16
|
+
const accountId = profile.accountId.trim();
|
|
17
|
+
if (accountId && !profilesByAccountId.has(accountId)) {
|
|
18
|
+
profilesByAccountId.set(accountId, profile);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return profilesByAccountId;
|
|
22
|
+
};
|
|
23
|
+
const resolveUser = (userId, profilesByAccountId) => {
|
|
24
|
+
const profile = profilesByAccountId.get(userId);
|
|
25
|
+
return profile ? {
|
|
26
|
+
avatarUrl: profile.avatarUrl,
|
|
27
|
+
kind: 'user',
|
|
28
|
+
name: profile.name
|
|
29
|
+
} : undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** An agent always resolves: its profile, else "Rovo" for a first-party type, else external. */
|
|
33
|
+
const resolveAgent = (attribution, profilesByAccountId) => {
|
|
34
|
+
var _attribution$agentId2, _attribution$agentTyp2;
|
|
35
|
+
const agentId = (_attribution$agentId2 = attribution.agentId) === null || _attribution$agentId2 === void 0 ? void 0 : _attribution$agentId2.trim();
|
|
36
|
+
const agentProfile = agentId ? profilesByAccountId.get(agentId) : undefined;
|
|
37
|
+
if (agentProfile) {
|
|
38
|
+
return {
|
|
39
|
+
agentKind: 'identified',
|
|
40
|
+
avatarUrl: agentProfile.avatarUrl,
|
|
41
|
+
kind: 'agent',
|
|
42
|
+
name: agentProfile.name
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const agentType = (_attribution$agentTyp2 = attribution.agentType) === null || _attribution$agentTyp2 === void 0 ? void 0 : _attribution$agentTyp2.trim().toLowerCase();
|
|
46
|
+
|
|
47
|
+
// An empty name lets the tag fall back to its own localised "External agent" label.
|
|
48
|
+
return agentType && ROVO_AGENT_TYPES.has(agentType) ? {
|
|
49
|
+
agentKind: 'rovo',
|
|
50
|
+
kind: 'agent',
|
|
51
|
+
name: ROVO_AGENT_NAME
|
|
52
|
+
} : {
|
|
53
|
+
agentKind: 'external',
|
|
54
|
+
kind: 'agent',
|
|
55
|
+
name: ''
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One contributor per distinct actor behind the attributed steps, resolved against the identities
|
|
61
|
+
* the consumer supplied. Returns `undefined` if any actor cannot be named: a half-credited diff
|
|
62
|
+
* reads as "nobody touched this", so the whole list is dropped and the plain diff renders instead.
|
|
63
|
+
*/
|
|
64
|
+
export const resolveDiffContributors = (stepsWithAttribution, profiles) => {
|
|
65
|
+
const profilesByAccountId = toProfilesByAccountId(profiles);
|
|
66
|
+
const contributors = [];
|
|
67
|
+
/** Keyed as the plugin keys changes, so no two entries can collapse onto one another. */
|
|
68
|
+
const creditedActors = new Set();
|
|
69
|
+
for (const {
|
|
70
|
+
stepAttribution
|
|
71
|
+
} of stepsWithAttribution) {
|
|
72
|
+
var _stepAttribution$user;
|
|
73
|
+
// An attribution naming nobody (e.g. one carrying only `wasOffline`) has nobody to credit.
|
|
74
|
+
const actor = stepAttribution ? getAttributionKey(stepAttribution) : undefined;
|
|
75
|
+
if (!stepAttribution || !actor || creditedActors.has(actor)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const userId = (_stepAttribution$user = stepAttribution.userId) === null || _stepAttribution$user === void 0 ? void 0 : _stepAttribution$user.trim();
|
|
79
|
+
if (hasAgentIdentity(stepAttribution)) {
|
|
80
|
+
const agent = resolveAgent(stepAttribution, profilesByAccountId);
|
|
81
|
+
if (!userId) {
|
|
82
|
+
// Agent mode: the agent acted on its own, so it is credited on its own.
|
|
83
|
+
contributors.push({
|
|
84
|
+
...agent,
|
|
85
|
+
attribution: stepAttribution
|
|
86
|
+
});
|
|
87
|
+
creditedActors.add(actor);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// User-invoked agent: credit both actors and link the agent back to the invoking user.
|
|
92
|
+
const invokingUser = {
|
|
93
|
+
userId
|
|
94
|
+
};
|
|
95
|
+
const user = resolveUser(userId, profilesByAccountId);
|
|
96
|
+
if (!user) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Skipped if the invoking user's own step already credited them.
|
|
101
|
+
const invokingUserActor = getAttributionKey(invokingUser);
|
|
102
|
+
if (invokingUserActor && !creditedActors.has(invokingUserActor)) {
|
|
103
|
+
contributors.push({
|
|
104
|
+
...user,
|
|
105
|
+
attribution: invokingUser
|
|
106
|
+
});
|
|
107
|
+
creditedActors.add(invokingUserActor);
|
|
108
|
+
}
|
|
109
|
+
contributors.push({
|
|
110
|
+
...agent,
|
|
111
|
+
attribution: stepAttribution,
|
|
112
|
+
connectedTo: invokingUser
|
|
113
|
+
});
|
|
114
|
+
creditedActors.add(actor);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const user = userId ? resolveUser(userId, profilesByAccountId) : undefined;
|
|
118
|
+
if (!user) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
contributors.push({
|
|
122
|
+
...user,
|
|
123
|
+
attribution: stepAttribution
|
|
124
|
+
});
|
|
125
|
+
creditedActors.add(actor);
|
|
126
|
+
}
|
|
127
|
+
return contributors.length > 0 ? contributors : undefined;
|
|
128
|
+
};
|