@atlaskit/editor-plugin-show-diff 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/afm-cc/tsconfig.json +3 -0
- package/dist/cjs/pm-plugins/calculateDiffDecorations.js +3 -2
- package/dist/cjs/pm-plugins/decorations.js +18 -4
- package/dist/cjs/pm-plugins/deletedRowsHandler.js +214 -0
- package/dist/es2019/pm-plugins/calculateDiffDecorations.js +3 -2
- package/dist/es2019/pm-plugins/decorations.js +14 -3
- package/dist/es2019/pm-plugins/deletedRowsHandler.js +185 -0
- package/dist/esm/pm-plugins/calculateDiffDecorations.js +3 -2
- package/dist/esm/pm-plugins/decorations.js +18 -4
- package/dist/esm/pm-plugins/deletedRowsHandler.js +207 -0
- package/dist/types/pm-plugins/decorations.d.ts +2 -2
- package/dist/types/pm-plugins/deletedRowsHandler.d.ts +30 -0
- package/dist/types-ts4.5/pm-plugins/decorations.d.ts +2 -2
- package/dist/types-ts4.5/pm-plugins/deletedRowsHandler.d.ts +30 -0
- package/package.json +7 -3
- package/afm-post-office/tsconfig.json +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-show-diff
|
|
2
2
|
|
|
3
|
+
## 3.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`da2782d8dc1e7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/da2782d8dc1e7) -
|
|
8
|
+
Support table row diff displaying in the editor
|
|
9
|
+
|
|
3
10
|
## 3.2.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/afm-cc/tsconfig.json
CHANGED
|
@@ -106,6 +106,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
106
106
|
var tr = state.tr;
|
|
107
107
|
var steppedDoc = originalDoc;
|
|
108
108
|
var stepMaps = [];
|
|
109
|
+
var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc);
|
|
109
110
|
var _iterator = _createForOfIteratorHelper(steps),
|
|
110
111
|
_step;
|
|
111
112
|
try {
|
|
@@ -115,6 +116,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
115
116
|
if (result.failed === null && result.doc) {
|
|
116
117
|
steppedDoc = result.doc;
|
|
117
118
|
stepMaps.push(step.getMap());
|
|
119
|
+
changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -128,7 +130,6 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
128
130
|
if (!(0, _document.areNodesEqualIgnoreAttrs)(steppedDoc, tr.doc)) {
|
|
129
131
|
return _view.DecorationSet.empty;
|
|
130
132
|
}
|
|
131
|
-
var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
|
|
132
133
|
var changes = (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, tr.doc);
|
|
133
134
|
var optimizedChanges = optimizeChanges(changes);
|
|
134
135
|
var decorations = [];
|
|
@@ -147,7 +148,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
147
148
|
intl: intl
|
|
148
149
|
});
|
|
149
150
|
if (decoration) {
|
|
150
|
-
decorations.push(decoration);
|
|
151
|
+
decorations.push.apply(decorations, (0, _toConsumableArray2.default)(decoration));
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
});
|
|
@@ -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
|
|
364
|
+
var isTableCellContent = slice.content.content.some(function () {
|
|
364
365
|
return slice.content.content.some(function (siblingNode) {
|
|
365
|
-
return ['tableHeader', 'tableCell'
|
|
366
|
+
return ['tableHeader', 'tableCell'].includes(siblingNode.type.name);
|
|
366
367
|
});
|
|
367
368
|
});
|
|
368
|
-
|
|
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
|
+
};
|
|
@@ -99,11 +99,13 @@ const calculateDiffDecorationsInner = ({
|
|
|
99
99
|
} = state;
|
|
100
100
|
let steppedDoc = originalDoc;
|
|
101
101
|
const stepMaps = [];
|
|
102
|
+
let changeset = ChangeSet.create(originalDoc);
|
|
102
103
|
for (const step of steps) {
|
|
103
104
|
const result = step.apply(steppedDoc);
|
|
104
105
|
if (result.failed === null && result.doc) {
|
|
105
106
|
steppedDoc = result.doc;
|
|
106
107
|
stepMaps.push(step.getMap());
|
|
108
|
+
changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
@@ -112,7 +114,6 @@ const calculateDiffDecorationsInner = ({
|
|
|
112
114
|
if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
|
|
113
115
|
return DecorationSet.empty;
|
|
114
116
|
}
|
|
115
|
-
const changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
|
|
116
117
|
const changes = simplifyChanges(changeset.changes, tr.doc);
|
|
117
118
|
const optimizedChanges = optimizeChanges(changes);
|
|
118
119
|
const decorations = [];
|
|
@@ -131,7 +132,7 @@ const calculateDiffDecorationsInner = ({
|
|
|
131
132
|
intl
|
|
132
133
|
});
|
|
133
134
|
if (decoration) {
|
|
134
|
-
decorations.push(decoration);
|
|
135
|
+
decorations.push(...decoration);
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
});
|
|
@@ -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
|
|
351
|
-
|
|
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
|
+
};
|
|
@@ -100,6 +100,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
100
100
|
var tr = state.tr;
|
|
101
101
|
var steppedDoc = originalDoc;
|
|
102
102
|
var stepMaps = [];
|
|
103
|
+
var changeset = ChangeSet.create(originalDoc);
|
|
103
104
|
var _iterator = _createForOfIteratorHelper(steps),
|
|
104
105
|
_step;
|
|
105
106
|
try {
|
|
@@ -109,6 +110,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
109
110
|
if (result.failed === null && result.doc) {
|
|
110
111
|
steppedDoc = result.doc;
|
|
111
112
|
stepMaps.push(step.getMap());
|
|
113
|
+
changeset = changeset.addSteps(steppedDoc, [step.getMap()], step);
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -122,7 +124,6 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
122
124
|
if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
|
|
123
125
|
return DecorationSet.empty;
|
|
124
126
|
}
|
|
125
|
-
var changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
|
|
126
127
|
var changes = simplifyChanges(changeset.changes, tr.doc);
|
|
127
128
|
var optimizedChanges = optimizeChanges(changes);
|
|
128
129
|
var decorations = [];
|
|
@@ -141,7 +142,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref)
|
|
|
141
142
|
intl: intl
|
|
142
143
|
});
|
|
143
144
|
if (decoration) {
|
|
144
|
-
decorations.push(decoration);
|
|
145
|
+
decorations.push.apply(decorations, _toConsumableArray(decoration));
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
});
|
|
@@ -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
|
|
358
|
+
var isTableCellContent = slice.content.content.some(function () {
|
|
358
359
|
return slice.content.content.some(function (siblingNode) {
|
|
359
|
-
return ['tableHeader', 'tableCell'
|
|
360
|
+
return ['tableHeader', 'tableCell'].includes(siblingNode.type.name);
|
|
360
361
|
});
|
|
361
362
|
});
|
|
362
|
-
|
|
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
|
+
};
|
|
@@ -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 {};
|
|
@@ -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.
|
|
3
|
+
"version": "3.2.1",
|
|
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.
|
|
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.
|
|
42
|
+
"@atlaskit/editor-common": "^110.21.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
|
-
}
|