@atlaskit/editor-plugin-show-diff 10.5.1 → 10.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 10.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`75c7e6a9aa4ec`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/75c7e6a9aa4ec) -
8
+ [ux] Improve showing diff for attribute changes for inline nodes.
9
+ - Updated dependencies
10
+
3
11
  ## 10.5.1
4
12
 
5
13
  ### Patch Changes
@@ -427,16 +427,43 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
427
427
  isInserted: true
428
428
  })));
429
429
  });
430
- (0, _getAttrChangeRanges.getAttrChangeRanges)(tr.doc, attrSteps).forEach(function (change) {
430
+ (0, _getAttrChangeRanges.getAttrChangeRanges)(tr.doc, attrSteps, originalDoc).forEach(function (change) {
431
431
  if (change.isInline) {
432
- // Inline nodes (e.g. date) need an inline decoration rather than a block decoration
432
+ // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
433
433
  var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
434
+ var isAtomicInlineNodeFlag = (0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true);
434
435
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
435
436
  change: change,
436
437
  colorScheme: colorScheme,
437
438
  isActive: isActive,
438
- isInserted: true
439
+ isInserted: true,
440
+ isAtomicInlineNode: isAtomicInlineNodeFlag,
441
+ inlineNodeName: change.inlineNodeName,
442
+ // Suppress the border-bottom underline for atomic inline nodeviews —
443
+ // it doesn't render on custom nodeview DOM elements and is unnecessary noise.
444
+ hideAddedDiffsUnderline: isAtomicInlineNodeFlag,
445
+ diffType: diffType
439
446
  })));
447
+ // If we have the original node position, also render the old node as a "deleted" widget
448
+ // so the user can see what it looked like before the change.
449
+ if (change.fromA !== undefined && change.toA !== undefined && (0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
450
+ decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createNodeChangedDecorationWidget.createNodeChangedDecorationWidget)({
451
+ change: {
452
+ fromA: change.fromA,
453
+ toA: change.toA,
454
+ fromB: change.fromB,
455
+ toB: change.toB,
456
+ deleted: []
457
+ },
458
+ doc: originalDoc,
459
+ nodeViewSerializer: nodeViewSerializer,
460
+ colorScheme: colorScheme,
461
+ newDoc: tr.doc,
462
+ intl: intl,
463
+ activeIndexPos: activeIndexPos,
464
+ isInserted: false
465
+ })));
466
+ }
440
467
  } else {
441
468
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)(calculateNodesForBlockDecoration({
442
469
  doc: tr.doc,
@@ -93,7 +93,7 @@ var deletedTraditionalBlockOutlineRoundedActive = exports.deletedTraditionalBloc
93
93
  borderRadius: "calc(".concat("var(--ds-radius-xsmall, 2px)", " + 1px)")
94
94
  });
95
95
 
96
- /** Scroll-navigation “new” emphasis: 4px ring (see {@link traditionalStyleNodeNew}). */
96
+ /** Scroll-navigation “new” emphasis: 4px ring. */
97
97
  var deletedTraditionalBlockOutlineNew = exports.deletedTraditionalBlockOutlineNew = (0, _lazyNodeView.convertToInlineCss)({
98
98
  boxShadow: "0 0 0 4px ".concat("var(--ds-background-accent-red-subtlest, #FFECEB)"),
99
99
  borderRadius: "var(--ds-radius-small, 4px)"
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.createInlineChangedDecoration = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
10
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
10
11
  var _view = require("@atlaskit/editor-prosemirror/view");
11
12
  var _isExtendedEnabled = require("../isExtendedEnabled");
@@ -13,9 +14,21 @@ var _standard = require("./colorSchemes/standard");
13
14
  var _traditional = require("./colorSchemes/traditional");
14
15
  var _createAnchorDecorationWidgets = require("./createAnchorDecorationWidgets");
15
16
  var _decorationKeys = require("./decorationKeys");
17
+ 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
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
16
19
  var displayNoneStyle = (0, _lazyNodeView.convertToInlineCss)({
17
20
  display: 'none'
18
21
  });
22
+ var getAtomicInlineNodeClassName = function getAtomicInlineNodeClassName(inlineNodeName, colorScheme) {
23
+ var classNames = ['show-diff-atomic-inline-changed'];
24
+ if (inlineNodeName) {
25
+ classNames.push("show-diff-atomic-inline-changed-".concat(inlineNodeName));
26
+ }
27
+ if (colorScheme === 'traditional') {
28
+ classNames.push('show-diff-atomic-inline-changed-traditional');
29
+ }
30
+ return classNames.join(' ');
31
+ };
19
32
  /**
20
33
  * Inline decoration used for insertions as the content already exists in the document
21
34
  *
@@ -30,6 +43,8 @@ var createInlineChangedDecoration = exports.createInlineChangedDecoration = func
30
43
  isActive = _ref$isActive === void 0 ? false : _ref$isActive,
31
44
  _ref$isInserted = _ref.isInserted,
32
45
  isInserted = _ref$isInserted === void 0 ? true : _ref$isInserted,
46
+ _ref$isAtomicInlineNo = _ref.isAtomicInlineNode,
47
+ isAtomicInlineNode = _ref$isAtomicInlineNo === void 0 ? false : _ref$isAtomicInlineNo,
33
48
  _ref$shouldHideDelete = _ref.shouldHideDeleted,
34
49
  shouldHideDeleted = _ref$shouldHideDelete === void 0 ? false : _ref$shouldHideDelete,
35
50
  _ref$showIndicators = _ref.showIndicators,
@@ -37,7 +52,8 @@ var createInlineChangedDecoration = exports.createInlineChangedDecoration = func
37
52
  doc = _ref.doc,
38
53
  diffType = _ref.diffType,
39
54
  _ref$hideAddedDiffsUn = _ref.hideAddedDiffsUnderline,
40
- hideAddedDiffsUnderline = _ref$hideAddedDiffsUn === void 0 ? false : _ref$hideAddedDiffsUn;
55
+ hideAddedDiffsUnderline = _ref$hideAddedDiffsUn === void 0 ? false : _ref$hideAddedDiffsUn,
56
+ inlineNodeName = _ref.inlineNodeName;
41
57
  var diffId = crypto.randomUUID();
42
58
  if (shouldHideDeleted) {
43
59
  return [_view.Decoration.inline(change.fromB, change.toB, {
@@ -76,10 +92,13 @@ var createInlineChangedDecoration = exports.createInlineChangedDecoration = func
76
92
  style = isActive ? _standard.editingStyleActive : _standard.editingStyle;
77
93
  }
78
94
  }
79
- var decorations = [_view.Decoration.inline(change.fromB, change.toB, {
80
- style: style,
95
+ var decorations = [_view.Decoration.inline(change.fromB, change.toB, _objectSpread(_objectSpread({
96
+ style: style
97
+ }, isAtomicInlineNode && isInserted && {
98
+ class: getAtomicInlineNodeClassName(inlineNodeName, colorScheme)
99
+ }), {}, {
81
100
  'data-testid': 'show-diff-changed-decoration'
82
- }, (0, _decorationKeys.buildDiffDecorationSpec)({
101
+ }), (0, _decorationKeys.buildDiffDecorationSpec)({
83
102
  decorationType: 'inline',
84
103
  diffId: diffId,
85
104
  isActive: isActive
@@ -1,11 +1,16 @@
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.stepIsValidAttrChange = exports.getAttrChangeRanges = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
7
9
  var _steps = require("@atlaskit/adf-schema/steps");
8
10
  var _transform = require("@atlaskit/editor-prosemirror/transform");
11
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
12
+ 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; }
13
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
9
14
  var filterUndefined = function filterUndefined(x) {
10
15
  return !!x;
11
16
  };
@@ -19,6 +24,26 @@ var dateAttrs = ['timestamp'];
19
24
  // Attribute that indicates a task item state change
20
25
  var taskItemAttrs = ['state'];
21
26
 
27
+ // Attributes that indicate an emoji change
28
+ var emojiAttrs = ['shortName', 'id', 'text'];
29
+
30
+ // Attributes that indicate a mention change (who is mentioned)
31
+ var mentionAttrs = ['id', 'text'];
32
+
33
+ // Attributes that indicate a status change (label or colour)
34
+ var statusAttrs = ['text', 'color'];
35
+
36
+ // Map of node type name → the attrs that represent a meaningful content change for that node
37
+ var inlineNodeAttrMap = {
38
+ date: dateAttrs,
39
+ emoji: emojiAttrs,
40
+ mention: mentionAttrs,
41
+ status: statusAttrs
42
+ };
43
+ var isInlineAttrChangeNodeName = function isInlineAttrChangeNodeName(nodeName) {
44
+ return nodeName in inlineNodeAttrMap;
45
+ };
46
+
22
47
  // Attributes excluded from extension change detection (not meaningful content changes)
23
48
  var extensionExcludedAttrs = ['localId'];
24
49
 
@@ -33,7 +58,7 @@ var getStepAttrs = function getStepAttrs(step) {
33
58
  }
34
59
  return [];
35
60
  };
36
- var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
61
+ var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRanges(doc, steps, originalDoc) {
37
62
  return steps.map(function (step) {
38
63
  if (!(step instanceof _transform.AttrStep) && !(step instanceof _steps.SetAttrsStep)) {
39
64
  return undefined;
@@ -45,14 +70,39 @@ var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRa
45
70
  // date node: timestamp attribute change — highlight the date node itself (inline)
46
71
  if (stepAttrs.some(function (v) {
47
72
  return dateAttrs.includes(v);
48
- }) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
73
+ }) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date' && !(0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
49
74
  return {
50
75
  fromB: step.pos,
51
76
  toB: step.pos + nodeAtPos.nodeSize,
52
- isInline: true
77
+ isInline: true,
78
+ inlineNodeName: 'date'
53
79
  };
54
80
  }
55
81
 
82
+ // The following inline node attr changes are gated behind platform_editor_improve_inline_diffs.
83
+ // The changeset path (createDecorationsForChange) handles the deletion widget via
84
+ // prosemirror-changeset; we only need to add the inline insertion highlight here.
85
+ if ((0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true) && nodeAtPos) {
86
+ var nodeName = nodeAtPos.type.name;
87
+ if (isInlineAttrChangeNodeName(nodeName)) {
88
+ var watchedAttrs = inlineNodeAttrMap[nodeName];
89
+ if (stepAttrs.some(function (v) {
90
+ return watchedAttrs.includes(v);
91
+ })) {
92
+ var originalNodeAtPos = originalDoc === null || originalDoc === void 0 ? void 0 : originalDoc.nodeAt(step.pos);
93
+ return _objectSpread({
94
+ fromB: step.pos,
95
+ toB: step.pos + nodeAtPos.nodeSize,
96
+ isInline: true,
97
+ inlineNodeName: nodeName
98
+ }, originalNodeAtPos && {
99
+ fromA: step.pos,
100
+ toA: step.pos + originalNodeAtPos.nodeSize
101
+ });
102
+ }
103
+ }
104
+ }
105
+
56
106
  // taskItem node: state attribute change — highlight the taskItem node
57
107
  if (stepAttrs.some(function (v) {
58
108
  return taskItemAttrs.includes(v);
@@ -86,7 +136,17 @@ var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRa
86
136
  };
87
137
  }
88
138
  return undefined;
89
- }).filter(filterUndefined);
139
+ }).filter(filterUndefined)
140
+ // Deduplicate by node position: multiple AttrSteps on the same node
141
+ // (e.g. setNodeAttribute(pos, 'text', ...) + setNodeAttribute(pos, 'color', ...))
142
+ // should produce only one decoration, not one per step.
143
+ // Gated behind the same experiment as the inline node attr changes that introduced
144
+ // the possibility of multiple ranges for the same node position.
145
+ .filter(function (range, i, arr) {
146
+ return !(0, _expValEquals.expValEquals)('platform_editor_improve_inline_diffs', 'isEnabled', true) || arr.findIndex(function (r) {
147
+ return r.fromB === range.fromB;
148
+ }) === i;
149
+ });
90
150
  };
91
151
 
92
152
  /**
@@ -390,16 +390,43 @@ const calculateDiffDecorationsInner = ({
390
390
  isInserted: true
391
391
  }));
392
392
  });
393
- getAttrChangeRanges(tr.doc, attrSteps).forEach(change => {
393
+ getAttrChangeRanges(tr.doc, attrSteps, originalDoc).forEach(change => {
394
394
  if (change.isInline) {
395
- // Inline nodes (e.g. date) need an inline decoration rather than a block decoration
395
+ // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
396
396
  const isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
397
+ const isAtomicInlineNodeFlag = expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true);
397
398
  decorations.push(...createInlineChangedDecoration({
398
399
  change,
399
400
  colorScheme,
400
401
  isActive,
401
- isInserted: true
402
+ isInserted: true,
403
+ isAtomicInlineNode: isAtomicInlineNodeFlag,
404
+ inlineNodeName: change.inlineNodeName,
405
+ // Suppress the border-bottom underline for atomic inline nodeviews —
406
+ // it doesn't render on custom nodeview DOM elements and is unnecessary noise.
407
+ hideAddedDiffsUnderline: isAtomicInlineNodeFlag,
408
+ diffType
402
409
  }));
410
+ // If we have the original node position, also render the old node as a "deleted" widget
411
+ // so the user can see what it looked like before the change.
412
+ if (change.fromA !== undefined && change.toA !== undefined && expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
413
+ decorations.push(...createNodeChangedDecorationWidget({
414
+ change: {
415
+ fromA: change.fromA,
416
+ toA: change.toA,
417
+ fromB: change.fromB,
418
+ toB: change.toB,
419
+ deleted: []
420
+ },
421
+ doc: originalDoc,
422
+ nodeViewSerializer,
423
+ colorScheme,
424
+ newDoc: tr.doc,
425
+ intl,
426
+ activeIndexPos,
427
+ isInserted: false
428
+ }));
429
+ }
403
430
  } else {
404
431
  decorations.push(...calculateNodesForBlockDecoration({
405
432
  doc: tr.doc,
@@ -82,7 +82,7 @@ export const deletedTraditionalBlockOutlineRoundedActive = convertToInlineCss({
82
82
  borderRadius: `calc(${"var(--ds-radius-xsmall, 2px)"} + 1px)`
83
83
  });
84
84
 
85
- /** Scroll-navigation “new” emphasis: 4px ring (see {@link traditionalStyleNodeNew}). */
85
+ /** Scroll-navigation “new” emphasis: 4px ring. */
86
86
  export const deletedTraditionalBlockOutlineNew = convertToInlineCss({
87
87
  boxShadow: `0 0 0 4px ${"var(--ds-background-accent-red-subtlest, #FFECEB)"}`,
88
88
  borderRadius: "var(--ds-radius-small, 4px)"
@@ -8,6 +8,16 @@ import { buildDiffDecorationSpec } from './decorationKeys';
8
8
  const displayNoneStyle = convertToInlineCss({
9
9
  display: 'none'
10
10
  });
11
+ const getAtomicInlineNodeClassName = (inlineNodeName, colorScheme) => {
12
+ const classNames = ['show-diff-atomic-inline-changed'];
13
+ if (inlineNodeName) {
14
+ classNames.push(`show-diff-atomic-inline-changed-${inlineNodeName}`);
15
+ }
16
+ if (colorScheme === 'traditional') {
17
+ classNames.push('show-diff-atomic-inline-changed-traditional');
18
+ }
19
+ return classNames.join(' ');
20
+ };
11
21
  /**
12
22
  * Inline decoration used for insertions as the content already exists in the document
13
23
  *
@@ -20,11 +30,13 @@ export const createInlineChangedDecoration = ({
20
30
  colorScheme,
21
31
  isActive = false,
22
32
  isInserted = true,
33
+ isAtomicInlineNode = false,
23
34
  shouldHideDeleted = false,
24
35
  showIndicators = false,
25
36
  doc,
26
37
  diffType,
27
- hideAddedDiffsUnderline = false
38
+ hideAddedDiffsUnderline = false,
39
+ inlineNodeName
28
40
  }) => {
29
41
  const diffId = crypto.randomUUID();
30
42
  if (shouldHideDeleted) {
@@ -66,6 +78,9 @@ export const createInlineChangedDecoration = ({
66
78
  }
67
79
  const decorations = [Decoration.inline(change.fromB, change.toB, {
68
80
  style,
81
+ ...(isAtomicInlineNode && isInserted && {
82
+ class: getAtomicInlineNodeClassName(inlineNodeName, colorScheme)
83
+ }),
69
84
  'data-testid': 'show-diff-changed-decoration'
70
85
  }, buildDiffDecorationSpec({
71
86
  decorationType: 'inline',
@@ -1,5 +1,6 @@
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
  // Attributes that indicate a change in media image
@@ -11,6 +12,24 @@ const dateAttrs = ['timestamp'];
11
12
  // Attribute that indicates a task item state change
12
13
  const taskItemAttrs = ['state'];
13
14
 
15
+ // Attributes that indicate an emoji change
16
+ const emojiAttrs = ['shortName', 'id', 'text'];
17
+
18
+ // Attributes that indicate a mention change (who is mentioned)
19
+ const mentionAttrs = ['id', 'text'];
20
+
21
+ // Attributes that indicate a status change (label or colour)
22
+ const statusAttrs = ['text', 'color'];
23
+
24
+ // Map of node type name → the attrs that represent a meaningful content change for that node
25
+ const inlineNodeAttrMap = {
26
+ date: dateAttrs,
27
+ emoji: emojiAttrs,
28
+ mention: mentionAttrs,
29
+ status: statusAttrs
30
+ };
31
+ const isInlineAttrChangeNodeName = nodeName => nodeName in inlineNodeAttrMap;
32
+
14
33
  // Attributes excluded from extension change detection (not meaningful content changes)
15
34
  const extensionExcludedAttrs = ['localId'];
16
35
 
@@ -25,7 +44,7 @@ const getStepAttrs = step => {
25
44
  }
26
45
  return [];
27
46
  };
28
- export const getAttrChangeRanges = (doc, steps) => {
47
+ export const getAttrChangeRanges = (doc, steps, originalDoc) => {
29
48
  return steps.map(step => {
30
49
  if (!(step instanceof AttrStep) && !(step instanceof SetAttrsStep)) {
31
50
  return undefined;
@@ -35,14 +54,38 @@ export const getAttrChangeRanges = (doc, steps) => {
35
54
  const nodeAtPos = doc.nodeAt(step.pos);
36
55
 
37
56
  // date node: timestamp attribute change — highlight the date node itself (inline)
38
- if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
57
+ if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date' && !expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
39
58
  return {
40
59
  fromB: step.pos,
41
60
  toB: step.pos + nodeAtPos.nodeSize,
42
- isInline: true
61
+ isInline: true,
62
+ inlineNodeName: 'date'
43
63
  };
44
64
  }
45
65
 
66
+ // The following inline node attr changes are gated behind platform_editor_improve_inline_diffs.
67
+ // The changeset path (createDecorationsForChange) handles the deletion widget via
68
+ // prosemirror-changeset; we only need to add the inline insertion highlight here.
69
+ if (expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true) && nodeAtPos) {
70
+ const nodeName = nodeAtPos.type.name;
71
+ if (isInlineAttrChangeNodeName(nodeName)) {
72
+ const watchedAttrs = inlineNodeAttrMap[nodeName];
73
+ if (stepAttrs.some(v => watchedAttrs.includes(v))) {
74
+ const originalNodeAtPos = originalDoc === null || originalDoc === void 0 ? void 0 : originalDoc.nodeAt(step.pos);
75
+ return {
76
+ fromB: step.pos,
77
+ toB: step.pos + nodeAtPos.nodeSize,
78
+ isInline: true,
79
+ inlineNodeName: nodeName,
80
+ ...(originalNodeAtPos && {
81
+ fromA: step.pos,
82
+ toA: step.pos + originalNodeAtPos.nodeSize
83
+ })
84
+ };
85
+ }
86
+ }
87
+ }
88
+
46
89
  // taskItem node: state attribute change — highlight the taskItem node
47
90
  if (stepAttrs.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
48
91
  return {
@@ -70,7 +113,13 @@ export const getAttrChangeRanges = (doc, steps) => {
70
113
  };
71
114
  }
72
115
  return undefined;
73
- }).filter(filterUndefined);
116
+ }).filter(filterUndefined)
117
+ // Deduplicate by node position: multiple AttrSteps on the same node
118
+ // (e.g. setNodeAttribute(pos, 'text', ...) + setNodeAttribute(pos, 'color', ...))
119
+ // should produce only one decoration, not one per step.
120
+ // Gated behind the same experiment as the inline node attr changes that introduced
121
+ // the possibility of multiple ranges for the same node position.
122
+ .filter((range, i, arr) => !expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true) || arr.findIndex(r => r.fromB === range.fromB) === i);
74
123
  };
75
124
 
76
125
  /**
@@ -421,16 +421,43 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref3
421
421
  isInserted: true
422
422
  })));
423
423
  });
424
- getAttrChangeRanges(tr.doc, attrSteps).forEach(function (change) {
424
+ getAttrChangeRanges(tr.doc, attrSteps, originalDoc).forEach(function (change) {
425
425
  if (change.isInline) {
426
- // Inline nodes (e.g. date) need an inline decoration rather than a block decoration
426
+ // Inline nodes (e.g. date, emoji, mention, status) need an inline decoration rather than a block decoration
427
427
  var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
428
+ var isAtomicInlineNodeFlag = expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true);
428
429
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration({
429
430
  change: change,
430
431
  colorScheme: colorScheme,
431
432
  isActive: isActive,
432
- isInserted: true
433
+ isInserted: true,
434
+ isAtomicInlineNode: isAtomicInlineNodeFlag,
435
+ inlineNodeName: change.inlineNodeName,
436
+ // Suppress the border-bottom underline for atomic inline nodeviews —
437
+ // it doesn't render on custom nodeview DOM elements and is unnecessary noise.
438
+ hideAddedDiffsUnderline: isAtomicInlineNodeFlag,
439
+ diffType: diffType
433
440
  })));
441
+ // If we have the original node position, also render the old node as a "deleted" widget
442
+ // so the user can see what it looked like before the change.
443
+ if (change.fromA !== undefined && change.toA !== undefined && expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
444
+ decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget({
445
+ change: {
446
+ fromA: change.fromA,
447
+ toA: change.toA,
448
+ fromB: change.fromB,
449
+ toB: change.toB,
450
+ deleted: []
451
+ },
452
+ doc: originalDoc,
453
+ nodeViewSerializer: nodeViewSerializer,
454
+ colorScheme: colorScheme,
455
+ newDoc: tr.doc,
456
+ intl: intl,
457
+ activeIndexPos: activeIndexPos,
458
+ isInserted: false
459
+ })));
460
+ }
434
461
  } else {
435
462
  decorations.push.apply(decorations, _toConsumableArray(calculateNodesForBlockDecoration({
436
463
  doc: tr.doc,
@@ -84,7 +84,7 @@ export var deletedTraditionalBlockOutlineRoundedActive = convertToInlineCss({
84
84
  borderRadius: "calc(".concat("var(--ds-radius-xsmall, 2px)", " + 1px)")
85
85
  });
86
86
 
87
- /** Scroll-navigation “new” emphasis: 4px ring (see {@link traditionalStyleNodeNew}). */
87
+ /** Scroll-navigation “new” emphasis: 4px ring. */
88
88
  export var deletedTraditionalBlockOutlineNew = convertToInlineCss({
89
89
  boxShadow: "0 0 0 4px ".concat("var(--ds-background-accent-red-subtlest, #FFECEB)"),
90
90
  borderRadius: "var(--ds-radius-small, 4px)"
@@ -1,4 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2
5
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
3
6
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
7
  import { isExtendedEnabled } from '../isExtendedEnabled';
@@ -9,6 +12,16 @@ import { buildDiffDecorationSpec } from './decorationKeys';
9
12
  var displayNoneStyle = convertToInlineCss({
10
13
  display: 'none'
11
14
  });
15
+ var getAtomicInlineNodeClassName = function getAtomicInlineNodeClassName(inlineNodeName, colorScheme) {
16
+ var classNames = ['show-diff-atomic-inline-changed'];
17
+ if (inlineNodeName) {
18
+ classNames.push("show-diff-atomic-inline-changed-".concat(inlineNodeName));
19
+ }
20
+ if (colorScheme === 'traditional') {
21
+ classNames.push('show-diff-atomic-inline-changed-traditional');
22
+ }
23
+ return classNames.join(' ');
24
+ };
12
25
  /**
13
26
  * Inline decoration used for insertions as the content already exists in the document
14
27
  *
@@ -23,6 +36,8 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
23
36
  isActive = _ref$isActive === void 0 ? false : _ref$isActive,
24
37
  _ref$isInserted = _ref.isInserted,
25
38
  isInserted = _ref$isInserted === void 0 ? true : _ref$isInserted,
39
+ _ref$isAtomicInlineNo = _ref.isAtomicInlineNode,
40
+ isAtomicInlineNode = _ref$isAtomicInlineNo === void 0 ? false : _ref$isAtomicInlineNo,
26
41
  _ref$shouldHideDelete = _ref.shouldHideDeleted,
27
42
  shouldHideDeleted = _ref$shouldHideDelete === void 0 ? false : _ref$shouldHideDelete,
28
43
  _ref$showIndicators = _ref.showIndicators,
@@ -30,7 +45,8 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
30
45
  doc = _ref.doc,
31
46
  diffType = _ref.diffType,
32
47
  _ref$hideAddedDiffsUn = _ref.hideAddedDiffsUnderline,
33
- hideAddedDiffsUnderline = _ref$hideAddedDiffsUn === void 0 ? false : _ref$hideAddedDiffsUn;
48
+ hideAddedDiffsUnderline = _ref$hideAddedDiffsUn === void 0 ? false : _ref$hideAddedDiffsUn,
49
+ inlineNodeName = _ref.inlineNodeName;
34
50
  var diffId = crypto.randomUUID();
35
51
  if (shouldHideDeleted) {
36
52
  return [Decoration.inline(change.fromB, change.toB, {
@@ -69,10 +85,13 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
69
85
  style = isActive ? editingStyleActive : editingStyle;
70
86
  }
71
87
  }
72
- var decorations = [Decoration.inline(change.fromB, change.toB, {
73
- style: style,
88
+ var decorations = [Decoration.inline(change.fromB, change.toB, _objectSpread(_objectSpread({
89
+ style: style
90
+ }, isAtomicInlineNode && isInserted && {
91
+ class: getAtomicInlineNodeClassName(inlineNodeName, colorScheme)
92
+ }), {}, {
74
93
  'data-testid': 'show-diff-changed-decoration'
75
- }, buildDiffDecorationSpec({
94
+ }), buildDiffDecorationSpec({
76
95
  decorationType: 'inline',
77
96
  diffId: diffId,
78
97
  isActive: isActive
@@ -1,5 +1,9 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
1
4
  import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
2
5
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
6
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
3
7
  var filterUndefined = function filterUndefined(x) {
4
8
  return !!x;
5
9
  };
@@ -13,6 +17,26 @@ var dateAttrs = ['timestamp'];
13
17
  // Attribute that indicates a task item state change
14
18
  var taskItemAttrs = ['state'];
15
19
 
20
+ // Attributes that indicate an emoji change
21
+ var emojiAttrs = ['shortName', 'id', 'text'];
22
+
23
+ // Attributes that indicate a mention change (who is mentioned)
24
+ var mentionAttrs = ['id', 'text'];
25
+
26
+ // Attributes that indicate a status change (label or colour)
27
+ var statusAttrs = ['text', 'color'];
28
+
29
+ // Map of node type name → the attrs that represent a meaningful content change for that node
30
+ var inlineNodeAttrMap = {
31
+ date: dateAttrs,
32
+ emoji: emojiAttrs,
33
+ mention: mentionAttrs,
34
+ status: statusAttrs
35
+ };
36
+ var isInlineAttrChangeNodeName = function isInlineAttrChangeNodeName(nodeName) {
37
+ return nodeName in inlineNodeAttrMap;
38
+ };
39
+
16
40
  // Attributes excluded from extension change detection (not meaningful content changes)
17
41
  var extensionExcludedAttrs = ['localId'];
18
42
 
@@ -27,7 +51,7 @@ var getStepAttrs = function getStepAttrs(step) {
27
51
  }
28
52
  return [];
29
53
  };
30
- export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
54
+ export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps, originalDoc) {
31
55
  return steps.map(function (step) {
32
56
  if (!(step instanceof AttrStep) && !(step instanceof SetAttrsStep)) {
33
57
  return undefined;
@@ -39,14 +63,39 @@ export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
39
63
  // date node: timestamp attribute change — highlight the date node itself (inline)
40
64
  if (stepAttrs.some(function (v) {
41
65
  return dateAttrs.includes(v);
42
- }) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
66
+ }) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date' && !expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true)) {
43
67
  return {
44
68
  fromB: step.pos,
45
69
  toB: step.pos + nodeAtPos.nodeSize,
46
- isInline: true
70
+ isInline: true,
71
+ inlineNodeName: 'date'
47
72
  };
48
73
  }
49
74
 
75
+ // The following inline node attr changes are gated behind platform_editor_improve_inline_diffs.
76
+ // The changeset path (createDecorationsForChange) handles the deletion widget via
77
+ // prosemirror-changeset; we only need to add the inline insertion highlight here.
78
+ if (expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true) && nodeAtPos) {
79
+ var nodeName = nodeAtPos.type.name;
80
+ if (isInlineAttrChangeNodeName(nodeName)) {
81
+ var watchedAttrs = inlineNodeAttrMap[nodeName];
82
+ if (stepAttrs.some(function (v) {
83
+ return watchedAttrs.includes(v);
84
+ })) {
85
+ var originalNodeAtPos = originalDoc === null || originalDoc === void 0 ? void 0 : originalDoc.nodeAt(step.pos);
86
+ return _objectSpread({
87
+ fromB: step.pos,
88
+ toB: step.pos + nodeAtPos.nodeSize,
89
+ isInline: true,
90
+ inlineNodeName: nodeName
91
+ }, originalNodeAtPos && {
92
+ fromA: step.pos,
93
+ toA: step.pos + originalNodeAtPos.nodeSize
94
+ });
95
+ }
96
+ }
97
+ }
98
+
50
99
  // taskItem node: state attribute change — highlight the taskItem node
51
100
  if (stepAttrs.some(function (v) {
52
101
  return taskItemAttrs.includes(v);
@@ -80,7 +129,17 @@ export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps) {
80
129
  };
81
130
  }
82
131
  return undefined;
83
- }).filter(filterUndefined);
132
+ }).filter(filterUndefined)
133
+ // Deduplicate by node position: multiple AttrSteps on the same node
134
+ // (e.g. setNodeAttribute(pos, 'text', ...) + setNodeAttribute(pos, 'color', ...))
135
+ // should produce only one decoration, not one per step.
136
+ // Gated behind the same experiment as the inline node attr changes that introduced
137
+ // the possibility of multiple ranges for the same node position.
138
+ .filter(function (range, i, arr) {
139
+ return !expValEquals('platform_editor_improve_inline_diffs', 'isEnabled', true) || arr.findIndex(function (r) {
140
+ return r.fromB === range.fromB;
141
+ }) === i;
142
+ });
84
143
  };
85
144
 
86
145
  /**
@@ -11,7 +11,7 @@ export declare const deletedTraditionalBlockOutline: string;
11
11
  export declare const deletedTraditionalBlockOutlineActive: string;
12
12
  export declare const deletedTraditionalBlockOutlineRounded: string;
13
13
  export declare const deletedTraditionalBlockOutlineRoundedActive: string;
14
- /** Scroll-navigation “new” emphasis: 4px ring (see {@link traditionalStyleNodeNew}). */
14
+ /** Scroll-navigation “new” emphasis: 4px ring. */
15
15
  export declare const deletedTraditionalBlockOutlineNew: string;
16
16
  export declare const deletedTraditionalBlockOutlineRoundedNew: string;
17
17
  export declare const deletedTraditionalRowStyle: string;
@@ -1,13 +1,14 @@
1
1
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
3
  import type { ColorScheme, DiffType } from '../../showDiffPluginType';
4
+ import type { InlineAttrChangeNodeName } from './utils/getAttrChangeRanges';
4
5
  /**
5
6
  * Inline decoration used for insertions as the content already exists in the document
6
7
  *
7
8
  * @param change Changeset "change" containing information about the change content + range
8
9
  * @returns Prosemirror inline decoration
9
10
  */
10
- export declare const createInlineChangedDecoration: ({ change, colorScheme, isActive, isInserted, shouldHideDeleted, showIndicators, doc, diffType, hideAddedDiffsUnderline, }: {
11
+ export declare const createInlineChangedDecoration: ({ change, colorScheme, isActive, isInserted, isAtomicInlineNode, shouldHideDeleted, showIndicators, doc, diffType, hideAddedDiffsUnderline, inlineNodeName, }: {
11
12
  change: {
12
13
  fromB: number;
13
14
  toB: number;
@@ -16,7 +17,9 @@ export declare const createInlineChangedDecoration: ({ change, colorScheme, isAc
16
17
  diffType?: DiffType;
17
18
  doc?: PMNode;
18
19
  hideAddedDiffsUnderline?: boolean;
20
+ inlineNodeName?: InlineAttrChangeNodeName;
19
21
  isActive?: boolean;
22
+ isAtomicInlineNode?: boolean;
20
23
  isInserted?: boolean;
21
24
  shouldHideDeleted?: boolean;
22
25
  showIndicators?: boolean;
@@ -1,12 +1,22 @@
1
1
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
2
  import { type Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
3
+ export type InlineAttrChangeNodeName = 'date' | 'emoji' | 'mention' | 'status';
3
4
  type StepRange = {
5
+ /**
6
+ * Position of the original (before) node in the original doc.
7
+ * Populated for inline attr changes (e.g. emoji, date) so the caller can
8
+ * render the old node as a "deleted" widget alongside the new highlighted node.
9
+ */
10
+ fromA?: number;
4
11
  fromB: number;
12
+ /** Inline node type, used by callers that need node-view-specific styling. */
13
+ inlineNodeName?: InlineAttrChangeNodeName;
5
14
  /** Whether the changed node is inline (true) or block (false/undefined) */
6
15
  isInline?: boolean;
16
+ toA?: number;
7
17
  toB: number;
8
18
  };
9
- export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[]) => StepRange[];
19
+ export declare const getAttrChangeRanges: (doc: PMNode, steps: ProseMirrorStep[], originalDoc: PMNode) => StepRange[];
10
20
  /**
11
21
  * Check if the step was a valid attr change and affected the doc
12
22
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "10.5.1",
3
+ "version": "10.5.2",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  "@atlaskit/editor-prosemirror": "^8.0.0",
29
29
  "@atlaskit/editor-tables": "^3.0.0",
30
30
  "@atlaskit/platform-feature-flags": "^2.1.0",
31
- "@atlaskit/tmp-editor-statsig": "^131.0.0",
31
+ "@atlaskit/tmp-editor-statsig": "^132.0.0",
32
32
  "@atlaskit/tokens": "^16.2.0",
33
33
  "@babel/runtime": "^7.0.0",
34
34
  "@compiled/react": "^1.0.0",
@@ -41,7 +41,7 @@
41
41
  "@atlaskit/button": "^24.3.0",
42
42
  "@atlaskit/css": "^1.0.0",
43
43
  "@atlaskit/dropdown-menu": "^17.1.0",
44
- "@atlaskit/editor-core": "^221.11.0",
44
+ "@atlaskit/editor-core": "^221.12.0",
45
45
  "@atlaskit/editor-json-transformer": "^9.0.0",
46
46
  "@atlaskit/form": "^16.1.0",
47
47
  "@atlaskit/primitives": "^22.1.0",