@atlaskit/editor-plugin-show-diff 3.2.0 → 3.2.2

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 3.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`1c0d87f570c52`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1c0d87f570c52) -
8
+ [ux] Update attributes to ignore attr steps that do not affect the document
9
+
10
+ ## 3.2.1
11
+
12
+ ### Patch Changes
13
+
14
+ - [`da2782d8dc1e7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/da2782d8dc1e7) -
15
+ Support table row diff displaying in the editor
16
+
3
17
  ## 3.2.0
4
18
 
5
19
  ### Minor Changes
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-tables/afm-cc/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../../platform/feature-flags/afm-cc/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-tables/afm-jira/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../../platform/feature-flags/afm-jira/tsconfig.json"
22
25
  },
@@ -17,6 +17,9 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-tables/afm-products/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../../platform/feature-flags/afm-products/tsconfig.json"
22
25
  },
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.getAttrChangeRanges = void 0;
7
+ exports.stepIsValidAttrChange = exports.getAttrChangeRanges = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _steps = require("@atlaskit/adf-schema/steps");
10
10
  var _transform = require("@atlaskit/editor-prosemirror/transform");
@@ -16,7 +16,7 @@ var filterUndefined = function filterUndefined(x) {
16
16
  var allowedAttrs = ['id', 'collection', 'url'];
17
17
  var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
18
18
  return steps.map(function (step) {
19
- if (step instanceof _transform.AttrStep && allowedAttrs.includes(step.attr) || step instanceof _steps.SetAttrsStep && (0, _toConsumableArray2.default)(Object.keys(step.attrs)).some(function (v) {
19
+ if (step instanceof _transform.AttrStep && allowedAttrs.includes(step.attr) || step instanceof _steps.SetAttrsStep && step.attrs && (0, _toConsumableArray2.default)(Object.keys(step.attrs)).some(function (v) {
20
20
  return allowedAttrs.includes(v);
21
21
  })) {
22
22
  var $pos = doc.resolve(step.pos);
@@ -30,4 +30,28 @@ var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRa
30
30
  }
31
31
  return undefined;
32
32
  }).filter(filterUndefined);
33
+ };
34
+
35
+ /**
36
+ * Check if the step was a valid attr change and affected the doc
37
+ *
38
+ * @param step Attr step to test
39
+ * @param beforeDoc Doc before the step
40
+ * @param afterDoc Doc after the step
41
+ * @returns Boolean if the change should show a decoration
42
+ */
43
+ var stepIsValidAttrChange = exports.stepIsValidAttrChange = function stepIsValidAttrChange(step, beforeDoc, afterDoc) {
44
+ try {
45
+ if (step instanceof _transform.AttrStep || step instanceof _steps.SetAttrsStep) {
46
+ var attrStepAfter = afterDoc.nodeAt(step.pos);
47
+ var attrStepBefore = beforeDoc.nodeAt(step.pos);
48
+ // The change affected the document
49
+ if (attrStepAfter && attrStepBefore && !attrStepAfter.eq(attrStepBefore)) {
50
+ return true;
51
+ }
52
+ }
53
+ return false;
54
+ } catch (e) {
55
+ return false;
56
+ }
33
57
  };
@@ -13,7 +13,6 @@ var _memoizeOne = _interopRequireDefault(require("memoize-one"));
13
13
  var _prosemirrorChangeset = require("prosemirror-changeset");
14
14
  var _steps = require("@atlaskit/adf-schema/steps");
15
15
  var _document = require("@atlaskit/editor-common/utils/document");
16
- var _transform = require("@atlaskit/editor-prosemirror/transform");
17
16
  var _view = require("@atlaskit/editor-prosemirror/view");
18
17
  var _attributeDecorations = require("./attributeDecorations");
19
18
  var _decorations = require("./decorations");
@@ -76,7 +75,7 @@ function simplifySteps(steps) {
76
75
  return steps
77
76
  // Remove steps that don't affect document structure or content
78
77
  .filter(function (step) {
79
- return !(step instanceof _steps.AnalyticsStep || step instanceof _transform.AttrStep || step instanceof _steps.SetAttrsStep);
78
+ return !(step instanceof _steps.AnalyticsStep);
80
79
  })
81
80
  // Merge consecutive steps where possible
82
81
  .reduce(function (acc, step) {
@@ -105,7 +104,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
105
104
  }
106
105
  var tr = state.tr;
107
106
  var steppedDoc = originalDoc;
108
- var stepMaps = [];
107
+ var attrSteps = [];
108
+ var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc);
109
109
  var _iterator = _createForOfIteratorHelper(steps),
110
110
  _step;
111
111
  try {
@@ -113,8 +113,11 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
113
113
  var step = _step.value;
114
114
  var result = step.apply(steppedDoc);
115
115
  if (result.failed === null && result.doc) {
116
+ if ((0, _attributeDecorations.stepIsValidAttrChange)(step, steppedDoc, result.doc)) {
117
+ attrSteps.push(step);
118
+ }
116
119
  steppedDoc = result.doc;
117
- stepMaps.push(step.getMap());
120
+ changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
118
121
  }
119
122
  }
120
123
 
@@ -128,7 +131,6 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
128
131
  if (!(0, _document.areNodesEqualIgnoreAttrs)(steppedDoc, tr.doc)) {
129
132
  return _view.DecorationSet.empty;
130
133
  }
131
- var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
132
134
  var changes = (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, tr.doc);
133
135
  var optimizedChanges = optimizeChanges(changes);
134
136
  var decorations = [];
@@ -147,14 +149,14 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
147
149
  intl: intl
148
150
  });
149
151
  if (decoration) {
150
- decorations.push(decoration);
152
+ decorations.push.apply(decorations, (0, _toConsumableArray2.default)(decoration));
151
153
  }
152
154
  }
153
155
  });
154
156
  (0, _markDecorations.getMarkChangeRanges)(steps).forEach(function (change) {
155
157
  decorations.push((0, _decorations.createInlineChangedDecoration)(change, colourScheme));
156
158
  });
157
- (0, _attributeDecorations.getAttrChangeRanges)(tr.doc, rawSteps).forEach(function (change) {
159
+ (0, _attributeDecorations.getAttrChangeRanges)(tr.doc, attrSteps).forEach(function (change) {
158
160
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)(calculateNodesForBlockDecoration(tr.doc, change.fromB, change.toB, colourScheme)));
159
161
  });
160
162
  return _view.DecorationSet.empty.add(tr.doc, decorations);
@@ -8,6 +8,7 @@ var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
8
8
  var _messages = require("@atlaskit/editor-common/messages");
9
9
  var _view = require("@atlaskit/editor-prosemirror/view");
10
10
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
+ var _deletedRowsHandler = require("./deletedRowsHandler");
11
12
  var _findSafeInsertPos = require("./findSafeInsertPos");
12
13
  var editingStyle = (0, _lazyNodeView.convertToInlineCss)({
13
14
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
@@ -360,14 +361,27 @@ var createDeletedContentDecoration = exports.createDeletedContentDecoration = fu
360
361
  if (slice.content.content.length === 0) {
361
362
  return;
362
363
  }
363
- var isTableContent = slice.content.content.some(function () {
364
+ var isTableCellContent = slice.content.content.some(function () {
364
365
  return slice.content.content.some(function (siblingNode) {
365
- return ['tableHeader', 'tableCell', 'tableRow'].includes(siblingNode.type.name);
366
+ return ['tableHeader', 'tableCell'].includes(siblingNode.type.name);
366
367
  });
367
368
  });
368
- if (isTableContent) {
369
+ var isTableRowContent = slice.content.content.some(function () {
370
+ return slice.content.content.some(function (siblingNode) {
371
+ return ['tableRow'].includes(siblingNode.type.name);
372
+ });
373
+ });
374
+ if (isTableCellContent) {
369
375
  return;
370
376
  }
377
+ if (isTableRowContent) {
378
+ if (!(0, _platformFeatureFlags.fg)('platform_editor_ai_aifc_patch_ga')) {
379
+ return;
380
+ }
381
+ var _handleDeletedRows = (0, _deletedRowsHandler.handleDeletedRows)([change], doc, newDoc, nodeViewSerializer, colourScheme),
382
+ decorations = _handleDeletedRows.decorations;
383
+ return decorations;
384
+ }
371
385
  var serializer = nodeViewSerializer;
372
386
 
373
387
  // For non-table content, use the existing span wrapper approach
@@ -502,5 +516,5 @@ var createDeletedContentDecoration = exports.createDeletedContentDecoration = fu
502
516
  // Widget decoration used for deletions as the content is not in the document
503
517
  // and we want to display the deleted content with a style.
504
518
  var safeInsertPos = (0, _findSafeInsertPos.findSafeInsertPos)(newDoc, change.fromB, slice);
505
- return _view.Decoration.widget(safeInsertPos, dom);
519
+ return [_view.Decoration.widget(safeInsertPos, dom)];
506
520
  };
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.handleDeletedRows = exports.createDeletedRowsDecorations = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
10
+ var _document = require("@atlaskit/editor-common/utils/document");
11
+ var _utils = require("@atlaskit/editor-prosemirror/utils");
12
+ var _view = require("@atlaskit/editor-prosemirror/view");
13
+ var _tableMap = require("@atlaskit/editor-tables/table-map");
14
+ var _findSafeInsertPos = require("./findSafeInsertPos");
15
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
16
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
17
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
18
+ var deletedRowStyle = (0, _lazyNodeView.convertToInlineCss)({
19
+ color: "var(--ds-text-accent-gray, #44546F)",
20
+ textDecoration: 'line-through',
21
+ opacity: 0.6,
22
+ display: 'table-row'
23
+ });
24
+ var deletedTraditionalRowStyle = (0, _lazyNodeView.convertToInlineCss)({
25
+ textDecorationColor: "var(--ds-border-accent-red, #E2483D)",
26
+ textDecoration: 'line-through',
27
+ opacity: 0.6,
28
+ display: 'table-row'
29
+ });
30
+ /**
31
+ * Extracts information about deleted table rows from a change
32
+ */
33
+ var extractDeletedRows = function extractDeletedRows(change, originalDoc, newDoc) {
34
+ var deletedRows = [];
35
+
36
+ // Find the table in the original document
37
+ var $fromPos = originalDoc.resolve(change.fromA);
38
+ var tableOld = (0, _utils.findParentNodeClosestToPos)($fromPos, function (node) {
39
+ return node.type.name === 'table';
40
+ });
41
+ if (!tableOld) {
42
+ return deletedRows;
43
+ }
44
+ var oldTableMap = _tableMap.TableMap.get(tableOld.node);
45
+
46
+ // Find the table in the new document at the insertion point
47
+ var $newPos = newDoc.resolve(change.fromB);
48
+ var tableNew = (0, _utils.findParentNodeClosestToPos)($newPos, function (node) {
49
+ return node.type.name === 'table';
50
+ });
51
+ if (!tableNew) {
52
+ return deletedRows;
53
+ }
54
+ var newTableMap = _tableMap.TableMap.get(tableNew.node);
55
+
56
+ // If no rows were deleted, return empty
57
+ if (oldTableMap.height <= newTableMap.height ||
58
+ // For now ignore if there are column deletions as well
59
+ oldTableMap.width !== newTableMap.width) {
60
+ return deletedRows;
61
+ }
62
+
63
+ // Find which rows were deleted by analyzing the change range
64
+ var changeStartInTable = change.fromA - tableOld.pos - 1;
65
+ var changeEndInTable = change.toA - tableOld.pos - 1;
66
+ var currentOffset = 0;
67
+ var rowIndex = 0;
68
+ tableOld.node.content.forEach(function (rowNode, index) {
69
+ var rowStart = currentOffset;
70
+ var rowEnd = currentOffset + rowNode.nodeSize;
71
+
72
+ // Check if this row overlaps with the deletion range
73
+ var rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
74
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
75
+ var startOfRow = newTableMap.mapByRow.slice().reverse().find(function (row) {
76
+ return row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos;
77
+ });
78
+ deletedRows.push({
79
+ rowIndex: rowIndex,
80
+ rowNode: rowNode,
81
+ fromA: tableOld.pos + 1 + rowStart,
82
+ toA: tableOld.pos + 1 + rowEnd,
83
+ fromB: startOfRow ? startOfRow[0] + tableNew.start : change.fromB
84
+ });
85
+ }
86
+ currentOffset += rowNode.nodeSize;
87
+ if (rowNode.type.name === 'tableRow') {
88
+ rowIndex++;
89
+ }
90
+ });
91
+
92
+ // Filter changes that never truly got deleted
93
+ return deletedRows.filter(function (deletedRow) {
94
+ return !tableNew.node.children.some(function (newRow) {
95
+ return (0, _document.areNodesEqualIgnoreAttrs)(newRow, deletedRow.rowNode);
96
+ });
97
+ });
98
+ };
99
+
100
+ /**
101
+ * Checks if a table row is empty (contains no meaningful content)
102
+ */
103
+ var isEmptyRow = function isEmptyRow(rowNode) {
104
+ var isEmpty = true;
105
+ rowNode.descendants(function (node) {
106
+ if (!isEmpty) {
107
+ return false;
108
+ }
109
+
110
+ // If we find any inline content with size > 0, the row is not empty
111
+ if (node.isInline && node.nodeSize > 0) {
112
+ isEmpty = false;
113
+ return false;
114
+ }
115
+
116
+ // If we find text content, the row is not empty
117
+ if (node.isText && node.text && node.text.trim() !== '') {
118
+ isEmpty = false;
119
+ return false;
120
+ }
121
+ return true;
122
+ });
123
+ return isEmpty;
124
+ };
125
+
126
+ /**
127
+ * Creates a DOM representation of a deleted table row
128
+ */
129
+ var createDeletedRowDOM = function createDeletedRowDOM(rowNode, nodeViewSerializer, colourScheme) {
130
+ var tr = document.createElement('tr');
131
+ tr.setAttribute('style', colourScheme === 'traditional' ? deletedTraditionalRowStyle : deletedRowStyle);
132
+ tr.setAttribute('data-testid', 'show-diff-deleted-row');
133
+
134
+ // Serialize each cell in the row
135
+ rowNode.content.forEach(function (cellNode) {
136
+ if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
137
+ var nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
138
+ if (nodeView) {
139
+ tr.appendChild(nodeView);
140
+ } else {
141
+ // Fallback to fragment serialization
142
+ var serializedContent = nodeViewSerializer.serializeFragment(cellNode.content);
143
+ if (serializedContent) {
144
+ tr.appendChild(serializedContent);
145
+ }
146
+ }
147
+ }
148
+ });
149
+ return tr;
150
+ };
151
+
152
+ /**
153
+ * Expands a diff to include whole deleted rows when table rows are affected
154
+ */
155
+ var expandDiffForDeletedRows = function expandDiffForDeletedRows(changes, originalDoc, newDoc) {
156
+ var deletedRowInfo = [];
157
+ var _iterator = _createForOfIteratorHelper(changes),
158
+ _step;
159
+ try {
160
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
161
+ var change = _step.value;
162
+ // Check if this change affects table content
163
+ var deletedRows = extractDeletedRows(change, originalDoc, newDoc);
164
+ if (deletedRows.length > 0) {
165
+ deletedRowInfo.push.apply(deletedRowInfo, (0, _toConsumableArray2.default)(deletedRows));
166
+ }
167
+ }
168
+ } catch (err) {
169
+ _iterator.e(err);
170
+ } finally {
171
+ _iterator.f();
172
+ }
173
+ return deletedRowInfo;
174
+ };
175
+
176
+ /**
177
+ * Creates decorations for deleted table rows
178
+ */
179
+ var createDeletedRowsDecorations = exports.createDeletedRowsDecorations = function createDeletedRowsDecorations(_ref) {
180
+ var deletedRows = _ref.deletedRows,
181
+ originalDoc = _ref.originalDoc,
182
+ newDoc = _ref.newDoc,
183
+ nodeViewSerializer = _ref.nodeViewSerializer,
184
+ colourScheme = _ref.colourScheme;
185
+ return deletedRows.map(function (deletedRow) {
186
+ var rowDOM = createDeletedRowDOM(deletedRow.rowNode, nodeViewSerializer, colourScheme);
187
+
188
+ // Find safe insertion position for the deleted row
189
+ var safeInsertPos = (0, _findSafeInsertPos.findSafeInsertPos)(newDoc, deletedRow.fromB - 1,
190
+ // -1 to find the first safe position from the table
191
+ originalDoc.slice(deletedRow.fromA, deletedRow.toA));
192
+ return _view.Decoration.widget(safeInsertPos, rowDOM, {});
193
+ });
194
+ };
195
+
196
+ /**
197
+ * Main function to handle deleted rows - computes diff and creates decorations
198
+ */
199
+ var handleDeletedRows = exports.handleDeletedRows = function handleDeletedRows(changes, originalDoc, newDoc, nodeViewSerializer, colourScheme) {
200
+ // First, expand the changes to include complete deleted rows
201
+ var deletedRows = expandDiffForDeletedRows(changes.filter(function (change) {
202
+ return change.deleted.length > 0;
203
+ }), originalDoc, newDoc);
204
+ var allDecorations = createDeletedRowsDecorations({
205
+ deletedRows: deletedRows,
206
+ originalDoc: originalDoc,
207
+ newDoc: newDoc,
208
+ nodeViewSerializer: nodeViewSerializer,
209
+ colourScheme: colourScheme
210
+ });
211
+ return {
212
+ decorations: allDecorations
213
+ };
214
+ };
@@ -6,7 +6,7 @@ const filterUndefined = x => !!x;
6
6
  const allowedAttrs = ['id', 'collection', 'url'];
7
7
  export const getAttrChangeRanges = (doc, steps) => {
8
8
  return steps.map(step => {
9
- if (step instanceof AttrStep && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && [...Object.keys(step.attrs)].some(v => allowedAttrs.includes(v))) {
9
+ if (step instanceof AttrStep && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && step.attrs && [...Object.keys(step.attrs)].some(v => allowedAttrs.includes(v))) {
10
10
  const $pos = doc.resolve(step.pos);
11
11
  if ($pos.parent.type === doc.type.schema.nodes.mediaSingle) {
12
12
  const startPos = $pos.pos + $pos.parentOffset;
@@ -18,4 +18,28 @@ export const getAttrChangeRanges = (doc, steps) => {
18
18
  }
19
19
  return undefined;
20
20
  }).filter(filterUndefined);
21
+ };
22
+
23
+ /**
24
+ * Check if the step was a valid attr change and affected the doc
25
+ *
26
+ * @param step Attr step to test
27
+ * @param beforeDoc Doc before the step
28
+ * @param afterDoc Doc after the step
29
+ * @returns Boolean if the change should show a decoration
30
+ */
31
+ export const stepIsValidAttrChange = (step, beforeDoc, afterDoc) => {
32
+ try {
33
+ if (step instanceof AttrStep || step instanceof SetAttrsStep) {
34
+ const attrStepAfter = afterDoc.nodeAt(step.pos);
35
+ const attrStepBefore = beforeDoc.nodeAt(step.pos);
36
+ // The change affected the document
37
+ if (attrStepAfter && attrStepBefore && !attrStepAfter.eq(attrStepBefore)) {
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ } catch (e) {
43
+ return false;
44
+ }
21
45
  };
@@ -2,11 +2,10 @@
2
2
  import isEqual from 'lodash/isEqual';
3
3
  import memoizeOne from 'memoize-one';
4
4
  import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
5
- import { AnalyticsStep, SetAttrsStep } from '@atlaskit/adf-schema/steps';
5
+ import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
6
6
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
7
- import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
8
7
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
9
- import { getAttrChangeRanges } from './attributeDecorations';
8
+ import { getAttrChangeRanges, stepIsValidAttrChange } from './attributeDecorations';
10
9
  import { createInlineChangedDecoration, createDeletedContentDecoration, createBlockChangedDecoration } from './decorations';
11
10
  import { getMarkChangeRanges } from './markDecorations';
12
11
  const calculateNodesForBlockDecoration = (doc, from, to, colourScheme) => {
@@ -65,7 +64,7 @@ function optimizeChanges(changes) {
65
64
  function simplifySteps(steps) {
66
65
  return steps
67
66
  // Remove steps that don't affect document structure or content
68
- .filter(step => !(step instanceof AnalyticsStep || step instanceof AttrStep || step instanceof SetAttrsStep))
67
+ .filter(step => !(step instanceof AnalyticsStep))
69
68
  // Merge consecutive steps where possible
70
69
  .reduce((acc, step) => {
71
70
  var _lastStep$merge;
@@ -98,12 +97,16 @@ const calculateDiffDecorationsInner = ({
98
97
  tr
99
98
  } = state;
100
99
  let steppedDoc = originalDoc;
101
- const stepMaps = [];
100
+ const attrSteps = [];
101
+ let changeset = ChangeSet.create(originalDoc);
102
102
  for (const step of steps) {
103
103
  const result = step.apply(steppedDoc);
104
104
  if (result.failed === null && result.doc) {
105
+ if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
106
+ attrSteps.push(step);
107
+ }
105
108
  steppedDoc = result.doc;
106
- stepMaps.push(step.getMap());
109
+ changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
107
110
  }
108
111
  }
109
112
 
@@ -112,7 +115,6 @@ const calculateDiffDecorationsInner = ({
112
115
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
113
116
  return DecorationSet.empty;
114
117
  }
115
- const changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
116
118
  const changes = simplifyChanges(changeset.changes, tr.doc);
117
119
  const optimizedChanges = optimizeChanges(changes);
118
120
  const decorations = [];
@@ -131,14 +133,14 @@ const calculateDiffDecorationsInner = ({
131
133
  intl
132
134
  });
133
135
  if (decoration) {
134
- decorations.push(decoration);
136
+ decorations.push(...decoration);
135
137
  }
136
138
  }
137
139
  });
138
140
  getMarkChangeRanges(steps).forEach(change => {
139
141
  decorations.push(createInlineChangedDecoration(change, colourScheme));
140
142
  });
141
- getAttrChangeRanges(tr.doc, rawSteps).forEach(change => {
143
+ getAttrChangeRanges(tr.doc, attrSteps).forEach(change => {
142
144
  decorations.push(...calculateNodesForBlockDecoration(tr.doc, change.fromB, change.toB, colourScheme));
143
145
  });
144
146
  return DecorationSet.empty.add(tr.doc, decorations);
@@ -2,6 +2,7 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { trackChangesMessages } from '@atlaskit/editor-common/messages';
3
3
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { handleDeletedRows } from './deletedRowsHandler';
5
6
  import { findSafeInsertPos } from './findSafeInsertPos';
6
7
  const editingStyle = convertToInlineCss({
7
8
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
@@ -347,10 +348,20 @@ export const createDeletedContentDecoration = ({
347
348
  if (slice.content.content.length === 0) {
348
349
  return;
349
350
  }
350
- const isTableContent = slice.content.content.some(() => slice.content.content.some(siblingNode => ['tableHeader', 'tableCell', 'tableRow'].includes(siblingNode.type.name)));
351
- 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) {
352
354
  return;
353
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
+ }
354
365
  const serializer = nodeViewSerializer;
355
366
 
356
367
  // For non-table content, use the existing span wrapper approach
@@ -481,5 +492,5 @@ export const createDeletedContentDecoration = ({
481
492
  // Widget decoration used for deletions as the content is not in the document
482
493
  // and we want to display the deleted content with a style.
483
494
  const safeInsertPos = findSafeInsertPos(newDoc, change.fromB, slice);
484
- return Decoration.widget(safeInsertPos, dom);
495
+ return [Decoration.widget(safeInsertPos, dom)];
485
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
+ };
@@ -9,7 +9,7 @@ var filterUndefined = function filterUndefined(x) {
9
9
  var allowedAttrs = ['id', 'collection', 'url'];
10
10
  export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
11
11
  return steps.map(function (step) {
12
- if (step instanceof AttrStep && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && _toConsumableArray(Object.keys(step.attrs)).some(function (v) {
12
+ if (step instanceof AttrStep && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && step.attrs && _toConsumableArray(Object.keys(step.attrs)).some(function (v) {
13
13
  return allowedAttrs.includes(v);
14
14
  })) {
15
15
  var $pos = doc.resolve(step.pos);
@@ -23,4 +23,28 @@ export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
23
23
  }
24
24
  return undefined;
25
25
  }).filter(filterUndefined);
26
+ };
27
+
28
+ /**
29
+ * Check if the step was a valid attr change and affected the doc
30
+ *
31
+ * @param step Attr step to test
32
+ * @param beforeDoc Doc before the step
33
+ * @param afterDoc Doc after the step
34
+ * @returns Boolean if the change should show a decoration
35
+ */
36
+ export var stepIsValidAttrChange = function stepIsValidAttrChange(step, beforeDoc, afterDoc) {
37
+ try {
38
+ if (step instanceof AttrStep || step instanceof SetAttrsStep) {
39
+ var attrStepAfter = afterDoc.nodeAt(step.pos);
40
+ var attrStepBefore = beforeDoc.nodeAt(step.pos);
41
+ // The change affected the document
42
+ if (attrStepAfter && attrStepBefore && !attrStepAfter.eq(attrStepBefore)) {
43
+ return true;
44
+ }
45
+ }
46
+ return false;
47
+ } catch (e) {
48
+ return false;
49
+ }
26
50
  };
@@ -10,11 +10,10 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
10
10
  import isEqual from 'lodash/isEqual';
11
11
  import memoizeOne from 'memoize-one';
12
12
  import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
13
- import { AnalyticsStep, SetAttrsStep } from '@atlaskit/adf-schema/steps';
13
+ import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
14
14
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
15
- import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
16
15
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
17
- import { getAttrChangeRanges } from './attributeDecorations';
16
+ import { getAttrChangeRanges, stepIsValidAttrChange } from './attributeDecorations';
18
17
  import { createInlineChangedDecoration, createDeletedContentDecoration, createBlockChangedDecoration } from './decorations';
19
18
  import { getMarkChangeRanges } from './markDecorations';
20
19
  var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration(doc, from, to, colourScheme) {
@@ -70,7 +69,7 @@ function simplifySteps(steps) {
70
69
  return steps
71
70
  // Remove steps that don't affect document structure or content
72
71
  .filter(function (step) {
73
- return !(step instanceof AnalyticsStep || step instanceof AttrStep || step instanceof SetAttrsStep);
72
+ return !(step instanceof AnalyticsStep);
74
73
  })
75
74
  // Merge consecutive steps where possible
76
75
  .reduce(function (acc, step) {
@@ -99,7 +98,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
99
98
  }
100
99
  var tr = state.tr;
101
100
  var steppedDoc = originalDoc;
102
- var stepMaps = [];
101
+ var attrSteps = [];
102
+ var changeset = ChangeSet.create(originalDoc);
103
103
  var _iterator = _createForOfIteratorHelper(steps),
104
104
  _step;
105
105
  try {
@@ -107,8 +107,11 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
107
107
  var step = _step.value;
108
108
  var result = step.apply(steppedDoc);
109
109
  if (result.failed === null && result.doc) {
110
+ if (stepIsValidAttrChange(step, steppedDoc, result.doc)) {
111
+ attrSteps.push(step);
112
+ }
110
113
  steppedDoc = result.doc;
111
- stepMaps.push(step.getMap());
114
+ changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
112
115
  }
113
116
  }
114
117
 
@@ -122,7 +125,6 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
122
125
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
123
126
  return DecorationSet.empty;
124
127
  }
125
- var changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
126
128
  var changes = simplifyChanges(changeset.changes, tr.doc);
127
129
  var optimizedChanges = optimizeChanges(changes);
128
130
  var decorations = [];
@@ -141,14 +143,14 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
141
143
  intl: intl
142
144
  });
143
145
  if (decoration) {
144
- decorations.push(decoration);
146
+ decorations.push.apply(decorations, _toConsumableArray(decoration));
145
147
  }
146
148
  }
147
149
  });
148
150
  getMarkChangeRanges(steps).forEach(function (change) {
149
151
  decorations.push(createInlineChangedDecoration(change, colourScheme));
150
152
  });
151
- getAttrChangeRanges(tr.doc, rawSteps).forEach(function (change) {
153
+ getAttrChangeRanges(tr.doc, attrSteps).forEach(function (change) {
152
154
  decorations.push.apply(decorations, _toConsumableArray(calculateNodesForBlockDecoration(tr.doc, change.fromB, change.toB, colourScheme)));
153
155
  });
154
156
  return DecorationSet.empty.add(tr.doc, decorations);
@@ -2,6 +2,7 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { trackChangesMessages } from '@atlaskit/editor-common/messages';
3
3
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { handleDeletedRows } from './deletedRowsHandler';
5
6
  import { findSafeInsertPos } from './findSafeInsertPos';
6
7
  var editingStyle = convertToInlineCss({
7
8
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
@@ -354,14 +355,27 @@ export var createDeletedContentDecoration = function createDeletedContentDecorat
354
355
  if (slice.content.content.length === 0) {
355
356
  return;
356
357
  }
357
- var isTableContent = slice.content.content.some(function () {
358
+ var isTableCellContent = slice.content.content.some(function () {
358
359
  return slice.content.content.some(function (siblingNode) {
359
- return ['tableHeader', 'tableCell', 'tableRow'].includes(siblingNode.type.name);
360
+ return ['tableHeader', 'tableCell'].includes(siblingNode.type.name);
360
361
  });
361
362
  });
362
- if (isTableContent) {
363
+ var isTableRowContent = slice.content.content.some(function () {
364
+ return slice.content.content.some(function (siblingNode) {
365
+ return ['tableRow'].includes(siblingNode.type.name);
366
+ });
367
+ });
368
+ if (isTableCellContent) {
363
369
  return;
364
370
  }
371
+ if (isTableRowContent) {
372
+ if (!fg('platform_editor_ai_aifc_patch_ga')) {
373
+ return;
374
+ }
375
+ var _handleDeletedRows = handleDeletedRows([change], doc, newDoc, nodeViewSerializer, colourScheme),
376
+ decorations = _handleDeletedRows.decorations;
377
+ return decorations;
378
+ }
365
379
  var serializer = nodeViewSerializer;
366
380
 
367
381
  // For non-table content, use the existing span wrapper approach
@@ -496,5 +510,5 @@ export var createDeletedContentDecoration = function createDeletedContentDecorat
496
510
  // Widget decoration used for deletions as the content is not in the document
497
511
  // and we want to display the deleted content with a style.
498
512
  var safeInsertPos = findSafeInsertPos(newDoc, change.fromB, slice);
499
- return Decoration.widget(safeInsertPos, dom);
513
+ return [Decoration.widget(safeInsertPos, dom)];
500
514
  };
@@ -0,0 +1,207 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
3
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
4
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
6
+ import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
7
+ import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
8
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
9
+ import { TableMap } from '@atlaskit/editor-tables/table-map';
10
+ import { findSafeInsertPos } from './findSafeInsertPos';
11
+ var deletedRowStyle = convertToInlineCss({
12
+ color: "var(--ds-text-accent-gray, #44546F)",
13
+ textDecoration: 'line-through',
14
+ opacity: 0.6,
15
+ display: 'table-row'
16
+ });
17
+ var deletedTraditionalRowStyle = convertToInlineCss({
18
+ textDecorationColor: "var(--ds-border-accent-red, #E2483D)",
19
+ textDecoration: 'line-through',
20
+ opacity: 0.6,
21
+ display: 'table-row'
22
+ });
23
+ /**
24
+ * Extracts information about deleted table rows from a change
25
+ */
26
+ var extractDeletedRows = function extractDeletedRows(change, originalDoc, newDoc) {
27
+ var deletedRows = [];
28
+
29
+ // Find the table in the original document
30
+ var $fromPos = originalDoc.resolve(change.fromA);
31
+ var tableOld = findParentNodeClosestToPos($fromPos, function (node) {
32
+ return node.type.name === 'table';
33
+ });
34
+ if (!tableOld) {
35
+ return deletedRows;
36
+ }
37
+ var oldTableMap = TableMap.get(tableOld.node);
38
+
39
+ // Find the table in the new document at the insertion point
40
+ var $newPos = newDoc.resolve(change.fromB);
41
+ var tableNew = findParentNodeClosestToPos($newPos, function (node) {
42
+ return node.type.name === 'table';
43
+ });
44
+ if (!tableNew) {
45
+ return deletedRows;
46
+ }
47
+ var newTableMap = TableMap.get(tableNew.node);
48
+
49
+ // If no rows were deleted, return empty
50
+ if (oldTableMap.height <= newTableMap.height ||
51
+ // For now ignore if there are column deletions as well
52
+ oldTableMap.width !== newTableMap.width) {
53
+ return deletedRows;
54
+ }
55
+
56
+ // Find which rows were deleted by analyzing the change range
57
+ var changeStartInTable = change.fromA - tableOld.pos - 1;
58
+ var changeEndInTable = change.toA - tableOld.pos - 1;
59
+ var currentOffset = 0;
60
+ var rowIndex = 0;
61
+ tableOld.node.content.forEach(function (rowNode, index) {
62
+ var rowStart = currentOffset;
63
+ var rowEnd = currentOffset + rowNode.nodeSize;
64
+
65
+ // Check if this row overlaps with the deletion range
66
+ var rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
67
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
68
+ var startOfRow = newTableMap.mapByRow.slice().reverse().find(function (row) {
69
+ return row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos;
70
+ });
71
+ deletedRows.push({
72
+ rowIndex: rowIndex,
73
+ rowNode: rowNode,
74
+ fromA: tableOld.pos + 1 + rowStart,
75
+ toA: tableOld.pos + 1 + rowEnd,
76
+ fromB: startOfRow ? startOfRow[0] + tableNew.start : change.fromB
77
+ });
78
+ }
79
+ currentOffset += rowNode.nodeSize;
80
+ if (rowNode.type.name === 'tableRow') {
81
+ rowIndex++;
82
+ }
83
+ });
84
+
85
+ // Filter changes that never truly got deleted
86
+ return deletedRows.filter(function (deletedRow) {
87
+ return !tableNew.node.children.some(function (newRow) {
88
+ return areNodesEqualIgnoreAttrs(newRow, deletedRow.rowNode);
89
+ });
90
+ });
91
+ };
92
+
93
+ /**
94
+ * Checks if a table row is empty (contains no meaningful content)
95
+ */
96
+ var isEmptyRow = function isEmptyRow(rowNode) {
97
+ var isEmpty = true;
98
+ rowNode.descendants(function (node) {
99
+ if (!isEmpty) {
100
+ return false;
101
+ }
102
+
103
+ // If we find any inline content with size > 0, the row is not empty
104
+ if (node.isInline && node.nodeSize > 0) {
105
+ isEmpty = false;
106
+ return false;
107
+ }
108
+
109
+ // If we find text content, the row is not empty
110
+ if (node.isText && node.text && node.text.trim() !== '') {
111
+ isEmpty = false;
112
+ return false;
113
+ }
114
+ return true;
115
+ });
116
+ return isEmpty;
117
+ };
118
+
119
+ /**
120
+ * Creates a DOM representation of a deleted table row
121
+ */
122
+ var createDeletedRowDOM = function createDeletedRowDOM(rowNode, nodeViewSerializer, colourScheme) {
123
+ var tr = document.createElement('tr');
124
+ tr.setAttribute('style', colourScheme === 'traditional' ? deletedTraditionalRowStyle : deletedRowStyle);
125
+ tr.setAttribute('data-testid', 'show-diff-deleted-row');
126
+
127
+ // Serialize each cell in the row
128
+ rowNode.content.forEach(function (cellNode) {
129
+ if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
130
+ var nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
131
+ if (nodeView) {
132
+ tr.appendChild(nodeView);
133
+ } else {
134
+ // Fallback to fragment serialization
135
+ var serializedContent = nodeViewSerializer.serializeFragment(cellNode.content);
136
+ if (serializedContent) {
137
+ tr.appendChild(serializedContent);
138
+ }
139
+ }
140
+ }
141
+ });
142
+ return tr;
143
+ };
144
+
145
+ /**
146
+ * Expands a diff to include whole deleted rows when table rows are affected
147
+ */
148
+ var expandDiffForDeletedRows = function expandDiffForDeletedRows(changes, originalDoc, newDoc) {
149
+ var deletedRowInfo = [];
150
+ var _iterator = _createForOfIteratorHelper(changes),
151
+ _step;
152
+ try {
153
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
154
+ var change = _step.value;
155
+ // Check if this change affects table content
156
+ var deletedRows = extractDeletedRows(change, originalDoc, newDoc);
157
+ if (deletedRows.length > 0) {
158
+ deletedRowInfo.push.apply(deletedRowInfo, _toConsumableArray(deletedRows));
159
+ }
160
+ }
161
+ } catch (err) {
162
+ _iterator.e(err);
163
+ } finally {
164
+ _iterator.f();
165
+ }
166
+ return deletedRowInfo;
167
+ };
168
+
169
+ /**
170
+ * Creates decorations for deleted table rows
171
+ */
172
+ export var createDeletedRowsDecorations = function createDeletedRowsDecorations(_ref) {
173
+ var deletedRows = _ref.deletedRows,
174
+ originalDoc = _ref.originalDoc,
175
+ newDoc = _ref.newDoc,
176
+ nodeViewSerializer = _ref.nodeViewSerializer,
177
+ colourScheme = _ref.colourScheme;
178
+ return deletedRows.map(function (deletedRow) {
179
+ var rowDOM = createDeletedRowDOM(deletedRow.rowNode, nodeViewSerializer, colourScheme);
180
+
181
+ // Find safe insertion position for the deleted row
182
+ var safeInsertPos = findSafeInsertPos(newDoc, deletedRow.fromB - 1,
183
+ // -1 to find the first safe position from the table
184
+ originalDoc.slice(deletedRow.fromA, deletedRow.toA));
185
+ return Decoration.widget(safeInsertPos, rowDOM, {});
186
+ });
187
+ };
188
+
189
+ /**
190
+ * Main function to handle deleted rows - computes diff and creates decorations
191
+ */
192
+ export var handleDeletedRows = function handleDeletedRows(changes, originalDoc, newDoc, nodeViewSerializer, colourScheme) {
193
+ // First, expand the changes to include complete deleted rows
194
+ var deletedRows = expandDiffForDeletedRows(changes.filter(function (change) {
195
+ return change.deleted.length > 0;
196
+ }), originalDoc, newDoc);
197
+ var allDecorations = createDeletedRowsDecorations({
198
+ deletedRows: deletedRows,
199
+ originalDoc: originalDoc,
200
+ newDoc: newDoc,
201
+ nodeViewSerializer: nodeViewSerializer,
202
+ colourScheme: colourScheme
203
+ });
204
+ return {
205
+ decorations: allDecorations
206
+ };
207
+ };
@@ -5,4 +5,13 @@ type StepRange = {
5
5
  toB: number;
6
6
  };
7
7
  export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[]) => StepRange[];
8
+ /**
9
+ * Check if the step was a valid attr change and affected the doc
10
+ *
11
+ * @param step Attr step to test
12
+ * @param beforeDoc Doc before the step
13
+ * @param afterDoc Doc after the step
14
+ * @returns Boolean if the change should show a decoration
15
+ */
16
+ export declare const stepIsValidAttrChange: (step: ProseMirrorStep, beforeDoc: PMNode, afterDoc: PMNode) => boolean;
8
17
  export {};
@@ -25,12 +25,12 @@ export declare const createBlockChangedDecoration: (change: {
25
25
  to: number;
26
26
  }, colourScheme?: "standard" | "traditional") => Decoration;
27
27
  interface DeletedContentDecorationProps {
28
- change: Change;
28
+ change: Pick<Change, 'fromA' | 'toA' | 'fromB' | 'deleted'>;
29
29
  colourScheme?: 'standard' | 'traditional';
30
30
  doc: PMNode;
31
31
  intl: IntlShape;
32
32
  newDoc: PMNode;
33
33
  nodeViewSerializer: NodeViewSerializer;
34
34
  }
35
- export declare const createDeletedContentDecoration: ({ change, doc, nodeViewSerializer, colourScheme, newDoc, intl, }: DeletedContentDecorationProps) => Decoration | undefined;
35
+ export declare const createDeletedContentDecoration: ({ change, doc, nodeViewSerializer, colourScheme, newDoc, intl, }: DeletedContentDecorationProps) => Decoration[] | undefined;
36
36
  export {};
@@ -0,0 +1,30 @@
1
+ import type { Change } from 'prosemirror-changeset';
2
+ import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
+ import type { NodeViewSerializer } from './NodeViewSerializer';
5
+ interface DeletedRowInfo {
6
+ fromA: number;
7
+ fromB: number;
8
+ rowIndex: number;
9
+ rowNode: PMNode;
10
+ toA: number;
11
+ }
12
+ type SimpleChange = Pick<Change, 'fromA' | 'toA' | 'fromB' | 'deleted'>;
13
+ interface DeletedRowsHandlerProps {
14
+ colourScheme?: 'standard' | 'traditional';
15
+ deletedRows: DeletedRowInfo[];
16
+ newDoc: PMNode;
17
+ nodeViewSerializer: NodeViewSerializer;
18
+ originalDoc: PMNode;
19
+ }
20
+ /**
21
+ * Creates decorations for deleted table rows
22
+ */
23
+ export declare const createDeletedRowsDecorations: ({ deletedRows, originalDoc, newDoc, nodeViewSerializer, colourScheme, }: DeletedRowsHandlerProps) => Decoration[];
24
+ /**
25
+ * Main function to handle deleted rows - computes diff and creates decorations
26
+ */
27
+ export declare const handleDeletedRows: (changes: SimpleChange[], originalDoc: PMNode, newDoc: PMNode, nodeViewSerializer: NodeViewSerializer, colourScheme?: "standard" | "traditional") => {
28
+ decorations: Decoration[];
29
+ };
30
+ export {};
@@ -5,4 +5,13 @@ type StepRange = {
5
5
  toB: number;
6
6
  };
7
7
  export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[]) => StepRange[];
8
+ /**
9
+ * Check if the step was a valid attr change and affected the doc
10
+ *
11
+ * @param step Attr step to test
12
+ * @param beforeDoc Doc before the step
13
+ * @param afterDoc Doc after the step
14
+ * @returns Boolean if the change should show a decoration
15
+ */
16
+ export declare const stepIsValidAttrChange: (step: ProseMirrorStep, beforeDoc: PMNode, afterDoc: PMNode) => boolean;
8
17
  export {};
@@ -25,12 +25,12 @@ export declare const createBlockChangedDecoration: (change: {
25
25
  to: number;
26
26
  }, colourScheme?: "standard" | "traditional") => Decoration;
27
27
  interface DeletedContentDecorationProps {
28
- change: Change;
28
+ change: Pick<Change, 'fromA' | 'toA' | 'fromB' | 'deleted'>;
29
29
  colourScheme?: 'standard' | 'traditional';
30
30
  doc: PMNode;
31
31
  intl: IntlShape;
32
32
  newDoc: PMNode;
33
33
  nodeViewSerializer: NodeViewSerializer;
34
34
  }
35
- export declare const createDeletedContentDecoration: ({ change, doc, nodeViewSerializer, colourScheme, newDoc, intl, }: DeletedContentDecorationProps) => Decoration | undefined;
35
+ export declare const createDeletedContentDecoration: ({ change, doc, nodeViewSerializer, colourScheme, newDoc, intl, }: DeletedContentDecorationProps) => Decoration[] | undefined;
36
36
  export {};
@@ -0,0 +1,30 @@
1
+ import type { Change } from 'prosemirror-changeset';
2
+ import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
+ import type { NodeViewSerializer } from './NodeViewSerializer';
5
+ interface DeletedRowInfo {
6
+ fromA: number;
7
+ fromB: number;
8
+ rowIndex: number;
9
+ rowNode: PMNode;
10
+ toA: number;
11
+ }
12
+ type SimpleChange = Pick<Change, 'fromA' | 'toA' | 'fromB' | 'deleted'>;
13
+ interface DeletedRowsHandlerProps {
14
+ colourScheme?: 'standard' | 'traditional';
15
+ deletedRows: DeletedRowInfo[];
16
+ newDoc: PMNode;
17
+ nodeViewSerializer: NodeViewSerializer;
18
+ originalDoc: PMNode;
19
+ }
20
+ /**
21
+ * Creates decorations for deleted table rows
22
+ */
23
+ export declare const createDeletedRowsDecorations: ({ deletedRows, originalDoc, newDoc, nodeViewSerializer, colourScheme, }: DeletedRowsHandlerProps) => Decoration[];
24
+ /**
25
+ * Main function to handle deleted rows - computes diff and creates decorations
26
+ */
27
+ export declare const handleDeletedRows: (changes: SimpleChange[], originalDoc: PMNode, newDoc: PMNode, nodeViewSerializer: NodeViewSerializer, colourScheme?: "standard" | "traditional") => {
28
+ decorations: Decoration[];
29
+ };
30
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -30,15 +30,16 @@
30
30
  "dependencies": {
31
31
  "@atlaskit/adf-schema": "^51.3.2",
32
32
  "@atlaskit/editor-prosemirror": "7.0.0",
33
+ "@atlaskit/editor-tables": "^2.9.0",
33
34
  "@atlaskit/platform-feature-flags": "^1.1.0",
34
- "@atlaskit/tokens": "^7.0.0",
35
+ "@atlaskit/tokens": "^7.1.0",
35
36
  "@babel/runtime": "^7.0.0",
36
37
  "lodash": "^4.17.21",
37
38
  "memoize-one": "^6.0.0",
38
39
  "prosemirror-changeset": "^2.2.1"
39
40
  },
40
41
  "peerDependencies": {
41
- "@atlaskit/editor-common": "^110.19.0",
42
+ "@atlaskit/editor-common": "^110.22.0",
42
43
  "react": "^18.2.0"
43
44
  },
44
45
  "techstack": {
@@ -80,6 +81,9 @@
80
81
  "platform-feature-flags": {
81
82
  "platform_editor_ai_aifc_patch_beta_2": {
82
83
  "type": "boolean"
84
+ },
85
+ "platform_editor_ai_aifc_patch_ga": {
86
+ "type": "boolean"
83
87
  }
84
88
  }
85
89
  }
@@ -1,30 +0,0 @@
1
- {
2
- "extends": "../../../../tsconfig.entry-points.post-office.json",
3
- "compilerOptions": {
4
- "target": "es5",
5
- "outDir": "../../../../../post-office/tsDist/@atlaskit__editor-plugin-show-diff/app",
6
- "rootDir": "../",
7
- "composite": true
8
- },
9
- "include": [
10
- "../src/**/*.ts",
11
- "../src/**/*.tsx"
12
- ],
13
- "exclude": [
14
- "../src/**/__tests__/*",
15
- "../src/**/*.test.*",
16
- "../src/**/test.*",
17
- "../src/**/examples.*"
18
- ],
19
- "references": [
20
- {
21
- "path": "../../../platform/feature-flags/afm-post-office/tsconfig.json"
22
- },
23
- {
24
- "path": "../../../design-system/tokens/afm-post-office/tsconfig.json"
25
- },
26
- {
27
- "path": "../../editor-common/afm-post-office/tsconfig.json"
28
- }
29
- ]
30
- }