@atlaskit/editor-plugin-show-diff 17.0.1 → 17.1.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 +27 -0
- package/dist/cjs/entry-points/show-diff-plugin-type.js +12 -1
- package/dist/cjs/pm-plugins/NodeViewSerializer.js +1 -1
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +128 -51
- package/dist/cjs/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +265 -0
- package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +14 -3
- package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +1 -1
- package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +100 -34
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +18 -13
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +82 -20
- package/dist/cjs/pm-plugins/decorations/utils/createDeletedLineBreakWidget.js +106 -0
- package/dist/cjs/pm-plugins/decorations/utils/tableDiffMode.js +36 -0
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +19 -8
- package/dist/cjs/pm-plugins/resolveDiffContributors.js +4 -3
- package/dist/cjs/pm-plugins/utils/emptyTextBlocks.js +66 -0
- package/dist/cjs/showDiffPluginType.js +87 -1
- package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +22 -14
- package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +4 -0
- package/dist/es2019/entry-points/calculate-diff.js +1 -0
- package/dist/es2019/entry-points/show-diff-plugin-type.js +3 -0
- package/dist/es2019/entry-points/show-diff-plugin.js +1 -0
- package/dist/es2019/pm-plugins/NodeViewSerializer.js +1 -1
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +63 -3
- package/dist/es2019/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +210 -0
- package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +12 -3
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +0 -1
- package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +1 -1
- package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +79 -13
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +8 -2
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +85 -15
- package/dist/es2019/pm-plugins/decorations/utils/createDeletedLineBreakWidget.js +100 -0
- package/dist/es2019/pm-plugins/decorations/utils/tableDiffMode.js +27 -0
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +17 -7
- package/dist/es2019/pm-plugins/resolveDiffContributors.js +3 -1
- package/dist/es2019/pm-plugins/scrollToDiff.js +1 -0
- package/dist/es2019/pm-plugins/utils/emptyTextBlocks.js +58 -0
- package/dist/es2019/showDiffPluginType.js +85 -1
- package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +22 -14
- package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +4 -0
- package/dist/esm/entry-points/calculate-diff.js +1 -0
- package/dist/esm/entry-points/show-diff-plugin-type.js +3 -0
- package/dist/esm/entry-points/show-diff-plugin.js +1 -0
- package/dist/esm/pm-plugins/NodeViewSerializer.js +1 -1
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +129 -52
- package/dist/esm/pm-plugins/calculateDiff/groupDeletedColumnChanges.js +258 -0
- package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +14 -3
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +0 -1
- package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +1 -1
- package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +100 -34
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +18 -13
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +84 -22
- package/dist/esm/pm-plugins/decorations/utils/createDeletedLineBreakWidget.js +100 -0
- package/dist/esm/pm-plugins/decorations/utils/tableDiffMode.js +30 -0
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +18 -7
- package/dist/esm/pm-plugins/resolveDiffContributors.js +4 -3
- package/dist/esm/pm-plugins/scrollToDiff.js +1 -0
- package/dist/esm/pm-plugins/utils/emptyTextBlocks.js +60 -0
- package/dist/esm/showDiffPluginType.js +85 -1
- package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +22 -14
- package/dist/esm/ui/ContributorTag/contributorTagIcons.js +4 -0
- package/dist/types/entry-points/show-diff-plugin-type.d.ts +2 -1
- package/dist/types/pm-plugins/calculateDiff/groupDeletedColumnChanges.d.ts +9 -0
- package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +2 -2
- package/dist/types/pm-plugins/decorations/createChangedRowDecorationWidgets.d.ts +3 -1
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +5 -1
- package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +3 -2
- package/dist/types/pm-plugins/decorations/utils/createDeletedLineBreakWidget.d.ts +50 -0
- package/dist/types/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +1 -1
- package/dist/types/pm-plugins/decorations/utils/getMarkChangeRanges.d.ts +1 -1
- package/dist/types/pm-plugins/decorations/utils/tableDiffMode.d.ts +26 -0
- package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +4 -0
- package/dist/types/pm-plugins/utils/emptyTextBlocks.d.ts +16 -0
- package/dist/types/showDiffPluginType.d.ts +6 -1
- package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
|
|
2
|
+
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
3
|
+
import { TableMap } from '@atlaskit/editor-tables/table-map';
|
|
4
|
+
export const CELL_CONTENT_OFFSET = 2;
|
|
5
|
+
const IGNORED_CELL_ATTRS = ['localId', 'colwidth'];
|
|
6
|
+
const tablesIn = doc => {
|
|
7
|
+
const tables = [];
|
|
8
|
+
doc.descendants((node, pos) => {
|
|
9
|
+
if (node.type.name === 'table') {
|
|
10
|
+
tables.push({
|
|
11
|
+
node,
|
|
12
|
+
pos
|
|
13
|
+
});
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
return tables;
|
|
19
|
+
};
|
|
20
|
+
const tableEnd = table => table.pos + table.node.nodeSize;
|
|
21
|
+
const documentsDifferOnlyByTable = (originalDoc, newDoc, before, after) => {
|
|
22
|
+
try {
|
|
23
|
+
return originalDoc.replace(before.pos, tableEnd(before), new Slice(Fragment.from(after.node), 0, 0)).eq(newDoc);
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const columnOffsets = (map, table) => Array.from({
|
|
29
|
+
length: map.width
|
|
30
|
+
}, (_, column) => map.positionAt(0, column, table));
|
|
31
|
+
const positionMatchedColumns = ({
|
|
32
|
+
before,
|
|
33
|
+
after,
|
|
34
|
+
afterToBefore,
|
|
35
|
+
beforeOffsets
|
|
36
|
+
}) => {
|
|
37
|
+
const afterMap = TableMap.get(after.node);
|
|
38
|
+
const surviving = new Set();
|
|
39
|
+
for (let column = 0; column < afterMap.width; column++) {
|
|
40
|
+
const offset = afterMap.positionAt(0, column, after.node);
|
|
41
|
+
const mapped = afterToBefore.mapResult(after.pos + CELL_CONTENT_OFFSET + offset);
|
|
42
|
+
const beforeColumn = beforeOffsets.indexOf(mapped.pos - before.pos - CELL_CONTENT_OFFSET);
|
|
43
|
+
if (mapped.deleted || beforeColumn === -1) {
|
|
44
|
+
return new Set();
|
|
45
|
+
}
|
|
46
|
+
surviving.add(beforeColumn);
|
|
47
|
+
}
|
|
48
|
+
return surviving;
|
|
49
|
+
};
|
|
50
|
+
const addContentMatchedColumns = (surviving, before, after) => {
|
|
51
|
+
var _before$node$firstChi, _before$node$firstChi2, _after$node$firstChil, _after$node$firstChil2;
|
|
52
|
+
const beforeCells = (_before$node$firstChi = (_before$node$firstChi2 = before.node.firstChild) === null || _before$node$firstChi2 === void 0 ? void 0 : _before$node$firstChi2.children) !== null && _before$node$firstChi !== void 0 ? _before$node$firstChi : [];
|
|
53
|
+
const afterCells = (_after$node$firstChil = (_after$node$firstChil2 = after.node.firstChild) === null || _after$node$firstChil2 === void 0 ? void 0 : _after$node$firstChil2.children) !== null && _after$node$firstChil !== void 0 ? _after$node$firstChil : [];
|
|
54
|
+
for (const afterCell of afterCells) {
|
|
55
|
+
const matches = beforeCells.flatMap((beforeCell, column) => areNodesEqualIgnoreAttrs(beforeCell, afterCell, IGNORED_CELL_ATTRS) ? [column] : []);
|
|
56
|
+
if (matches.length !== 1) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
surviving.add(matches[0]);
|
|
60
|
+
}
|
|
61
|
+
const ordered = [...surviving];
|
|
62
|
+
return !ordered.some((column, index) => index > 0 && column <= ordered[index - 1]);
|
|
63
|
+
};
|
|
64
|
+
const findDeletedColumns = (before, after, afterToBefore) => {
|
|
65
|
+
const beforeMap = TableMap.get(before.node);
|
|
66
|
+
const afterMap = TableMap.get(after.node);
|
|
67
|
+
// Merged or malformed tables do not have reliable column indices.
|
|
68
|
+
if (beforeMap.hasMergedCells() || afterMap.hasMergedCells() || beforeMap.problems || afterMap.problems) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const beforeOffsets = columnOffsets(beforeMap, before.node);
|
|
72
|
+
const surviving = positionMatchedColumns({
|
|
73
|
+
before,
|
|
74
|
+
after,
|
|
75
|
+
afterToBefore,
|
|
76
|
+
beforeOffsets
|
|
77
|
+
});
|
|
78
|
+
if (surviving.size !== afterMap.width && !addContentMatchedColumns(surviving, before, after)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return beforeOffsets.flatMap((_, column) => surviving.has(column) ? [] : [column]);
|
|
82
|
+
};
|
|
83
|
+
const findCounterpart = ({
|
|
84
|
+
table,
|
|
85
|
+
index,
|
|
86
|
+
originalTables,
|
|
87
|
+
newTables,
|
|
88
|
+
mapping
|
|
89
|
+
}) => {
|
|
90
|
+
const from = mapping.map(table.pos, -1);
|
|
91
|
+
const to = mapping.map(tableEnd(table), 1);
|
|
92
|
+
const inside = mapping.mapResult(table.pos + 1);
|
|
93
|
+
const candidates = newTables.filter(candidate => inside.deleted ? candidate.pos >= from && tableEnd(candidate) <= to : candidate.pos < inside.pos && inside.pos < tableEnd(candidate));
|
|
94
|
+
if (candidates.length === 1) {
|
|
95
|
+
return candidates[0];
|
|
96
|
+
}
|
|
97
|
+
return originalTables.length === newTables.length ? newTables[index] : undefined;
|
|
98
|
+
};
|
|
99
|
+
const orient = ({
|
|
100
|
+
table,
|
|
101
|
+
counterpart,
|
|
102
|
+
mapping,
|
|
103
|
+
isInverted
|
|
104
|
+
}) => ({
|
|
105
|
+
before: isInverted ? counterpart : table,
|
|
106
|
+
after: isInverted ? table : counterpart,
|
|
107
|
+
afterToBefore: (isInverted ? mapping.invert() : mapping).invert()
|
|
108
|
+
});
|
|
109
|
+
const changesInTable = (changes, table) => changes.filter(change => change.fromA < tableEnd(table) && change.toA > table.pos);
|
|
110
|
+
const isNarrowingSafe = ({
|
|
111
|
+
replacedChanges,
|
|
112
|
+
table,
|
|
113
|
+
counterpart,
|
|
114
|
+
originalDoc,
|
|
115
|
+
newDoc
|
|
116
|
+
}) => {
|
|
117
|
+
const staysInside = replacedChanges.every(change => change.fromA >= table.pos && change.toA <= tableEnd(table) && change.fromB >= counterpart.pos && change.toB <= tableEnd(counterpart));
|
|
118
|
+
return staysInside || documentsDifferOnlyByTable(originalDoc, newDoc, table, counterpart);
|
|
119
|
+
};
|
|
120
|
+
const planTableChange = ({
|
|
121
|
+
table,
|
|
122
|
+
index,
|
|
123
|
+
originalTables,
|
|
124
|
+
newTables,
|
|
125
|
+
changes,
|
|
126
|
+
originalDoc,
|
|
127
|
+
newDoc,
|
|
128
|
+
mapping,
|
|
129
|
+
isInverted
|
|
130
|
+
}) => {
|
|
131
|
+
const counterpart = findCounterpart({
|
|
132
|
+
table,
|
|
133
|
+
index,
|
|
134
|
+
originalTables,
|
|
135
|
+
newTables,
|
|
136
|
+
mapping
|
|
137
|
+
});
|
|
138
|
+
if (!counterpart) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const {
|
|
142
|
+
before,
|
|
143
|
+
after,
|
|
144
|
+
afterToBefore
|
|
145
|
+
} = orient({
|
|
146
|
+
table,
|
|
147
|
+
counterpart,
|
|
148
|
+
mapping,
|
|
149
|
+
isInverted
|
|
150
|
+
});
|
|
151
|
+
if (TableMap.get(before.node).width <= TableMap.get(after.node).width) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const replacedChanges = changesInTable(changes, table);
|
|
155
|
+
if (!replacedChanges.length) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (!isNarrowingSafe({
|
|
159
|
+
replacedChanges,
|
|
160
|
+
table,
|
|
161
|
+
counterpart,
|
|
162
|
+
originalDoc,
|
|
163
|
+
newDoc
|
|
164
|
+
})) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const spans = replacedChanges.flatMap(change => [...change.deleted, ...change.inserted]);
|
|
168
|
+
return {
|
|
169
|
+
replacedChanges,
|
|
170
|
+
tableChange: {
|
|
171
|
+
deletedColumns: findDeletedColumns(before, after, afterToBefore),
|
|
172
|
+
fromA: table.pos,
|
|
173
|
+
toA: tableEnd(table),
|
|
174
|
+
fromB: counterpart.pos,
|
|
175
|
+
toB: tableEnd(counterpart),
|
|
176
|
+
deleted: spans,
|
|
177
|
+
inserted: spans
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Groups a deleted column's cell changes into one whole-table change. */
|
|
183
|
+
export const groupDeletedColumnChanges = (changes, originalDoc, newDoc, mapping, isInverted) => {
|
|
184
|
+
const originalTables = tablesIn(originalDoc);
|
|
185
|
+
const newTables = tablesIn(newDoc);
|
|
186
|
+
const plans = [];
|
|
187
|
+
const replaced = new Set();
|
|
188
|
+
originalTables.forEach((table, index) => {
|
|
189
|
+
const plan = planTableChange({
|
|
190
|
+
table,
|
|
191
|
+
index,
|
|
192
|
+
originalTables,
|
|
193
|
+
newTables,
|
|
194
|
+
changes: changes.filter(change => !replaced.has(change)),
|
|
195
|
+
originalDoc,
|
|
196
|
+
newDoc,
|
|
197
|
+
mapping,
|
|
198
|
+
isInverted
|
|
199
|
+
});
|
|
200
|
+
if (!plan) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
plans.push(plan);
|
|
204
|
+
plan.replacedChanges.forEach(change => replaced.add(change));
|
|
205
|
+
});
|
|
206
|
+
if (!plans.length) {
|
|
207
|
+
return [...changes];
|
|
208
|
+
}
|
|
209
|
+
return [...changes.filter(change => !replaced.has(change)), ...plans.map(plan => plan.tableChange)].sort((left, right) => left.fromB - right.fromB);
|
|
210
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getParticipantColor } from '@atlaskit/editor-shared-styles/utils';
|
|
2
2
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
3
3
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
4
|
+
import { DIFF_AGENT_BRANDS } from '../../../showDiffPluginType';
|
|
4
5
|
import { PARTICIPANT_COLOR_SCHEMES } from './types';
|
|
5
6
|
|
|
6
7
|
/** A contributor with both of its identities collapsed to internal attribution keys. */
|
|
@@ -77,6 +78,14 @@ export const getAttributionKey = attribution => {
|
|
|
77
78
|
var _getAttributionIdenti;
|
|
78
79
|
return (_getAttributionIdenti = getAttributionIdentity(attribution)) === null || _getAttributionIdenti === void 0 ? void 0 : _getAttributionIdenti.key;
|
|
79
80
|
};
|
|
81
|
+
const isDiffAgentBrand = agentBrand => agentBrand !== undefined && DIFF_AGENT_BRANDS.has(agentBrand);
|
|
82
|
+
|
|
83
|
+
/** Prefers the resolved branded profile over the raw attribution agent type. */
|
|
84
|
+
const getAgentBrandForColor = (attribution, contributors) => {
|
|
85
|
+
var _contributors, _getAttributionKey;
|
|
86
|
+
const agentBrand = contributors === null || contributors === void 0 ? void 0 : (_contributors = contributors[(_getAttributionKey = getAttributionKey(attribution)) !== null && _getAttributionKey !== void 0 ? _getAttributionKey : '']) === null || _contributors === void 0 ? void 0 : _contributors.agentKind;
|
|
87
|
+
return isDiffAgentBrand(agentBrand) ? agentBrand : attribution === null || attribution === void 0 ? void 0 : attribution.agentType;
|
|
88
|
+
};
|
|
80
89
|
|
|
81
90
|
/**
|
|
82
91
|
* Public contributor list to internal key-addressed record. A contributor whose attribution carries
|
|
@@ -123,7 +132,7 @@ export const isAttributionColoringEnabled = stepAttributions => {
|
|
|
123
132
|
* a tag only has to name one.
|
|
124
133
|
*/
|
|
125
134
|
export const isContributorTagsEnabled = (stepAttributions, contributors) => areAttributionColorGatesEnabled() && Boolean(stepAttributions === null || stepAttributions === void 0 ? void 0 : stepAttributions.some(attribution => getAttributionKey(attribution) !== undefined)) && contributors !== undefined && Object.keys(contributors).length > 0;
|
|
126
|
-
export const createAttributionColorMap = stepAttributions => {
|
|
135
|
+
export const createAttributionColorMap = (stepAttributions, contributors) => {
|
|
127
136
|
const colors = new Map();
|
|
128
137
|
const allocatedColors = new Set();
|
|
129
138
|
|
|
@@ -137,7 +146,7 @@ export const createAttributionColorMap = stepAttributions => {
|
|
|
137
146
|
const {
|
|
138
147
|
index,
|
|
139
148
|
isFixed
|
|
140
|
-
} = getParticipantColor(identity.colorSeed, attribution
|
|
149
|
+
} = getParticipantColor(identity.colorSeed, getAgentBrandForColor(attribution, contributors));
|
|
141
150
|
const color = HASHED_PARTICIPANT_COLOR_SCHEMES[index];
|
|
142
151
|
if (isFixed && color) {
|
|
143
152
|
colors.set(identity.key, color);
|
|
@@ -151,7 +160,7 @@ export const createAttributionColorMap = stepAttributions => {
|
|
|
151
160
|
}
|
|
152
161
|
const {
|
|
153
162
|
index
|
|
154
|
-
} = getParticipantColor(identity.colorSeed, attribution
|
|
163
|
+
} = getParticipantColor(identity.colorSeed, getAgentBrandForColor(attribution, contributors));
|
|
155
164
|
const hashedColor = getSlotColor(index);
|
|
156
165
|
if (!hashedColor) {
|
|
157
166
|
continue;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { agentBrandColorSchemes } from '@atlaskit/agent-color/agent-brand-color-schemes';
|
|
2
2
|
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
3
3
|
import { getStandardDeletedTextDecorationStyle } from './getStandardDeletedTextDecorationStyle';
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* Hover highlight for deleted text under an 'onEmphasis' scheme. Inline styles cannot express
|
|
7
6
|
* `:hover`, so the resting style resolves its background from this property and
|
|
@@ -8,9 +8,9 @@ import { isTaggableBlockNode } from '../utils/taggableBlockNodes';
|
|
|
8
8
|
import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
|
|
9
9
|
import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
|
|
10
10
|
import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
|
|
11
|
+
import { getBlockNodeStyleLegacy, resolveCellOverlayStyleLegacy } from './createBlockChangedDecoration.styles.legacy';
|
|
11
12
|
import { createContributorTagWidget, isContributorTagWidgetEnabled } from './createContributorTagWidget';
|
|
12
13
|
import { AnchorTypeKey, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopStyle } from './decorationKeys';
|
|
13
|
-
import { getBlockNodeStyleLegacy, resolveCellOverlayStyleLegacy } from './createBlockChangedDecoration.styles.legacy';
|
|
14
14
|
const displayNoneStyle = convertToInlineCss({
|
|
15
15
|
display: 'none'
|
|
16
16
|
});
|
|
@@ -14,6 +14,7 @@ import { createContributorTagWidget, isContributorTagWidgetEnabled } from './cre
|
|
|
14
14
|
import { AnchorTypeKey, buildAnchorDecorationKey, buildDiffDecorationSpec, scrollMarginTopValue } from './decorationKeys';
|
|
15
15
|
import { findSafeInsertPos } from './utils/findSafeInsertPos';
|
|
16
16
|
import { applyCellEdgeAttrs, getRowCellEdgeAttrs, getTableTopAndBottomCellEdgeAttrs } from './utils/tableCellEdgeAttrs';
|
|
17
|
+
import { createRemovedLozenge } from './utils/wrapBlockNodeView';
|
|
17
18
|
/**
|
|
18
19
|
* Extracts information about deleted table rows from a change
|
|
19
20
|
*/
|
|
@@ -144,17 +145,28 @@ const isEmptyRow = rowNode => {
|
|
|
144
145
|
});
|
|
145
146
|
return isEmpty;
|
|
146
147
|
};
|
|
147
|
-
|
|
148
148
|
/**
|
|
149
149
|
* Creates a DOM representation of a deleted table row
|
|
150
150
|
*/
|
|
151
|
-
const createChangedRowDOM = (
|
|
151
|
+
const createChangedRowDOM = ({
|
|
152
|
+
rowNode,
|
|
153
|
+
cellEdgeAttrs,
|
|
154
|
+
nodeViewSerializer,
|
|
155
|
+
colorScheme,
|
|
156
|
+
isInserted,
|
|
157
|
+
diffType,
|
|
158
|
+
hasAnchoredRemovedLozenge,
|
|
159
|
+
intl,
|
|
160
|
+
isActive
|
|
161
|
+
}) => {
|
|
152
162
|
const tr = document.createElement('tr');
|
|
153
163
|
const colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard'];
|
|
164
|
+
const deletedTreatment = isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? buildDeletedRowStyle(colors) : resolveDeletedRowStyleLegacy(getLegacyColorScheme(colorScheme));
|
|
165
|
+
const hostsRemovedLozenge = Boolean(intl) && isExtendedEnabled(diffType) && !isInserted && !hasAnchoredRemovedLozenge;
|
|
154
166
|
|
|
155
167
|
// Inserted rows keep their natural styling; the row strikethrough is deletions only.
|
|
156
168
|
if (!isExtendedEnabled(diffType) || !isInserted) {
|
|
157
|
-
tr.setAttribute('style',
|
|
169
|
+
tr.setAttribute('style', deletedTreatment);
|
|
158
170
|
}
|
|
159
171
|
// Mirrors the strikethrough condition above: under the extended experience an `isInserted` row
|
|
160
172
|
// is ADDED content, so it must not claim the "deleted" testid — page models match that as
|
|
@@ -195,8 +207,40 @@ const createChangedRowDOM = (rowNode, cellEdgeAttrs, nodeViewSerializer, colorSc
|
|
|
195
207
|
cellIndex++;
|
|
196
208
|
}
|
|
197
209
|
});
|
|
210
|
+
|
|
211
|
+
// A "Removed" label on the row's last cell, so it reads at the row's top right corner. Deletions
|
|
212
|
+
// only: an inserted row is added content and carries no removal label.
|
|
213
|
+
if (intl && hostsRemovedLozenge) {
|
|
214
|
+
const lastCell = tr.lastElementChild;
|
|
215
|
+
if (lastCell instanceof HTMLElement) {
|
|
216
|
+
lastCell.style.position = 'relative';
|
|
217
|
+
// Append, never prepend. Editor CSS resets the top margin of a cell's first child, so a
|
|
218
|
+
// prepended label pushes the paragraph out of that reset and the row grows taller.
|
|
219
|
+
lastCell.append(createRemovedLozenge(intl, isActive, colorScheme, true));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
198
222
|
return tr;
|
|
199
223
|
};
|
|
224
|
+
const supportsAnchorPositioning = () => typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('top', 'anchor(--a top)');
|
|
225
|
+
const createAnchoredRemovedLozengeWidget = ({
|
|
226
|
+
anchorName,
|
|
227
|
+
colorScheme,
|
|
228
|
+
from,
|
|
229
|
+
intl,
|
|
230
|
+
isActive
|
|
231
|
+
}) => {
|
|
232
|
+
const lozenge = createRemovedLozenge(intl, isActive, colorScheme, true);
|
|
233
|
+
const inset = lozenge.style.top;
|
|
234
|
+
lozenge.style.setProperty('position', 'fixed');
|
|
235
|
+
lozenge.style.setProperty('top', `calc(anchor(--${anchorName} top) + ${inset})`);
|
|
236
|
+
lozenge.style.setProperty('left', `calc(anchor(--${anchorName} right) - ${inset})`);
|
|
237
|
+
lozenge.style.setProperty('right', 'auto');
|
|
238
|
+
lozenge.style.setProperty('transform', 'translateX(-100%)');
|
|
239
|
+
return Decoration.widget(from, lozenge, {
|
|
240
|
+
key: `removed-lozenge-${anchorName}`,
|
|
241
|
+
side: -1
|
|
242
|
+
});
|
|
243
|
+
};
|
|
200
244
|
|
|
201
245
|
/**
|
|
202
246
|
* Expands a diff to include whole changed rows when table rows are affected
|
|
@@ -236,6 +280,7 @@ export const createChangedRowDecorationWidgets = ({
|
|
|
236
280
|
isActive,
|
|
237
281
|
isInserted = false,
|
|
238
282
|
diffType,
|
|
283
|
+
intl,
|
|
239
284
|
showIndicators = false,
|
|
240
285
|
showContributorTags = false,
|
|
241
286
|
tagMountContext
|
|
@@ -248,16 +293,25 @@ export const createChangedRowDecorationWidgets = ({
|
|
|
248
293
|
diffType
|
|
249
294
|
});
|
|
250
295
|
return changedRows.flatMap(changedRow => {
|
|
251
|
-
|
|
296
|
+
// Keep the ID stable while contributor tags are enabled so recalculation preserves their state.
|
|
297
|
+
const diffId = showContributorTags ? `widget-row-${changedRow.fromA}-${changedRow.toA}` : crypto.randomUUID();
|
|
298
|
+
const hasAnchoredRemovedLozenge = Boolean(intl) && isExtendedEnabled(diffType) && !isInserted && supportsAnchorPositioning();
|
|
299
|
+
const rowDOM = createChangedRowDOM({
|
|
300
|
+
rowNode: changedRow.rowNode,
|
|
301
|
+
cellEdgeAttrs: changedRow.cellEdgeAttrs,
|
|
302
|
+
nodeViewSerializer,
|
|
303
|
+
colorScheme,
|
|
304
|
+
isInserted,
|
|
305
|
+
diffType,
|
|
306
|
+
hasAnchoredRemovedLozenge,
|
|
307
|
+
intl,
|
|
308
|
+
isActive
|
|
309
|
+
});
|
|
252
310
|
|
|
253
311
|
// Find safe insertion position for the deleted row
|
|
254
312
|
const safeInsertPos = findSafeInsertPos(newDoc, changedRow.fromB - 1,
|
|
255
313
|
// -1 to find the first safe position from the table
|
|
256
314
|
originalDoc.slice(changedRow.fromA, changedRow.toA));
|
|
257
|
-
|
|
258
|
-
// Stable while contributor tags are enabled so a decoration recalculation preserves the tag's
|
|
259
|
-
// identity and controller state.
|
|
260
|
-
const diffId = showContributorTags ? `widget-row-${changedRow.fromA}-${changedRow.toA}` : crypto.randomUUID();
|
|
261
315
|
const decorations = [];
|
|
262
316
|
const rowAnchorNames = [];
|
|
263
317
|
|
|
@@ -286,6 +340,11 @@ export const createChangedRowDecorationWidgets = ({
|
|
|
286
340
|
diffId,
|
|
287
341
|
anchorType: AnchorTypeKey.tag
|
|
288
342
|
}) : undefined;
|
|
343
|
+
const removedLozengeAnchorName = hasAnchoredRemovedLozenge ? tagAnchorName !== null && tagAnchorName !== void 0 ? tagAnchorName : buildAnchorDecorationKey({
|
|
344
|
+
diffId,
|
|
345
|
+
anchorType: AnchorTypeKey.tag
|
|
346
|
+
}) : undefined;
|
|
347
|
+
const rowLabelAnchorName = tagAnchorName !== null && tagAnchorName !== void 0 ? tagAnchorName : removedLozengeAnchorName;
|
|
289
348
|
const tagWidget = tagAnchorName ? createContributorTagWidget({
|
|
290
349
|
anchorAtRangeStart: true,
|
|
291
350
|
anchorName: tagAnchorName,
|
|
@@ -295,11 +354,9 @@ export const createChangedRowDecorationWidgets = ({
|
|
|
295
354
|
mountContext: tagMountContext,
|
|
296
355
|
to: safeInsertPos
|
|
297
356
|
}) : undefined;
|
|
298
|
-
if (
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
// opacity and strikethrough.
|
|
302
|
-
rowAnchorNames.push(tagAnchorName);
|
|
357
|
+
if (rowLabelAnchorName) {
|
|
358
|
+
// Labels stay outside the table row so they do not inherit its deletion treatment.
|
|
359
|
+
rowAnchorNames.push(rowLabelAnchorName);
|
|
303
360
|
}
|
|
304
361
|
if (rowAnchorNames.length > 0) {
|
|
305
362
|
rowDOM.style.setProperty('anchor-name', rowAnchorNames.map(anchorName => `--${anchorName}`).join(', '));
|
|
@@ -320,6 +377,15 @@ export const createChangedRowDecorationWidgets = ({
|
|
|
320
377
|
})
|
|
321
378
|
});
|
|
322
379
|
decorations.push(rowWidget);
|
|
380
|
+
if (intl && removedLozengeAnchorName) {
|
|
381
|
+
decorations.push(createAnchoredRemovedLozengeWidget({
|
|
382
|
+
anchorName: removedLozengeAnchorName,
|
|
383
|
+
colorScheme,
|
|
384
|
+
from: safeInsertPos,
|
|
385
|
+
intl,
|
|
386
|
+
isActive
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
323
389
|
if (tagWidget) {
|
|
324
390
|
decorations.push(tagWidget);
|
|
325
391
|
}
|
|
@@ -88,14 +88,20 @@ const supportsAnchorPositioning = () => typeof CSS !== 'undefined' && typeof CSS
|
|
|
88
88
|
* The host for a tag on deleted content, which renders inside its own widget decoration rather than
|
|
89
89
|
* needing one of its own. Unmount with `unmountContributorTag` from that widget's `destroy`.
|
|
90
90
|
*/
|
|
91
|
-
export const createContributorTagHost = (
|
|
91
|
+
export const createContributorTagHost = ({
|
|
92
|
+
anchorName,
|
|
93
|
+
diffId,
|
|
94
|
+
mountContext
|
|
95
|
+
}) => {
|
|
92
96
|
if (!isContributorTagWidgetEnabled()) {
|
|
93
97
|
return undefined;
|
|
94
98
|
}
|
|
95
|
-
const
|
|
99
|
+
const tagAnchorName = anchorName && supportsAnchorPositioning() ? anchorName : undefined;
|
|
100
|
+
const host = buildContributorTagHost(diffId, tagAnchorName);
|
|
96
101
|
return {
|
|
97
102
|
host,
|
|
98
103
|
mount: mountContributorTag({
|
|
104
|
+
anchorName: tagAnchorName,
|
|
99
105
|
diffId,
|
|
100
106
|
host,
|
|
101
107
|
mountContext
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
3
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
4
|
+
import { countEmptyTextBlockOnlySlice } from '../utils/emptyTextBlocks';
|
|
4
5
|
import { isEmptyParagraphSlice } from '../utils/isEmptyParagraphSlice';
|
|
5
6
|
import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
|
|
6
7
|
import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
|
|
7
8
|
import { createContributorTagHost, unmountContributorTag } from './createContributorTagWidget';
|
|
8
|
-
import { buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
|
|
9
|
+
import { AnchorTypeKey, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
|
|
9
10
|
import { absorbFirstChildMarginReset } from './utils/absorbFirstChildMarginReset';
|
|
11
|
+
import { createDeletedLineBreakDecoration } from './utils/createDeletedLineBreakWidget';
|
|
10
12
|
import { createMarginAbsorber } from './utils/createMarginAbsorber';
|
|
11
13
|
import { createNodeShapedMarginSpacer } from './utils/createNodeShapedMarginSpacer';
|
|
12
14
|
import { findSafeInsertPos } from './utils/findSafeInsertPos';
|
|
13
15
|
import { safeResolve } from './utils/safeResolve';
|
|
14
|
-
import {
|
|
16
|
+
import { getTableDiffMode } from './utils/tableDiffMode';
|
|
17
|
+
import { wrapBlockNodeView, createRemovedLozenge, injectInnerWrapper, createContentWrapper } from './utils/wrapBlockNodeView';
|
|
15
18
|
const isHeadingLevel = level => typeof level === 'number' && level >= 1 && level <= 6;
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -119,11 +122,20 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
119
122
|
diffType,
|
|
120
123
|
hideAddedDiffsUnderline = false,
|
|
121
124
|
reveal,
|
|
122
|
-
tagMountContext
|
|
125
|
+
tagMountContext,
|
|
126
|
+
deletedColumns
|
|
123
127
|
}) => {
|
|
124
128
|
var _$safeInsertPos$nodeB, _slice$content$firstC, _newDoc$firstChild, _slice$content$firstC2;
|
|
125
129
|
const slice = doc.slice(change.fromA, change.toA);
|
|
126
130
|
const shouldSkipDeletedEmptyParagraphDecoration = !isInserted && isEmptyParagraphSlice(slice);
|
|
131
|
+
// How many blank lines the deletion took, when it took nothing else. Blank means an empty
|
|
132
|
+
// paragraph or heading — both are a line the author put there deliberately, and neither leaves
|
|
133
|
+
// the widget any content to serialize.
|
|
134
|
+
//
|
|
135
|
+
// Kept separate from `shouldSkipDeletedEmptyParagraphDecoration` and gated, so the ungated early
|
|
136
|
+
// return below still covers only the single-empty-paragraph case it always did. Everything else
|
|
137
|
+
// degrades to what it rendered before the gate: an empty block, serialized invisibly.
|
|
138
|
+
const deletedBlankLineCount = !isInserted && fg('platform_editor_ai_show_diff_patch_2') ? countEmptyTextBlockOnlySlice(slice) : 0;
|
|
127
139
|
// Widget decoration used for deletions as the content is not in the document
|
|
128
140
|
// and we want to display the deleted content with a style.
|
|
129
141
|
// For `placeBelow`, anchor at the END of the new content (change.toB) so the deleted
|
|
@@ -131,16 +143,33 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
131
143
|
const anchorPos = placeBelow ? change.toB : change.fromB;
|
|
132
144
|
const safeInsertPos = findSafeInsertPos(newDoc, anchorPos, slice);
|
|
133
145
|
const isActive = activeIndexPos && (fg('confluence_ncs_step_diffing_version_history') ? safeInsertPos >= activeIndexPos.from && safeInsertPos <= activeIndexPos.to : safeInsertPos === activeIndexPos.from && safeInsertPos === activeIndexPos.to);
|
|
134
|
-
if (
|
|
146
|
+
if (deletedBlankLineCount > 0) {
|
|
147
|
+
// The blocks have no content to serialize, so the deletion is shown as one struck-through
|
|
148
|
+
// return glyph per line the author removed. A single widget, not one per line: the lines are
|
|
149
|
+
// gone from `newDoc`, so they all resolve to the same anchor and separate widgets would stack.
|
|
150
|
+
//
|
|
151
|
+
// No attribution: the glyph is not a tag host, matching `canTagWidget` below.
|
|
152
|
+
return [createDeletedLineBreakDecoration({
|
|
153
|
+
colorScheme,
|
|
154
|
+
count: deletedBlankLineCount,
|
|
155
|
+
diffType,
|
|
156
|
+
isActive,
|
|
157
|
+
pos: safeInsertPos,
|
|
158
|
+
reveal,
|
|
159
|
+
side: placeBelow ? 1 : -1
|
|
160
|
+
})];
|
|
161
|
+
}
|
|
162
|
+
if (shouldSkipDeletedEmptyParagraphDecoration) {
|
|
135
163
|
return [];
|
|
136
164
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
165
|
+
if (slice.content.content.length === 0) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const tableDiffMode = getTableDiffMode({
|
|
169
|
+
slice,
|
|
170
|
+
isInserted
|
|
171
|
+
});
|
|
172
|
+
if (tableDiffMode.kind === 'cells') {
|
|
144
173
|
return createTableCellContentWidgets({
|
|
145
174
|
slice,
|
|
146
175
|
newDoc,
|
|
@@ -151,11 +180,10 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
151
180
|
diffType
|
|
152
181
|
});
|
|
153
182
|
}
|
|
154
|
-
if (
|
|
155
|
-
// Deleted whole cells: nothing to render (see note above).
|
|
183
|
+
if (tableDiffMode.kind === 'none') {
|
|
156
184
|
return [];
|
|
157
185
|
}
|
|
158
|
-
if (
|
|
186
|
+
if (tableDiffMode.kind === 'rows') {
|
|
159
187
|
return createChangedRowDecorationWidgets({
|
|
160
188
|
attributionKey,
|
|
161
189
|
changes: [change],
|
|
@@ -166,6 +194,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
166
194
|
isActive,
|
|
167
195
|
isInserted,
|
|
168
196
|
diffType,
|
|
197
|
+
intl,
|
|
169
198
|
// Needed for the row's own indicator anchor; this path returns before the
|
|
170
199
|
// `anchor-name` assignment further down.
|
|
171
200
|
showIndicators,
|
|
@@ -173,6 +202,9 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
173
202
|
tagMountContext
|
|
174
203
|
});
|
|
175
204
|
}
|
|
205
|
+
// `wholeTable` and `generic` both continue into the generic block widget below. Splitting the
|
|
206
|
+
// whole-table case into its own renderer is the next step of this refactor.
|
|
207
|
+
|
|
176
208
|
const serializer = nodeViewSerializer;
|
|
177
209
|
|
|
178
210
|
// For non-table content, use the existing span wrapper approach
|
|
@@ -368,6 +400,40 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
368
400
|
}
|
|
369
401
|
}
|
|
370
402
|
});
|
|
403
|
+
let contributorTagAnchorName;
|
|
404
|
+
if (!isInserted && deletedColumns !== null && deletedColumns !== void 0 && deletedColumns.length) {
|
|
405
|
+
const table = dom.querySelector('table');
|
|
406
|
+
const firstRow = table === null || table === void 0 ? void 0 : table.rows[0];
|
|
407
|
+
if (table && canTagWidget) {
|
|
408
|
+
contributorTagAnchorName = buildAnchorDecorationKey({
|
|
409
|
+
diffId,
|
|
410
|
+
anchorType: AnchorTypeKey.tag
|
|
411
|
+
});
|
|
412
|
+
// The widget host sits before the table's preserved block margin. Anchor the tag to the
|
|
413
|
+
// table itself so its bottom edge meets the table's top edge with no visual gap.
|
|
414
|
+
table.style.setProperty('anchor-name', `--${contributorTagAnchorName}`);
|
|
415
|
+
}
|
|
416
|
+
deletedColumns.forEach(column => {
|
|
417
|
+
var _table$rows;
|
|
418
|
+
Array.from((_table$rows = table === null || table === void 0 ? void 0 : table.rows) !== null && _table$rows !== void 0 ? _table$rows : []).forEach(row => {
|
|
419
|
+
var _cell$children;
|
|
420
|
+
const cell = row.cells[column];
|
|
421
|
+
Array.from((_cell$children = cell === null || cell === void 0 ? void 0 : cell.children) !== null && _cell$children !== void 0 ? _cell$children : []).forEach(child => {
|
|
422
|
+
if (child instanceof HTMLElement) {
|
|
423
|
+
child.style.setProperty('text-decoration-line', 'line-through');
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
const headerCell = firstRow === null || firstRow === void 0 ? void 0 : firstRow.cells[column];
|
|
428
|
+
if (headerCell) {
|
|
429
|
+
headerCell.style.position = 'relative';
|
|
430
|
+
// Append, never prepend. Editor CSS resets the top margin of a cell's first child, so
|
|
431
|
+
// a prepended label pushes the paragraph out of that reset and the cell grows.
|
|
432
|
+
// The label is absolutely positioned, so DOM order does not move it.
|
|
433
|
+
headerCell.append(createRemovedLozenge(intl, isActive, colorScheme, true));
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
371
437
|
dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
|
|
372
438
|
if (fg('platform_editor_ai_show_diff_patch_1')) {
|
|
373
439
|
dom.style.setProperty('scroll-margin-top', scrollMarginTopValue);
|
|
@@ -405,7 +471,11 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
405
471
|
|
|
406
472
|
// Hosted at the start of the deleted run rather than on `dom` itself: `dom` is inline, so once
|
|
407
473
|
// the deleted content wraps its box is the union of its line fragments.
|
|
408
|
-
const tagHost = createContributorTagHost(
|
|
474
|
+
const tagHost = createContributorTagHost({
|
|
475
|
+
anchorName: contributorTagAnchorName,
|
|
476
|
+
diffId,
|
|
477
|
+
mountContext: tagMountContext
|
|
478
|
+
});
|
|
409
479
|
if (tagHost) {
|
|
410
480
|
dom.prepend(tagHost.host);
|
|
411
481
|
tagMount = tagHost.mount;
|