@atlaskit/editor-plugin-show-diff 3.1.5 → 3.2.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 +19 -0
- package/afm-cc/tsconfig.json +6 -0
- package/afm-jira/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/pm-plugins/calculateDiffDecorations.js +12 -7
- package/dist/cjs/pm-plugins/decorations.js +242 -10
- package/dist/cjs/pm-plugins/deletedRowsHandler.js +214 -0
- package/dist/cjs/pm-plugins/main.js +6 -5
- package/dist/cjs/showDiffPlugin.js +4 -3
- package/dist/es2019/pm-plugins/calculateDiffDecorations.js +12 -7
- package/dist/es2019/pm-plugins/decorations.js +238 -9
- package/dist/es2019/pm-plugins/deletedRowsHandler.js +185 -0
- package/dist/es2019/pm-plugins/main.js +4 -3
- package/dist/es2019/showDiffPlugin.js +4 -2
- package/dist/esm/pm-plugins/calculateDiffDecorations.js +12 -7
- package/dist/esm/pm-plugins/decorations.js +242 -10
- package/dist/esm/pm-plugins/deletedRowsHandler.js +207 -0
- package/dist/esm/pm-plugins/main.js +4 -3
- package/dist/esm/showDiffPlugin.js +4 -3
- package/dist/types/pm-plugins/calculateDiffDecorations.d.ts +3 -1
- package/dist/types/pm-plugins/decorations.d.ts +4 -2
- package/dist/types/pm-plugins/deletedRowsHandler.d.ts +30 -0
- package/dist/types/pm-plugins/main.d.ts +2 -1
- package/dist/types-ts4.5/pm-plugins/calculateDiffDecorations.d.ts +3 -1
- package/dist/types-ts4.5/pm-plugins/decorations.d.ts +4 -2
- package/dist/types-ts4.5/pm-plugins/deletedRowsHandler.d.ts +30 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -1
- package/package.json +13 -3
- package/afm-post-office/tsconfig.json +0 -27
- package/afm-townsquare/tsconfig.json +0 -27
|
@@ -8,15 +8,15 @@ exports.showDiffPluginKey = exports.createPlugin = void 0;
|
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _processRawValue = require("@atlaskit/editor-common/process-raw-value");
|
|
10
10
|
var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
|
|
11
|
-
var
|
|
11
|
+
var _state2 = require("@atlaskit/editor-prosemirror/state");
|
|
12
12
|
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
13
13
|
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
14
14
|
var _calculateDiffDecorations = require("./calculateDiffDecorations");
|
|
15
15
|
var _NodeViewSerializer = require("./NodeViewSerializer");
|
|
16
16
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
17
17
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
18
|
-
var showDiffPluginKey = exports.showDiffPluginKey = new
|
|
19
|
-
var createPlugin = exports.createPlugin = function createPlugin(config) {
|
|
18
|
+
var showDiffPluginKey = exports.showDiffPluginKey = new _state2.PluginKey('showDiffPlugin');
|
|
19
|
+
var createPlugin = exports.createPlugin = function createPlugin(config, getIntl) {
|
|
20
20
|
var nodeViewSerializer = new _NodeViewSerializer.NodeViewSerializer({});
|
|
21
21
|
var setNodeViewSerializer = function setNodeViewSerializer(editorView) {
|
|
22
22
|
nodeViewSerializer.init({
|
|
@@ -26,7 +26,7 @@ var createPlugin = exports.createPlugin = function createPlugin(config) {
|
|
|
26
26
|
return new _safePlugin.SafePlugin({
|
|
27
27
|
key: showDiffPluginKey,
|
|
28
28
|
state: {
|
|
29
|
-
init: function init(_,
|
|
29
|
+
init: function init(_, _state) {
|
|
30
30
|
// We do initial setup after we setup the editor view
|
|
31
31
|
return {
|
|
32
32
|
steps: [],
|
|
@@ -49,7 +49,8 @@ var createPlugin = exports.createPlugin = function createPlugin(config) {
|
|
|
49
49
|
state: newState,
|
|
50
50
|
pluginState: newPluginState,
|
|
51
51
|
nodeViewSerializer: nodeViewSerializer,
|
|
52
|
-
colourScheme: config === null || config === void 0 ? void 0 : config.colourScheme
|
|
52
|
+
colourScheme: config === null || config === void 0 ? void 0 : config.colourScheme,
|
|
53
|
+
intl: getIntl()
|
|
53
54
|
});
|
|
54
55
|
// Update the decorations
|
|
55
56
|
newPluginState.decorations = decorations;
|
|
@@ -10,7 +10,7 @@ var _main = require("./pm-plugins/main");
|
|
|
10
10
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
11
11
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
12
12
|
var showDiffPlugin = exports.showDiffPlugin = function showDiffPlugin(_ref) {
|
|
13
|
-
var
|
|
13
|
+
var _api = _ref.api,
|
|
14
14
|
config = _ref.config;
|
|
15
15
|
return {
|
|
16
16
|
name: 'showDiff',
|
|
@@ -34,8 +34,9 @@ var showDiffPlugin = exports.showDiffPlugin = function showDiffPlugin(_ref) {
|
|
|
34
34
|
pmPlugins: function pmPlugins() {
|
|
35
35
|
return [{
|
|
36
36
|
name: 'showDiffPlugin',
|
|
37
|
-
plugin: function plugin() {
|
|
38
|
-
|
|
37
|
+
plugin: function plugin(_ref4) {
|
|
38
|
+
var getIntl = _ref4.getIntl;
|
|
39
|
+
return (0, _main.createPlugin)(config, getIntl);
|
|
39
40
|
}
|
|
40
41
|
}];
|
|
41
42
|
},
|
|
@@ -83,7 +83,8 @@ const calculateDiffDecorationsInner = ({
|
|
|
83
83
|
state,
|
|
84
84
|
pluginState,
|
|
85
85
|
nodeViewSerializer,
|
|
86
|
-
colourScheme
|
|
86
|
+
colourScheme,
|
|
87
|
+
intl
|
|
87
88
|
}) => {
|
|
88
89
|
const {
|
|
89
90
|
originalDoc,
|
|
@@ -98,11 +99,13 @@ const calculateDiffDecorationsInner = ({
|
|
|
98
99
|
} = state;
|
|
99
100
|
let steppedDoc = originalDoc;
|
|
100
101
|
const stepMaps = [];
|
|
102
|
+
let changeset = ChangeSet.create(originalDoc);
|
|
101
103
|
for (const step of steps) {
|
|
102
104
|
const result = step.apply(steppedDoc);
|
|
103
105
|
if (result.failed === null && result.doc) {
|
|
104
106
|
steppedDoc = result.doc;
|
|
105
107
|
stepMaps.push(step.getMap());
|
|
108
|
+
changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
|
|
@@ -111,7 +114,6 @@ const calculateDiffDecorationsInner = ({
|
|
|
111
114
|
if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
|
|
112
115
|
return DecorationSet.empty;
|
|
113
116
|
}
|
|
114
|
-
const changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
|
|
115
117
|
const changes = simplifyChanges(changeset.changes, tr.doc);
|
|
116
118
|
const optimizedChanges = optimizeChanges(changes);
|
|
117
119
|
const decorations = [];
|
|
@@ -126,10 +128,11 @@ const calculateDiffDecorationsInner = ({
|
|
|
126
128
|
doc: originalDoc,
|
|
127
129
|
nodeViewSerializer,
|
|
128
130
|
colourScheme,
|
|
129
|
-
newDoc: tr.doc
|
|
131
|
+
newDoc: tr.doc,
|
|
132
|
+
intl
|
|
130
133
|
});
|
|
131
134
|
if (decoration) {
|
|
132
|
-
decorations.push(decoration);
|
|
135
|
+
decorations.push(...decoration);
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
});
|
|
@@ -146,13 +149,15 @@ export const calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner
|
|
|
146
149
|
([{
|
|
147
150
|
pluginState,
|
|
148
151
|
state,
|
|
149
|
-
colourScheme
|
|
152
|
+
colourScheme,
|
|
153
|
+
intl
|
|
150
154
|
}], [{
|
|
151
155
|
pluginState: lastPluginState,
|
|
152
156
|
state: lastState,
|
|
153
|
-
colourScheme: lastColourScheme
|
|
157
|
+
colourScheme: lastColourScheme,
|
|
158
|
+
intl: lastIntl
|
|
154
159
|
}]) => {
|
|
155
160
|
var _ref;
|
|
156
161
|
const originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
|
|
157
|
-
return (_ref = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && colourScheme === lastColourScheme) !== null && _ref !== void 0 ? _ref : false;
|
|
162
|
+
return (_ref = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && colourScheme === lastColourScheme && intl.locale === lastIntl.locale) !== null && _ref !== void 0 ? _ref : false;
|
|
158
163
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
|
+
import { trackChangesMessages } from '@atlaskit/editor-common/messages';
|
|
2
3
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { handleDeletedRows } from './deletedRowsHandler';
|
|
3
6
|
import { findSafeInsertPos } from './findSafeInsertPos';
|
|
4
7
|
const editingStyle = convertToInlineCss({
|
|
5
8
|
background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
|
|
@@ -68,6 +71,59 @@ const getEditorStyleNode = (nodeName, colourScheme) => {
|
|
|
68
71
|
return colourScheme === 'traditional' ? traditionalStyleNode : editingStyleNode;
|
|
69
72
|
}
|
|
70
73
|
};
|
|
74
|
+
const getDeletedStyleNode = nodeName => {
|
|
75
|
+
switch (nodeName) {
|
|
76
|
+
case 'blockquote':
|
|
77
|
+
return deletedStyleQuoteNode;
|
|
78
|
+
case 'expand':
|
|
79
|
+
case 'decisionList':
|
|
80
|
+
return deletedBlockOutline;
|
|
81
|
+
case 'panel':
|
|
82
|
+
case 'codeBlock':
|
|
83
|
+
return deletedBlockOutlineRounded;
|
|
84
|
+
default:
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const shouldShowRemovedLozenge = nodeName => {
|
|
89
|
+
switch (nodeName) {
|
|
90
|
+
case 'expand':
|
|
91
|
+
case 'codeBlock':
|
|
92
|
+
case 'mediaSingle':
|
|
93
|
+
case 'panel':
|
|
94
|
+
case 'decisionList':
|
|
95
|
+
return true;
|
|
96
|
+
default:
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const shouldFitContentWidth = nodeName => {
|
|
101
|
+
switch (nodeName) {
|
|
102
|
+
case 'mediaSingle':
|
|
103
|
+
case 'embedCard':
|
|
104
|
+
case 'blockCard':
|
|
105
|
+
return true;
|
|
106
|
+
default:
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const shouldAddShowDiffDeletedNodeClass = nodeName => {
|
|
111
|
+
switch (nodeName) {
|
|
112
|
+
case 'mediaSingle':
|
|
113
|
+
case 'embedCard':
|
|
114
|
+
return true;
|
|
115
|
+
default:
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Checks if a node should apply deleted styles directly without wrapper
|
|
122
|
+
* to preserve natural block-level margins
|
|
123
|
+
*/
|
|
124
|
+
const shouldApplyDeletedStylesDirectly = nodeName => {
|
|
125
|
+
return nodeName === 'blockquote' || nodeName === 'heading';
|
|
126
|
+
};
|
|
71
127
|
const editingStyleQuoteNode = convertToInlineCss({
|
|
72
128
|
borderLeft: `2px solid ${"var(--ds-border-accent-purple, #8270DB)"}`
|
|
73
129
|
});
|
|
@@ -96,6 +152,18 @@ const traditionalStyleCardBlockNode = convertToInlineCss({
|
|
|
96
152
|
boxShadow: `0 0 0 1px ${"var(--ds-border-accent-green, #22A06B)"}`,
|
|
97
153
|
borderRadius: "var(--ds-radius-medium, 6px)"
|
|
98
154
|
});
|
|
155
|
+
const deletedStyleQuoteNode = convertToInlineCss({
|
|
156
|
+
borderLeft: `2px solid ${"var(--ds-border-accent-gray, #758195)"}`
|
|
157
|
+
});
|
|
158
|
+
const deletedBlockOutline = convertToInlineCss({
|
|
159
|
+
boxShadow: `0 0 0 1px ${"var(--ds-border-accent-gray, #758195)"}`,
|
|
160
|
+
borderRadius: "var(--ds-radius-small, 4px)"
|
|
161
|
+
});
|
|
162
|
+
const deletedBlockOutlineRounded = convertToInlineCss({
|
|
163
|
+
boxShadow: `0 0 0 1px ${"var(--ds-border-accent-gray, #758195)"}`,
|
|
164
|
+
borderRadius: `calc(${"var(--ds-radius-xsmall, 2px)"} + 1px)`
|
|
165
|
+
});
|
|
166
|
+
|
|
99
167
|
/**
|
|
100
168
|
* Inline decoration used for insertions as the content already exists in the document
|
|
101
169
|
*
|
|
@@ -136,28 +204,171 @@ const deletedTraditionalContentStyleUnbounded = convertToInlineCss({
|
|
|
136
204
|
pointerEvents: 'none',
|
|
137
205
|
zIndex: 1
|
|
138
206
|
});
|
|
207
|
+
const lozengeStyle = convertToInlineCss({
|
|
208
|
+
display: 'inline-flex',
|
|
209
|
+
boxSizing: 'border-box',
|
|
210
|
+
position: 'static',
|
|
211
|
+
blockSize: 'min-content',
|
|
212
|
+
borderRadius: "var(--ds-radius-small, 4px)",
|
|
213
|
+
overflow: 'hidden',
|
|
214
|
+
paddingInlineStart: "var(--ds-space-050, 4px)",
|
|
215
|
+
paddingInlineEnd: "var(--ds-space-050, 4px)",
|
|
216
|
+
backgroundColor: "var(--ds-background-accent-gray-subtler, #DCDFE4)",
|
|
217
|
+
font: "var(--ds-font-body-small, normal 400 11px/16px ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
|
|
218
|
+
fontWeight: "var(--ds-font-weight-bold, 700)",
|
|
219
|
+
textOverflow: 'ellipsis',
|
|
220
|
+
whiteSpace: 'nowrap',
|
|
221
|
+
color: "var(--ds-text-warning-inverse, #172B4D)"
|
|
222
|
+
});
|
|
139
223
|
const getDeletedContentStyleUnbounded = colourScheme => colourScheme === 'traditional' ? deletedTraditionalContentStyleUnbounded : deletedContentStyleUnbounded;
|
|
140
224
|
const getDeletedContentStyle = colourScheme => colourScheme === 'traditional' ? deletedTraditionalContentStyle : deletedContentStyle;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates a "Removed" lozenge to be displayed at the top right corner of deleted block nodes
|
|
228
|
+
*/
|
|
229
|
+
const createRemovedLozenge = (intl, nodeName) => {
|
|
230
|
+
const container = document.createElement('span');
|
|
231
|
+
let borderTopRightRadius;
|
|
232
|
+
let borderTopLeftRadius;
|
|
233
|
+
if (['expand', 'decisionList'].includes(nodeName || '')) {
|
|
234
|
+
borderTopRightRadius = "var(--ds-radius-small, 4px)";
|
|
235
|
+
} else if (['panel', 'codeBlock'].includes(nodeName || '')) {
|
|
236
|
+
borderTopRightRadius = `calc(${"var(--ds-radius-xsmall, 2px)"} + 1px)`;
|
|
237
|
+
} else if (nodeName === 'mediaSingle') {
|
|
238
|
+
borderTopLeftRadius = "var(--ds-radius-small, 4px)";
|
|
239
|
+
}
|
|
240
|
+
const containerStyle = convertToInlineCss({
|
|
241
|
+
position: 'absolute',
|
|
242
|
+
top: nodeName === 'mediaSingle' ? "var(--ds-space-300, 24px)" : "var(--ds-space-0, 0px)",
|
|
243
|
+
right: nodeName === 'mediaSingle' ? undefined : "var(--ds-space-0, 0px)",
|
|
244
|
+
left: nodeName === 'mediaSingle' ? "var(--ds-space-0, 0px)" : undefined,
|
|
245
|
+
zIndex: 2,
|
|
246
|
+
pointerEvents: 'none',
|
|
247
|
+
display: 'flex',
|
|
248
|
+
overflow: 'hidden',
|
|
249
|
+
borderTopRightRadius,
|
|
250
|
+
borderTopLeftRadius
|
|
251
|
+
});
|
|
252
|
+
container.setAttribute('style', containerStyle);
|
|
253
|
+
container.setAttribute('data-testid', 'show-diff-removed-lozenge');
|
|
254
|
+
|
|
255
|
+
// Create vanilla HTML lozenge element with Atlaskit Lozenge styling (visual refresh)
|
|
256
|
+
const lozengeElement = document.createElement('span');
|
|
257
|
+
lozengeElement.setAttribute('style', lozengeStyle);
|
|
258
|
+
lozengeElement.textContent = intl.formatMessage(trackChangesMessages.removed).toUpperCase();
|
|
259
|
+
container.appendChild(lozengeElement);
|
|
260
|
+
return container;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Wraps a block node in a container with relative positioning to support absolute positioned lozenge
|
|
265
|
+
*/
|
|
266
|
+
const createBlockNodeWrapper = nodeName => {
|
|
267
|
+
const wrapper = document.createElement('div');
|
|
268
|
+
const fitContent = shouldFitContentWidth(nodeName);
|
|
269
|
+
const baseStyle = convertToInlineCss({
|
|
270
|
+
position: 'relative',
|
|
271
|
+
display: fitContent ? 'inline-block' : 'block',
|
|
272
|
+
width: fitContent ? 'fit-content' : undefined,
|
|
273
|
+
height: fitContent ? 'fit-content' : undefined,
|
|
274
|
+
opacity: 1
|
|
275
|
+
});
|
|
276
|
+
wrapper.setAttribute('style', baseStyle);
|
|
277
|
+
return wrapper;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Wraps content with deleted styling without opacity (for use when content is a direct child of dom)
|
|
282
|
+
*/
|
|
283
|
+
const createDeletedStyleWrapperWithoutOpacity = colourScheme => {
|
|
284
|
+
const wrapper = document.createElement('span');
|
|
285
|
+
wrapper.setAttribute('style', getDeletedContentStyle(colourScheme));
|
|
286
|
+
return wrapper;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Applies deleted styles directly to an HTML element by merging with existing styles
|
|
291
|
+
*/
|
|
292
|
+
const applyDeletedStylesToElement = (element, targetNode, colourScheme) => {
|
|
293
|
+
const currentStyle = element.getAttribute('style') || '';
|
|
294
|
+
const deletedContentStyle = getDeletedContentStyle(colourScheme);
|
|
295
|
+
const nodeSpecificStyle = getDeletedStyleNode(targetNode.type.name) || '';
|
|
296
|
+
element.setAttribute('style', `${currentStyle}${deletedContentStyle}${nodeSpecificStyle}`);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Appends a block node with wrapper, lozenge, and appropriate styling
|
|
301
|
+
*/
|
|
302
|
+
const appendBlockNodeWithWrapper = (dom, nodeView, targetNode, colourScheme, intl) => {
|
|
303
|
+
const blockWrapper = createBlockNodeWrapper(targetNode.type.name);
|
|
304
|
+
if (shouldShowRemovedLozenge(targetNode.type.name)) {
|
|
305
|
+
const lozenge = createRemovedLozenge(intl, targetNode.type.name);
|
|
306
|
+
blockWrapper.append(lozenge);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Wrap the nodeView in a content wrapper that has the opacity style AND the box-shadow
|
|
310
|
+
// This keeps the lozenge at full opacity while the content AND border are faded
|
|
311
|
+
const contentWrapper = document.createElement('div');
|
|
312
|
+
const nodeStyle = getDeletedStyleNode(targetNode.type.name);
|
|
313
|
+
contentWrapper.setAttribute('style', `${getDeletedContentStyle(colourScheme)}${nodeStyle || ''}`);
|
|
314
|
+
contentWrapper.append(nodeView);
|
|
315
|
+
blockWrapper.append(contentWrapper);
|
|
316
|
+
if (nodeView instanceof HTMLElement) {
|
|
317
|
+
if (shouldAddShowDiffDeletedNodeClass(targetNode.type.name)) {
|
|
318
|
+
nodeView.classList.add('show-diff-deleted-node');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
dom.append(blockWrapper);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Handles all block node rendering with appropriate deleted styling.
|
|
326
|
+
* For blockquote and heading nodes, applies styles directly to preserve natural margins.
|
|
327
|
+
* For other block nodes, uses wrapper approach with optional lozenge.
|
|
328
|
+
*/
|
|
329
|
+
const handleBlockNodeView = (dom, nodeView, targetNode, colourScheme, intl) => {
|
|
330
|
+
if (shouldApplyDeletedStylesDirectly(targetNode.type.name) && nodeView instanceof HTMLElement) {
|
|
331
|
+
// Apply deleted styles directly to preserve natural block-level margins
|
|
332
|
+
applyDeletedStylesToElement(nodeView, targetNode, colourScheme);
|
|
333
|
+
dom.append(nodeView);
|
|
334
|
+
} else {
|
|
335
|
+
// Use wrapper approach for other block nodes
|
|
336
|
+
appendBlockNodeWithWrapper(dom, nodeView, targetNode, colourScheme, intl);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
141
339
|
export const createDeletedContentDecoration = ({
|
|
142
340
|
change,
|
|
143
341
|
doc,
|
|
144
342
|
nodeViewSerializer,
|
|
145
343
|
colourScheme,
|
|
146
|
-
newDoc
|
|
344
|
+
newDoc,
|
|
345
|
+
intl
|
|
147
346
|
}) => {
|
|
148
347
|
const slice = doc.slice(change.fromA, change.toA);
|
|
149
348
|
if (slice.content.content.length === 0) {
|
|
150
349
|
return;
|
|
151
350
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
351
|
+
const isTableCellContent = slice.content.content.some(() => slice.content.content.some(siblingNode => ['tableHeader', 'tableCell'].includes(siblingNode.type.name)));
|
|
352
|
+
const isTableRowContent = slice.content.content.some(() => slice.content.content.some(siblingNode => ['tableRow'].includes(siblingNode.type.name)));
|
|
353
|
+
if (isTableCellContent) {
|
|
154
354
|
return;
|
|
155
355
|
}
|
|
356
|
+
if (isTableRowContent) {
|
|
357
|
+
if (!fg('platform_editor_ai_aifc_patch_ga')) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const {
|
|
361
|
+
decorations
|
|
362
|
+
} = handleDeletedRows([change], doc, newDoc, nodeViewSerializer, colourScheme);
|
|
363
|
+
return decorations;
|
|
364
|
+
}
|
|
156
365
|
const serializer = nodeViewSerializer;
|
|
157
366
|
|
|
158
367
|
// For non-table content, use the existing span wrapper approach
|
|
159
368
|
const dom = document.createElement('span');
|
|
160
|
-
|
|
369
|
+
if (!fg('platform_editor_ai_aifc_patch_beta_2')) {
|
|
370
|
+
dom.setAttribute('style', getDeletedContentStyle(colourScheme));
|
|
371
|
+
}
|
|
161
372
|
|
|
162
373
|
/*
|
|
163
374
|
* The thinking is we separate out the fragment we got from doc.slice
|
|
@@ -169,8 +380,11 @@ export const createDeletedContentDecoration = ({
|
|
|
169
380
|
// Create a wrapper for each node with strikethrough
|
|
170
381
|
const createWrapperWithStrikethrough = () => {
|
|
171
382
|
const wrapper = document.createElement('span');
|
|
172
|
-
|
|
173
|
-
|
|
383
|
+
const baseStyle = convertToInlineCss({
|
|
384
|
+
position: 'relative',
|
|
385
|
+
width: 'fit-content'
|
|
386
|
+
});
|
|
387
|
+
wrapper.setAttribute('style', `${baseStyle}${getDeletedContentStyle(colourScheme)}`);
|
|
174
388
|
const strikethrough = document.createElement('span');
|
|
175
389
|
strikethrough.setAttribute('style', getDeletedContentStyleUnbounded(colourScheme));
|
|
176
390
|
wrapper.append(strikethrough);
|
|
@@ -193,7 +407,13 @@ export const createDeletedContentDecoration = ({
|
|
|
193
407
|
// Fallback to serializing the individual child node
|
|
194
408
|
const serializedChild = serializer.serializeNode(childNode);
|
|
195
409
|
if (serializedChild) {
|
|
196
|
-
|
|
410
|
+
if (fg('platform_editor_ai_aifc_patch_beta_2')) {
|
|
411
|
+
const wrapper = createWrapperWithStrikethrough();
|
|
412
|
+
wrapper.append(serializedChild);
|
|
413
|
+
dom.append(wrapper);
|
|
414
|
+
} else {
|
|
415
|
+
dom.append(serializedChild);
|
|
416
|
+
}
|
|
197
417
|
}
|
|
198
418
|
}
|
|
199
419
|
});
|
|
@@ -245,6 +465,9 @@ export const createDeletedContentDecoration = ({
|
|
|
245
465
|
const wrapper = createWrapperWithStrikethrough();
|
|
246
466
|
wrapper.append(nodeView);
|
|
247
467
|
dom.append(wrapper);
|
|
468
|
+
} else if (fg('platform_editor_ai_aifc_patch_beta_2')) {
|
|
469
|
+
// Handle all block nodes with unified function
|
|
470
|
+
handleBlockNodeView(dom, nodeView, targetNode, colourScheme, intl);
|
|
248
471
|
} else {
|
|
249
472
|
dom.append(nodeView);
|
|
250
473
|
}
|
|
@@ -254,7 +477,13 @@ export const createDeletedContentDecoration = ({
|
|
|
254
477
|
} else {
|
|
255
478
|
const fallbackNode = fallbackSerialization();
|
|
256
479
|
if (fallbackNode) {
|
|
257
|
-
|
|
480
|
+
if (fg('platform_editor_ai_aifc_patch_beta_2')) {
|
|
481
|
+
const wrapper = createDeletedStyleWrapperWithoutOpacity(colourScheme);
|
|
482
|
+
wrapper.append(fallbackNode);
|
|
483
|
+
dom.append(wrapper);
|
|
484
|
+
} else {
|
|
485
|
+
dom.append(fallbackNode);
|
|
486
|
+
}
|
|
258
487
|
}
|
|
259
488
|
}
|
|
260
489
|
});
|
|
@@ -263,5 +492,5 @@ export const createDeletedContentDecoration = ({
|
|
|
263
492
|
// Widget decoration used for deletions as the content is not in the document
|
|
264
493
|
// and we want to display the deleted content with a style.
|
|
265
494
|
const safeInsertPos = findSafeInsertPos(newDoc, change.fromB, slice);
|
|
266
|
-
return Decoration.widget(safeInsertPos, dom
|
|
495
|
+
return [Decoration.widget(safeInsertPos, dom)];
|
|
267
496
|
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
|
+
import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
|
|
3
|
+
import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
|
|
4
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
import { TableMap } from '@atlaskit/editor-tables/table-map';
|
|
6
|
+
import { findSafeInsertPos } from './findSafeInsertPos';
|
|
7
|
+
const deletedRowStyle = convertToInlineCss({
|
|
8
|
+
color: "var(--ds-text-accent-gray, #44546F)",
|
|
9
|
+
textDecoration: 'line-through',
|
|
10
|
+
opacity: 0.6,
|
|
11
|
+
display: 'table-row'
|
|
12
|
+
});
|
|
13
|
+
const deletedTraditionalRowStyle = convertToInlineCss({
|
|
14
|
+
textDecorationColor: "var(--ds-border-accent-red, #E2483D)",
|
|
15
|
+
textDecoration: 'line-through',
|
|
16
|
+
opacity: 0.6,
|
|
17
|
+
display: 'table-row'
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Extracts information about deleted table rows from a change
|
|
21
|
+
*/
|
|
22
|
+
const extractDeletedRows = (change, originalDoc, newDoc) => {
|
|
23
|
+
const deletedRows = [];
|
|
24
|
+
|
|
25
|
+
// Find the table in the original document
|
|
26
|
+
const $fromPos = originalDoc.resolve(change.fromA);
|
|
27
|
+
const tableOld = findParentNodeClosestToPos($fromPos, node => node.type.name === 'table');
|
|
28
|
+
if (!tableOld) {
|
|
29
|
+
return deletedRows;
|
|
30
|
+
}
|
|
31
|
+
const oldTableMap = TableMap.get(tableOld.node);
|
|
32
|
+
|
|
33
|
+
// Find the table in the new document at the insertion point
|
|
34
|
+
const $newPos = newDoc.resolve(change.fromB);
|
|
35
|
+
const tableNew = findParentNodeClosestToPos($newPos, node => node.type.name === 'table');
|
|
36
|
+
if (!tableNew) {
|
|
37
|
+
return deletedRows;
|
|
38
|
+
}
|
|
39
|
+
const newTableMap = TableMap.get(tableNew.node);
|
|
40
|
+
|
|
41
|
+
// If no rows were deleted, return empty
|
|
42
|
+
if (oldTableMap.height <= newTableMap.height ||
|
|
43
|
+
// For now ignore if there are column deletions as well
|
|
44
|
+
oldTableMap.width !== newTableMap.width) {
|
|
45
|
+
return deletedRows;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find which rows were deleted by analyzing the change range
|
|
49
|
+
const changeStartInTable = change.fromA - tableOld.pos - 1;
|
|
50
|
+
const changeEndInTable = change.toA - tableOld.pos - 1;
|
|
51
|
+
let currentOffset = 0;
|
|
52
|
+
let rowIndex = 0;
|
|
53
|
+
tableOld.node.content.forEach((rowNode, index) => {
|
|
54
|
+
const rowStart = currentOffset;
|
|
55
|
+
const rowEnd = currentOffset + rowNode.nodeSize;
|
|
56
|
+
|
|
57
|
+
// Check if this row overlaps with the deletion range
|
|
58
|
+
const rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
|
|
59
|
+
if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
|
|
60
|
+
const startOfRow = newTableMap.mapByRow.slice().reverse().find(row => row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos);
|
|
61
|
+
deletedRows.push({
|
|
62
|
+
rowIndex,
|
|
63
|
+
rowNode,
|
|
64
|
+
fromA: tableOld.pos + 1 + rowStart,
|
|
65
|
+
toA: tableOld.pos + 1 + rowEnd,
|
|
66
|
+
fromB: startOfRow ? startOfRow[0] + tableNew.start : change.fromB
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
currentOffset += rowNode.nodeSize;
|
|
70
|
+
if (rowNode.type.name === 'tableRow') {
|
|
71
|
+
rowIndex++;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Filter changes that never truly got deleted
|
|
76
|
+
return deletedRows.filter(deletedRow => {
|
|
77
|
+
return !tableNew.node.children.some(newRow => areNodesEqualIgnoreAttrs(newRow, deletedRow.rowNode));
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Checks if a table row is empty (contains no meaningful content)
|
|
83
|
+
*/
|
|
84
|
+
const isEmptyRow = rowNode => {
|
|
85
|
+
let isEmpty = true;
|
|
86
|
+
rowNode.descendants(node => {
|
|
87
|
+
if (!isEmpty) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If we find any inline content with size > 0, the row is not empty
|
|
92
|
+
if (node.isInline && node.nodeSize > 0) {
|
|
93
|
+
isEmpty = false;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If we find text content, the row is not empty
|
|
98
|
+
if (node.isText && node.text && node.text.trim() !== '') {
|
|
99
|
+
isEmpty = false;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
});
|
|
104
|
+
return isEmpty;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a DOM representation of a deleted table row
|
|
109
|
+
*/
|
|
110
|
+
const createDeletedRowDOM = (rowNode, nodeViewSerializer, colourScheme) => {
|
|
111
|
+
const tr = document.createElement('tr');
|
|
112
|
+
tr.setAttribute('style', colourScheme === 'traditional' ? deletedTraditionalRowStyle : deletedRowStyle);
|
|
113
|
+
tr.setAttribute('data-testid', 'show-diff-deleted-row');
|
|
114
|
+
|
|
115
|
+
// Serialize each cell in the row
|
|
116
|
+
rowNode.content.forEach(cellNode => {
|
|
117
|
+
if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
|
|
118
|
+
const nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
|
|
119
|
+
if (nodeView) {
|
|
120
|
+
tr.appendChild(nodeView);
|
|
121
|
+
} else {
|
|
122
|
+
// Fallback to fragment serialization
|
|
123
|
+
const serializedContent = nodeViewSerializer.serializeFragment(cellNode.content);
|
|
124
|
+
if (serializedContent) {
|
|
125
|
+
tr.appendChild(serializedContent);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return tr;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Expands a diff to include whole deleted rows when table rows are affected
|
|
135
|
+
*/
|
|
136
|
+
const expandDiffForDeletedRows = (changes, originalDoc, newDoc) => {
|
|
137
|
+
const deletedRowInfo = [];
|
|
138
|
+
for (const change of changes) {
|
|
139
|
+
// Check if this change affects table content
|
|
140
|
+
const deletedRows = extractDeletedRows(change, originalDoc, newDoc);
|
|
141
|
+
if (deletedRows.length > 0) {
|
|
142
|
+
deletedRowInfo.push(...deletedRows);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return deletedRowInfo;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates decorations for deleted table rows
|
|
150
|
+
*/
|
|
151
|
+
export const createDeletedRowsDecorations = ({
|
|
152
|
+
deletedRows,
|
|
153
|
+
originalDoc,
|
|
154
|
+
newDoc,
|
|
155
|
+
nodeViewSerializer,
|
|
156
|
+
colourScheme
|
|
157
|
+
}) => {
|
|
158
|
+
return deletedRows.map(deletedRow => {
|
|
159
|
+
const rowDOM = createDeletedRowDOM(deletedRow.rowNode, nodeViewSerializer, colourScheme);
|
|
160
|
+
|
|
161
|
+
// Find safe insertion position for the deleted row
|
|
162
|
+
const safeInsertPos = findSafeInsertPos(newDoc, deletedRow.fromB - 1,
|
|
163
|
+
// -1 to find the first safe position from the table
|
|
164
|
+
originalDoc.slice(deletedRow.fromA, deletedRow.toA));
|
|
165
|
+
return Decoration.widget(safeInsertPos, rowDOM, {});
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Main function to handle deleted rows - computes diff and creates decorations
|
|
171
|
+
*/
|
|
172
|
+
export const handleDeletedRows = (changes, originalDoc, newDoc, nodeViewSerializer, colourScheme) => {
|
|
173
|
+
// First, expand the changes to include complete deleted rows
|
|
174
|
+
const deletedRows = expandDiffForDeletedRows(changes.filter(change => change.deleted.length > 0), originalDoc, newDoc);
|
|
175
|
+
const allDecorations = createDeletedRowsDecorations({
|
|
176
|
+
deletedRows,
|
|
177
|
+
originalDoc,
|
|
178
|
+
newDoc,
|
|
179
|
+
nodeViewSerializer,
|
|
180
|
+
colourScheme
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
decorations: allDecorations
|
|
184
|
+
};
|
|
185
|
+
};
|
|
@@ -6,7 +6,7 @@ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
|
6
6
|
import { calculateDiffDecorations } from './calculateDiffDecorations';
|
|
7
7
|
import { NodeViewSerializer } from './NodeViewSerializer';
|
|
8
8
|
export const showDiffPluginKey = new PluginKey('showDiffPlugin');
|
|
9
|
-
export const createPlugin = config => {
|
|
9
|
+
export const createPlugin = (config, getIntl) => {
|
|
10
10
|
const nodeViewSerializer = new NodeViewSerializer({});
|
|
11
11
|
const setNodeViewSerializer = editorView => {
|
|
12
12
|
nodeViewSerializer.init({
|
|
@@ -16,7 +16,7 @@ export const createPlugin = config => {
|
|
|
16
16
|
return new SafePlugin({
|
|
17
17
|
key: showDiffPluginKey,
|
|
18
18
|
state: {
|
|
19
|
-
init(_,
|
|
19
|
+
init(_, _state) {
|
|
20
20
|
// We do initial setup after we setup the editor view
|
|
21
21
|
return {
|
|
22
22
|
steps: [],
|
|
@@ -41,7 +41,8 @@ export const createPlugin = config => {
|
|
|
41
41
|
state: newState,
|
|
42
42
|
pluginState: newPluginState,
|
|
43
43
|
nodeViewSerializer,
|
|
44
|
-
colourScheme: config === null || config === void 0 ? void 0 : config.colourScheme
|
|
44
|
+
colourScheme: config === null || config === void 0 ? void 0 : config.colourScheme,
|
|
45
|
+
intl: getIntl()
|
|
45
46
|
});
|
|
46
47
|
// Update the decorations
|
|
47
48
|
newPluginState.decorations = decorations;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPlugin, showDiffPluginKey } from './pm-plugins/main';
|
|
2
2
|
export const showDiffPlugin = ({
|
|
3
|
-
api,
|
|
3
|
+
api: _api,
|
|
4
4
|
config
|
|
5
5
|
}) => ({
|
|
6
6
|
name: 'showDiff',
|
|
@@ -25,7 +25,9 @@ export const showDiffPlugin = ({
|
|
|
25
25
|
pmPlugins() {
|
|
26
26
|
return [{
|
|
27
27
|
name: 'showDiffPlugin',
|
|
28
|
-
plugin: (
|
|
28
|
+
plugin: ({
|
|
29
|
+
getIntl
|
|
30
|
+
}) => createPlugin(config, getIntl)
|
|
29
31
|
}];
|
|
30
32
|
},
|
|
31
33
|
getSharedState: editorState => {
|