@atlaskit/editor-plugin-show-diff 8.4.0 → 8.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/dist/cjs/pm-plugins/areDocsEqualByBlockStructureAndText.js +32 -2
- package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +20 -9
- package/dist/cjs/pm-plugins/calculateDiff/diffBySteps.js +45 -4
- package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +3 -1
- package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +71 -12
- package/dist/es2019/pm-plugins/areDocsEqualByBlockStructureAndText.js +33 -2
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +20 -9
- package/dist/es2019/pm-plugins/calculateDiff/diffBySteps.js +45 -4
- package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +1 -1
- package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +63 -8
- package/dist/esm/pm-plugins/areDocsEqualByBlockStructureAndText.js +33 -2
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +20 -9
- package/dist/esm/pm-plugins/calculateDiff/diffBySteps.js +45 -4
- package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +2 -1
- package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +71 -11
- package/dist/types/pm-plugins/areDocsEqualByBlockStructureAndText.d.ts +5 -0
- package/dist/types/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/areDocsEqualByBlockStructureAndText.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/decorations/utils/getAttrChangeRanges.d.ts +2 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-show-diff
|
|
2
2
|
|
|
3
|
+
## 8.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`5789b1638025b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5789b1638025b) -
|
|
8
|
+
EDITOR-6631: If only marks has changed, don't use granular diffing as that won't show any diffs.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
|
|
11
|
+
## 8.4.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`8a9f26c6c71bc`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8a9f26c6c71bc) -
|
|
16
|
+
[ux] Improve diff logic for some nodes and edge cases where marks are causing the diff to fail
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
|
|
3
19
|
## 8.4.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
6
22
|
|
|
7
|
-
- [`
|
|
23
|
+
- [`ebab8f80bfc40`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ebab8f80bfc40) -
|
|
8
24
|
Autofix: add explicit package exports (barrel removal)
|
|
9
25
|
|
|
10
26
|
### Patch Changes
|
|
@@ -4,8 +4,22 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.areDocsEqualByBlockStructureAndText = areDocsEqualByBlockStructureAndText;
|
|
7
|
+
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
8
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
7
9
|
/**
|
|
8
|
-
* Returns
|
|
10
|
+
* Returns a copy of the document with all marks removed from all text.
|
|
11
|
+
* This normalises mark fragmentation — e.g. annotation marks reordering can split
|
|
12
|
+
* a single text run into many text nodes, making structural comparison unreliable.
|
|
13
|
+
* After stripping, ProseMirror will merge adjacent text nodes so childCounts are stable.
|
|
14
|
+
*/
|
|
15
|
+
function stripMarks(doc) {
|
|
16
|
+
var tr = new _transform.Transform(doc);
|
|
17
|
+
tr.removeMark(0, doc.content.size);
|
|
18
|
+
return tr.doc;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns true if both (mark-stripped) nodes have the same block tree structure (node type and child count at every level)
|
|
9
23
|
*/
|
|
10
24
|
function isBlockStructureEqual(node1, node2) {
|
|
11
25
|
if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
|
|
@@ -23,7 +37,23 @@ function isBlockStructureEqual(node1, node2) {
|
|
|
23
37
|
* Looser equality for "safe diff" cases: same full text content and same block structure
|
|
24
38
|
* (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
|
|
25
39
|
* This is safe because we ensure decorations get applied to valid positions.
|
|
40
|
+
*
|
|
41
|
+
* Marks are intentionally ignored — two documents that differ only in mark application
|
|
42
|
+
* (e.g. bold/italic boundaries or annotation mark ordering) are considered equal here.
|
|
43
|
+
* Both documents are mark-stripped before comparison so that mark-driven text fragmentation
|
|
44
|
+
* does not produce false inequalities.
|
|
26
45
|
*/
|
|
27
46
|
function areDocsEqualByBlockStructureAndText(doc1, doc2) {
|
|
28
|
-
|
|
47
|
+
if (doc1.textContent !== doc2.textContent) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if ((0, _expValEquals.expValEquals)('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
51
|
+
// Strip marks before comparing so that mark-driven text fragmentation
|
|
52
|
+
// (e.g. annotation mark reordering producing different childCounts) does not
|
|
53
|
+
// cause false inequalities.
|
|
54
|
+
var stripped1 = stripMarks(doc1);
|
|
55
|
+
var stripped2 = stripMarks(doc2);
|
|
56
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(stripped1, stripped2);
|
|
57
|
+
}
|
|
58
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
|
|
29
59
|
}
|
|
@@ -221,15 +221,26 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
|
|
|
221
221
|
}));
|
|
222
222
|
});
|
|
223
223
|
(0, _getAttrChangeRanges.getAttrChangeRanges)(tr.doc, attrSteps).forEach(function (change) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
from
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
if ((0, _expValEquals.expValEquals)('platform_editor_show_diff_improvements', 'isEnabled', true) && change.isInline) {
|
|
225
|
+
// Inline nodes (e.g. date) need an inline decoration rather than a block decoration
|
|
226
|
+
var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
|
|
227
|
+
decorations.push((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
|
|
228
|
+
change: change,
|
|
229
|
+
colorScheme: colorScheme,
|
|
230
|
+
isActive: isActive,
|
|
231
|
+
isInserted: true
|
|
232
|
+
}));
|
|
233
|
+
} else {
|
|
234
|
+
decorations.push.apply(decorations, (0, _toConsumableArray2.default)(calculateNodesForBlockDecoration({
|
|
235
|
+
doc: tr.doc,
|
|
236
|
+
from: change.fromB,
|
|
237
|
+
to: change.toB,
|
|
238
|
+
colorScheme: colorScheme,
|
|
239
|
+
isInserted: true,
|
|
240
|
+
activeIndexPos: activeIndexPos,
|
|
241
|
+
intl: intl
|
|
242
|
+
})));
|
|
243
|
+
}
|
|
233
244
|
});
|
|
234
245
|
return _view.DecorationSet.empty.add(tr.doc, decorations);
|
|
235
246
|
};
|
|
@@ -8,6 +8,7 @@ exports.diffBySteps = void 0;
|
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
10
|
var _prosemirrorChangeset = require("prosemirror-changeset");
|
|
11
|
+
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
11
12
|
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
12
13
|
var _optimizeChanges = require("./optimizeChanges");
|
|
13
14
|
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; }
|
|
@@ -18,6 +19,22 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
18
19
|
var mapPosition = function mapPosition(mapping, pos) {
|
|
19
20
|
return mapping.map(pos);
|
|
20
21
|
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compare marks between two nodes
|
|
25
|
+
* We have to check each child because adding a mark splits text into multiple nodes
|
|
26
|
+
*/
|
|
27
|
+
var hasSameChildMarks = function hasSameChildMarks(left, right) {
|
|
28
|
+
if (left.childCount !== right.childCount) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
for (var index = 0; index < left.childCount; index++) {
|
|
32
|
+
if (!_model.Mark.sameSet(left.child(index).marks, right.child(index).marks)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
21
38
|
var createMapping = function createMapping(maps) {
|
|
22
39
|
var mapping = new _transform.Mapping();
|
|
23
40
|
var _iterator = _createForOfIteratorHelper(maps),
|
|
@@ -69,8 +86,20 @@ var mergeOverlappingByNewDocRange = function mergeOverlappingByNewDocRange(chang
|
|
|
69
86
|
merged.push(current);
|
|
70
87
|
return merged;
|
|
71
88
|
};
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* This function checks whether to do granular diffing.
|
|
92
|
+
* We should do granular diffing if:
|
|
93
|
+
* - The step is a replace step
|
|
94
|
+
* - The step is not open
|
|
95
|
+
* - The replaced slice is not open
|
|
96
|
+
* - The replaced slice has only one child
|
|
97
|
+
* - The replacing slice has only one child
|
|
98
|
+
* - The replaced slice and replacing slice have the same text content
|
|
99
|
+
* - The replaced slice and replacing slice have the same child marks (if text content is equal)
|
|
100
|
+
*/
|
|
101
|
+
var shouldCheckGranularDiff = function shouldCheckGranularDiff(step, before, from, to) {
|
|
102
|
+
var _replacedNode$marks, _replacingNode$marks;
|
|
74
103
|
if (!(step instanceof _transform.ReplaceStep)) {
|
|
75
104
|
return false;
|
|
76
105
|
}
|
|
@@ -79,7 +108,19 @@ var isReplaceStepForTextBlockNode = function isReplaceStepForTextBlockNode(step,
|
|
|
79
108
|
}
|
|
80
109
|
var replacedSlice = before.slice(from, to);
|
|
81
110
|
var replacingSlice = step.slice;
|
|
82
|
-
|
|
111
|
+
if (replacedSlice.openStart !== 0 || replacedSlice.openEnd !== 0 || replacedSlice.content.childCount !== 1 || replacingSlice.content.childCount !== 1) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
var replacedNode = replacedSlice.content.firstChild;
|
|
115
|
+
var replacingNode = replacingSlice.content.firstChild;
|
|
116
|
+
if ((replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.type.name) !== (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.type.name) || !(replacedNode !== null && replacedNode !== void 0 && replacedNode.type.isTextblock)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (!_model.Mark.sameSet((_replacedNode$marks = replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.marks) !== null && _replacedNode$marks !== void 0 ? _replacedNode$marks : [], (_replacingNode$marks = replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.marks) !== null && _replacingNode$marks !== void 0 ? _replacingNode$marks : [])) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
var isTextContentEqual = (replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.textContent) === (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.textContent);
|
|
123
|
+
return !isTextContentEqual || isTextContentEqual && hasSameChildMarks(replacedNode, replacingNode);
|
|
83
124
|
};
|
|
84
125
|
var diffBySteps = exports.diffBySteps = function diffBySteps(originalDoc, steps) {
|
|
85
126
|
var changes = [];
|
|
@@ -131,7 +172,7 @@ var diffBySteps = exports.diffBySteps = function diffBySteps(originalDoc, steps)
|
|
|
131
172
|
var afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
|
|
132
173
|
var fromB = mapPosition(afterStepToFinal, fromAfterStep);
|
|
133
174
|
var toB = mapPosition(afterStepToFinal, toAfterStep);
|
|
134
|
-
if (
|
|
175
|
+
if (shouldCheckGranularDiff(rangedStep.step, rangedStep.before, rangedStep.from, rangedStep.to)) {
|
|
135
176
|
var granularStepChanges = _prosemirrorChangeset.ChangeSet.create(rangedStep.before).addSteps(rangedStep.doc, [rangedStep.stepMap], null);
|
|
136
177
|
|
|
137
178
|
// `simplifyChanges` reads text using `Change.fromB`/`toB`, which are
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
7
|
exports.createBlockChangedDecoration = void 0;
|
|
8
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
7
9
|
var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
|
|
8
10
|
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
9
11
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
@@ -33,7 +35,7 @@ var getBlockNodeStyle = function getBlockNodeStyle(_ref) {
|
|
|
33
35
|
// Handle table separately to avoid border issues
|
|
34
36
|
'tableRow', 'paragraph',
|
|
35
37
|
// Paragraph and heading nodes do not need special styling
|
|
36
|
-
'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
|
|
38
|
+
'heading', 'hardBreak', 'decisionList', 'taskList'].concat((0, _toConsumableArray2.default)((0, _expValEquals.expValEquals)('platform_editor_show_diff_improvements', 'isEnabled', true) ? [] : ['taskItem']), ['bulletList', 'orderedList', 'layoutSection']).includes(nodeName)) {
|
|
37
39
|
// Layout nodes do not need special styling
|
|
38
40
|
return undefined;
|
|
39
41
|
}
|
|
@@ -1,32 +1,91 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
3
|
Object.defineProperty(exports, "__esModule", {
|
|
5
4
|
value: true
|
|
6
5
|
});
|
|
7
6
|
exports.stepIsValidAttrChange = exports.getAttrChangeRanges = void 0;
|
|
8
|
-
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
7
|
var _steps = require("@atlaskit/adf-schema/steps");
|
|
10
8
|
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
9
|
+
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
11
10
|
var filterUndefined = function filterUndefined(x) {
|
|
12
11
|
return !!x;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
//
|
|
16
|
-
var
|
|
14
|
+
// Attributes that indicate a change in media image
|
|
15
|
+
var mediaAttrs = ['id', 'collection', 'url'];
|
|
16
|
+
|
|
17
|
+
// Attribute that indicates a date change
|
|
18
|
+
var dateAttrs = ['timestamp'];
|
|
19
|
+
|
|
20
|
+
// Attribute that indicates a task item state change
|
|
21
|
+
var taskItemAttrs = ['state'];
|
|
22
|
+
|
|
23
|
+
// Attributes excluded from extension change detection (not meaningful content changes)
|
|
24
|
+
var extensionExcludedAttrs = ['localId'];
|
|
25
|
+
|
|
26
|
+
// Extension node type names
|
|
27
|
+
var extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
|
|
28
|
+
var getStepAttrs = function getStepAttrs(step) {
|
|
29
|
+
if (step instanceof _transform.AttrStep) {
|
|
30
|
+
return [step.attr];
|
|
31
|
+
}
|
|
32
|
+
if (step instanceof _steps.SetAttrsStep && step.attrs) {
|
|
33
|
+
return Object.keys(step.attrs);
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
};
|
|
17
37
|
var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
|
|
18
38
|
return steps.map(function (step) {
|
|
19
|
-
if (step instanceof _transform.AttrStep &&
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
if (!(step instanceof _transform.AttrStep) && !(step instanceof _steps.SetAttrsStep)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
var stepAttrs = getStepAttrs(step);
|
|
43
|
+
var $pos = doc.resolve(step.pos);
|
|
44
|
+
var nodeAtPos = doc.nodeAt(step.pos);
|
|
45
|
+
if ((0, _expValEquals.expValEquals)('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
46
|
+
// date node: timestamp attribute change — highlight the date node itself (inline)
|
|
47
|
+
if (stepAttrs.some(function (v) {
|
|
48
|
+
return dateAttrs.includes(v);
|
|
49
|
+
}) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
|
|
50
|
+
return {
|
|
51
|
+
fromB: step.pos,
|
|
52
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
53
|
+
isInline: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// taskItem node: state attribute change — highlight the taskItem node
|
|
58
|
+
if (stepAttrs.some(function (v) {
|
|
59
|
+
return taskItemAttrs.includes(v);
|
|
60
|
+
}) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
|
|
25
61
|
return {
|
|
26
|
-
fromB:
|
|
27
|
-
toB:
|
|
62
|
+
fromB: step.pos,
|
|
63
|
+
toB: step.pos + nodeAtPos.nodeSize
|
|
28
64
|
};
|
|
29
65
|
}
|
|
66
|
+
|
|
67
|
+
// extension nodes: any attribute change except localId — highlight the node
|
|
68
|
+
if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(function (v) {
|
|
69
|
+
return !extensionExcludedAttrs.includes(v);
|
|
70
|
+
})) {
|
|
71
|
+
var isInline = nodeAtPos.type.name === 'inlineExtension';
|
|
72
|
+
return {
|
|
73
|
+
fromB: step.pos,
|
|
74
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
75
|
+
isInline: isInline
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// media node: id/collection/url attribute change — highlight the mediaSingle parent
|
|
81
|
+
if (stepAttrs.some(function (v) {
|
|
82
|
+
return mediaAttrs.includes(v);
|
|
83
|
+
}) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
|
|
84
|
+
var startPos = $pos.pos + $pos.parentOffset;
|
|
85
|
+
return {
|
|
86
|
+
fromB: startPos,
|
|
87
|
+
toB: startPos + $pos.parent.nodeSize - 1
|
|
88
|
+
};
|
|
30
89
|
}
|
|
31
90
|
return undefined;
|
|
32
91
|
}).filter(filterUndefined);
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { Transform } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a copy of the document with all marks removed from all text.
|
|
6
|
+
* This normalises mark fragmentation — e.g. annotation marks reordering can split
|
|
7
|
+
* a single text run into many text nodes, making structural comparison unreliable.
|
|
8
|
+
* After stripping, ProseMirror will merge adjacent text nodes so childCounts are stable.
|
|
9
|
+
*/
|
|
10
|
+
function stripMarks(doc) {
|
|
11
|
+
const tr = new Transform(doc);
|
|
12
|
+
tr.removeMark(0, doc.content.size);
|
|
13
|
+
return tr.doc;
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
/**
|
|
2
|
-
* Returns true if both nodes have the same tree structure (type and child count at every level)
|
|
17
|
+
* Returns true if both (mark-stripped) nodes have the same block tree structure (node type and child count at every level)
|
|
3
18
|
*/
|
|
4
19
|
function isBlockStructureEqual(node1, node2) {
|
|
5
20
|
if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
|
|
@@ -17,7 +32,23 @@ function isBlockStructureEqual(node1, node2) {
|
|
|
17
32
|
* Looser equality for "safe diff" cases: same full text content and same block structure
|
|
18
33
|
* (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
|
|
19
34
|
* This is safe because we ensure decorations get applied to valid positions.
|
|
35
|
+
*
|
|
36
|
+
* Marks are intentionally ignored — two documents that differ only in mark application
|
|
37
|
+
* (e.g. bold/italic boundaries or annotation mark ordering) are considered equal here.
|
|
38
|
+
* Both documents are mark-stripped before comparison so that mark-driven text fragmentation
|
|
39
|
+
* does not produce false inequalities.
|
|
20
40
|
*/
|
|
21
41
|
export function areDocsEqualByBlockStructureAndText(doc1, doc2) {
|
|
22
|
-
|
|
42
|
+
if (doc1.textContent !== doc2.textContent) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
46
|
+
// Strip marks before comparing so that mark-driven text fragmentation
|
|
47
|
+
// (e.g. annotation mark reordering producing different childCounts) does not
|
|
48
|
+
// cause false inequalities.
|
|
49
|
+
const stripped1 = stripMarks(doc1);
|
|
50
|
+
const stripped2 = stripMarks(doc2);
|
|
51
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(stripped1, stripped2);
|
|
52
|
+
}
|
|
53
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
|
|
23
54
|
}
|
|
@@ -202,15 +202,26 @@ const calculateDiffDecorationsInner = ({
|
|
|
202
202
|
}));
|
|
203
203
|
});
|
|
204
204
|
getAttrChangeRanges(tr.doc, attrSteps).forEach(change => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
from
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
205
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) && change.isInline) {
|
|
206
|
+
// Inline nodes (e.g. date) need an inline decoration rather than a block decoration
|
|
207
|
+
const isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
|
|
208
|
+
decorations.push(createInlineChangedDecoration({
|
|
209
|
+
change,
|
|
210
|
+
colorScheme,
|
|
211
|
+
isActive,
|
|
212
|
+
isInserted: true
|
|
213
|
+
}));
|
|
214
|
+
} else {
|
|
215
|
+
decorations.push(...calculateNodesForBlockDecoration({
|
|
216
|
+
doc: tr.doc,
|
|
217
|
+
from: change.fromB,
|
|
218
|
+
to: change.toB,
|
|
219
|
+
colorScheme,
|
|
220
|
+
isInserted: true,
|
|
221
|
+
activeIndexPos,
|
|
222
|
+
intl
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
214
225
|
});
|
|
215
226
|
return DecorationSet.empty.add(tr.doc, decorations);
|
|
216
227
|
};
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { simplifyChanges, ChangeSet } from 'prosemirror-changeset';
|
|
2
|
+
import { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
2
3
|
import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
4
|
import { optimizeChanges } from './optimizeChanges';
|
|
4
5
|
const mapPosition = (mapping, pos) => mapping.map(pos);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compare marks between two nodes
|
|
9
|
+
* We have to check each child because adding a mark splits text into multiple nodes
|
|
10
|
+
*/
|
|
11
|
+
const hasSameChildMarks = (left, right) => {
|
|
12
|
+
if (left.childCount !== right.childCount) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for (let index = 0; index < left.childCount; index++) {
|
|
16
|
+
if (!Mark.sameSet(left.child(index).marks, right.child(index).marks)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
};
|
|
5
22
|
const createMapping = maps => {
|
|
6
23
|
const mapping = new Mapping();
|
|
7
24
|
for (const map of maps) {
|
|
@@ -44,8 +61,20 @@ const mergeOverlappingByNewDocRange = changes => {
|
|
|
44
61
|
merged.push(current);
|
|
45
62
|
return merged;
|
|
46
63
|
};
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* This function checks whether to do granular diffing.
|
|
67
|
+
* We should do granular diffing if:
|
|
68
|
+
* - The step is a replace step
|
|
69
|
+
* - The step is not open
|
|
70
|
+
* - The replaced slice is not open
|
|
71
|
+
* - The replaced slice has only one child
|
|
72
|
+
* - The replacing slice has only one child
|
|
73
|
+
* - The replaced slice and replacing slice have the same text content
|
|
74
|
+
* - The replaced slice and replacing slice have the same child marks (if text content is equal)
|
|
75
|
+
*/
|
|
76
|
+
const shouldCheckGranularDiff = (step, before, from, to) => {
|
|
77
|
+
var _replacedNode$marks, _replacingNode$marks;
|
|
49
78
|
if (!(step instanceof ReplaceStep)) {
|
|
50
79
|
return false;
|
|
51
80
|
}
|
|
@@ -54,7 +83,19 @@ const isReplaceStepForTextBlockNode = (step, before, from, to) => {
|
|
|
54
83
|
}
|
|
55
84
|
const replacedSlice = before.slice(from, to);
|
|
56
85
|
const replacingSlice = step.slice;
|
|
57
|
-
|
|
86
|
+
if (replacedSlice.openStart !== 0 || replacedSlice.openEnd !== 0 || replacedSlice.content.childCount !== 1 || replacingSlice.content.childCount !== 1) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const replacedNode = replacedSlice.content.firstChild;
|
|
90
|
+
const replacingNode = replacingSlice.content.firstChild;
|
|
91
|
+
if ((replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.type.name) !== (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.type.name) || !(replacedNode !== null && replacedNode !== void 0 && replacedNode.type.isTextblock)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (!Mark.sameSet((_replacedNode$marks = replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.marks) !== null && _replacedNode$marks !== void 0 ? _replacedNode$marks : [], (_replacingNode$marks = replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.marks) !== null && _replacingNode$marks !== void 0 ? _replacingNode$marks : [])) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const isTextContentEqual = (replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.textContent) === (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.textContent);
|
|
98
|
+
return !isTextContentEqual || isTextContentEqual && hasSameChildMarks(replacedNode, replacingNode);
|
|
58
99
|
};
|
|
59
100
|
export const diffBySteps = (originalDoc, steps) => {
|
|
60
101
|
const changes = [];
|
|
@@ -96,7 +137,7 @@ export const diffBySteps = (originalDoc, steps) => {
|
|
|
96
137
|
const afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
|
|
97
138
|
const fromB = mapPosition(afterStepToFinal, fromAfterStep);
|
|
98
139
|
const toB = mapPosition(afterStepToFinal, toAfterStep);
|
|
99
|
-
if (
|
|
140
|
+
if (shouldCheckGranularDiff(rangedStep.step, rangedStep.before, rangedStep.from, rangedStep.to)) {
|
|
100
141
|
const granularStepChanges = ChangeSet.create(rangedStep.before).addSteps(rangedStep.doc, [rangedStep.stepMap], null);
|
|
101
142
|
|
|
102
143
|
// `simplifyChanges` reads text using `Change.fromB`/`toB`, which are
|
|
@@ -26,7 +26,7 @@ const getBlockNodeStyle = ({
|
|
|
26
26
|
// Handle table separately to avoid border issues
|
|
27
27
|
'tableRow', 'paragraph',
|
|
28
28
|
// Paragraph and heading nodes do not need special styling
|
|
29
|
-
'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
|
|
29
|
+
'heading', 'hardBreak', 'decisionList', 'taskList', ...(expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) ? [] : ['taskItem']), 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
|
|
30
30
|
// Layout nodes do not need special styling
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
@@ -1,20 +1,75 @@
|
|
|
1
1
|
import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
|
|
2
2
|
import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
3
4
|
const filterUndefined = x => !!x;
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
-
const
|
|
6
|
+
// Attributes that indicate a change in media image
|
|
7
|
+
const mediaAttrs = ['id', 'collection', 'url'];
|
|
8
|
+
|
|
9
|
+
// Attribute that indicates a date change
|
|
10
|
+
const dateAttrs = ['timestamp'];
|
|
11
|
+
|
|
12
|
+
// Attribute that indicates a task item state change
|
|
13
|
+
const taskItemAttrs = ['state'];
|
|
14
|
+
|
|
15
|
+
// Attributes excluded from extension change detection (not meaningful content changes)
|
|
16
|
+
const extensionExcludedAttrs = ['localId'];
|
|
17
|
+
|
|
18
|
+
// Extension node type names
|
|
19
|
+
const extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
|
|
20
|
+
const getStepAttrs = step => {
|
|
21
|
+
if (step instanceof AttrStep) {
|
|
22
|
+
return [step.attr];
|
|
23
|
+
}
|
|
24
|
+
if (step instanceof SetAttrsStep && step.attrs) {
|
|
25
|
+
return Object.keys(step.attrs);
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
};
|
|
7
29
|
export const getAttrChangeRanges = (doc, steps) => {
|
|
8
30
|
return steps.map(step => {
|
|
9
|
-
if (step instanceof AttrStep &&
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
if (!(step instanceof AttrStep) && !(step instanceof SetAttrsStep)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const stepAttrs = getStepAttrs(step);
|
|
35
|
+
const $pos = doc.resolve(step.pos);
|
|
36
|
+
const nodeAtPos = doc.nodeAt(step.pos);
|
|
37
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
38
|
+
// date node: timestamp attribute change — highlight the date node itself (inline)
|
|
39
|
+
if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
|
|
40
|
+
return {
|
|
41
|
+
fromB: step.pos,
|
|
42
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
43
|
+
isInline: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// taskItem node: state attribute change — highlight the taskItem node
|
|
48
|
+
if (stepAttrs.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
|
|
13
49
|
return {
|
|
14
|
-
fromB:
|
|
15
|
-
toB:
|
|
50
|
+
fromB: step.pos,
|
|
51
|
+
toB: step.pos + nodeAtPos.nodeSize
|
|
16
52
|
};
|
|
17
53
|
}
|
|
54
|
+
|
|
55
|
+
// extension nodes: any attribute change except localId — highlight the node
|
|
56
|
+
if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => !extensionExcludedAttrs.includes(v))) {
|
|
57
|
+
const isInline = nodeAtPos.type.name === 'inlineExtension';
|
|
58
|
+
return {
|
|
59
|
+
fromB: step.pos,
|
|
60
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
61
|
+
isInline
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// media node: id/collection/url attribute change — highlight the mediaSingle parent
|
|
67
|
+
if (stepAttrs.some(v => mediaAttrs.includes(v)) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
|
|
68
|
+
const startPos = $pos.pos + $pos.parentOffset;
|
|
69
|
+
return {
|
|
70
|
+
fromB: startPos,
|
|
71
|
+
toB: startPos + $pos.parent.nodeSize - 1
|
|
72
|
+
};
|
|
18
73
|
}
|
|
19
74
|
return undefined;
|
|
20
75
|
}).filter(filterUndefined);
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { Transform } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a copy of the document with all marks removed from all text.
|
|
6
|
+
* This normalises mark fragmentation — e.g. annotation marks reordering can split
|
|
7
|
+
* a single text run into many text nodes, making structural comparison unreliable.
|
|
8
|
+
* After stripping, ProseMirror will merge adjacent text nodes so childCounts are stable.
|
|
9
|
+
*/
|
|
10
|
+
function stripMarks(doc) {
|
|
11
|
+
var tr = new Transform(doc);
|
|
12
|
+
tr.removeMark(0, doc.content.size);
|
|
13
|
+
return tr.doc;
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
/**
|
|
2
|
-
* Returns true if both nodes have the same tree structure (type and child count at every level)
|
|
17
|
+
* Returns true if both (mark-stripped) nodes have the same block tree structure (node type and child count at every level)
|
|
3
18
|
*/
|
|
4
19
|
function isBlockStructureEqual(node1, node2) {
|
|
5
20
|
if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
|
|
@@ -17,7 +32,23 @@ function isBlockStructureEqual(node1, node2) {
|
|
|
17
32
|
* Looser equality for "safe diff" cases: same full text content and same block structure
|
|
18
33
|
* (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
|
|
19
34
|
* This is safe because we ensure decorations get applied to valid positions.
|
|
35
|
+
*
|
|
36
|
+
* Marks are intentionally ignored — two documents that differ only in mark application
|
|
37
|
+
* (e.g. bold/italic boundaries or annotation mark ordering) are considered equal here.
|
|
38
|
+
* Both documents are mark-stripped before comparison so that mark-driven text fragmentation
|
|
39
|
+
* does not produce false inequalities.
|
|
20
40
|
*/
|
|
21
41
|
export function areDocsEqualByBlockStructureAndText(doc1, doc2) {
|
|
22
|
-
|
|
42
|
+
if (doc1.textContent !== doc2.textContent) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
46
|
+
// Strip marks before comparing so that mark-driven text fragmentation
|
|
47
|
+
// (e.g. annotation mark reordering producing different childCounts) does not
|
|
48
|
+
// cause false inequalities.
|
|
49
|
+
var stripped1 = stripMarks(doc1);
|
|
50
|
+
var stripped2 = stripMarks(doc2);
|
|
51
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(stripped1, stripped2);
|
|
52
|
+
}
|
|
53
|
+
return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
|
|
23
54
|
}
|
|
@@ -215,15 +215,26 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
|
|
|
215
215
|
}));
|
|
216
216
|
});
|
|
217
217
|
getAttrChangeRanges(tr.doc, attrSteps).forEach(function (change) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
from
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
218
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) && change.isInline) {
|
|
219
|
+
// Inline nodes (e.g. date) need an inline decoration rather than a block decoration
|
|
220
|
+
var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
|
|
221
|
+
decorations.push(createInlineChangedDecoration({
|
|
222
|
+
change: change,
|
|
223
|
+
colorScheme: colorScheme,
|
|
224
|
+
isActive: isActive,
|
|
225
|
+
isInserted: true
|
|
226
|
+
}));
|
|
227
|
+
} else {
|
|
228
|
+
decorations.push.apply(decorations, _toConsumableArray(calculateNodesForBlockDecoration({
|
|
229
|
+
doc: tr.doc,
|
|
230
|
+
from: change.fromB,
|
|
231
|
+
to: change.toB,
|
|
232
|
+
colorScheme: colorScheme,
|
|
233
|
+
isInserted: true,
|
|
234
|
+
activeIndexPos: activeIndexPos,
|
|
235
|
+
intl: intl
|
|
236
|
+
})));
|
|
237
|
+
}
|
|
227
238
|
});
|
|
228
239
|
return DecorationSet.empty.add(tr.doc, decorations);
|
|
229
240
|
};
|
|
@@ -6,11 +6,28 @@ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol
|
|
|
6
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
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
8
|
import { simplifyChanges, ChangeSet } from 'prosemirror-changeset';
|
|
9
|
+
import { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
9
10
|
import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
10
11
|
import { optimizeChanges } from './optimizeChanges';
|
|
11
12
|
var mapPosition = function mapPosition(mapping, pos) {
|
|
12
13
|
return mapping.map(pos);
|
|
13
14
|
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compare marks between two nodes
|
|
18
|
+
* We have to check each child because adding a mark splits text into multiple nodes
|
|
19
|
+
*/
|
|
20
|
+
var hasSameChildMarks = function hasSameChildMarks(left, right) {
|
|
21
|
+
if (left.childCount !== right.childCount) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
for (var index = 0; index < left.childCount; index++) {
|
|
25
|
+
if (!Mark.sameSet(left.child(index).marks, right.child(index).marks)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
14
31
|
var createMapping = function createMapping(maps) {
|
|
15
32
|
var mapping = new Mapping();
|
|
16
33
|
var _iterator = _createForOfIteratorHelper(maps),
|
|
@@ -62,8 +79,20 @@ var mergeOverlappingByNewDocRange = function mergeOverlappingByNewDocRange(chang
|
|
|
62
79
|
merged.push(current);
|
|
63
80
|
return merged;
|
|
64
81
|
};
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* This function checks whether to do granular diffing.
|
|
85
|
+
* We should do granular diffing if:
|
|
86
|
+
* - The step is a replace step
|
|
87
|
+
* - The step is not open
|
|
88
|
+
* - The replaced slice is not open
|
|
89
|
+
* - The replaced slice has only one child
|
|
90
|
+
* - The replacing slice has only one child
|
|
91
|
+
* - The replaced slice and replacing slice have the same text content
|
|
92
|
+
* - The replaced slice and replacing slice have the same child marks (if text content is equal)
|
|
93
|
+
*/
|
|
94
|
+
var shouldCheckGranularDiff = function shouldCheckGranularDiff(step, before, from, to) {
|
|
95
|
+
var _replacedNode$marks, _replacingNode$marks;
|
|
67
96
|
if (!(step instanceof ReplaceStep)) {
|
|
68
97
|
return false;
|
|
69
98
|
}
|
|
@@ -72,7 +101,19 @@ var isReplaceStepForTextBlockNode = function isReplaceStepForTextBlockNode(step,
|
|
|
72
101
|
}
|
|
73
102
|
var replacedSlice = before.slice(from, to);
|
|
74
103
|
var replacingSlice = step.slice;
|
|
75
|
-
|
|
104
|
+
if (replacedSlice.openStart !== 0 || replacedSlice.openEnd !== 0 || replacedSlice.content.childCount !== 1 || replacingSlice.content.childCount !== 1) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
var replacedNode = replacedSlice.content.firstChild;
|
|
108
|
+
var replacingNode = replacingSlice.content.firstChild;
|
|
109
|
+
if ((replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.type.name) !== (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.type.name) || !(replacedNode !== null && replacedNode !== void 0 && replacedNode.type.isTextblock)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (!Mark.sameSet((_replacedNode$marks = replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.marks) !== null && _replacedNode$marks !== void 0 ? _replacedNode$marks : [], (_replacingNode$marks = replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.marks) !== null && _replacingNode$marks !== void 0 ? _replacingNode$marks : [])) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
var isTextContentEqual = (replacedNode === null || replacedNode === void 0 ? void 0 : replacedNode.textContent) === (replacingNode === null || replacingNode === void 0 ? void 0 : replacingNode.textContent);
|
|
116
|
+
return !isTextContentEqual || isTextContentEqual && hasSameChildMarks(replacedNode, replacingNode);
|
|
76
117
|
};
|
|
77
118
|
export var diffBySteps = function diffBySteps(originalDoc, steps) {
|
|
78
119
|
var changes = [];
|
|
@@ -124,7 +165,7 @@ export var diffBySteps = function diffBySteps(originalDoc, steps) {
|
|
|
124
165
|
var afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
|
|
125
166
|
var fromB = mapPosition(afterStepToFinal, fromAfterStep);
|
|
126
167
|
var toB = mapPosition(afterStepToFinal, toAfterStep);
|
|
127
|
-
if (
|
|
168
|
+
if (shouldCheckGranularDiff(rangedStep.step, rangedStep.before, rangedStep.from, rangedStep.to)) {
|
|
128
169
|
var granularStepChanges = ChangeSet.create(rangedStep.before).addSteps(rangedStep.doc, [rangedStep.stepMap], null);
|
|
129
170
|
|
|
130
171
|
// `simplifyChanges` reads text using `Change.fromB`/`toB`, which are
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
1
2
|
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
3
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
4
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
@@ -27,7 +28,7 @@ var getBlockNodeStyle = function getBlockNodeStyle(_ref) {
|
|
|
27
28
|
// Handle table separately to avoid border issues
|
|
28
29
|
'tableRow', 'paragraph',
|
|
29
30
|
// Paragraph and heading nodes do not need special styling
|
|
30
|
-
'heading', 'hardBreak', 'decisionList', 'taskList', 'taskItem', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
|
|
31
|
+
'heading', 'hardBreak', 'decisionList', 'taskList'].concat(_toConsumableArray(expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) ? [] : ['taskItem']), ['bulletList', 'orderedList', 'layoutSection']).includes(nodeName)) {
|
|
31
32
|
// Layout nodes do not need special styling
|
|
32
33
|
return undefined;
|
|
33
34
|
}
|
|
@@ -1,25 +1,85 @@
|
|
|
1
|
-
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
1
|
import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
|
|
3
2
|
import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
4
4
|
var filterUndefined = function filterUndefined(x) {
|
|
5
5
|
return !!x;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
var
|
|
8
|
+
// Attributes that indicate a change in media image
|
|
9
|
+
var mediaAttrs = ['id', 'collection', 'url'];
|
|
10
|
+
|
|
11
|
+
// Attribute that indicates a date change
|
|
12
|
+
var dateAttrs = ['timestamp'];
|
|
13
|
+
|
|
14
|
+
// Attribute that indicates a task item state change
|
|
15
|
+
var taskItemAttrs = ['state'];
|
|
16
|
+
|
|
17
|
+
// Attributes excluded from extension change detection (not meaningful content changes)
|
|
18
|
+
var extensionExcludedAttrs = ['localId'];
|
|
19
|
+
|
|
20
|
+
// Extension node type names
|
|
21
|
+
var extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
|
|
22
|
+
var getStepAttrs = function getStepAttrs(step) {
|
|
23
|
+
if (step instanceof AttrStep) {
|
|
24
|
+
return [step.attr];
|
|
25
|
+
}
|
|
26
|
+
if (step instanceof SetAttrsStep && step.attrs) {
|
|
27
|
+
return Object.keys(step.attrs);
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
};
|
|
10
31
|
export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
|
|
11
32
|
return steps.map(function (step) {
|
|
12
|
-
if (step instanceof AttrStep &&
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
if (!(step instanceof AttrStep) && !(step instanceof SetAttrsStep)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
var stepAttrs = getStepAttrs(step);
|
|
37
|
+
var $pos = doc.resolve(step.pos);
|
|
38
|
+
var nodeAtPos = doc.nodeAt(step.pos);
|
|
39
|
+
if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
|
|
40
|
+
// date node: timestamp attribute change — highlight the date node itself (inline)
|
|
41
|
+
if (stepAttrs.some(function (v) {
|
|
42
|
+
return dateAttrs.includes(v);
|
|
43
|
+
}) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
|
|
44
|
+
return {
|
|
45
|
+
fromB: step.pos,
|
|
46
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
47
|
+
isInline: true
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// taskItem node: state attribute change — highlight the taskItem node
|
|
52
|
+
if (stepAttrs.some(function (v) {
|
|
53
|
+
return taskItemAttrs.includes(v);
|
|
54
|
+
}) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
|
|
18
55
|
return {
|
|
19
|
-
fromB:
|
|
20
|
-
toB:
|
|
56
|
+
fromB: step.pos,
|
|
57
|
+
toB: step.pos + nodeAtPos.nodeSize
|
|
21
58
|
};
|
|
22
59
|
}
|
|
60
|
+
|
|
61
|
+
// extension nodes: any attribute change except localId — highlight the node
|
|
62
|
+
if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(function (v) {
|
|
63
|
+
return !extensionExcludedAttrs.includes(v);
|
|
64
|
+
})) {
|
|
65
|
+
var isInline = nodeAtPos.type.name === 'inlineExtension';
|
|
66
|
+
return {
|
|
67
|
+
fromB: step.pos,
|
|
68
|
+
toB: step.pos + nodeAtPos.nodeSize,
|
|
69
|
+
isInline: isInline
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// media node: id/collection/url attribute change — highlight the mediaSingle parent
|
|
75
|
+
if (stepAttrs.some(function (v) {
|
|
76
|
+
return mediaAttrs.includes(v);
|
|
77
|
+
}) && $pos.parent.type === doc.type.schema.nodes.mediaSingle) {
|
|
78
|
+
var startPos = $pos.pos + $pos.parentOffset;
|
|
79
|
+
return {
|
|
80
|
+
fromB: startPos,
|
|
81
|
+
toB: startPos + $pos.parent.nodeSize - 1
|
|
82
|
+
};
|
|
23
83
|
}
|
|
24
84
|
return undefined;
|
|
25
85
|
}).filter(filterUndefined);
|
|
@@ -3,5 +3,10 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
|
3
3
|
* Looser equality for "safe diff" cases: same full text content and same block structure
|
|
4
4
|
* (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
|
|
5
5
|
* This is safe because we ensure decorations get applied to valid positions.
|
|
6
|
+
*
|
|
7
|
+
* Marks are intentionally ignored — two documents that differ only in mark application
|
|
8
|
+
* (e.g. bold/italic boundaries or annotation mark ordering) are considered equal here.
|
|
9
|
+
* Both documents are mark-stripped before comparison so that mark-driven text fragmentation
|
|
10
|
+
* does not produce false inequalities.
|
|
6
11
|
*/
|
|
7
12
|
export declare function areDocsEqualByBlockStructureAndText(doc1: PMNode, doc2: PMNode): boolean;
|
|
@@ -2,6 +2,8 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
|
2
2
|
import { type Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
3
|
type StepRange = {
|
|
4
4
|
fromB: number;
|
|
5
|
+
/** Whether the changed node is inline (true) or block (false/undefined) */
|
|
6
|
+
isInline?: boolean;
|
|
5
7
|
toB: number;
|
|
6
8
|
};
|
|
7
9
|
export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[]) => StepRange[];
|
|
@@ -3,5 +3,10 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
|
3
3
|
* Looser equality for "safe diff" cases: same full text content and same block structure
|
|
4
4
|
* (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
|
|
5
5
|
* This is safe because we ensure decorations get applied to valid positions.
|
|
6
|
+
*
|
|
7
|
+
* Marks are intentionally ignored — two documents that differ only in mark application
|
|
8
|
+
* (e.g. bold/italic boundaries or annotation mark ordering) are considered equal here.
|
|
9
|
+
* Both documents are mark-stripped before comparison so that mark-driven text fragmentation
|
|
10
|
+
* does not produce false inequalities.
|
|
6
11
|
*/
|
|
7
12
|
export declare function areDocsEqualByBlockStructureAndText(doc1: PMNode, doc2: PMNode): boolean;
|
|
@@ -2,6 +2,8 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
|
2
2
|
import { type Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
3
|
type StepRange = {
|
|
4
4
|
fromB: number;
|
|
5
|
+
/** Whether the changed node is inline (true) or block (false/undefined) */
|
|
6
|
+
isInline?: boolean;
|
|
5
7
|
toB: number;
|
|
6
8
|
};
|
|
7
9
|
export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[]) => StepRange[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-show-diff",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.2",
|
|
4
4
|
"description": "ShowDiff plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@atlaskit/editor-prosemirror": "^7.3.0",
|
|
36
36
|
"@atlaskit/editor-tables": "^2.9.0",
|
|
37
37
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
38
|
-
"@atlaskit/tmp-editor-statsig": "^80.
|
|
38
|
+
"@atlaskit/tmp-editor-statsig": "^80.1.0",
|
|
39
39
|
"@atlaskit/tokens": "^13.0.0",
|
|
40
40
|
"@babel/runtime": "^7.0.0",
|
|
41
41
|
"lodash": "^4.17.21",
|