@atlaskit/editor-plugin-show-diff 6.2.19 → 6.3.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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 6.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 6.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`5d32941f15d07`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5d32941f15d07) -
14
+ EDITOR-5949: Change diffing logic to support closest block diffs + preliminary support for row
15
+ diffs.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+
3
21
  ## 6.2.19
4
22
 
5
23
  ### Patch Changes
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.calculateDiffDecorations = void 0;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
- var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
11
  var _isEqual = _interopRequireDefault(require("lodash/isEqual"));
12
12
  var _memoizeOne = _interopRequireDefault(require("memoize-one"));
13
13
  var _prosemirrorChangeset = require("prosemirror-changeset");
@@ -21,6 +21,7 @@ var _createInlineChangedDecoration = require("../decorations/createInlineChanged
21
21
  var _createNodeChangedDecorationWidget = require("../decorations/createNodeChangedDecorationWidget");
22
22
  var _getAttrChangeRanges = require("../decorations/utils/getAttrChangeRanges");
23
23
  var _getMarkChangeRanges = require("../decorations/utils/getMarkChangeRanges");
24
+ var _diffBySteps = require("./diffBySteps");
24
25
  var _groupChangesByBlock = require("./groupChangesByBlock");
25
26
  var _optimizeChanges = require("./optimizeChanges");
26
27
  var _simplifySteps = require("./simplifySteps");
@@ -34,9 +35,16 @@ var getChanges = function getChanges(_ref) {
34
35
  originalDoc = _ref.originalDoc,
35
36
  steppedDoc = _ref.steppedDoc,
36
37
  diffType = _ref.diffType,
37
- tr = _ref.tr;
38
- if (diffType === 'block' && (0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
39
- return (0, _groupChangesByBlock.groupChangesByBlock)(changeset.changes, originalDoc, steppedDoc);
38
+ tr = _ref.tr,
39
+ steps = _ref.steps;
40
+ if ((0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
41
+ if (diffType === 'step') {
42
+ return (0, _diffBySteps.diffBySteps)(originalDoc, steps);
43
+ }
44
+ if (diffType === 'block') {
45
+ return (0, _groupChangesByBlock.groupChangesByBlock)(changeset.changes, originalDoc, steppedDoc);
46
+ }
47
+ return (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, tr.doc);
40
48
  }
41
49
  var changes = (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, tr.doc);
42
50
  return (0, _optimizeChanges.optimizeChanges)(changes);
@@ -55,7 +63,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
55
63
  if (node.isBlock && (!(0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true) || pos + node.nodeSize <= to)) {
56
64
  var nodeEnd = pos + node.nodeSize;
57
65
  var isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
58
- var decoration = (0, _createBlockChangedDecoration.createBlockChangedDecoration)({
66
+ var blockChangedDecorations = (0, _createBlockChangedDecoration.createBlockChangedDecoration)({
59
67
  change: {
60
68
  from: pos,
61
69
  to: nodeEnd,
@@ -65,8 +73,8 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
65
73
  isInserted: isInserted,
66
74
  isActive: isActive
67
75
  });
68
- if (decoration) {
69
- decorations.push(decoration);
76
+ if (blockChangedDecorations.length) {
77
+ decorations.push.apply(decorations, (0, _toConsumableArray2.default)(blockChangedDecorations));
70
78
  }
71
79
  }
72
80
  });
@@ -145,7 +153,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
145
153
  originalDoc: originalDoc,
146
154
  steppedDoc: steppedDoc,
147
155
  diffType: diffType,
148
- tr: tr
156
+ tr: tr,
157
+ steps: steps
149
158
  });
150
159
  var decorations = [];
151
160
  changes.forEach(function (change) {
@@ -182,7 +191,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
182
191
  intl: intl,
183
192
  activeIndexPos: activeIndexPos
184
193
  }, (0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true) && {
185
- isInserted: !isInserted
194
+ isInserted: !isInserted,
195
+ diffType: diffType
186
196
  }));
187
197
  if (decoration) {
188
198
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)(decoration));
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.diffBySteps = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
11
+ 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; }
12
+ 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; }
13
+ 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; } } }; }
14
+ 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; } }
15
+ 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; }
16
+ var mapPosition = function mapPosition(mapping, pos) {
17
+ return mapping.map(pos);
18
+ };
19
+ var createMapping = function createMapping(maps) {
20
+ var mapping = new _transform.Mapping();
21
+ var _iterator = _createForOfIteratorHelper(maps),
22
+ _step;
23
+ try {
24
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
25
+ var map = _step.value;
26
+ mapping.appendMap(map);
27
+ }
28
+ } catch (err) {
29
+ _iterator.e(err);
30
+ } finally {
31
+ _iterator.f();
32
+ }
33
+ return mapping;
34
+ };
35
+ var createSpans = function createSpans(length) {
36
+ return length > 0 ? [{
37
+ length: length,
38
+ data: null
39
+ }] : [];
40
+ };
41
+ var mergeOverlappingByNewDocRange = function mergeOverlappingByNewDocRange(changes) {
42
+ if (changes.length <= 1) {
43
+ return changes;
44
+ }
45
+ var sortedChanges = (0, _toConsumableArray2.default)(changes).sort(function (left, right) {
46
+ return left.fromB - right.fromB;
47
+ });
48
+ var merged = [];
49
+ var current = _objectSpread({}, sortedChanges[0]);
50
+ for (var i = 1; i < sortedChanges.length; i++) {
51
+ var next = sortedChanges[i];
52
+ var isOverlapping = next.fromB <= current.toB;
53
+ if (isOverlapping) {
54
+ current = {
55
+ fromA: Math.min(current.fromA, next.fromA),
56
+ toA: Math.max(current.toA, next.toA),
57
+ fromB: Math.min(current.fromB, next.fromB),
58
+ toB: Math.max(current.toB, next.toB),
59
+ deleted: [].concat((0, _toConsumableArray2.default)(current.deleted), (0, _toConsumableArray2.default)(next.deleted)),
60
+ inserted: [].concat((0, _toConsumableArray2.default)(current.inserted), (0, _toConsumableArray2.default)(next.inserted))
61
+ };
62
+ } else {
63
+ merged.push(current);
64
+ current = _objectSpread({}, next);
65
+ }
66
+ }
67
+ merged.push(current);
68
+ return merged;
69
+ };
70
+ var diffBySteps = exports.diffBySteps = function diffBySteps(originalDoc, steps) {
71
+ var changes = [];
72
+ var currentDoc = originalDoc;
73
+ var successfulStepMaps = [];
74
+ var rangedSteps = [];
75
+ var _iterator2 = _createForOfIteratorHelper(steps),
76
+ _step2;
77
+ try {
78
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
79
+ var step = _step2.value;
80
+ var result = step.apply(currentDoc);
81
+ if (result.failed !== null || !result.doc) {
82
+ continue;
83
+ }
84
+ var stepMap = step.getMap();
85
+ var rangeStep = step;
86
+ if (typeof rangeStep.from === 'number' && typeof rangeStep.to === 'number') {
87
+ rangedSteps.push({
88
+ from: rangeStep.from,
89
+ to: rangeStep.to,
90
+ mapIndex: successfulStepMaps.length,
91
+ stepMap: stepMap
92
+ });
93
+ }
94
+ successfulStepMaps.push(stepMap);
95
+ currentDoc = result.doc;
96
+ }
97
+ } catch (err) {
98
+ _iterator2.e(err);
99
+ } finally {
100
+ _iterator2.f();
101
+ }
102
+ for (var _i = 0, _rangedSteps = rangedSteps; _i < _rangedSteps.length; _i++) {
103
+ var rangedStep = _rangedSteps[_i];
104
+ // Mapping from original -> doc before this step.
105
+ var originalToBeforeStep = createMapping(successfulStepMaps.slice(0, rangedStep.mapIndex));
106
+ var beforeStepToOriginal = originalToBeforeStep.invert();
107
+ var fromA = mapPosition(beforeStepToOriginal, rangedStep.from);
108
+ var toA = mapPosition(beforeStepToOriginal, rangedStep.to);
109
+
110
+ // Map the step range into final steppedDoc coordinates.
111
+ var fromAfterStep = rangedStep.stepMap.map(rangedStep.from, -1);
112
+ var toAfterStep = rangedStep.stepMap.map(rangedStep.to, 1);
113
+ var afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
114
+ var fromB = mapPosition(afterStepToFinal, fromAfterStep);
115
+ var toB = mapPosition(afterStepToFinal, toAfterStep);
116
+ changes.push({
117
+ fromA: fromA,
118
+ toA: toA,
119
+ fromB: fromB,
120
+ toB: toB,
121
+ deleted: createSpans(Math.max(0, toA - fromA)),
122
+ inserted: createSpans(Math.max(0, toB - fromB))
123
+ });
124
+ }
125
+ return mergeOverlappingByNewDocRange(changes);
126
+ };
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.createBlockChangedDecoration = void 0;
7
+ var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
7
8
  var _view = require("@atlaskit/editor-prosemirror/view");
