@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.
Files changed (30) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/afm-cc/tsconfig.json +6 -0
  3. package/afm-jira/tsconfig.json +3 -0
  4. package/afm-products/tsconfig.json +3 -0
  5. package/dist/cjs/pm-plugins/calculateDiffDecorations.js +12 -7
  6. package/dist/cjs/pm-plugins/decorations.js +242 -10
  7. package/dist/cjs/pm-plugins/deletedRowsHandler.js +214 -0
  8. package/dist/cjs/pm-plugins/main.js +6 -5
  9. package/dist/cjs/showDiffPlugin.js +4 -3
  10. package/dist/es2019/pm-plugins/calculateDiffDecorations.js +12 -7
  11. package/dist/es2019/pm-plugins/decorations.js +238 -9
  12. package/dist/es2019/pm-plugins/deletedRowsHandler.js +185 -0
  13. package/dist/es2019/pm-plugins/main.js +4 -3
  14. package/dist/es2019/showDiffPlugin.js +4 -2
  15. package/dist/esm/pm-plugins/calculateDiffDecorations.js +12 -7
  16. package/dist/esm/pm-plugins/decorations.js +242 -10
  17. package/dist/esm/pm-plugins/deletedRowsHandler.js +207 -0
  18. package/dist/esm/pm-plugins/main.js +4 -3
  19. package/dist/esm/showDiffPlugin.js +4 -3
  20. package/dist/types/pm-plugins/calculateDiffDecorations.d.ts +3 -1
  21. package/dist/types/pm-plugins/decorations.d.ts +4 -2
  22. package/dist/types/pm-plugins/deletedRowsHandler.d.ts +30 -0
  23. package/dist/types/pm-plugins/main.d.ts +2 -1
  24. package/dist/types-ts4.5/pm-plugins/calculateDiffDecorations.d.ts +3 -1
  25. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +4 -2
  26. package/dist/types-ts4.5/pm-plugins/deletedRowsHandler.d.ts +30 -0
  27. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -1
  28. package/package.json +13 -3
  29. package/afm-post-office/tsconfig.json +0 -27
  30. 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 _state = require("@atlaskit/editor-prosemirror/state");
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 _state.PluginKey('showDiffPlugin');
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(_, state) {
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 api = _ref.api,
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
- return (0, _main.createPlugin)(config);
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 isTableContent = slice.content.content.some(() => slice.content.content.some(siblingNode => ['tableHeader', 'tableCell', 'tableRow'].includes(siblingNode.type.name)));
153
- if (isTableContent) {
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
- dom.setAttribute('style', getDeletedContentStyle(colourScheme));
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
- wrapper.style.position = 'relative';
173
- wrapper.style.width = 'fit-content';
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
- dom.append(serializedChild);
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
- dom.append(fallbackNode);
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(_, state) {
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: () => createPlugin(config)
28
+ plugin: ({
29
+ getIntl
30
+ }) => createPlugin(config, getIntl)
29
31
  }];
30
32
  },
31
33
  getSharedState: editorState => {