@atlaskit/editor-plugin-show-diff 25.1.0 → 26.0.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 +27 -0
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +39 -6
- package/dist/cjs/pm-plugins/calculateDiff/diffBySteps.js +14 -2
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +14 -0
- package/dist/cjs/pm-plugins/decorations/createReplacedCellDecorations.js +350 -0
- package/dist/cjs/pm-plugins/decorations/utils/cellAttrs.js +30 -0
- package/dist/cjs/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +13 -4
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +40 -7
- package/dist/es2019/pm-plugins/calculateDiff/diffBySteps.js +12 -2
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +14 -0
- package/dist/es2019/pm-plugins/decorations/createReplacedCellDecorations.js +338 -0
- package/dist/es2019/pm-plugins/decorations/utils/cellAttrs.js +19 -0
- package/dist/es2019/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +13 -4
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +39 -6
- package/dist/esm/pm-plugins/calculateDiff/diffBySteps.js +14 -2
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +14 -0
- package/dist/esm/pm-plugins/decorations/createReplacedCellDecorations.js +343 -0
- package/dist/esm/pm-plugins/decorations/utils/cellAttrs.js +25 -0
- package/dist/esm/pm-plugins/decorations/utils/createNodeShapedMarginSpacer.js +13 -4
- package/dist/types/pm-plugins/decorations/createReplacedCellDecorations.d.ts +67 -0
- package/dist/types/pm-plugins/decorations/utils/cellAttrs.d.ts +13 -0
- package/package.json +9 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import isEqual from 'lodash/isEqual';
|
|
2
2
|
import memoizeOne from 'memoize-one';
|
|
3
3
|
import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
|
|
4
|
+
import { isAiXstateMigrationEnabledNoExposure } from '@atlaskit/editor-common/ai-xstate-migration-no-exposure';
|
|
4
5
|
import { areDocsEqualByBlockStructureAndText } from '@atlaskit/editor-common/utils/areDocsEqualByBlockStructureAndText';
|
|
5
6
|
import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
|
|
6
7
|
import { Mapping } from '@atlaskit/editor-prosemirror/transform';
|
|
@@ -8,12 +9,12 @@ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
|
8
9
|
import { TableMap } from '@atlaskit/editor-tables/table-map';
|
|
9
10
|
import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
|
|
10
11
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
11
|
-
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
12
12
|
import { createAttributionColorMap, getAttributionKey, getAttributionKeyForChange, getColorSchemeForChange, isAttributionColoringEnabled, isContributorTagsEnabled } from '../decorations/colorSchemes/attributions';
|
|
13
13
|
import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorationWidgets';
|
|
14
14
|
import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
|
|
15
15
|
import { createInlineChangedDecoration } from '../decorations/createInlineChangedDecoration';
|
|
16
16
|
import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
|
|
17
|
+
import { createReplacedCellDecorations, getReplacedCells } from '../decorations/createReplacedCellDecorations';
|
|
17
18
|
import { extractDiffDescriptors } from '../decorations/decorationKeys';
|
|
18
19
|
import { extractContributorTags } from '../decorations/extractContributorTags';
|
|
19
20
|
import { createDeletedLineBreakDecoration } from '../decorations/utils/createDeletedLineBreakWidget';
|
|
@@ -548,7 +549,11 @@ const calculateDiffDecorationsInner = ({
|
|
|
548
549
|
// suppressed in the clean view. `hideDeletedDiffs` is load-bearing: when false the reviewer
|
|
549
550
|
// asked to compare against the original, and the deleted side is the only place the
|
|
550
551
|
// previous formatting is visible.
|
|
551
|
-
|
|
552
|
+
//
|
|
553
|
+
// Not on an inverted diff: there the widget's side is the suggested content, which the clean
|
|
554
|
+
// view keeps. Suppressing it hides the suggestion along with the displayed side it replaces,
|
|
555
|
+
// e.g. a cell whose only change is its background, as the signature ignores attrs.
|
|
556
|
+
const isMarkOnly = shouldHideMarkOnlyDeletions && hideDeletedDiffs && !(isInverted && fg('platform_editor_ai_show_diff_patch_2')) && change.deleted.length > 0 && isMarkOnlyChange({
|
|
552
557
|
change,
|
|
553
558
|
originalDoc,
|
|
554
559
|
newDoc: tr.doc
|
|
@@ -615,7 +620,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
615
620
|
// For a large inserted table, skip the per-cell inline decorations that
|
|
616
621
|
// freeze the browser on re-render. Only active when Review moment is
|
|
617
622
|
// eligible (xstate + M1).
|
|
618
|
-
const useCoarseTableDecoration = isSmartNodeLevel &&
|
|
623
|
+
const useCoarseTableDecoration = isSmartNodeLevel && isAiXstateMigrationEnabledNoExposure() && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
|
|
619
624
|
// Whether the deleted-content widget will actually be rendered for this
|
|
620
625
|
// change. Used to decide if indicator anchor positions should be adjusted
|
|
621
626
|
// inward — when the widget is present the anchor must stay at the block
|
|
@@ -625,7 +630,35 @@ const calculateDiffDecorationsInner = ({
|
|
|
625
630
|
hideDeletedDiffs,
|
|
626
631
|
isInverted
|
|
627
632
|
});
|
|
628
|
-
|
|
633
|
+
|
|
634
|
+
// A whole-cell replacement keeps its cells in place, so it takes its own decorations
|
|
635
|
+
// instead of the generic inline and block ones.
|
|
636
|
+
const replacedCells = fg('platform_editor_ai_show_diff_patch_2') ? getReplacedCells({
|
|
637
|
+
slice: originalDoc.slice(change.fromA, change.toA),
|
|
638
|
+
doc: tr.doc,
|
|
639
|
+
fromB: change.fromB,
|
|
640
|
+
toB: change.toB
|
|
641
|
+
}) : [];
|
|
642
|
+
if (replacedCells.length > 0) {
|
|
643
|
+
decorations.push(...createReplacedCellDecorations({
|
|
644
|
+
replacedCells,
|
|
645
|
+
change,
|
|
646
|
+
doc: tr.doc,
|
|
647
|
+
attributionKey,
|
|
648
|
+
colorScheme: changeColorScheme,
|
|
649
|
+
hasDeletedWidget: willRenderDeletedWidget,
|
|
650
|
+
hideAddedDiffsUnderline,
|
|
651
|
+
isActive,
|
|
652
|
+
isDeletedWidgetBelow,
|
|
653
|
+
isInserted,
|
|
654
|
+
leftAnchorId,
|
|
655
|
+
reveal,
|
|
656
|
+
shouldHideDeleted,
|
|
657
|
+
showContributorTags,
|
|
658
|
+
showIndicators,
|
|
659
|
+
tagMountContext
|
|
660
|
+
}));
|
|
661
|
+
} else if (isSmartNodeLevel) {
|
|
629
662
|
// For a large inserted table, skip the O(cells) inline decorations inside the
|
|
630
663
|
// table (the cell overlays added below provide the highlight without the
|
|
631
664
|
// expensive content re-render); leaf blocks outside the table still get theirs.
|
|
@@ -674,7 +707,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
674
707
|
tagMountContext
|
|
675
708
|
}));
|
|
676
709
|
}
|
|
677
|
-
decorations.push(...calculateNodesForBlockDecoration({
|
|
710
|
+
decorations.push(...(replacedCells.length > 0 ? [] : calculateNodesForBlockDecoration({
|
|
678
711
|
// A deleted widget placed below anchors at the change's end, inside the very block
|
|
679
712
|
// this would let the decoration claim.
|
|
680
713
|
allowTrailingCloseTokens: !isDeletedWidgetBelow,
|
|
@@ -694,7 +727,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
694
727
|
intl,
|
|
695
728
|
coarseTableCellsOnly: useCoarseTableDecoration,
|
|
696
729
|
leftAnchorId
|
|
697
|
-
}));
|
|
730
|
+
})));
|
|
698
731
|
}
|
|
699
732
|
if (change.deleted.length > 0 && !isMarkOnly && !hasNoDeletedContent) {
|
|
700
733
|
const shouldHideDeleted = shouldHideDeletedSide({
|
|
@@ -845,7 +878,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
845
878
|
}));
|
|
846
879
|
// If the original node position is known (e.g. a panel type change), also render
|
|
847
880
|
// the old block node as a "deleted" widget so the reviewer sees the before/after.
|
|
848
|
-
const isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') &&
|
|
881
|
+
const isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && isAiXstateMigrationEnabledNoExposure() && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true;
|
|
849
882
|
if (isSmartNodeLevelAttrChange && change.fromA !== undefined && change.toA !== undefined) {
|
|
850
883
|
const placeBelow = deletedDiffPlacement === 'bottom';
|
|
851
884
|
decorations.push(...createNodeChangedDecorationWidget({
|
|
@@ -3,6 +3,7 @@ import { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
|
3
3
|
import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
4
4
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
5
5
|
import { getAttributionKey } from '../decorations/colorSchemes/attributions';
|
|
6
|
+
import { isCellAttrChanged } from '../decorations/utils/cellAttrs';
|
|
6
7
|
import { buildCharsByOffset } from '../utils/charsByOffset';
|
|
7
8
|
import { attrAwareTokenEncoder } from './attrAwareTokenEncoder';
|
|
8
9
|
import { optimizeChanges } from './optimizeChanges';
|
|
@@ -17,6 +18,14 @@ const WHITESPACE_REGEX = /\s/u;
|
|
|
17
18
|
* excluded: `groupDeletedColumnChanges` relies on a replaced table being one whole-table change.
|
|
18
19
|
*/
|
|
19
20
|
const isGranularIsolatingNode = node => !!node.type.spec.isolating && node.type.spec.tableRole !== 'table';
|
|
21
|
+
const CELL_TYPE_NAMES = ['tableCell', 'tableHeader'];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether a table cell's replacement changes one of its diffable attributes, such as its background.
|
|
25
|
+
* Such a cell is diffed whole: it renders as a cell style plus a copy of its row, which already shows
|
|
26
|
+
* the cell's new content, so inline marks inside it would only repeat that.
|
|
27
|
+
*/
|
|
28
|
+
const isCellAttrChange = (replacedNode, replacingNode) => CELL_TYPE_NAMES.includes(replacedNode.type.name) && isCellAttrChanged(replacedNode, replacingNode);
|
|
20
29
|
const mapPosition = (mapping, pos) => mapping.map(pos);
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -206,7 +215,8 @@ const mergeOverlappingByNewDocRange = changes => {
|
|
|
206
215
|
* - The replaced slice has only one child
|
|
207
216
|
* - The replacing slice has only one child
|
|
208
217
|
* - Both children are the same textblock type, or (behind `platform_editor_ai_show_diff_patch_2`)
|
|
209
|
-
* the same isolating, non-table node type whose text content differs
|
|
218
|
+
* the same isolating, non-table node type whose text content differs (for a table cell, only when
|
|
219
|
+
* its diffable attributes are unchanged)
|
|
210
220
|
* - The replaced slice and replacing slice have the same text content
|
|
211
221
|
* - The replaced slice and replacing slice have the same child marks (if text content is equal)
|
|
212
222
|
*/
|
|
@@ -244,7 +254,7 @@ const shouldCheckGranularDiff = (step, before, from, to) => {
|
|
|
244
254
|
// `hasSameChildMarks` only compares an isolating node's direct (block) children, so it cannot
|
|
245
255
|
// vouch for one with unchanged text. Keep the existing whole-node fallback for that case.
|
|
246
256
|
if (isIsolatingNode) {
|
|
247
|
-
return !isTextContentEqual;
|
|
257
|
+
return !isTextContentEqual && !(replacingNode && isCellAttrChange(replacedNode, replacingNode));
|
|
248
258
|
}
|
|
249
259
|
return !isTextContentEqual || isTextContentEqual && hasSameChildMarks(replacedNode, replacingNode);
|
|
250
260
|
};
|
|
@@ -10,6 +10,7 @@ import { clampAnchorPosIntoCell, createAnchorNameSpan, createLeftAnchorWidget }
|
|
|
10
10
|
import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
|
|
11
11
|
import { createContributorTagHost, resolveHoistedCodeBlockAnchor, unmountContributorTag } from './createContributorTagWidget';
|
|
12
12
|
import { getAtomicInlineChangedAttrs } from './createInlineChangedDecoration';
|
|
13
|
+
import { createReplacedCellWidgets } from './createReplacedCellDecorations';
|
|
13
14
|
import { AnchorTypeKey, buildContributorTagDecorationSpec, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
|
|
14
15
|
import { absorbFirstChildMarginReset } from './utils/absorbFirstChildMarginReset';
|
|
15
16
|
import { createDeletedLineBreakDecoration } from './utils/createDeletedLineBreakWidget';
|
|
@@ -214,6 +215,19 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
214
215
|
isInserted
|
|
215
216
|
});
|
|
216
217
|
if (tableDiffMode.kind === 'cells') {
|
|
218
|
+
if (fg('platform_editor_ai_show_diff_patch_2')) {
|
|
219
|
+
return createReplacedCellWidgets({
|
|
220
|
+
slice,
|
|
221
|
+
newDoc,
|
|
222
|
+
change,
|
|
223
|
+
nodeViewSerializer,
|
|
224
|
+
colorScheme,
|
|
225
|
+
intl,
|
|
226
|
+
isActive,
|
|
227
|
+
isInserted,
|
|
228
|
+
leftAnchorId
|
|
229
|
+
});
|
|
230
|
+
}
|
|
217
231
|
return createTableCellContentWidgets({
|
|
218
232
|
slice,
|
|
219
233
|
newDoc,
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { getDarkModeLCHColor } from '@atlaskit/adf-schema/get-dark-mode-lch-color';
|
|
2
|
+
import { isRgb } from '@atlaskit/adf-schema/is-rgb';
|
|
3
|
+
import { rgbToHex } from '@atlaskit/adf-schema/rgb-to-hex';
|
|
4
|
+
import { getCellDomAttrs } from '@atlaskit/adf-schema/tableNodes';
|
|
5
|
+
import { convertToInlineCss } from '@atlaskit/editor-common/css-helper';
|
|
6
|
+
import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
|
|
7
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
8
|
+
import { createBlockChangedDecoration } from './createBlockChangedDecoration';
|
|
9
|
+
import { createInlineChangedDecoration } from './createInlineChangedDecoration';
|
|
10
|
+
import { buildDiffDecorationSpec } from './decorationKeys';
|
|
11
|
+
import { isCellAttrChanged, isCellBackgroundChanged, normalizeCellBackground } from './utils/cellAttrs';
|
|
12
|
+
import { injectInnerWrapper, wrapBlockNodeView } from './utils/wrapBlockNodeView';
|
|
13
|
+
const CELL_TYPE_NAMES = ['tableCell', 'tableHeader'];
|
|
14
|
+
/**
|
|
15
|
+
* Pairs each cell of a whole-cell replacement with the live cell it replaces, or `[]` when the slice
|
|
16
|
+
* is not a run of whole cells matching the live ones. Whole-cell replacement preserves cell count and
|
|
17
|
+
* order, so the nth live cell in the range matches the nth cell in the slice.
|
|
18
|
+
*/
|
|
19
|
+
export const getReplacedCells = ({
|
|
20
|
+
slice,
|
|
21
|
+
doc,
|
|
22
|
+
fromB,
|
|
23
|
+
toB
|
|
24
|
+
}) => {
|
|
25
|
+
const otherCells = slice.content.content;
|
|
26
|
+
if (slice.openStart !== 0 || slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(node => CELL_TYPE_NAMES.includes(node.type.name))) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const liveCells = [];
|
|
30
|
+
doc.nodesBetween(fromB, toB, (node, pos) => {
|
|
31
|
+
if (CELL_TYPE_NAMES.includes(node.type.name)) {
|
|
32
|
+
liveCells.push({
|
|
33
|
+
node,
|
|
34
|
+
pos
|
|
35
|
+
});
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
if (liveCells.length !== otherCells.length) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return otherCells.map((otherCell, index) => ({
|
|
44
|
+
otherCell,
|
|
45
|
+
liveCell: liveCells[index].node,
|
|
46
|
+
liveCellPos: liveCells[index].pos
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether two cells hold the same content, ignoring the cells' own attributes and `localId`s, which
|
|
52
|
+
* a suggestion can regenerate. Such a replacement only changed the cell (e.g. its background), so
|
|
53
|
+
* there is no content to diff.
|
|
54
|
+
*/
|
|
55
|
+
const isCellContentEqual = (left, right) => {
|
|
56
|
+
if (left.childCount !== right.childCount) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
for (let index = 0; index < left.childCount; index++) {
|
|
60
|
+
if (!areNodesEqualIgnoreAttrs(left.child(index), right.child(index), ['localId'])) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the cell itself changed — its type, or an attribute that represents a change such as its
|
|
69
|
+
* background or span — as opposed to only its content.
|
|
70
|
+
*/
|
|
71
|
+
const isCellChanged = (left, right) => left.type !== right.type || isCellAttrChanged(left, right);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A custom (non-palette) cell colour as the schema renders it: inverted in dark mode so text stays
|
|
75
|
+
* readable, except for design tokens, which are already themed.
|
|
76
|
+
*/
|
|
77
|
+
const getCustomCellColor = background => {
|
|
78
|
+
const color = isRgb(background) && rgbToHex(background) || background;
|
|
79
|
+
if (color.startsWith('var(') || document.documentElement.getAttribute('data-color-mode') !== 'dark') {
|
|
80
|
+
return background;
|
|
81
|
+
}
|
|
82
|
+
return getDarkModeLCHColor(color);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The colour `cell` renders with. A palette colour is the editor's own themed variable for its colour
|
|
87
|
+
* name (e.g. for dark mode), the one `td[colorname]` cells use; a custom colour is the schema's own
|
|
88
|
+
* treatment of it; no colour is the editor's default.
|
|
89
|
+
*/
|
|
90
|
+
const getCellBackgroundColor = cell => {
|
|
91
|
+
const background = normalizeCellBackground(cell.attrs.background);
|
|
92
|
+
if (!background) {
|
|
93
|
+
return cell.type.name === 'tableHeader' ? "var(--ds-background-accent-gray-subtlest, #F0F1F2)" : "var(--ds-surface, #FFFFFF)";
|
|
94
|
+
}
|
|
95
|
+
const {
|
|
96
|
+
colorname
|
|
97
|
+
} = getCellDomAttrs(cell);
|
|
98
|
+
return colorname ? `var(--ak-editor-table-cell-background-${colorname.toLowerCase().split(' ').join('-')})` : getCustomCellColor(background);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A layer painting `color` over the cell's own background and under its content, so the cell reads
|
|
103
|
+
* as having the new background without any of the cell's own attributes being overridden: removing
|
|
104
|
+
* a decoration removes the attributes it set rather than restoring the ones they replaced.
|
|
105
|
+
*/
|
|
106
|
+
const createCellBackgroundLayer = color => {
|
|
107
|
+
const layer = document.createElement('div');
|
|
108
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
109
|
+
layer.contentEditable = 'false';
|
|
110
|
+
layer.dataset.testid = 'show-diff-new-cell-background';
|
|
111
|
+
layer.setAttribute('style', convertToInlineCss({
|
|
112
|
+
position: 'absolute',
|
|
113
|
+
inset: 0,
|
|
114
|
+
zIndex: -1,
|
|
115
|
+
borderRadius: 'inherit',
|
|
116
|
+
pointerEvents: 'none',
|
|
117
|
+
backgroundColor: color
|
|
118
|
+
}));
|
|
119
|
+
return layer;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** The other side's cell content, as blocks, for the top of the live cell. */
|
|
123
|
+
const createCellContentWidget = ({
|
|
124
|
+
cell,
|
|
125
|
+
pos,
|
|
126
|
+
nodeViewSerializer,
|
|
127
|
+
colorScheme,
|
|
128
|
+
intl,
|
|
129
|
+
isActive,
|
|
130
|
+
isInserted,
|
|
131
|
+
leftAnchorId
|
|
132
|
+
}) => {
|
|
133
|
+
const dom = document.createElement('div');
|
|
134
|
+
cell.content.forEach(block => {
|
|
135
|
+
const nodeView = nodeViewSerializer.tryCreateNodeView(block, 0, undefined, isInserted, colorScheme);
|
|
136
|
+
if (nodeView) {
|
|
137
|
+
wrapBlockNodeView({
|
|
138
|
+
dom,
|
|
139
|
+
nodeView,
|
|
140
|
+
targetNode: block,
|
|
141
|
+
colorScheme,
|
|
142
|
+
intl,
|
|
143
|
+
isActive,
|
|
144
|
+
isInserted
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const serialized = nodeViewSerializer.serializeNode(block);
|
|
149
|
+
if (serialized instanceof HTMLElement) {
|
|
150
|
+
dom.append(injectInnerWrapper({
|
|
151
|
+
node: serialized,
|
|
152
|
+
colorScheme,
|
|
153
|
+
isActive,
|
|
154
|
+
isInserted
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
if (dom.childNodes.length === 0) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
dom.setAttribute('data-testid', isInserted ? 'show-diff-changed-decoration' : 'show-diff-deleted-decoration');
|
|
162
|
+
|
|
163
|
+
// Before the cell's own blocks, as a block of its own, so it never lands inside a paragraph the
|
|
164
|
+
// displayed side may hide.
|
|
165
|
+
return Decoration.widget(pos + 1, dom, {
|
|
166
|
+
...buildDiffDecorationSpec({
|
|
167
|
+
colorScheme,
|
|
168
|
+
decorationType: 'widget',
|
|
169
|
+
diffId: crypto.randomUUID(),
|
|
170
|
+
leftAnchorId,
|
|
171
|
+
isActive,
|
|
172
|
+
isInserted,
|
|
173
|
+
side: -1
|
|
174
|
+
}),
|
|
175
|
+
marks: []
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The other side of a whole-cell replacement, drawn inside the live cells so the table keeps its
|
|
181
|
+
* shape: each live cell takes the other cell's background when it differs, and its content when that
|
|
182
|
+
* differs. A cell whose content did not change has nothing to diff.
|
|
183
|
+
*/
|
|
184
|
+
export const createReplacedCellWidgets = ({
|
|
185
|
+
slice,
|
|
186
|
+
newDoc,
|
|
187
|
+
change,
|
|
188
|
+
nodeViewSerializer,
|
|
189
|
+
colorScheme,
|
|
190
|
+
intl,
|
|
191
|
+
isActive,
|
|
192
|
+
isInserted,
|
|
193
|
+
leftAnchorId
|
|
194
|
+
}) => getReplacedCells({
|
|
195
|
+
slice,
|
|
196
|
+
doc: newDoc,
|
|
197
|
+
fromB: change.fromB,
|
|
198
|
+
toB: change.toB
|
|
199
|
+
}).flatMap(({
|
|
200
|
+
otherCell,
|
|
201
|
+
liveCell,
|
|
202
|
+
liveCellPos
|
|
203
|
+
}) => {
|
|
204
|
+
const decorations = [];
|
|
205
|
+
if (isInserted && isCellBackgroundChanged(otherCell, liveCell)) {
|
|
206
|
+
const cellEnd = liveCellPos + liveCell.nodeSize;
|
|
207
|
+
decorations.push(
|
|
208
|
+
// The cell becomes the layer's stacking context, so the layer sits above the cell's own
|
|
209
|
+
// background rather than behind it. Through `z-index` at the cell overlay's own level, not
|
|
210
|
+
// `isolation`: an isolated cell would trap its overlay below later cells, which then cover
|
|
211
|
+
// the overlay's outline where it overlaps their border. Properties the cell never sets, so
|
|
212
|
+
// removing them takes nothing of its own.
|
|
213
|
+
Decoration.node(liveCellPos, cellEnd, {
|
|
214
|
+
style: convertToInlineCss({
|
|
215
|
+
position: 'relative',
|
|
216
|
+
// The highest z-index a cell diff overlay takes (the rounded-corner variant).
|
|
217
|
+
zIndex: 2
|
|
218
|
+
})
|
|
219
|
+
}, {
|
|
220
|
+
key: `show-diff-new-cell-background-host-${liveCellPos}`
|
|
221
|
+
}),
|
|
222
|
+
// At the cell's end, beside its diff overlay, so the cell's first block stays its first child.
|
|
223
|
+
Decoration.widget(cellEnd - 1, createCellBackgroundLayer(getCellBackgroundColor(otherCell)), {
|
|
224
|
+
key: `show-diff-new-cell-background-${liveCellPos}`,
|
|
225
|
+
side: -1,
|
|
226
|
+
marks: [],
|
|
227
|
+
ignoreSelection: true
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
if (!isCellContentEqual(otherCell, liveCell)) {
|
|
231
|
+
const contentWidget = createCellContentWidget({
|
|
232
|
+
cell: otherCell,
|
|
233
|
+
pos: liveCellPos,
|
|
234
|
+
nodeViewSerializer,
|
|
235
|
+
colorScheme,
|
|
236
|
+
intl,
|
|
237
|
+
isActive,
|
|
238
|
+
isInserted,
|
|
239
|
+
leftAnchorId
|
|
240
|
+
});
|
|
241
|
+
if (contentWidget) {
|
|
242
|
+
decorations.push(contentWidget);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return decorations;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The displayed side of a whole-cell replacement. A table cell is never removed on its own, only
|
|
250
|
+
* replaced in place, so a live cell is never hidden — hiding it would leave the row a cell short. It
|
|
251
|
+
* takes the changed-cell treatment only when the cell itself changed; its content is diffed, and
|
|
252
|
+
* hidden in the clean view, only when that changed. A cell whose content stayed the same keeps
|
|
253
|
+
* showing it.
|
|
254
|
+
*/
|
|
255
|
+
export const createReplacedCellDecorations = ({
|
|
256
|
+
replacedCells,
|
|
257
|
+
change,
|
|
258
|
+
doc,
|
|
259
|
+
attributionKey,
|
|
260
|
+
colorScheme,
|
|
261
|
+
hasDeletedWidget,
|
|
262
|
+
hideAddedDiffsUnderline,
|
|
263
|
+
isActive,
|
|
264
|
+
isDeletedWidgetBelow,
|
|
265
|
+
isInserted,
|
|
266
|
+
leftAnchorId,
|
|
267
|
+
reveal,
|
|
268
|
+
shouldHideDeleted,
|
|
269
|
+
showContributorTags,
|
|
270
|
+
showIndicators,
|
|
271
|
+
tagMountContext
|
|
272
|
+
}) => replacedCells.flatMap(({
|
|
273
|
+
otherCell,
|
|
274
|
+
liveCell,
|
|
275
|
+
liveCellPos
|
|
276
|
+
}) => {
|
|
277
|
+
const cellEnd = liveCellPos + liveCell.nodeSize;
|
|
278
|
+
const decorations = isCellChanged(otherCell, liveCell) ? createBlockChangedDecoration({
|
|
279
|
+
attributionKey,
|
|
280
|
+
change: {
|
|
281
|
+
from: liveCellPos,
|
|
282
|
+
to: cellEnd,
|
|
283
|
+
name: liveCell.type.name
|
|
284
|
+
},
|
|
285
|
+
colorScheme,
|
|
286
|
+
doc,
|
|
287
|
+
isActive,
|
|
288
|
+
// The cell stays either way, so it reads as changed, not removed.
|
|
289
|
+
isInserted: true,
|
|
290
|
+
leftAnchorId,
|
|
291
|
+
showContributorTags,
|
|
292
|
+
showIndicators,
|
|
293
|
+
tagMountContext
|
|
294
|
+
}) : [];
|
|
295
|
+
if (isCellContentEqual(otherCell, liveCell)) {
|
|
296
|
+
return decorations;
|
|
297
|
+
}
|
|
298
|
+
decorations.push(...createInlineChangedDecoration({
|
|
299
|
+
attributionKey,
|
|
300
|
+
change: {
|
|
301
|
+
...change,
|
|
302
|
+
fromB: liveCellPos,
|
|
303
|
+
toB: cellEnd
|
|
304
|
+
},
|
|
305
|
+
colorScheme,
|
|
306
|
+
doc,
|
|
307
|
+
hasDeletedWidget,
|
|
308
|
+
hideAddedDiffsUnderline,
|
|
309
|
+
isActive,
|
|
310
|
+
isDeletedWidgetBelow,
|
|
311
|
+
isInserted,
|
|
312
|
+
leftAnchorId,
|
|
313
|
+
reveal,
|
|
314
|
+
shouldHideDeleted,
|
|
315
|
+
showContributorTags,
|
|
316
|
+
showIndicators,
|
|
317
|
+
tagMountContext
|
|
318
|
+
}));
|
|
319
|
+
if (shouldHideDeleted) {
|
|
320
|
+
liveCell.forEach((block, offset) => {
|
|
321
|
+
const blockPos = liveCellPos + 1 + offset;
|
|
322
|
+
decorations.push(...createBlockChangedDecoration({
|
|
323
|
+
change: {
|
|
324
|
+
from: blockPos,
|
|
325
|
+
to: blockPos + block.nodeSize,
|
|
326
|
+
name: block.type.name
|
|
327
|
+
},
|
|
328
|
+
colorScheme,
|
|
329
|
+
doc,
|
|
330
|
+
isActive,
|
|
331
|
+
isInserted,
|
|
332
|
+
leftAnchorId,
|
|
333
|
+
shouldHideDeleted
|
|
334
|
+
}));
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return decorations;
|
|
338
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getDiffableAttrNames } from './diffableAttrs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A cell background as it renders: absent, empty and `"null"` — the string agent payloads send for
|
|
5
|
+
* no background — all mean the cell has none.
|
|
6
|
+
*/
|
|
7
|
+
export const normalizeCellBackground = background => typeof background === 'string' && background !== '' && background !== 'null' ? background : null;
|
|
8
|
+
|
|
9
|
+
/** Whether two cells render different backgrounds. */
|
|
10
|
+
export const isCellBackgroundChanged = (left, right) => normalizeCellBackground(left.attrs.background) !== normalizeCellBackground(right.attrs.background);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether two cells differ by an attribute that represents a change, such as their background or
|
|
14
|
+
* span, as opposed to only their content.
|
|
15
|
+
*/
|
|
16
|
+
export const isCellAttrChanged = (left, right) => {
|
|
17
|
+
var _getDiffableAttrNames;
|
|
18
|
+
return ((_getDiffableAttrNames = getDiffableAttrNames(left.type.name, left.attrs)) !== null && _getDiffableAttrNames !== void 0 ? _getDiffableAttrNames : []).some(name => name === 'background' ? isCellBackgroundChanged(left, right) : left.attrs[name] !== right.attrs[name]);
|
|
19
|
+
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { PanelSharedCssClassName } from '@atlaskit/editor-common/styles/panel';
|
|
2
|
+
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
|
|
1
3
|
/**
|
|
2
4
|
* Node types that can safely be reduced to an empty shell.
|
|
3
5
|
*
|
|
4
6
|
* A shell only replicates a margin if it collapses to nothing itself, which needs zero height and
|
|
5
|
-
* no vertical border or padding. Textblocks and lists qualify;
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* no vertical border or padding. Textblocks and lists qualify; panels need their painted box
|
|
8
|
+
* hidden and their flex layout removed. Nodes with intrinsic sizing (tables are `display: table`,
|
|
9
|
+
* media has a measured height) are left alone.
|
|
8
10
|
*/
|
|
9
|
-
const isShapeableNode = node => node.isTextblock || ['bulletList', 'orderedList'].includes(node.type.name);
|
|
11
|
+
const isShapeableNode = node => node.isTextblock || ['bulletList', 'orderedList'].includes(node.type.name) || getBaseNodeTypeName(node.type) === 'panel';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* An invisible element shaped like `node`, used to supply the top margin `node` no longer gets.
|
|
@@ -50,6 +52,13 @@ export const createNodeShapedMarginSpacer = ({
|
|
|
50
52
|
serialized.dataset.testid = testId;
|
|
51
53
|
serialized.setAttribute('aria-hidden', 'true');
|
|
52
54
|
serialized.contentEditable = 'false';
|
|
55
|
+
if (getBaseNodeTypeName(node.type) === 'panel') {
|
|
56
|
+
// Schema serialization lacks the node view's class, which carries the panel's margin.
|
|
57
|
+
// Hide the painted shell and allow its margins to collapse through a zero-height block.
|
|
58
|
+
serialized.classList.add(PanelSharedCssClassName.prefix);
|
|
59
|
+
serialized.style.display = 'block';
|
|
60
|
+
serialized.style.visibility = 'hidden';
|
|
61
|
+
}
|
|
53
62
|
|
|
54
63
|
// Zero height with no vertical border or padding keeps the spacer self-collapsing: its margins
|
|
55
64
|
// stay adjoining, so they collapse with the neighbours into a single margin equal to the largest
|
|
@@ -9,6 +9,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
9
9
|
import isEqual from 'lodash/isEqual';
|
|
10
10
|
import memoizeOne from 'memoize-one';
|
|
11
11
|
import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
|
|
12
|
+
import { isAiXstateMigrationEnabledNoExposure } from '@atlaskit/editor-common/ai-xstate-migration-no-exposure';
|
|
12
13
|
import { areDocsEqualByBlockStructureAndText } from '@atlaskit/editor-common/utils/areDocsEqualByBlockStructureAndText';
|
|
13
14
|
import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
|
|
14
15
|
import { Mapping } from '@atlaskit/editor-prosemirror/transform';
|
|
@@ -16,12 +17,12 @@ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
|
16
17
|
import { TableMap } from '@atlaskit/editor-tables/table-map';
|
|
17
18
|
import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
|
|
18
19
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
19
|
-
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
20
20
|
import { createAttributionColorMap, getAttributionKey, getAttributionKeyForChange, getColorSchemeForChange, isAttributionColoringEnabled, isContributorTagsEnabled } from '../decorations/colorSchemes/attributions';
|
|
21
21
|
import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorationWidgets';
|
|
22
22
|
import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
|
|
23
23
|
import { createInlineChangedDecoration } from '../decorations/createInlineChangedDecoration';
|
|
24
24
|
import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
|
|
25
|
+
import { createReplacedCellDecorations, getReplacedCells } from '../decorations/createReplacedCellDecorations';
|
|
25
26
|
import { extractDiffDescriptors } from '../decorations/decorationKeys';
|
|
26
27
|
import { extractContributorTags } from '../decorations/extractContributorTags';
|
|
27
28
|
import { createDeletedLineBreakDecoration } from '../decorations/utils/createDeletedLineBreakWidget';
|
|
@@ -596,7 +597,11 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
596
597
|
// suppressed in the clean view. `hideDeletedDiffs` is load-bearing: when false the reviewer
|
|
597
598
|
// asked to compare against the original, and the deleted side is the only place the
|
|
598
599
|
// previous formatting is visible.
|
|
599
|
-
|
|
600
|
+
//
|
|
601
|
+
// Not on an inverted diff: there the widget's side is the suggested content, which the clean
|
|
602
|
+
// view keeps. Suppressing it hides the suggestion along with the displayed side it replaces,
|
|
603
|
+
// e.g. a cell whose only change is its background, as the signature ignores attrs.
|
|
604
|
+
var isMarkOnly = shouldHideMarkOnlyDeletions && hideDeletedDiffs && !(isInverted && fg('platform_editor_ai_show_diff_patch_2')) && change.deleted.length > 0 && isMarkOnlyChange({
|
|
600
605
|
change: change,
|
|
601
606
|
originalDoc: originalDoc,
|
|
602
607
|
newDoc: tr.doc
|
|
@@ -672,7 +677,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
672
677
|
// For a large inserted table, skip the per-cell inline decorations that
|
|
673
678
|
// freeze the browser on re-render. Only active when Review moment is
|
|
674
679
|
// eligible (xstate + M1).
|
|
675
|
-
var useCoarseTableDecoration = isSmartNodeLevel &&
|
|
680
|
+
var useCoarseTableDecoration = isSmartNodeLevel && isAiXstateMigrationEnabledNoExposure() && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
|
|
676
681
|
// Whether the deleted-content widget will actually be rendered for this
|
|
677
682
|
// change. Used to decide if indicator anchor positions should be adjusted
|
|
678
683
|
// inward — when the widget is present the anchor must stay at the block
|
|
@@ -682,7 +687,35 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
682
687
|
hideDeletedDiffs: hideDeletedDiffs,
|
|
683
688
|
isInverted: isInverted
|
|
684
689
|
});
|
|
685
|
-
|
|
690
|
+
|
|
691
|
+
// A whole-cell replacement keeps its cells in place, so it takes its own decorations
|
|
692
|
+
// instead of the generic inline and block ones.
|
|
693
|
+
var replacedCells = fg('platform_editor_ai_show_diff_patch_2') ? getReplacedCells({
|
|
694
|
+
slice: originalDoc.slice(change.fromA, change.toA),
|
|
695
|
+
doc: tr.doc,
|
|
696
|
+
fromB: change.fromB,
|
|
697
|
+
toB: change.toB
|
|
698
|
+
}) : [];
|
|
699
|
+
if (replacedCells.length > 0) {
|
|
700
|
+
decorations.push.apply(decorations, _toConsumableArray(createReplacedCellDecorations({
|
|
701
|
+
replacedCells: replacedCells,
|
|
702
|
+
change: change,
|
|
703
|
+
doc: tr.doc,
|
|
704
|
+
attributionKey: attributionKey,
|
|
705
|
+
colorScheme: changeColorScheme,
|
|
706
|
+
hasDeletedWidget: willRenderDeletedWidget,
|
|
707
|
+
hideAddedDiffsUnderline: hideAddedDiffsUnderline,
|
|
708
|
+
isActive: isActive,
|
|
709
|
+
isDeletedWidgetBelow: isDeletedWidgetBelow,
|
|
710
|
+
isInserted: isInserted,
|
|
711
|
+
leftAnchorId: leftAnchorId,
|
|
712
|
+
reveal: reveal,
|
|
713
|
+
shouldHideDeleted: shouldHideDeleted,
|
|
714
|
+
showContributorTags: showContributorTags,
|
|
715
|
+
showIndicators: showIndicators,
|
|
716
|
+
tagMountContext: tagMountContext
|
|
717
|
+
})));
|
|
718
|
+
} else if (isSmartNodeLevel) {
|
|
686
719
|
// For a large inserted table, skip the O(cells) inline decorations inside the
|
|
687
720
|
// table (the cell overlays added below provide the highlight without the
|
|
688
721
|
// expensive content re-render); leaf blocks outside the table still get theirs.
|
|
@@ -742,7 +775,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
742
775
|
tagMountContext: tagMountContext
|
|
743
776
|
})));
|
|
744
777
|
}
|
|
745
|
-
decorations.push.apply(decorations, _toConsumableArray(calculateNodesForBlockDecoration({
|
|
778
|
+
decorations.push.apply(decorations, _toConsumableArray(replacedCells.length > 0 ? [] : calculateNodesForBlockDecoration({
|
|
746
779
|
// A deleted widget placed below anchors at the change's end, inside the very block
|
|
747
780
|
// this would let the decoration claim.
|
|
748
781
|
allowTrailingCloseTokens: !isDeletedWidgetBelow,
|
|
@@ -932,7 +965,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
932
965
|
})));
|
|
933
966
|
// If the original node position is known (e.g. a panel type change), also render
|
|
934
967
|
// the old block node as a "deleted" widget so the reviewer sees the before/after.
|
|
935
|
-
var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') &&
|
|
968
|
+
var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && isAiXstateMigrationEnabledNoExposure() && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true;
|
|
936
969
|
if (isSmartNodeLevelAttrChange && change.fromA !== undefined && change.toA !== undefined) {
|
|
937
970
|
var placeBelow = deletedDiffPlacement === 'bottom';
|
|
938
971
|
decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget({
|