8
9
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
9
10
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
@@ -27,12 +28,22 @@ var getBlockNodeStyle = function getBlockNodeStyle(_ref) {
27
28
  var isTraditional = colorScheme === 'traditional';
28
29
  if (['mediaSingle', 'mediaGroup', 'table',
29
30
  // Handle table separately to avoid border issues
30
- 'tableRow', 'tableCell', 'tableHeader', 'paragraph',
31
+ 'tableRow', 'paragraph',
31
32
  // Paragraph and heading nodes do not need special styling
32
33
  'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
33
34
  // Layout nodes do not need special styling
34
35
  return undefined;
35
36
  }
37
+ if (['tableCell', 'tableHeader'].includes(nodeName)) {
38
+ if ((0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
39
+ // This is used for positioning the cell overlay widget decorations
40
+ return (0, _lazyNodeView.convertToInlineCss)({
41
+ position: 'relative'
42
+ });
43
+ }
44
+ // When gate is off, it should return undefined as above
45
+ return undefined;
46
+ }
36
47
  if (['extension', 'embedCard', 'listItem'].includes(nodeName)) {
37
48
  if ((0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
38
49
  if (isInserted) {
@@ -98,6 +109,17 @@ var createBlockChangedDecoration = exports.createBlockChangedDecoration = functi
98
109
  isInserted = _ref2$isInserted === void 0 ? true : _ref2$isInserted,
99
110
  _ref2$isActive = _ref2.isActive,
100
111
  isActive = _ref2$isActive === void 0 ? false : _ref2$isActive;
112
+ var decorations = [];
113
+ if ((0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true) && ['tableCell', 'tableHeader'].includes(change.name)) {
114
+ var cellOverlay = document.createElement('div');
115
+ var cellOverlayStyle = isInserted ? colorScheme === 'traditional' ? _traditional.traditionalAddedCellOverlayStyle : _standard.addedCellOverlayStyle : colorScheme === 'traditional' ? _traditional.deletedTraditionalCellOverlayStyle : _standard.deletedCellOverlayStyle;
116
+ cellOverlay.setAttribute('style', cellOverlayStyle);
117
+ decorations.push(
118
+ // change.to - 1 to position the overlay inside the end of the cell
119
+ _view.Decoration.widget(change.to - 1, cellOverlay, {
120
+ key: "diff-widget-cell-overlay-".concat(change.to)
121
+ }));
122
+ }
101
123
  var style = getBlockNodeStyle({
102
124
  nodeName: change.name,
103
125
  colorScheme: colorScheme,
@@ -114,24 +136,24 @@ var createBlockChangedDecoration = exports.createBlockChangedDecoration = functi
114
136
  var className = getNodeClass(change.name);
115
137
  if ((0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation')) {
116
138
  if (style || className) {
117
- return _view.Decoration.node(change.from, change.to, {
139
+ decorations.push(_view.Decoration.node(change.from, change.to, {
118
140
  style: style,
119
141
  'data-testid': 'show-diff-changed-decoration-node',
120
142
  class: className
121
143
  }, {
122
144
  key: 'diff-block',
123
145
  nodeName: change.name
124
- });
125
- } else {
126
- return undefined;
146
+ }));
127
147
  }
148
+ } else {
149
+ decorations.push(_view.Decoration.node(change.from, change.to, {
150
+ style: style,
151
+ 'data-testid': 'show-diff-changed-decoration-node',
152
+ class: className
153
+ }, {
154
+ key: 'diff-block',
155
+ nodeName: change.name
156
+ }));
128
157
  }
129
- return _view.Decoration.node(change.from, change.to, {
130
- style: style,
131
- 'data-testid': 'show-diff-changed-decoration-node',
132
- class: className
133
- }, {
134
- key: 'diff-block',
135
- nodeName: change.name
136
- });
158
+ return decorations;
137
159
  };
@@ -20,7 +20,10 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
20
20
  /**
21
21
  * Extracts information about deleted table rows from a change
22
22
  */
23
- var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc) {
23
+ var extractChangedRows = function extractChangedRows(_ref) {
24
+ var change = _ref.change,
25
+ originalDoc = _ref.originalDoc,
26
+ newDoc = _ref.newDoc;
24
27
  var changedRows = [];
25
28
 
26
29
  // Find the table in the original document
@@ -42,7 +45,6 @@ var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc
42
45
  return changedRows;
43
46
  }
44
47
  var newTableMap = _tableMap.TableMap.get(tableNew.node);
45
-
46
48
  // If no rows were changed, return empty
47
49
  if (oldTableMap.height <= newTableMap.height ||
48
50
  // For now ignore if there are column deletions as well
@@ -61,7 +63,7 @@ var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc
61
63
 
62
64
  // Check if this row overlaps with the deletion range
63
65
  var rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
64
- if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
66
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && ((0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true) || !isEmptyRow(rowNode))) {
65
67
  var startOfRow = newTableMap.mapByRow.slice().reverse().find(function (row) {
66
68
  return row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos;
67
69
  });
@@ -132,7 +134,7 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, nodeViewSerializ
132
134
  if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
133
135
  var nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
134
136
  if (nodeView) {
135
- if (isInserted && nodeView instanceof HTMLElement && (0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
137
+ if (nodeView instanceof HTMLElement && (0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
136
138
  var overlay = document.createElement('span');
137
139
  var overlayStyle = colorScheme === 'traditional' ? isInserted ? _traditional.traditionalAddedCellOverlayStyle : _traditional.deletedTraditionalCellOverlayStyle : isInserted ? _standard.addedCellOverlayStyle : _standard.deletedCellOverlayStyle;
138
140
  overlay.setAttribute('style', overlayStyle);
@@ -154,7 +156,10 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, nodeViewSerializ
154
156
  /**
155
157
  * Expands a diff to include whole changed rows when table rows are affected
156
158
  */
157
- var expandDiffForChangedRows = function expandDiffForChangedRows(changes, originalDoc, newDoc) {
159
+ var expandDiffForChangedRows = function expandDiffForChangedRows(_ref2) {
160
+ var changes = _ref2.changes,
161
+ originalDoc = _ref2.originalDoc,
162
+ newDoc = _ref2.newDoc;
158
163
  var rowInfo = [];
159
164
  var _iterator = _createForOfIteratorHelper(changes),
160
165
  _step;
@@ -162,7 +167,11 @@ var expandDiffForChangedRows = function expandDiffForChangedRows(changes, origin
162
167
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
163
168
  var change = _step.value;
164
169
  // Check if this change affects table content
165
- var changedRows = extractChangedRows(change, originalDoc, newDoc);
170
+ var changedRows = extractChangedRows({
171
+ change: change,
172
+ originalDoc: originalDoc,
173
+ newDoc: newDoc
174
+ });
166
175
  if (changedRows.length > 0) {
167
176
  rowInfo.push.apply(rowInfo, (0, _toConsumableArray2.default)(changedRows));
168
177
  }
@@ -178,18 +187,22 @@ var expandDiffForChangedRows = function expandDiffForChangedRows(changes, origin
178
187
  /**
179
188
  * Main function to handle deleted rows - computes diff and creates decorations
180
189
  */
181
- var createChangedRowDecorationWidgets = exports.createChangedRowDecorationWidgets = function createChangedRowDecorationWidgets(_ref) {
182
- var changes = _ref.changes,
183
- originalDoc = _ref.originalDoc,
184
- newDoc = _ref.newDoc,
185
- nodeViewSerializer = _ref.nodeViewSerializer,
186
- colorScheme = _ref.colorScheme,
187
- _ref$isInserted = _ref.isInserted,
188
- isInserted = _ref$isInserted === void 0 ? false : _ref$isInserted;
190
+ var createChangedRowDecorationWidgets = exports.createChangedRowDecorationWidgets = function createChangedRowDecorationWidgets(_ref3) {
191
+ var changes = _ref3.changes,
192
+ originalDoc = _ref3.originalDoc,
193
+ newDoc = _ref3.newDoc,
194
+ nodeViewSerializer = _ref3.nodeViewSerializer,
195
+ colorScheme = _ref3.colorScheme,
196
+ _ref3$isInserted = _ref3.isInserted,
197
+ isInserted = _ref3$isInserted === void 0 ? false : _ref3$isInserted;
189
198
  // First, expand the changes to include complete deleted rows
190
- var changedRows = expandDiffForChangedRows(changes.filter(function (change) {
191
- return change.deleted.length > 0;
192
- }), originalDoc, newDoc);
199
+ var changedRows = expandDiffForChangedRows({
200
+ changes: changes.filter(function (change) {
201
+ return change.deleted.length > 0;
202
+ }),
203
+ originalDoc: originalDoc,
204
+ newDoc: newDoc
205
+ });
193
206
  return changedRows.map(function (changedRow) {
194
207
  var rowDOM = createChangedRowDOM(changedRow.rowNode, nodeViewSerializer, colorScheme, isInserted);
195
208
 
@@ -12,6 +12,7 @@ import { createInlineChangedDecoration } from '../decorations/createInlineChange
12
12
  import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
13
13
  import { getAttrChangeRanges, stepIsValidAttrChange } from '../decorations/utils/getAttrChangeRanges';
14
14
  import { getMarkChangeRanges } from '../decorations/utils/getMarkChangeRanges';
15
+ import { diffBySteps } from './diffBySteps';
15
16
  import { groupChangesByBlock } from './groupChangesByBlock';
16
17
  import { optimizeChanges } from './optimizeChanges';
17
18
  import { simplifySteps } from './simplifySteps';
@@ -20,10 +21,17 @@ const getChanges = ({
20
21
  originalDoc,
21
22
  steppedDoc,
22
23
  diffType,
23
- tr
24
+ tr,
25
+ steps
24
26
  }) => {
25
- if (diffType === 'block' && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
26
- return groupChangesByBlock(changeset.changes, originalDoc, steppedDoc);
27
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
28
+ if (diffType === 'step') {
29
+ return diffBySteps(originalDoc, steps);
30
+ }
31
+ if (diffType === 'block') {
32
+ return groupChangesByBlock(changeset.changes, originalDoc, steppedDoc);
33
+ }
34
+ return simplifyChanges(changeset.changes, tr.doc);
27
35
  }
28
36
  const changes = simplifyChanges(changeset.changes, tr.doc);
29
37
  return optimizeChanges(changes);
@@ -42,7 +50,7 @@ const calculateNodesForBlockDecoration = ({
42
50
  if (node.isBlock && (!expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || pos + node.nodeSize <= to)) {
43
51
  const nodeEnd = pos + node.nodeSize;
44
52
  const isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
45
- const decoration = createBlockChangedDecoration({
53
+ const blockChangedDecorations = createBlockChangedDecoration({
46
54
  change: {
47
55
  from: pos,
48
56
  to: nodeEnd,
@@ -52,8 +60,8 @@ const calculateNodesForBlockDecoration = ({
52
60
  isInserted,
53
61
  isActive
54
62
  });
55
- if (decoration) {
56
- decorations.push(decoration);
63
+ if (blockChangedDecorations.length) {
64
+ decorations.push(...blockChangedDecorations);
57
65
  }
58
66
  }
59
67
  });
@@ -126,7 +134,8 @@ const calculateDiffDecorationsInner = ({
126
134
  originalDoc,
127
135
  steppedDoc,
128
136
  diffType,
129
- tr
137
+ tr,
138
+ steps
130
139
  });
131
140
  const decorations = [];
132
141
  changes.forEach(change => {
@@ -164,7 +173,8 @@ const calculateDiffDecorationsInner = ({
164
173
  intl,
165
174
  activeIndexPos,
166
175
  ...(expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && {
167
- isInserted: !isInserted
176
+ isInserted: !isInserted,
177
+ diffType
168
178
  })
169
179
  });
170
180
  if (decoration) {
@@ -0,0 +1,91 @@
1
+ import { Mapping } from '@atlaskit/editor-prosemirror/transform';
2
+ const mapPosition = (mapping, pos) => mapping.map(pos);
3
+ const createMapping = maps => {
4
+ const mapping = new Mapping();
5
+ for (const map of maps) {
6
+ mapping.appendMap(map);
7
+ }
8
+ return mapping;
9
+ };
10
+ const createSpans = length => length > 0 ? [{
11
+ length,
12
+ data: null
13
+ }] : [];
14
+ const mergeOverlappingByNewDocRange = changes => {
15
+ if (changes.length <= 1) {
16
+ return changes;
17
+ }
18
+ const sortedChanges = [...changes].sort((left, right) => left.fromB - right.fromB);
19
+ const merged = [];
20
+ let current = {
21
+ ...sortedChanges[0]
22
+ };
23
+ for (let i = 1; i < sortedChanges.length; i++) {
24
+ const next = sortedChanges[i];
25
+ const isOverlapping = next.fromB <= current.toB;
26
+ if (isOverlapping) {
27
+ current = {
28
+ fromA: Math.min(current.fromA, next.fromA),
29
+ toA: Math.max(current.toA, next.toA),
30
+ fromB: Math.min(current.fromB, next.fromB),
31
+ toB: Math.max(current.toB, next.toB),
32
+ deleted: [...current.deleted, ...next.deleted],
33
+ inserted: [...current.inserted, ...next.inserted]
34
+ };
35
+ } else {
36
+ merged.push(current);
37
+ current = {
38
+ ...next
39
+ };
40
+ }
41
+ }
42
+ merged.push(current);
43
+ return merged;
44
+ };
45
+ export const diffBySteps = (originalDoc, steps) => {
46
+ const changes = [];
47
+ let currentDoc = originalDoc;
48
+ const successfulStepMaps = [];
49
+ const rangedSteps = [];
50
+ for (const step of steps) {
51
+ const result = step.apply(currentDoc);
52
+ if (result.failed !== null || !result.doc) {
53
+ continue;
54
+ }
55
+ const stepMap = step.getMap();
56
+ const rangeStep = step;
57
+ if (typeof rangeStep.from === 'number' && typeof rangeStep.to === 'number') {
58
+ rangedSteps.push({
59
+ from: rangeStep.from,
60
+ to: rangeStep.to,
61
+ mapIndex: successfulStepMaps.length,
62
+ stepMap
63
+ });
64
+ }
65
+ successfulStepMaps.push(stepMap);
66
+ currentDoc = result.doc;
67
+ }
68
+ for (const rangedStep of rangedSteps) {
69
+ // Mapping from original -> doc before this step.
70
+ const originalToBeforeStep = createMapping(successfulStepMaps.slice(0, rangedStep.mapIndex));
71
+ const beforeStepToOriginal = originalToBeforeStep.invert();
72
+ const fromA = mapPosition(beforeStepToOriginal, rangedStep.from);
73
+ const toA = mapPosition(beforeStepToOriginal, rangedStep.to);
74
+
75
+ // Map the step range into final steppedDoc coordinates.
76
+ const fromAfterStep = rangedStep.stepMap.map(rangedStep.from, -1);
77
+ const toAfterStep = rangedStep.stepMap.map(rangedStep.to, 1);
78
+ const afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
79
+ const fromB = mapPosition(afterStepToFinal, fromAfterStep);
80
+ const toB = mapPosition(afterStepToFinal, toAfterStep);
81
+ changes.push({
82
+ fromA,
83
+ toA,
84
+ fromB,
85
+ toB,
86
+ deleted: createSpans(Math.max(0, toA - fromA)),
87
+ inserted: createSpans(Math.max(0, toB - fromB))
88
+ });
89
+ }
90
+ return mergeOverlappingByNewDocRange(changes);
91
+ };
@@ -1,8 +1,9 @@
1
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
1
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
2
3
  import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
- import { standardDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode } from './colorSchemes/standard';
5
- import { traditionalDecorationMarkerVariable, traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariable, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNode, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNode, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNode, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNode, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode } from './colorSchemes/traditional';
5
+ import { standardDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
6
+ import { traditionalDecorationMarkerVariable, traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariable, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNode, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNode, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNode, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNode, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from './colorSchemes/traditional';
6
7
  const getNodeClass = name => {
7
8
  switch (name) {
8
9
  case 'extension':
@@ -20,12 +21,22 @@ const getBlockNodeStyle = ({
20
21
  const isTraditional = colorScheme === 'traditional';
21
22
  if (['mediaSingle', 'mediaGroup', 'table',
22
23
  // Handle table separately to avoid border issues
23
- 'tableRow', 'tableCell', 'tableHeader', 'paragraph',
24
+ 'tableRow', 'paragraph',
24
25
  // Paragraph and heading nodes do not need special styling
25
26
  'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
26
27
  // Layout nodes do not need special styling
27
28
  return undefined;
28
29
  }
30
+ if (['tableCell', 'tableHeader'].includes(nodeName)) {
31
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
32
+ // This is used for positioning the cell overlay widget decorations
33
+ return convertToInlineCss({
34
+ position: 'relative'
35
+ });
36
+ }
37
+ // When gate is off, it should return undefined as above
38
+ return undefined;
39
+ }
29
40
  if (['extension', 'embedCard', 'listItem'].includes(nodeName)) {
30
41
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
31
42
  if (isInserted) {
@@ -90,6 +101,17 @@ export const createBlockChangedDecoration = ({
90
101
  isInserted = true,
91
102
  isActive = false
92
103
  }) => {
104
+ const decorations = [];
105
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && ['tableCell', 'tableHeader'].includes(change.name)) {
106
+ const cellOverlay = document.createElement('div');
107
+ const cellOverlayStyle = isInserted ? colorScheme === 'traditional' ? traditionalAddedCellOverlayStyle : addedCellOverlayStyle : colorScheme === 'traditional' ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
108
+ cellOverlay.setAttribute('style', cellOverlayStyle);
109
+ decorations.push(
110
+ // change.to - 1 to position the overlay inside the end of the cell
111
+ Decoration.widget(change.to - 1, cellOverlay, {
112
+ key: `diff-widget-cell-overlay-${change.to}`
113
+ }));
114
+ }
93
115
  let style = getBlockNodeStyle({
94
116
  nodeName: change.name,
95
117
  colorScheme,
@@ -106,24 +128,24 @@ export const createBlockChangedDecoration = ({
106
128
  const className = getNodeClass(change.name);
107
129
  if (fg('platform_editor_show_diff_scroll_navigation')) {
108
130
  if (style || className) {
109
- return Decoration.node(change.from, change.to, {
131
+ decorations.push(Decoration.node(change.from, change.to, {
110
132
  style: style,
111
133
  'data-testid': 'show-diff-changed-decoration-node',
112
134
  class: className
113
135
  }, {
114
136
  key: 'diff-block',
115
137
  nodeName: change.name
116
- });
117
- } else {
118
- return undefined;
138
+ }));
119
139
  }
140
+ } else {
141
+ decorations.push(Decoration.node(change.from, change.to, {
142
+ style,
143
+ 'data-testid': 'show-diff-changed-decoration-node',
144
+ class: className
145
+ }, {
146
+ key: 'diff-block',
147
+ nodeName: change.name
148
+ }));
120
149
  }
121
- return Decoration.node(change.from, change.to, {
122
- style,
123
- 'data-testid': 'show-diff-changed-decoration-node',
124
- class: className
125
- }, {
126
- key: 'diff-block',
127
- nodeName: change.name
128
- });
150
+ return decorations;
129
151
  };
@@ -9,7 +9,11 @@ import { findSafeInsertPos } from './utils/findSafeInsertPos';
9
9
  /**
10
10
  * Extracts information about deleted table rows from a change
11
11
  */
12
- const extractChangedRows = (change, originalDoc, newDoc) => {
12
+ const extractChangedRows = ({
13
+ change,
14
+ originalDoc,
15
+ newDoc
16
+ }) => {
13
17
  const changedRows = [];
14
18
 
15
19
  // Find the table in the original document
@@ -27,7 +31,6 @@ const extractChangedRows = (change, originalDoc, newDoc) => {
27
31
  return changedRows;
28
32
  }
29
33
  const newTableMap = TableMap.get(tableNew.node);
30
-
31
34
  // If no rows were changed, return empty
32
35
  if (oldTableMap.height <= newTableMap.height ||
33
36
  // For now ignore if there are column deletions as well
@@ -46,7 +49,7 @@ const extractChangedRows = (change, originalDoc, newDoc) => {
46
49
 
47
50
  // Check if this row overlaps with the deletion range
48
51
  const rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
49
- if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
52
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || !isEmptyRow(rowNode))) {
50
53
  const startOfRow = newTableMap.mapByRow.slice().reverse().find(row => row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos);
51
54
  changedRows.push({
52
55
  rowIndex,
@@ -113,7 +116,7 @@ const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserte
113
116
  if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
114
117
  const nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
115
118
  if (nodeView) {
116
- if (isInserted && nodeView instanceof HTMLElement && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
119
+ if (nodeView instanceof HTMLElement && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
117
120
  const overlay = document.createElement('span');
118
121
  const overlayStyle = colorScheme === 'traditional' ? isInserted ? traditionalAddedCellOverlayStyle : deletedTraditionalCellOverlayStyle : isInserted ? addedCellOverlayStyle : deletedCellOverlayStyle;
119
122
  overlay.setAttribute('style', overlayStyle);
@@ -135,11 +138,19 @@ const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserte
135
138
  /**
136
139
  * Expands a diff to include whole changed rows when table rows are affected
137
140
  */
138
- const expandDiffForChangedRows = (changes, originalDoc, newDoc) => {
141
+ const expandDiffForChangedRows = ({
142
+ changes,
143
+ originalDoc,
144
+ newDoc
145
+ }) => {
139
146
  const rowInfo = [];
140
147
  for (const change of changes) {
141
148
  // Check if this change affects table content
142
- const changedRows = extractChangedRows(change, originalDoc, newDoc);
149
+ const changedRows = extractChangedRows({
150
+ change,
151
+ originalDoc,
152
+ newDoc
153
+ });
143
154
  if (changedRows.length > 0) {
144
155
  rowInfo.push(...changedRows);
145
156
  }
@@ -159,7 +170,11 @@ export const createChangedRowDecorationWidgets = ({
159
170
  isInserted = false
160
171
  }) => {
161
172
  // First, expand the changes to include complete deleted rows
162
- const changedRows = expandDiffForChangedRows(changes.filter(change => change.deleted.length > 0), originalDoc, newDoc);
173
+ const changedRows = expandDiffForChangedRows({
174
+ changes: changes.filter(change => change.deleted.length > 0),
175
+ originalDoc,
176
+ newDoc
177
+ });
163
178
  return changedRows.map(changedRow => {
164
179
  const rowDOM = createChangedRowDOM(changedRow.rowNode, nodeViewSerializer, colorScheme, isInserted);
165
180
 
@@ -1,6 +1,6 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
- import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
4
4
  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; }
5
5
  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) { _defineProperty(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; }
6
6
  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; } } }; }
@@ -20,6 +20,7 @@ import { createInlineChangedDecoration } from '../decorations/createInlineChange
20
20
  import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
21
21
  import { getAttrChangeRanges, stepIsValidAttrChange } from '../decorations/utils/getAttrChangeRanges';
22
22
  import { getMarkChangeRanges } from '../decorations/utils/getMarkChangeRanges';
23
+ import { diffBySteps } from './diffBySteps';
23
24
  import { groupChangesByBlock } from './groupChangesByBlock';
24
25
  import { optimizeChanges } from './optimizeChanges';
25
26
  import { simplifySteps } from './simplifySteps';
@@ -28,9 +29,16 @@ var getChanges = function getChanges(_ref) {
28
29
  originalDoc = _ref.originalDoc,
29
30
  steppedDoc = _ref.steppedDoc,
30
31
  diffType = _ref.diffType,
31
- tr = _ref.tr;
32
- if (diffType === 'block' && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
33
- return groupChangesByBlock(changeset.changes, originalDoc, steppedDoc);
32
+ tr = _ref.tr,
33
+ steps = _ref.steps;
34
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
35
+ if (diffType === 'step') {
36
+ return diffBySteps(originalDoc, steps);
37
+ }
38
+ if (diffType === 'block') {
39
+ return groupChangesByBlock(changeset.changes, originalDoc, steppedDoc);
40
+ }
41
+ return simplifyChanges(changeset.changes, tr.doc);
34
42
  }
35
43
  var changes = simplifyChanges(changeset.changes, tr.doc);
36
44
  return optimizeChanges(changes);
@@ -49,7 +57,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
49
57
  if (node.isBlock && (!expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || pos + node.nodeSize <= to)) {
50
58
  var nodeEnd = pos + node.nodeSize;
51
59
  var isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
52
- var decoration = createBlockChangedDecoration({
60
+ var blockChangedDecorations = createBlockChangedDecoration({
53
61
  change: {
54
62
  from: pos,
55
63
  to: nodeEnd,
@@ -59,8 +67,8 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
59
67
  isInserted: isInserted,
60
68
  isActive: isActive
61
69
  });
62
- if (decoration) {
63
- decorations.push(decoration);
70
+ if (blockChangedDecorations.length) {
71
+ decorations.push.apply(decorations, _toConsumableArray(blockChangedDecorations));
64
72
  }
65
73
  }
66
74
  });
@@ -139,7 +147,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
139
147
  originalDoc: originalDoc,
140
148
  steppedDoc: steppedDoc,
141
149
  diffType: diffType,
142
- tr: tr
150
+ tr: tr,
151
+ steps: steps
143
152
  });
144
153
  var decorations = [];
145
154
  changes.forEach(function (change) {
@@ -176,7 +185,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
176
185
  intl: intl,
177
186
  activeIndexPos: activeIndexPos
178
187
  }, expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && {
179
- isInserted: !isInserted
188
+ isInserted: !isInserted,
189
+ diffType: diffType
180
190
  }));
181
191
  if (decoration) {
182
192
  decorations.push.apply(decorations, _toConsumableArray(decoration));
@@ -0,0 +1,119 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
+ 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; }
4
+ 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) { _defineProperty(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; }
5
+ 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; } } }; }
6
+ 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; } }
7
+ 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; }
8
+ import { Mapping } from '@atlaskit/editor-prosemirror/transform';
9
+ var mapPosition = function mapPosition(mapping, pos) {
10
+ return mapping.map(pos);
11
+ };
12
+ var createMapping = function createMapping(maps) {
13
+ var mapping = new Mapping();
14
+ var _iterator = _createForOfIteratorHelper(maps),
15
+ _step;
16
+ try {
17
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
18
+ var map = _step.value;
19
+ mapping.appendMap(map);
20
+ }
21
+ } catch (err) {
22
+ _iterator.e(err);
23
+ } finally {
24
+ _iterator.f();
25
+ }
26
+ return mapping;
27
+ };
28
+ var createSpans = function createSpans(length) {
29
+ return length > 0 ? [{
30
+ length: length,
31
+ data: null
32
+ }] : [];
33
+ };
34
+ var mergeOverlappingByNewDocRange = function mergeOverlappingByNewDocRange(changes) {
35
+ if (changes.length <= 1) {
36
+ return changes;
37
+ }
38
+ var sortedChanges = _toConsumableArray(changes).sort(function (left, right) {
39
+ return left.fromB - right.fromB;
40
+ });
41
+ var merged = [];
42
+ var current = _objectSpread({}, sortedChanges[0]);
43
+ for (var i = 1; i < sortedChanges.length; i++) {
44
+ var next = sortedChanges[i];
45
+ var isOverlapping = next.fromB <= current.toB;
46
+ if (isOverlapping) {
47
+ current = {
48
+ fromA: Math.min(current.fromA, next.fromA),
49
+ toA: Math.max(current.toA, next.toA),
50
+ fromB: Math.min(current.fromB, next.fromB),
51
+ toB: Math.max(current.toB, next.toB),
52
+ deleted: [].concat(_toConsumableArray(current.deleted), _toConsumableArray(next.deleted)),
53
+ inserted: [].concat(_toConsumableArray(current.inserted), _toConsumableArray(next.inserted))
54
+ };
55
+ } else {
56
+ merged.push(current);
57
+ current = _objectSpread({}, next);
58
+ }
59
+ }
60
+ merged.push(current);
61
+ return merged;
62
+ };
63
+ export var diffBySteps = function diffBySteps(originalDoc, steps) {
64
+ var changes = [];
65
+ var currentDoc = originalDoc;
66
+ var successfulStepMaps = [];
67
+ var rangedSteps = [];
68
+ var _iterator2 = _createForOfIteratorHelper(steps),
69
+ _step2;
70
+ try {
71
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
72
+ var step = _step2.value;
73
+ var result = step.apply(currentDoc);
74
+ if (result.failed !== null || !result.doc) {
75
+ continue;
76
+ }
77
+ var stepMap = step.getMap();
78
+ var rangeStep = step;
79
+ if (typeof rangeStep.from === 'number' && typeof rangeStep.to === 'number') {
80
+ rangedSteps.push({
81
+ from: rangeStep.from,
82
+ to: rangeStep.to,
83
+ mapIndex: successfulStepMaps.length,
84
+ stepMap: stepMap
85
+ });
86
+ }
87
+ successfulStepMaps.push(stepMap);
88
+ currentDoc = result.doc;
89
+ }
90
+ } catch (err) {
91
+ _iterator2.e(err);
92
+ } finally {
93
+ _iterator2.f();
94
+ }
95
+ for (var _i = 0, _rangedSteps = rangedSteps; _i < _rangedSteps.length; _i++) {
96
+ var rangedStep = _rangedSteps[_i];
97
+ // Mapping from original -> doc before this step.
98
+ var originalToBeforeStep = createMapping(successfulStepMaps.slice(0, rangedStep.mapIndex));
99
+ var beforeStepToOriginal = originalToBeforeStep.invert();
100
+ var fromA = mapPosition(beforeStepToOriginal, rangedStep.from);
101
+ var toA = mapPosition(beforeStepToOriginal, rangedStep.to);
102
+
103
+ // Map the step range into final steppedDoc coordinates.
104
+ var fromAfterStep = rangedStep.stepMap.map(rangedStep.from, -1);
105
+ var toAfterStep = rangedStep.stepMap.map(rangedStep.to, 1);
106
+ var afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
107
+ var fromB = mapPosition(afterStepToFinal, fromAfterStep);
108
+ var toB = mapPosition(afterStepToFinal, toAfterStep);
109
+ changes.push({
110
+ fromA: fromA,
111
+ toA: toA,
112
+ fromB: fromB,
113
+ toB: toB,
114
+ deleted: createSpans(Math.max(0, toA - fromA)),
115
+ inserted: createSpans(Math.max(0, toB - fromB))
116
+ });
117
+ }
118
+ return mergeOverlappingByNewDocRange(changes);
119
+ };
@@ -1,8 +1,9 @@
1
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
1
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
2
3
  import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
- import { standardDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode } from './colorSchemes/standard';
5
- import { traditionalDecorationMarkerVariable, traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariable, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNode, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNode, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNode, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNode, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode } from './colorSchemes/traditional';
5
+ import { standardDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
6
+ import { traditionalDecorationMarkerVariable, traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariable, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNode, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNode, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNode, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNode, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from './colorSchemes/traditional';
6
7
  var getNodeClass = function getNodeClass(name) {
7
8
  switch (name) {
8
9
  case 'extension':
@@ -21,12 +22,22 @@ var getBlockNodeStyle = function getBlockNodeStyle(_ref) {
21
22
  var isTraditional = colorScheme === 'traditional';
22
23
  if (['mediaSingle', 'mediaGroup', 'table',
23
24
  // Handle table separately to avoid border issues
24
- 'tableRow', 'tableCell', 'tableHeader', 'paragraph',
25
+ 'tableRow', 'paragraph',
25
26
  // Paragraph and heading nodes do not need special styling
26
27
  'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
27
28
  // Layout nodes do not need special styling
28
29
  return undefined;
29
30
  }
31
+ if (['tableCell', 'tableHeader'].includes(nodeName)) {
32
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
33
+ // This is used for positioning the cell overlay widget decorations
34
+ return convertToInlineCss({
35
+ position: 'relative'
36
+ });
37
+ }
38
+ // When gate is off, it should return undefined as above
39
+ return undefined;
40
+ }
30
41
  if (['extension', 'embedCard', 'listItem'].includes(nodeName)) {
31
42
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
32
43
  if (isInserted) {
@@ -92,6 +103,17 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
92
103
  isInserted = _ref2$isInserted === void 0 ? true : _ref2$isInserted,
93
104
  _ref2$isActive = _ref2.isActive,
94
105
  isActive = _ref2$isActive === void 0 ? false : _ref2$isActive;
106
+ var decorations = [];
107
+ if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && ['tableCell', 'tableHeader'].includes(change.name)) {
108
+ var cellOverlay = document.createElement('div');
109
+ var cellOverlayStyle = isInserted ? colorScheme === 'traditional' ? traditionalAddedCellOverlayStyle : addedCellOverlayStyle : colorScheme === 'traditional' ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
110
+ cellOverlay.setAttribute('style', cellOverlayStyle);
111
+ decorations.push(
112
+ // change.to - 1 to position the overlay inside the end of the cell
113
+ Decoration.widget(change.to - 1, cellOverlay, {
114
+ key: "diff-widget-cell-overlay-".concat(change.to)
115
+ }));
116
+ }
95
117
  var style = getBlockNodeStyle({
96
118
  nodeName: change.name,
97
119
  colorScheme: colorScheme,
@@ -108,24 +130,24 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
108
130
  var className = getNodeClass(change.name);
109
131
  if (fg('platform_editor_show_diff_scroll_navigation')) {
110
132
  if (style || className) {
111
- return Decoration.node(change.from, change.to, {
133
+ decorations.push(Decoration.node(change.from, change.to, {
112
134
  style: style,
113
135
  'data-testid': 'show-diff-changed-decoration-node',
114
136
  class: className
115
137
  }, {
116
138
  key: 'diff-block',
117
139
  nodeName: change.name
118
- });
119
- } else {
120
- return undefined;
140
+ }));
121
141
  }
142
+ } else {
143
+ decorations.push(Decoration.node(change.from, change.to, {
144
+ style: style,
145
+ 'data-testid': 'show-diff-changed-decoration-node',
146
+ class: className
147
+ }, {
148
+ key: 'diff-block',
149
+ nodeName: change.name
150
+ }));
122
151
  }
123
- return Decoration.node(change.from, change.to, {
124
- style: style,
125
- 'data-testid': 'show-diff-changed-decoration-node',
126
- class: className
127
- }, {
128
- key: 'diff-block',
129
- nodeName: change.name
130
- });
152
+ return decorations;
131
153
  };
@@ -13,7 +13,10 @@ import { findSafeInsertPos } from './utils/findSafeInsertPos';
13
13
  /**
14
14
  * Extracts information about deleted table rows from a change
15
15
  */
16
- var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc) {
16
+ var extractChangedRows = function extractChangedRows(_ref) {
17
+ var change = _ref.change,
18
+ originalDoc = _ref.originalDoc,
19
+ newDoc = _ref.newDoc;
17
20
  var changedRows = [];
18
21
 
19
22
  // Find the table in the original document
@@ -35,7 +38,6 @@ var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc
35
38
  return changedRows;
36
39
  }
37
40
  var newTableMap = TableMap.get(tableNew.node);
38
-
39
41
  // If no rows were changed, return empty
40
42
  if (oldTableMap.height <= newTableMap.height ||
41
43
  // For now ignore if there are column deletions as well
@@ -54,7 +56,7 @@ var extractChangedRows = function extractChangedRows(change, originalDoc, newDoc
54
56
 
55
57
  // Check if this row overlaps with the deletion range
56
58
  var rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
57
- if (rowOverlapsChange && rowNode.type.name === 'tableRow' && !isEmptyRow(rowNode)) {
59
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || !isEmptyRow(rowNode))) {
58
60
  var startOfRow = newTableMap.mapByRow.slice().reverse().find(function (row) {
59
61
  return row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos;
60
62
  });
@@ -125,7 +127,7 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, nodeViewSerializ
125
127
  if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
126
128
  var nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
127
129
  if (nodeView) {
128
- if (isInserted && nodeView instanceof HTMLElement && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
130
+ if (nodeView instanceof HTMLElement && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
129
131
  var overlay = document.createElement('span');
130
132
  var overlayStyle = colorScheme === 'traditional' ? isInserted ? traditionalAddedCellOverlayStyle : deletedTraditionalCellOverlayStyle : isInserted ? addedCellOverlayStyle : deletedCellOverlayStyle;
131
133
  overlay.setAttribute('style', overlayStyle);
@@ -147,7 +149,10 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, nodeViewSerializ
147
149
  /**
148
150
  * Expands a diff to include whole changed rows when table rows are affected
149
151
  */
150
- var expandDiffForChangedRows = function expandDiffForChangedRows(changes, originalDoc, newDoc) {
152
+ var expandDiffForChangedRows = function expandDiffForChangedRows(_ref2) {
153
+ var changes = _ref2.changes,
154
+ originalDoc = _ref2.originalDoc,
155
+ newDoc = _ref2.newDoc;
151
156
  var rowInfo = [];
152
157
  var _iterator = _createForOfIteratorHelper(changes),
153
158
  _step;
@@ -155,7 +160,11 @@ var expandDiffForChangedRows = function expandDiffForChangedRows(changes, origin
155
160
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
156
161
  var change = _step.value;
157
162
  // Check if this change affects table content
158
- var changedRows = extractChangedRows(change, originalDoc, newDoc);
163
+ var changedRows = extractChangedRows({
164
+ change: change,
165
+ originalDoc: originalDoc,
166
+ newDoc: newDoc
167
+ });
159
168
  if (changedRows.length > 0) {
160
169
  rowInfo.push.apply(rowInfo, _toConsumableArray(changedRows));
161
170
  }
@@ -171,18 +180,22 @@ var expandDiffForChangedRows = function expandDiffForChangedRows(changes, origin
171
180
  /**
172
181
  * Main function to handle deleted rows - computes diff and creates decorations
173
182
  */
174
- export var createChangedRowDecorationWidgets = function createChangedRowDecorationWidgets(_ref) {
175
- var changes = _ref.changes,
176
- originalDoc = _ref.originalDoc,
177
- newDoc = _ref.newDoc,
178
- nodeViewSerializer = _ref.nodeViewSerializer,
179
- colorScheme = _ref.colorScheme,
180
- _ref$isInserted = _ref.isInserted,
181
- isInserted = _ref$isInserted === void 0 ? false : _ref$isInserted;
183
+ export var createChangedRowDecorationWidgets = function createChangedRowDecorationWidgets(_ref3) {
184
+ var changes = _ref3.changes,
185
+ originalDoc = _ref3.originalDoc,
186
+ newDoc = _ref3.newDoc,
187
+ nodeViewSerializer = _ref3.nodeViewSerializer,
188
+ colorScheme = _ref3.colorScheme,
189
+ _ref3$isInserted = _ref3.isInserted,
190
+ isInserted = _ref3$isInserted === void 0 ? false : _ref3$isInserted;
182
191
  // First, expand the changes to include complete deleted rows
183
- var changedRows = expandDiffForChangedRows(changes.filter(function (change) {
184
- return change.deleted.length > 0;
185
- }), originalDoc, newDoc);
192
+ var changedRows = expandDiffForChangedRows({
193
+ changes: changes.filter(function (change) {
194
+ return change.deleted.length > 0;
195
+ }),
196
+ originalDoc: originalDoc,
197
+ newDoc: newDoc
198
+ });
186
199
  return changedRows.map(function (changedRow) {
187
200
  var rowDOM = createChangedRowDOM(changedRow.rowNode, nodeViewSerializer, colorScheme, isInserted);
188
201
 
@@ -0,0 +1,4 @@
1
+ import type { Change } from 'prosemirror-changeset';
2
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { Step } from '@atlaskit/editor-prosemirror/transform';
4
+ export declare const diffBySteps: (originalDoc: PMNode, steps: Step[]) => Change[];
@@ -17,4 +17,4 @@ export declare const createBlockChangedDecoration: ({ change, colorScheme, isIns
17
17
  colorScheme?: ColorScheme;
18
18
  isActive?: boolean;
19
19
  isInserted?: boolean;
20
- }) => Decoration | undefined;
20
+ }) => Decoration[];
@@ -5,7 +5,7 @@ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
5
  import type { Node } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { Step } from '@atlaskit/editor-prosemirror/transform';
7
7
  export type ColorScheme = 'standard' | 'traditional';
8
- export type DiffType = 'inline' | 'block';
8
+ export type DiffType = 'inline' | 'block' | 'step';
9
9
  export type DiffParams = {
10
10
  /**
11
11
  * Color scheme to use for displaying diffs.
@@ -0,0 +1,4 @@
1
+ import type { Change } from 'prosemirror-changeset';
2
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { Step } from '@atlaskit/editor-prosemirror/transform';
4
+ export declare const diffBySteps: (originalDoc: PMNode, steps: Step[]) => Change[];
@@ -17,4 +17,4 @@ export declare const createBlockChangedDecoration: ({ change, colorScheme, isIns
17
17
  colorScheme?: ColorScheme;
18
18
  isActive?: boolean;
19
19
  isInserted?: boolean;
20
- }) => Decoration | undefined;
20
+ }) => Decoration[];
@@ -5,7 +5,7 @@ import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
5
5
  import type { Node } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { Step } from '@atlaskit/editor-prosemirror/transform';
7
7
  export type ColorScheme = 'standard' | 'traditional';
8
- export type DiffType = 'inline' | 'block';
8
+ export type DiffType = 'inline' | 'block' | 'step';
9
9
  export type DiffParams = {
10
10
  /**
11
11
  * Color scheme to use for displaying diffs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "6.2.19",
3
+ "version": "6.3.1",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -34,7 +34,7 @@
34
34
  "@atlaskit/editor-prosemirror": "^7.3.0",
35
35
  "@atlaskit/editor-tables": "^2.9.0",
36
36
  "@atlaskit/platform-feature-flags": "^1.1.0",
37
- "@atlaskit/tmp-editor-statsig": "^59.1.0",
37
+ "@atlaskit/tmp-editor-statsig": "^61.0.0",
38
38
  "@atlaskit/tokens": "^13.0.0",
39
39
  "@babel/runtime": "^7.0.0",
40
40
  "lodash": "^4.17.21",