@atlaskit/editor-plugin-show-diff 15.0.0 → 15.1.1
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 +35 -0
- package/afm-cc/tsconfig.json +0 -12
- package/afm-products/tsconfig.json +0 -12
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +131 -79
- package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +32 -0
- package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +87 -25
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +27 -4
- package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +17 -3
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
- package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +43 -14
- package/dist/cjs/pm-plugins/decorations/revealStyles.js +156 -0
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -5
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +4 -2
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +2 -1
- package/dist/cjs/pm-plugins/main.js +28 -10
- package/dist/cjs/pm-plugins/resolveDiffContributors.js +4 -0
- package/dist/cjs/pm-plugins/revealAnimation.js +524 -0
- package/dist/cjs/showDiffPlugin.js +42 -7
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +59 -9
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +30 -0
- package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +62 -4
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +25 -3
- package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +18 -1
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
- package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +36 -9
- package/dist/es2019/pm-plugins/decorations/revealStyles.js +139 -0
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -6
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +6 -3
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +5 -2
- package/dist/es2019/pm-plugins/main.js +28 -9
- package/dist/es2019/pm-plugins/resolveDiffContributors.js +4 -0
- package/dist/es2019/pm-plugins/revealAnimation.js +495 -0
- package/dist/es2019/showDiffPlugin.js +37 -7
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +131 -79
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +30 -0
- package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +87 -25
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +27 -4
- package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +17 -3
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +5 -3
- package/dist/esm/pm-plugins/decorations/extractContributorTags.js +43 -14
- package/dist/esm/pm-plugins/decorations/revealStyles.js +149 -0
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +25 -5
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +4 -2
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.js +2 -1
- package/dist/esm/pm-plugins/main.js +28 -10
- package/dist/esm/pm-plugins/resolveDiffContributors.js +4 -0
- package/dist/esm/pm-plugins/revealAnimation.js +518 -0
- package/dist/esm/showDiffPlugin.js +42 -7
- package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
- package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +2 -1
- package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +24 -0
- package/dist/types/pm-plugins/decorations/createBlockChangedDecoration.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +9 -3
- package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +3 -2
- package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +3 -2
- package/dist/types/pm-plugins/decorations/revealStyles.d.ts +49 -0
- package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +4 -3
- package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.d.ts +1 -1
- package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.legacy.d.ts +1 -1
- package/dist/types/pm-plugins/main.d.ts +6 -1
- package/dist/types/pm-plugins/revealAnimation.d.ts +33 -0
- package/dist/types/showDiffPluginType.d.ts +29 -1
- package/package.json +10 -4
|
@@ -157,6 +157,7 @@ const tableRanges = (doc, from, to) => {
|
|
|
157
157
|
};
|
|
158
158
|
const isInsideTable = (tables, pos) => tables.some(([tFrom, tTo]) => pos >= tFrom && pos < tTo);
|
|
159
159
|
const calculateNodesForBlockDecoration = ({
|
|
160
|
+
attributionKey,
|
|
160
161
|
doc,
|
|
161
162
|
from,
|
|
162
163
|
to,
|
|
@@ -164,9 +165,11 @@ const calculateNodesForBlockDecoration = ({
|
|
|
164
165
|
isInserted = true,
|
|
165
166
|
activeIndexPos,
|
|
166
167
|
shouldHideDeleted = false,
|
|
168
|
+
showContributorTags = false,
|
|
167
169
|
showIndicators = false,
|
|
168
170
|
diffType,
|
|
169
|
-
coarseTableCellsOnly = false
|
|
171
|
+
coarseTableCellsOnly = false,
|
|
172
|
+
tagMountContext
|
|
170
173
|
}) => {
|
|
171
174
|
const decorations = [];
|
|
172
175
|
// Coarse path: inside the large table, only cell/header overlays are kept — the
|
|
@@ -185,6 +188,7 @@ const calculateNodesForBlockDecoration = ({
|
|
|
185
188
|
const nodeEnd = pos + node.nodeSize;
|
|
186
189
|
const isActive = isRangeActive(activeIndexPos, pos, nodeEnd);
|
|
187
190
|
decorations.push(...createBlockChangedDecoration({
|
|
191
|
+
attributionKey,
|
|
188
192
|
change: {
|
|
189
193
|
from: pos,
|
|
190
194
|
to: nodeEnd,
|
|
@@ -194,9 +198,11 @@ const calculateNodesForBlockDecoration = ({
|
|
|
194
198
|
isInserted,
|
|
195
199
|
isActive,
|
|
196
200
|
shouldHideDeleted,
|
|
201
|
+
showContributorTags,
|
|
197
202
|
showIndicators,
|
|
198
203
|
doc,
|
|
199
|
-
diffType
|
|
204
|
+
diffType,
|
|
205
|
+
tagMountContext
|
|
200
206
|
}));
|
|
201
207
|
}
|
|
202
208
|
});
|
|
@@ -226,6 +232,25 @@ export const isDeletedContentPlacedBelow = ({
|
|
|
226
232
|
// Inline-level (undefined) and sentence-level changes.
|
|
227
233
|
return inlineDeletedDiffPlacement === 'after';
|
|
228
234
|
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Single place `platform_editor_diff_hide_pure_deletions` is read, shared by the widget-anchor
|
|
238
|
+
* calculation and the decoration push below so the two cannot drift.
|
|
239
|
+
*
|
|
240
|
+
* The clean view ("new state") historically only suppressed a deleted side when the same change
|
|
241
|
+
* also carried an insertion. A PURE deletion — text removed with nothing put in its place — fell
|
|
242
|
+
* through and leaked struck-through text into a view that is supposed to show none. `smart` never
|
|
243
|
+
* showed this because it pairs a rewrite into a single change that always carries an insertion;
|
|
244
|
+
* `inline` emits delete-only changes, so it did.
|
|
245
|
+
*
|
|
246
|
+
* With the gate off this returns exactly what both callers computed before it existed.
|
|
247
|
+
*/
|
|
248
|
+
const shouldHideDeletedSide = ({
|
|
249
|
+
change,
|
|
250
|
+
diffType,
|
|
251
|
+
hideDeletedDiffs,
|
|
252
|
+
isInverted
|
|
253
|
+
}) => isExtendedEnabled(diffType) && !isInverted && !!hideDeletedDiffs && (change.inserted.length > 0 || fg('platform_editor_diff_hide_pure_deletions'));
|
|
229
254
|
const calculateDiffDecorationsInner = ({
|
|
230
255
|
state,
|
|
231
256
|
pluginState,
|
|
@@ -242,6 +267,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
242
267
|
smartThresholds,
|
|
243
268
|
deletedDiffPlacement = 'top',
|
|
244
269
|
inlineDeletedDiffPlacement = 'before',
|
|
270
|
+
reveal,
|
|
245
271
|
tagMountContext
|
|
246
272
|
}) => {
|
|
247
273
|
const {
|
|
@@ -457,7 +483,12 @@ const calculateDiffDecorationsInner = ({
|
|
|
457
483
|
// change. Used to decide if indicator anchor positions should be adjusted
|
|
458
484
|
// inward — when the widget is present the anchor must stay at the block
|
|
459
485
|
// boundary to keep the indicator bar continuous with the deleted content.
|
|
460
|
-
const willRenderDeletedWidget = change.deleted.length > 0 && !isMarkOnly && !(
|
|
486
|
+
const willRenderDeletedWidget = change.deleted.length > 0 && !isMarkOnly && !shouldHideDeletedSide({
|
|
487
|
+
change,
|
|
488
|
+
diffType,
|
|
489
|
+
hideDeletedDiffs,
|
|
490
|
+
isInverted
|
|
491
|
+
});
|
|
461
492
|
if (isSmartNodeLevel) {
|
|
462
493
|
// For a large inserted table, skip the O(cells) inline decorations inside the
|
|
463
494
|
// table (the cell overlays added below provide the highlight without the
|
|
@@ -485,6 +516,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
485
516
|
hideAddedDiffsUnderline,
|
|
486
517
|
hasDeletedWidget: willRenderDeletedWidget,
|
|
487
518
|
isDeletedWidgetBelow,
|
|
519
|
+
reveal,
|
|
488
520
|
tagMountContext
|
|
489
521
|
})
|
|
490
522
|
}));
|
|
@@ -505,11 +537,13 @@ const calculateDiffDecorationsInner = ({
|
|
|
505
537
|
hideAddedDiffsUnderline,
|
|
506
538
|
hasDeletedWidget: willRenderDeletedWidget,
|
|
507
539
|
isDeletedWidgetBelow,
|
|
540
|
+
reveal,
|
|
508
541
|
tagMountContext
|
|
509
542
|
})
|
|
510
543
|
}));
|
|
511
544
|
}
|
|
512
545
|
decorations.push(...calculateNodesForBlockDecoration({
|
|
546
|
+
attributionKey,
|
|
513
547
|
doc: tr.doc,
|
|
514
548
|
from: change.fromB,
|
|
515
549
|
to: change.toB,
|
|
@@ -517,7 +551,9 @@ const calculateDiffDecorationsInner = ({
|
|
|
517
551
|
...(isExtendedEnabled(diffType) && {
|
|
518
552
|
isInserted,
|
|
519
553
|
shouldHideDeleted,
|
|
520
|
-
|
|
554
|
+
showContributorTags,
|
|
555
|
+
showIndicators,
|
|
556
|
+
tagMountContext
|
|
521
557
|
}),
|
|
522
558
|
activeIndexPos,
|
|
523
559
|
intl,
|
|
@@ -526,11 +562,19 @@ const calculateDiffDecorationsInner = ({
|
|
|
526
562
|
}));
|
|
527
563
|
}
|
|
528
564
|
if (change.deleted.length > 0 && !isMarkOnly) {
|
|
529
|
-
const shouldHideDeleted =
|
|
565
|
+
const shouldHideDeleted = shouldHideDeletedSide({
|
|
566
|
+
change,
|
|
567
|
+
diffType,
|
|
568
|
+
hideDeletedDiffs,
|
|
569
|
+
isInverted
|
|
570
|
+
});
|
|
530
571
|
if (!shouldHideDeleted) {
|
|
531
572
|
decorations.push(...createNodeChangedDecorationWidget({
|
|
532
573
|
// The deleted side has no inline decoration, so the widget carries the actor.
|
|
533
574
|
attributionKey,
|
|
575
|
+
// Extended pipeline only, matching the inline path: the non-extended styles have no
|
|
576
|
+
// background for the wipe to act on.
|
|
577
|
+
reveal: isExtendedEnabled(diffType) ? reveal : undefined,
|
|
534
578
|
change,
|
|
535
579
|
doc: originalDoc,
|
|
536
580
|
nodeViewSerializer,
|
|
@@ -568,7 +612,8 @@ const calculateDiffDecorationsInner = ({
|
|
|
568
612
|
colorScheme,
|
|
569
613
|
diffType,
|
|
570
614
|
isActive,
|
|
571
|
-
isInserted: true
|
|
615
|
+
isInserted: true,
|
|
616
|
+
reveal
|
|
572
617
|
}));
|
|
573
618
|
});
|
|
574
619
|
getAttrChangeRanges(tr.doc, attrStepContexts, originalDoc).forEach(change => {
|
|
@@ -671,7 +716,8 @@ export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner
|
|
|
671
716
|
showIndicators,
|
|
672
717
|
smartThresholds,
|
|
673
718
|
deletedDiffPlacement,
|
|
674
|
-
inlineDeletedDiffPlacement
|
|
719
|
+
inlineDeletedDiffPlacement,
|
|
720
|
+
reveal
|
|
675
721
|
}], [{
|
|
676
722
|
pluginState: lastPluginState,
|
|
677
723
|
state: lastState,
|
|
@@ -685,7 +731,8 @@ export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner
|
|
|
685
731
|
showIndicators: lastShowIndicators,
|
|
686
732
|
smartThresholds: lastSmartThresholds,
|
|
687
733
|
deletedDiffPlacement: lastDeletedDiffPlacement,
|
|
688
|
-
inlineDeletedDiffPlacement: lastInlineDeletedDiffPlacement
|
|
734
|
+
inlineDeletedDiffPlacement: lastInlineDeletedDiffPlacement,
|
|
735
|
+
reveal: lastReveal
|
|
689
736
|
}]) => {
|
|
690
737
|
var _ref2;
|
|
691
738
|
const originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
|
|
@@ -693,7 +740,10 @@ export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner
|
|
|
693
740
|
var _ref;
|
|
694
741
|
return (_ref = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) &&
|
|
695
742
|
// Contributors can arrive after the first calculation; without this, tags never appear.
|
|
696
|
-
isEqual(pluginState.contributors, lastPluginState.contributors) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement
|
|
743
|
+
isEqual(pluginState.contributors, lastPluginState.contributors) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement &&
|
|
744
|
+
// Turning the reveal on or off changes the emitted styles, so a cached result from the
|
|
745
|
+
// other setting would leave the diff painted in the wrong resting state.
|
|
746
|
+
isEqual(reveal, lastReveal)) !== null && _ref !== void 0 ? _ref : false;
|
|
697
747
|
}
|
|
698
748
|
return (_ref2 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && isEqual(pluginState.stepAttributions, lastPluginState.stepAttributions) && isEqual(pluginState.contributors, lastPluginState.contributors) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref2 !== void 0 ? _ref2 : false;
|
|
699
749
|
});
|
|
@@ -683,6 +683,36 @@ export function buildInsertedInlineStyle(colors, isActive, hideAddedDiffsUnderli
|
|
|
683
683
|
return hideAddedDiffsUnderline ? buildInsertStyleExtendedNoUnderline(colors) : buildInsertStyleExtended(colors);
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
+
/**
|
|
687
|
+
* The colours `buildInsertedInlineStyle()` above would paint, for the reveal to animate towards.
|
|
688
|
+
*
|
|
689
|
+
* MUST mirror that function: the reveal replaces the flat highlight with a sized gradient, so a
|
|
690
|
+
* divergence here means the animation settles on a different colour than a static render produces.
|
|
691
|
+
* `revealStyles.ts` in the test package asserts the two agree across the isActive /
|
|
692
|
+
* hideAddedDiffsUnderline matrix.
|
|
693
|
+
*
|
|
694
|
+
* A transparent border is how "no underline" is expressed: the box still reserves the 2px so the
|
|
695
|
+
* text does not shift when the underline arrives.
|
|
696
|
+
*/
|
|
697
|
+
export function getInsertedInlineRevealColors(colors, isActive, hideAddedDiffsUnderline) {
|
|
698
|
+
return {
|
|
699
|
+
background: isActive ? bgSubtlerPressed(colors.insertActiveColor) : bgSubtlest(colors.insertColor),
|
|
700
|
+
border: hideAddedDiffsUnderline ? 'transparent' : borderAccent(colors.insertColor)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* The colours `buildDeletedInlineContentStyleExtended()` paints, for the reveal to animate towards.
|
|
706
|
+
* Deleted content's highlight does not vary with the active state — only its text does, which the
|
|
707
|
+
* reveal keeps as-is.
|
|
708
|
+
*/
|
|
709
|
+
export function getDeletedInlineRevealColors(colors) {
|
|
710
|
+
return {
|
|
711
|
+
background: bgSubtlest(colors.deleteColor),
|
|
712
|
+
border: borderAccent(colors.deleteColor)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
686
716
|
/** The three visual states deleted inline content can be in. */
|
|
687
717
|
|
|
688
718
|
/**
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
|
+
import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
|
|
2
3
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
4
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
4
5
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
5
6
|
import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
|
|
6
7
|
import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
|
|
7
8
|
import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
|
|
9
|
+
import { createContributorTagWidget } from './createContributorTagWidget';
|
|
8
10
|
import { buildDiffDecorationSpec } from './decorationKeys';
|
|
9
11
|
import { getBlockNodeStyleLegacy, resolveCellOverlayStyleLegacy } from './createBlockChangedDecoration.styles.legacy';
|
|
10
12
|
const displayNoneStyle = convertToInlineCss({
|
|
@@ -20,6 +22,24 @@ const UNSTYLED_NODES = ['mediaSingle', 'mediaGroup', 'table',
|
|
|
20
22
|
'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'];
|
|
21
23
|
const CELL_NODES = ['tableCell', 'tableHeader'];
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Block nodes that host a contributor tag of their own — those whose whole box reads as one change.
|
|
27
|
+
* Lists and tables are excluded: they decorate per item and per cell, with no one-tag-per-block
|
|
28
|
+
* design yet. `blockCard` covers the datasource variant, which is the same node type.
|
|
29
|
+
*/
|
|
30
|
+
const TAGGABLE_BLOCK_NODES = new Set(['blockCard', 'blockquote', 'bodiedExtension', 'codeBlock', 'embedCard', 'expand', 'extension', 'media', 'multiBodiedExtension', 'panel', 'rule']);
|
|
31
|
+
|
|
32
|
+
/** Panels, rules and media can also sit in a table cell, which is out of scope for block tags. */
|
|
33
|
+
const isInsideTable = (doc, pos) => findParentNodeClosestToPos(doc.resolve(pos), node => node.type.name === 'table') !== undefined;
|
|
34
|
+
|
|
35
|
+
/** Whether this block can host a contributor tag of its own. */
|
|
36
|
+
const canTagBlock = ({
|
|
37
|
+
diffType,
|
|
38
|
+
doc,
|
|
39
|
+
from,
|
|
40
|
+
name
|
|
41
|
+
}) => doc !== undefined && isExtendedEnabled(diffType) && TAGGABLE_BLOCK_NODES.has(name) && !isInsideTable(doc, from);
|
|
42
|
+
|
|
23
43
|
/** Positioning context for the cell overlay widget decorations. */
|
|
24
44
|
const cellPositionStyle = convertToInlineCss({
|
|
25
45
|
position: 'relative'
|
|
@@ -134,17 +154,28 @@ const isCellEmpty = cellNode => {
|
|
|
134
154
|
* @returns Prosemirror node decoration or undefined
|
|
135
155
|
*/
|
|
136
156
|
export const createBlockChangedDecoration = ({
|
|
157
|
+
attributionKey,
|
|
137
158
|
change,
|
|
138
159
|
colorScheme,
|
|
139
160
|
isInserted = true,
|
|
140
161
|
isActive = false,
|
|
141
162
|
shouldHideDeleted = false,
|
|
163
|
+
showContributorTags = false,
|
|
142
164
|
showIndicators = false,
|
|
143
165
|
doc,
|
|
144
|
-
diffType
|
|
166
|
+
diffType,
|
|
167
|
+
tagMountContext
|
|
145
168
|
}) => {
|
|
146
169
|
const decorations = [];
|
|
147
|
-
|
|
170
|
+
// Derived from the node range so it survives a recalculation, as in
|
|
171
|
+
// `createInlineChangedDecoration`. Changes are disjoint, so no two of them decorate one node.
|
|
172
|
+
const diffId = showContributorTags ? `block-${change.from}-${change.to}` : crypto.randomUUID();
|
|
173
|
+
const shouldTagBlock = showContributorTags && canTagBlock({
|
|
174
|
+
diffType,
|
|
175
|
+
doc,
|
|
176
|
+
from: change.from,
|
|
177
|
+
name: change.name
|
|
178
|
+
});
|
|
148
179
|
if (shouldHideDeleted) {
|
|
149
180
|
return [Decoration.node(change.from, change.to, {
|
|
150
181
|
style: displayNoneStyle
|
|
@@ -193,16 +224,26 @@ export const createBlockChangedDecoration = ({
|
|
|
193
224
|
decorations.push(Decoration.node(change.from, change.to, {
|
|
194
225
|
style: style,
|
|
195
226
|
'data-testid': 'show-diff-changed-decoration-node',
|
|
196
|
-
class: className
|
|
227
|
+
class: className,
|
|
228
|
+
// Lets the contributor tag find the block it captions on hover.
|
|
229
|
+
...(shouldTagBlock && {
|
|
230
|
+
'data-diff-id': diffId
|
|
231
|
+
})
|
|
197
232
|
}, buildDiffDecorationSpec({
|
|
233
|
+
attributionKey: shouldTagBlock ? attributionKey : undefined,
|
|
234
|
+
colorScheme,
|
|
198
235
|
decorationType: 'block',
|
|
199
236
|
diffId,
|
|
200
237
|
isActive,
|
|
238
|
+
isInserted,
|
|
201
239
|
nodeName: change.name,
|
|
202
240
|
diffType
|
|
203
241
|
})));
|
|
204
242
|
}
|
|
205
|
-
if (decorations.length
|
|
243
|
+
if (decorations.length === 0) {
|
|
244
|
+
return decorations;
|
|
245
|
+
}
|
|
246
|
+
if (showIndicators && doc && isExtendedEnabled(diffType)) {
|
|
206
247
|
decorations.push(...createBlockIndicatorAnchorWidgets({
|
|
207
248
|
doc,
|
|
208
249
|
from: change.from,
|
|
@@ -210,5 +251,22 @@ export const createBlockChangedDecoration = ({
|
|
|
210
251
|
diffId
|
|
211
252
|
}));
|
|
212
253
|
}
|
|
254
|
+
|
|
255
|
+
// `change.from` is just before the node, so the host sits outside it and the tag renders on the
|
|
256
|
+
// block's own top-left corner. Resolving inline content instead would put the host inside a
|
|
257
|
+
// container block's content DOM (panel, expand, blockquote).
|
|
258
|
+
if (shouldTagBlock && doc) {
|
|
259
|
+
const tagWidget = createContributorTagWidget({
|
|
260
|
+
anchorAtRangeStart: true,
|
|
261
|
+
doc,
|
|
262
|
+
from: change.from,
|
|
263
|
+
to: change.to,
|
|
264
|
+
diffId,
|
|
265
|
+
mountContext: tagMountContext
|
|
266
|
+
});
|
|
267
|
+
if (tagWidget) {
|
|
268
|
+
decorations.push(tagWidget);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
213
271
|
return decorations;
|
|
214
272
|
};
|
|
@@ -103,13 +103,32 @@ const resolveInlineContentStart = (doc, from, to) => {
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
106
|
+
* The position just before the code block enclosing `pos`, or `undefined` when nothing does.
|
|
107
|
+
*
|
|
108
|
+
* `.code-block-content-wrapper` hides its block-axis overflow, so a host left inside the code has
|
|
109
|
+
* its tag clipped away. Hosting it before the block puts the tag on the block's top-left corner,
|
|
110
|
+
* where a whole-code-block change is tagged too (EDITOR-8766).
|
|
111
|
+
*/
|
|
112
|
+
const resolveCodeBlockStart = (doc, pos) => {
|
|
113
|
+
const $pos = doc.resolve(pos);
|
|
114
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
115
|
+
if ($pos.node(depth).type.name === 'codeBlock') {
|
|
116
|
+
return $pos.before(depth);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A widget hosting the contributor tag of one diff range, placed on the first character the range
|
|
124
|
+
* highlights — or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would land
|
|
125
|
+
* inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
|
|
108
126
|
*
|
|
109
127
|
* The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
|
|
110
128
|
* element ProseMirror drew it into — see `mountContributorTag`.
|
|
111
129
|
*/
|
|
112
130
|
export const createContributorTagWidget = ({
|
|
131
|
+
anchorAtRangeStart = false,
|
|
113
132
|
doc,
|
|
114
133
|
from,
|
|
115
134
|
to,
|
|
@@ -123,9 +142,12 @@ export const createContributorTagWidget = ({
|
|
|
123
142
|
// Reassigned when ProseMirror redraws this decoration, which it may do more than once for the
|
|
124
143
|
// same `Decoration` instance.
|
|
125
144
|
let mount;
|
|
145
|
+
const anchorPos = anchorAtRangeStart ? from : resolveInlineContentStart(doc, from, to);
|
|
146
|
+
// A change inside a code block is hoisted out of it; anything else keeps its own anchor.
|
|
147
|
+
const codeBlockPos = resolveCodeBlockStart(doc, anchorPos);
|
|
126
148
|
return Decoration.widget(
|
|
127
149
|
// Keep the host out of the table row's grid (EDITOR-8442).
|
|
128
|
-
clampAnchorPosIntoCell(doc,
|
|
150
|
+
clampAnchorPosIntoCell(doc, codeBlockPos !== null && codeBlockPos !== void 0 ? codeBlockPos : anchorPos, 1), () => {
|
|
129
151
|
const host = buildContributorTagHost(diffId);
|
|
130
152
|
mount = mountContributorTag({
|
|
131
153
|
diffId,
|
|
@@ -8,6 +8,7 @@ import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidg
|
|
|
8
8
|
import { createContributorTagWidget } from './createContributorTagWidget';
|
|
9
9
|
import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy';
|
|
10
10
|
import { buildDiffDecorationSpec } from './decorationKeys';
|
|
11
|
+
import { REVEAL_ATTR, resolveRevealStyle } from './revealStyles';
|
|
11
12
|
const displayNoneStyle = convertToInlineCss({
|
|
12
13
|
display: 'none'
|
|
13
14
|
});
|
|
@@ -107,8 +108,10 @@ export const createInlineChangedDecoration = ({
|
|
|
107
108
|
inlineNodeName,
|
|
108
109
|
hasDeletedWidget = false,
|
|
109
110
|
isDeletedWidgetBelow = false,
|
|
111
|
+
reveal,
|
|
110
112
|
tagMountContext
|
|
111
113
|
}) => {
|
|
114
|
+
var _revealed$style;
|
|
112
115
|
// Derived from the range so it survives a recalculation: stepping to the next/previous change
|
|
113
116
|
// rebuilds decorations without touching the doc, and a fresh id would change the React key and the
|
|
114
117
|
// tag host's key, remounting every tag mid-step. Random ids are kept when tags are off.
|
|
@@ -127,7 +130,18 @@ export const createInlineChangedDecoration = ({
|
|
|
127
130
|
}))];
|
|
128
131
|
}
|
|
129
132
|
const colors = getColorScheme(colorScheme);
|
|
130
|
-
|
|
133
|
+
|
|
134
|
+
// The reveal withholds the static highlight so the wipe has something to reveal, so it supplies
|
|
135
|
+
// the whole style rather than adding to the usual one. Extended pipeline only — the AI review
|
|
136
|
+
// surface is the only consumer, and the non-extended styles have no background to wipe.
|
|
137
|
+
const revealed = isExtendedEnabled(diffType) ? resolveRevealStyle({
|
|
138
|
+
colorScheme,
|
|
139
|
+
hideAddedDiffsUnderline,
|
|
140
|
+
isActive,
|
|
141
|
+
isInserted,
|
|
142
|
+
reveal
|
|
143
|
+
}) : undefined;
|
|
144
|
+
const style = (_revealed$style = revealed === null || revealed === void 0 ? void 0 : revealed.style) !== null && _revealed$style !== void 0 ? _revealed$style : resolveInlineChangedStyle({
|
|
131
145
|
colors,
|
|
132
146
|
colorScheme,
|
|
133
147
|
diffType,
|
|
@@ -142,6 +156,9 @@ export const createInlineChangedDecoration = ({
|
|
|
142
156
|
...(atomicInlineAttrs && {
|
|
143
157
|
class: atomicInlineAttrs.className
|
|
144
158
|
}),
|
|
159
|
+
...(revealed && {
|
|
160
|
+
[REVEAL_ATTR]: revealed.role
|
|
161
|
+
}),
|
|
145
162
|
'data-testid': 'show-diff-changed-decoration',
|
|
146
163
|
// Lets the contributor tag find its highlight on hover.
|
|
147
164
|
...(showContributorTags && {
|
|
@@ -108,6 +108,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
108
108
|
placeBelow = false,
|
|
109
109
|
diffType,
|
|
110
110
|
hideAddedDiffsUnderline = false,
|
|
111
|
+
reveal,
|
|
111
112
|
tagMountContext
|
|
112
113
|
}) => {
|
|
113
114
|
var _slice$content, _slice$content2, _slice$content2$first, _slice$content3, _slice$content3$first, _$safeInsertPos$nodeB, _slice$content$firstC, _newDoc$firstChild, _slice$content$firstC2;
|
|
@@ -277,7 +278,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
277
278
|
const nodeView = serializer.tryCreateNodeView(node);
|
|
278
279
|
if (nodeView) {
|
|
279
280
|
if (node.isInline) {
|
|
280
|
-
const wrapper = createContentWrapper(colorScheme, isActive, isInserted, diffType);
|
|
281
|
+
const wrapper = createContentWrapper(colorScheme, isActive, isInserted, diffType, reveal);
|
|
281
282
|
wrapper.append(nodeView);
|
|
282
283
|
dom.append(wrapper);
|
|
283
284
|
} else {
|
|
@@ -307,11 +308,12 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
307
308
|
colorScheme,
|
|
308
309
|
isActive,
|
|
309
310
|
isInserted,
|
|
310
|
-
diffType
|
|
311
|
+
diffType,
|
|
312
|
+
reveal
|
|
311
313
|
});
|
|
312
314
|
dom.append(injectedNode);
|
|
313
315
|
} else {
|
|
314
|
-
const wrapper = createContentWrapper(colorScheme, isActive, isInserted, diffType);
|
|
316
|
+
const wrapper = createContentWrapper(colorScheme, isActive, isInserted, diffType, reveal);
|
|
315
317
|
wrapper.append(fallbackNode);
|
|
316
318
|
dom.append(wrapper);
|
|
317
319
|
}
|
|
@@ -2,9 +2,9 @@ import { isDiffDecoration, isDiffDecorationSpec } from './decorationKeys';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Diff kinds that expose an anchor a tag can be pinned to: `inline` is added/changed content,
|
|
5
|
-
* `widget` is deleted content
|
|
5
|
+
* `widget` is deleted content, `block` a whole changed block node.
|
|
6
6
|
*/
|
|
7
|
-
const TAGGABLE_DECORATION_TYPES = new Set(['inline', 'widget']);
|
|
7
|
+
const TAGGABLE_DECORATION_TYPES = new Set(['block', 'inline', 'widget']);
|
|
8
8
|
|
|
9
9
|
/** The connection is an internal key, so it is dropped on the way to the tag. */
|
|
10
10
|
const toTagContributor = ({
|
|
@@ -18,6 +18,13 @@ const toTagContributor = ({
|
|
|
18
18
|
*/
|
|
19
19
|
const isSameChange = (widget, inline) => widget.spec.attributionKey === inline.spec.attributionKey && widget.from >= inline.from && widget.from <= inline.to;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Whether `inner` captions the same change as the block decoration `block`, in which case only the
|
|
23
|
+
* block keeps the tag — one tag per block. Containment counts in either direction: a leaf block's
|
|
24
|
+
* highlight spans the wrapper the block sits in. Merely adjacent ranges keep a tag each.
|
|
25
|
+
*/
|
|
26
|
+
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);
|
|
27
|
+
|
|
21
28
|
/**
|
|
22
29
|
* One model per diff decoration whose attribution resolves to a supplied contributor; anything
|
|
23
30
|
* unattributed, untaggable or unresolved is skipped. A replacement's two decorations are collapsed
|
|
@@ -52,22 +59,42 @@ export const extractContributorTags = (decorations, contributors) => {
|
|
|
52
59
|
const inlineTargets = targets.filter(({
|
|
53
60
|
decoration
|
|
54
61
|
}) => decoration.spec.decorationType === 'inline');
|
|
62
|
+
const blockTargets = targets.filter(({
|
|
63
|
+
decoration
|
|
64
|
+
}) => decoration.spec.decorationType === 'block');
|
|
55
65
|
const linkedDiffIds = new Map();
|
|
56
66
|
const folded = new Set();
|
|
57
|
-
|
|
67
|
+
const fold = (host, target) => {
|
|
58
68
|
var _linkedDiffIds$get;
|
|
59
|
-
|
|
69
|
+
folded.add(target);
|
|
70
|
+
const hostDiffId = host.decoration.spec.diffId;
|
|
71
|
+
linkedDiffIds.set(hostDiffId, [...((_linkedDiffIds$get = linkedDiffIds.get(hostDiffId)) !== null && _linkedDiffIds$get !== void 0 ? _linkedDiffIds$get : []), target.decoration.spec.diffId]);
|
|
72
|
+
};
|
|
73
|
+
for (const target of targets) {
|
|
74
|
+
const {
|
|
75
|
+
decorationType
|
|
76
|
+
} = target.decoration.spec;
|
|
77
|
+
if (decorationType === 'block') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A block outranks both other kinds, so it is tried first.
|
|
82
|
+
const block = blockTargets.find(({
|
|
83
|
+
decoration
|
|
84
|
+
}) => isSameBlockChange(target.decoration, decoration));
|
|
85
|
+
if (block) {
|
|
86
|
+
fold(block, target);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (decorationType !== 'widget') {
|
|
60
90
|
continue;
|
|
61
91
|
}
|
|
62
92
|
const host = inlineTargets.find(({
|
|
63
93
|
decoration
|
|
64
94
|
}) => isSameChange(target.decoration, decoration));
|
|
65
|
-
if (
|
|
66
|
-
|
|
95
|
+
if (host) {
|
|
96
|
+
fold(host, target);
|
|
67
97
|
}
|
|
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
98
|
}
|
|
72
99
|
return targets.filter(target => !folded.has(target)).map(({
|
|
73
100
|
contributor,
|