@atlaskit/editor-plugin-show-diff 8.4.0 → 8.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,18 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 8.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`8a9f26c6c71bc`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8a9f26c6c71bc) -
8
+ [ux] Improve diff logic for some nodes and edge cases where marks are causing the diff to fail
9
+ - Updated dependencies
10
+
3
11
  ## 8.4.0
4
12
 
5
13
  ### Minor Changes
6
14
 
7
- - [`41168b2bd2790`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/41168b2bd2790) -
15
+ - [`ebab8f80bfc40`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ebab8f80bfc40) -
8
16
  Autofix: add explicit package exports (barrel removal)
9
17
 
10
18
  ### 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 true if both nodes have the same tree structure (type and child count at every level).
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
- return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
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
- decorations.push.apply(decorations, (0, _toConsumableArray2.default)(calculateNodesForBlockDecoration({
225
- doc: tr.doc,
226
- from: change.fromB,
227
- to: change.toB,
228
- colorScheme: colorScheme,
229
- isInserted: true,
230
- activeIndexPos: activeIndexPos,
231
- intl: intl
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
  };
@@ -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
- // Currently allow attributes that indicats a change in media image
16
- var allowedAttrs = ['id', 'collection', 'url'];
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 && allowedAttrs.includes(step.attr) || step instanceof _steps.SetAttrsStep && step.attrs && (0, _toConsumableArray2.default)(Object.keys(step.attrs)).some(function (v) {
20
- return allowedAttrs.includes(v);
21
- })) {
22
- var $pos = doc.resolve(step.pos);
23
- if ($pos.parent.type === doc.type.schema.nodes.mediaSingle) {
24
- var startPos = $pos.pos + $pos.parentOffset;
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: startPos,
27
- toB: startPos + $pos.parent.nodeSize - 1
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
- return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
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
- decorations.push(...calculateNodesForBlockDecoration({
206
- doc: tr.doc,
207
- from: change.fromB,
208
- to: change.toB,
209
- colorScheme,
210
- isInserted: true,
211
- activeIndexPos,
212
- intl
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
  };
@@ -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
- // Currently allow attributes that indicats a change in media image
6
- const allowedAttrs = ['id', 'collection', 'url'];
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 && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && step.attrs && [...Object.keys(step.attrs)].some(v => allowedAttrs.includes(v))) {
10
- const $pos = doc.resolve(step.pos);
11
- if ($pos.parent.type === doc.type.schema.nodes.mediaSingle) {
12
- const startPos = $pos.pos + $pos.parentOffset;
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: startPos,
15
- toB: startPos + $pos.parent.nodeSize - 1
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
- return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
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
- decorations.push.apply(decorations, _toConsumableArray(calculateNodesForBlockDecoration({
219
- doc: tr.doc,
220
- from: change.fromB,
221
- to: change.toB,
222
- colorScheme: colorScheme,
223
- isInserted: true,
224
- activeIndexPos: activeIndexPos,
225
- intl: intl
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
  };
@@ -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
- // Currently allow attributes that indicats a change in media image
9
- var allowedAttrs = ['id', 'collection', 'url'];
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 && allowedAttrs.includes(step.attr) || step instanceof SetAttrsStep && step.attrs && _toConsumableArray(Object.keys(step.attrs)).some(function (v) {
13
- return allowedAttrs.includes(v);
14
- })) {
15
- var $pos = doc.resolve(step.pos);
16
- if ($pos.parent.type === doc.type.schema.nodes.mediaSingle) {
17
- var startPos = $pos.pos + $pos.parentOffset;
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: startPos,
20
- toB: startPos + $pos.parent.nodeSize - 1
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.0",
3
+ "version": "8.4.1",
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.0.0",
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",