@atlaskit/editor-plugin-show-diff 13.0.5 → 13.0.7

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.
Files changed (22) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +13 -32
  3. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +1 -1
  4. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +1 -1
  5. package/dist/cjs/pm-plugins/decorations/utils/diffableAttrs.js +96 -0
  6. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +12 -23
  7. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +1 -1
  8. package/dist/es2019/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +14 -32
  9. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +3 -3
  10. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +3 -3
  11. package/dist/es2019/pm-plugins/decorations/utils/diffableAttrs.js +88 -0
  12. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +12 -23
  13. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +3 -3
  14. package/dist/esm/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +14 -32
  15. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +3 -3
  16. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +3 -3
  17. package/dist/esm/pm-plugins/decorations/utils/diffableAttrs.js +90 -0
  18. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +12 -23
  19. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +3 -3
  20. package/dist/types/pm-plugins/calculateDiff/attrAwareTokenEncoder.d.ts +0 -5
  21. package/dist/types/pm-plugins/decorations/utils/diffableAttrs.d.ts +20 -0
  22. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 13.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [`2c712e57d3793`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2c712e57d3793) -
8
+ Consolidate diffable-attribute rules into a single shared source of truth (`diffableAttrs`) used
9
+ by both the changeset token encoder (detection) and `getAttrChangeRanges` (rendering), and extend
10
+ the encoder to fold all mapped node types.
11
+ - Updated dependencies
12
+
13
+ ## 13.0.6
14
+
15
+ ### Patch Changes
16
+
17
+ - [`7393a83aff767`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7393a83aff767) -
18
+ [ux] Fix inserted table diff overlays to follow rounded table corners when
19
+ platform_editor_table_diff_rounded_corners is enabled.
20
+ - Updated dependencies
21
+
3
22
  ## 13.0.5
4
23
 
5
24
  ### Patch Changes
@@ -4,33 +4,19 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.attrAwareTokenEncoder = void 0;
7
+ var _diffableAttrs = require("../decorations/utils/diffableAttrs");
7
8
  /**
8
- * Attribute-aware token encoder for `prosemirror-changeset`.
9
- *
10
- * The library's default encoder reduces a node's open token to `node.type.name`,
11
- * ignoring attributes. An attribute-only change (e.g. recolouring a table cell's
12
- * `background`) therefore tokenises identically on both sides and reports no
13
- * change at all.
14
- *
15
- * This encoder folds an allow-listed set of attributes into the open token so
16
- * such changes register. Everything else encodes exactly as the default. The
17
- * allow-list is deliberately narrow — folding in ephemeral attrs like `localId`
18
- * would produce phantom diffs.
9
+ * Attribute-aware token encoder for `prosemirror-changeset`. The default encoder
10
+ * reduces a node to `node.type.name`, so an attribute-only change (e.g. a table
11
+ * cell recolour) tokenises identically on both sides and goes undetected. This
12
+ * folds the diffable attributes (from the shared `diffableAttrs` map) into the
13
+ * open token so such changes register.
19
14
  */
20
15
 
21
- var DIFFED_ATTRS_BY_NODE_TYPE = {
22
- tableCell: ['background', 'colspan', 'rowspan'],
23
- tableHeader: ['background', 'colspan', 'rowspan']
24
- };
25
-
26
- /**
27
- * Builds a stable composite token. Iterates the allow-list rather than
28
- * `Object.keys` so ordering is deterministic for string comparison.
29
- */
16
+ // Stable composite token; attr names are pre-ordered by the caller.
30
17
  var encodeNodeWithAttrs = function encodeNodeWithAttrs(node, attrNames) {
31
18
  var _node$attrs;
32
19
  var attrs = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {};
33
- // Deterministic order: iterate the allow-list, not `Object.keys(attrs)`.
34
20
  var parts = attrNames.map(function (name) {
35
21
  var _attrs$name;
36
22
  return "".concat(name, "=").concat(JSON.stringify((_attrs$name = attrs[name]) !== null && _attrs$name !== void 0 ? _attrs$name : null));
@@ -38,18 +24,16 @@ var encodeNodeWithAttrs = function encodeNodeWithAttrs(node, attrNames) {
38
24
  return "".concat(node.type.name, "|").concat(parts.join('|'));
39
25
  };
40
26
 
41
- /**
42
- * Identical to the library default except that allow-listed node types encode
43
- * their allow-listed attributes into the open token. Characters and node-end
44
- * tokens are left as-is, hence the `string | number` token type.
45
- */
27
+ // Like the library default, but node types with a `diffableAttrs` rule fold their
28
+ // attrs into the open token. Chars/node-end are unchanged, hence `string | number`.
46
29
  var attrAwareTokenEncoder = exports.attrAwareTokenEncoder = {
47
30
  encodeCharacter: function encodeCharacter(char, _marks) {
48
31
  return char;
49
32
  },
50
33
  encodeNodeStart: function encodeNodeStart(node) {
51
- var attrNames = DIFFED_ATTRS_BY_NODE_TYPE[node.type.name];
52
- if (attrNames) {
34
+ var _node$attrs2;
35
+ var attrNames = (0, _diffableAttrs.getDiffableAttrNames)(node.type.name, (_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 ? _node$attrs2 : {});
36
+ if (attrNames && attrNames.length > 0) {
53
37
  return encodeNodeWithAttrs(node, attrNames);
54
38
  }
55
39
  return node.type.name;
@@ -62,10 +46,7 @@ var attrAwareTokenEncoder = exports.attrAwareTokenEncoder = {
62
46
  }
63
47
  };
64
48
 
65
- /**
66
- * Mirrors the library's private `typeID` so node-end tokens match the default
67
- * encoding exactly. Reimplemented here because it is not exported.
68
- */
49
+ // Mirrors the library's private (unexported) `typeID` so node-end tokens match.
69
50
  function typeID(type) {
70
51
  var cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
71
52
  var id = cache[type.name];
@@ -184,7 +184,7 @@ var createBlockChangedDecoration = exports.createBlockChangedDecoration = functi
184
184
  var cellOverlay = document.createElement('div');
185
185
  var isTraditional = colorScheme === 'traditional';
186
186
  var isRoundedTable = (0, _expValEquals.expValEquals)('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
187
- var addedCellStyle = isTraditional ? _traditional.traditionalAddedCellOverlayStyle : _standard.addedCellOverlayStyle;
187
+ var addedCellStyle = isTraditional ? isRoundedTable ? _traditional.traditionalAddedCellOverlayRoundedStyle : _traditional.traditionalAddedCellOverlayStyle : isRoundedTable ? _standard.addedCellOverlayRoundedStyle : _standard.addedCellOverlayStyle;
188
188
  var deletedCellStyle = isTraditional ? isRoundedTable ? _traditional.deletedTraditionalCellOverlayRoundedStyle : _traditional.deletedTraditionalCellOverlayStyle : isRoundedTable ? _standard.deletedCellOverlayRoundedStyle : _standard.deletedCellOverlayStyle;
189
189
 
190
190
  // On an inverted diff, an empty cell being filled is an addition, so give it the
@@ -154,7 +154,7 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, cellEdgeAttrs, n
154
154
  var overlay = document.createElement('span');
155
155
  var isRoundedTable = (0, _expValEquals.expValEquals)('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
156
156
  var isTraditional = colorScheme === 'traditional';
157
- var deletedCellStyle = isTraditional ? _traditional.deletedTraditionalCellOverlayStyle : _standard.deletedCellOverlayStyle;
157
+ var deletedCellStyle = isTraditional ? isRoundedTable ? _traditional.deletedTraditionalCellOverlayRoundedStyle : _traditional.deletedTraditionalCellOverlayStyle : isRoundedTable ? _standard.deletedCellOverlayRoundedStyle : _standard.deletedCellOverlayStyle;
158
158
  var addedCellStyle = isTraditional ? isRoundedTable ? _traditional.traditionalAddedCellOverlayRoundedStyle : _traditional.traditionalAddedCellOverlayStyle : isRoundedTable ? _standard.addedCellOverlayRoundedStyle : _standard.addedCellOverlayStyle;
159
159
  var overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
160
160
  overlay.setAttribute('style', overlayStyle);
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isDiffableAttr = exports.getRequiredIncludedDiffableAttrs = exports.getIncludedDiffableAttrs = exports.getDiffableAttrNames = exports.DIFFABLE_ATTRS_BY_NODE_TYPE = void 0;
7
+ /**
8
+ * Single source of truth for which attributes of a node type represent a
9
+ * meaningful change. Shared by `attrAwareTokenEncoder` (changeset detection)
10
+ * and `getAttrChangeRanges` (decoration rendering) so the two stages agree.
11
+ */
12
+
13
+ // Node types absent from this map have no attribute-level diffing.
14
+ var DIFFABLE_ATTRS_BY_NODE_TYPE = exports.DIFFABLE_ATTRS_BY_NODE_TYPE = {
15
+ tableCell: {
16
+ include: ['background', 'colspan', 'rowspan']
17
+ },
18
+ tableHeader: {
19
+ include: ['background', 'colspan', 'rowspan']
20
+ },
21
+ // Inline nodes whose identity is their attrs.
22
+ date: {
23
+ include: ['timestamp']
24
+ },
25
+ emoji: {
26
+ include: ['shortName', 'id', 'text']
27
+ },
28
+ mention: {
29
+ include: ['id', 'text']
30
+ },
31
+ status: {
32
+ include: ['text', 'color']
33
+ },
34
+ taskItem: {
35
+ include: ['state']
36
+ },
37
+ // Media: id/collection/url identify the image.
38
+ media: {
39
+ include: ['id', 'collection', 'url']
40
+ },
41
+ // Extensions: any attribute change is meaningful except localId.
42
+ extension: {
43
+ exclude: ['localId']
44
+ },
45
+ inlineExtension: {
46
+ exclude: ['localId']
47
+ },
48
+ bodiedExtension: {
49
+ exclude: ['localId']
50
+ }
51
+ };
52
+
53
+ // Whether `attrName` is a meaningful change for `nodeTypeName`. False if no rule.
54
+ var isDiffableAttr = exports.isDiffableAttr = function isDiffableAttr(nodeTypeName, attrName) {
55
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
56
+ if (!rule) {
57
+ return false;
58
+ }
59
+ return 'include' in rule ? rule.include.includes(attrName) : !rule.exclude.includes(attrName);
60
+ };
61
+
62
+ // The fixed include list for a node type, or `undefined` for no/exclude rule.
63
+ // Use where a node-independent attr list is needed (e.g. the inline node map).
64
+ var getIncludedDiffableAttrs = exports.getIncludedDiffableAttrs = function getIncludedDiffableAttrs(nodeTypeName) {
65
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
66
+ return rule && 'include' in rule ? rule.include : undefined;
67
+ };
68
+
69
+ // Like `getIncludedDiffableAttrs` but asserts the include rule exists, so a
70
+ // consumer relying on a fixed list fails loudly if the rule is removed or
71
+ // switched to an exclude rule rather than silently drifting.
72
+ var getRequiredIncludedDiffableAttrs = exports.getRequiredIncludedDiffableAttrs = function getRequiredIncludedDiffableAttrs(nodeTypeName) {
73
+ var attrs = getIncludedDiffableAttrs(nodeTypeName);
74
+ if (!attrs) {
75
+ throw new Error("diffableAttrs: expected an include rule for \"".concat(nodeTypeName, "\""));
76
+ }
77
+ return attrs;
78
+ };
79
+
80
+ /**
81
+ * Concrete attr names to diff for a node instance, honouring include and exclude
82
+ * rules; `undefined` when the type has no rule. Exclude rules read the node's
83
+ * attrs and sort them, since key order isn't stable across the two diffed docs.
84
+ */
85
+ var getDiffableAttrNames = exports.getDiffableAttrNames = function getDiffableAttrNames(nodeTypeName, attrs) {
86
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
87
+ if (!rule) {
88
+ return undefined;
89
+ }
90
+ if ('include' in rule) {
91
+ return rule.include;
92
+ }
93
+ return Object.keys(attrs).filter(function (name) {
94
+ return !rule.exclude.includes(name);
95
+ }).sort();
96
+ };
@@ -10,29 +10,20 @@ var _steps = require("@atlaskit/adf-schema/steps");
10
10
  var _deprecatedPlatformFeatureExperiments = require("@atlaskit/editor-common/deprecated-platform-feature-experiments");
11
11
  var _transform = require("@atlaskit/editor-prosemirror/transform");
12
12
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
13
+ var _diffableAttrs = require("./diffableAttrs");
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; }
14
15
  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; }
15
16
  var filterUndefined = function filterUndefined(x) {
16
17
  return !!x;
17
18
  };
18
19
 
19
- // Attributes that indicate a change in media image
20
- var mediaAttrs = ['id', 'collection', 'url'];
21
-
22
- // Attribute that indicates a date change
23
- var dateAttrs = ['timestamp'];
24
-
25
- // Attribute that indicates a task item state change
26
- var taskItemAttrs = ['state'];
27
-
28
- // Attributes that indicate an emoji change
29
- var emojiAttrs = ['shortName', 'id', 'text'];
30
-
31
- // Attributes that indicate a mention change (who is mentioned)
32
- var mentionAttrs = ['id', 'text'];
33
-
34
- // Attributes that indicate a status change (label or colour)
35
- var statusAttrs = ['text', 'color'];
20
+ // Attr lists sourced from the shared `diffableAttrs` map (shared with the encoder).
21
+ var mediaAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('media');
22
+ var dateAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('date');
23
+ var taskItemAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('taskItem');
24
+ var emojiAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('emoji');
25
+ var mentionAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('mention');
26
+ var statusAttrs = (0, _diffableAttrs.getRequiredIncludedDiffableAttrs)('status');
36
27
 
37
28
  // Map of node type name → the attrs that represent a meaningful content change for that node
38
29
  var inlineNodeAttrMap = {
@@ -45,10 +36,8 @@ var isInlineAttrChangeNodeName = function isInlineAttrChangeNodeName(nodeName) {
45
36
  return nodeName in inlineNodeAttrMap;
46
37
  };
47
38
 
48
- // Attributes excluded from extension change detection (not meaningful content changes)
49
- var extensionExcludedAttrs = ['localId'];
50
-
51
- // Extension node type names
39
+ // Extension node type names. Their "any attr except localId" exclude rule lives
40
+ // in the shared `diffableAttrs` map and is applied via `isDiffableAttr` below.
52
41
  var extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
53
42
  var getStepAttrs = function getStepAttrs(step) {
54
43
  if (step instanceof _transform.AttrStep) {
@@ -118,9 +107,9 @@ var getAttrChangeRanges = exports.getAttrChangeRanges = function getAttrChangeRa
118
107
  };
119
108
  }
120
109
 
121
- // extension nodes: any attribute change except localId — highlight the node
110
+ // extension nodes: highlight on any diffable attr change (exclude rule in shared map)
122
111
  if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(function (v) {
123
- return !extensionExcludedAttrs.includes(v);
112
+ return (0, _diffableAttrs.isDiffableAttr)(nodeAtPos.type.name, v);
124
113
  })) {
125
114
  var isInline = nodeAtPos.type.name === 'inlineExtension';
126
115
  return {
@@ -183,7 +183,7 @@ var applyCellOverlayStyles = function applyCellOverlayStyles(_ref2) {
183
183
  element.querySelectorAll('td, th').forEach(function (cell) {
184
184
  var overlay = document.createElement('span');
185
185
  var isTraditional = colorScheme === 'traditional';
186
- var deletedCellStyle = isTraditional ? _traditional.deletedTraditionalCellOverlayStyle : _standard.deletedCellOverlayStyle;
186
+ var deletedCellStyle = isTraditional ? isRoundedTable ? _traditional.deletedTraditionalCellOverlayRoundedStyle : _traditional.deletedTraditionalCellOverlayStyle : isRoundedTable ? _standard.deletedCellOverlayRoundedStyle : _standard.deletedCellOverlayStyle;
187
187
  var addedCellStyle = isTraditional ? isRoundedTable ? _traditional.traditionalAddedCellOverlayRoundedStyle : _traditional.traditionalAddedCellOverlayStyleNew : isRoundedTable ? _standard.addedCellOverlayRoundedStyle : _standard.addedCellOverlayStyle;
188
188
  var overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
189
189
  overlay.setAttribute('style', overlayStyle);
@@ -1,30 +1,17 @@
1
- /**
2
- * Attribute-aware token encoder for `prosemirror-changeset`.
3
- *
4
- * The library's default encoder reduces a node's open token to `node.type.name`,
5
- * ignoring attributes. An attribute-only change (e.g. recolouring a table cell's
6
- * `background`) therefore tokenises identically on both sides and reports no
7
- * change at all.
8
- *
9
- * This encoder folds an allow-listed set of attributes into the open token so
10
- * such changes register. Everything else encodes exactly as the default. The
11
- * allow-list is deliberately narrow — folding in ephemeral attrs like `localId`
12
- * would produce phantom diffs.
13
- */
14
-
15
- const DIFFED_ATTRS_BY_NODE_TYPE = {
16
- tableCell: ['background', 'colspan', 'rowspan'],
17
- tableHeader: ['background', 'colspan', 'rowspan']
18
- };
1
+ import { getDiffableAttrNames } from '../decorations/utils/diffableAttrs';
19
2
 
20
3
  /**
21
- * Builds a stable composite token. Iterates the allow-list rather than
22
- * `Object.keys` so ordering is deterministic for string comparison.
4
+ * Attribute-aware token encoder for `prosemirror-changeset`. The default encoder
5
+ * reduces a node to `node.type.name`, so an attribute-only change (e.g. a table
6
+ * cell recolour) tokenises identically on both sides and goes undetected. This
7
+ * folds the diffable attributes (from the shared `diffableAttrs` map) into the
8
+ * open token so such changes register.
23
9
  */
10
+
11
+ // Stable composite token; attr names are pre-ordered by the caller.
24
12
  const encodeNodeWithAttrs = (node, attrNames) => {
25
13
  var _node$attrs;
26
14
  const attrs = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {};
27
- // Deterministic order: iterate the allow-list, not `Object.keys(attrs)`.
28
15
  const parts = attrNames.map(name => {
29
16
  var _attrs$name;
30
17
  return `${name}=${JSON.stringify((_attrs$name = attrs[name]) !== null && _attrs$name !== void 0 ? _attrs$name : null)}`;
@@ -32,16 +19,14 @@ const encodeNodeWithAttrs = (node, attrNames) => {
32
19
  return `${node.type.name}|${parts.join('|')}`;
33
20
  };
34
21
 
35
- /**
36
- * Identical to the library default except that allow-listed node types encode
37
- * their allow-listed attributes into the open token. Characters and node-end
38
- * tokens are left as-is, hence the `string | number` token type.
39
- */
22
+ // Like the library default, but node types with a `diffableAttrs` rule fold their
23
+ // attrs into the open token. Chars/node-end are unchanged, hence `string | number`.
40
24
  export const attrAwareTokenEncoder = {
41
25
  encodeCharacter: (char, _marks) => char,
42
26
  encodeNodeStart: node => {
43
- const attrNames = DIFFED_ATTRS_BY_NODE_TYPE[node.type.name];
44
- if (attrNames) {
27
+ var _node$attrs2;
28
+ const attrNames = getDiffableAttrNames(node.type.name, (_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 ? _node$attrs2 : {});
29
+ if (attrNames && attrNames.length > 0) {
45
30
  return encodeNodeWithAttrs(node, attrNames);
46
31
  }
47
32
  return node.type.name;
@@ -50,10 +35,7 @@ export const attrAwareTokenEncoder = {
50
35
  compareTokens: (a, b) => a === b
51
36
  };
52
37
 
53
- /**
54
- * Mirrors the library's private `typeID` so node-end tokens match the default
55
- * encoding exactly. Reimplemented here because it is not exported.
56
- */
38
+ // Mirrors the library's private (unexported) `typeID` so node-end tokens match.
57
39
  function typeID(type) {
58
40
  const cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
59
41
  let id = cache[type.name];
@@ -2,8 +2,8 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
3
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
4
  import { isExtendedEnabled } from '../isExtendedEnabled';
5
- import { standardDecorationMarkerVariable, deletedDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
6
- import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from './colorSchemes/traditional';
5
+ import { standardDecorationMarkerVariable, deletedDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayRoundedStyle, addedCellOverlayStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
6
+ import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from './colorSchemes/traditional';
7
7
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
8
8
  import { buildDiffDecorationSpec } from './decorationKeys';
9
9
  const displayNoneStyle = convertToInlineCss({
@@ -172,7 +172,7 @@ export const createBlockChangedDecoration = ({
172
172
  const cellOverlay = document.createElement('div');
173
173
  const isTraditional = colorScheme === 'traditional';
174
174
  const isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
175
- const addedCellStyle = isTraditional ? traditionalAddedCellOverlayStyle : addedCellOverlayStyle;
175
+ const addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyle : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
176
176
  const deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
177
177
 
178
178
  // On an inverted diff, an empty cell being filled is an addition, so give it the
@@ -4,8 +4,8 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { TableMap } from '@atlaskit/editor-tables/table-map';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
6
  import { isExtendedEnabled } from '../isExtendedEnabled';
7
- import { addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedRowStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
8
- import { deletedTraditionalRowStyle, deletedTraditionalCellOverlayStyle, traditionalAddedCellOverlayStyle, traditionalAddedCellOverlayRoundedStyle } from './colorSchemes/traditional';
7
+ import { addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedRowStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
8
+ import { deletedTraditionalRowStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle, traditionalAddedCellOverlayStyle, traditionalAddedCellOverlayRoundedStyle } from './colorSchemes/traditional';
9
9
  import { buildDiffDecorationSpec } from './decorationKeys';
10
10
  import { findSafeInsertPos } from './utils/findSafeInsertPos';
11
11
  import { applyCellEdgeAttrs, getRowCellEdgeAttrs } from './utils/tableCellEdgeAttrs';
@@ -133,7 +133,7 @@ const createChangedRowDOM = (rowNode, cellEdgeAttrs, nodeViewSerializer, colorSc
133
133
  const overlay = document.createElement('span');
134
134
  const isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
135
135
  const isTraditional = colorScheme === 'traditional';
136
- const deletedCellStyle = isTraditional ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
136
+ const deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
137
137
  const addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyle : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
138
138
  const overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
139
139
  overlay.setAttribute('style', overlayStyle);
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Single source of truth for which attributes of a node type represent a
3
+ * meaningful change. Shared by `attrAwareTokenEncoder` (changeset detection)
4
+ * and `getAttrChangeRanges` (decoration rendering) so the two stages agree.
5
+ */
6
+
7
+ // Node types absent from this map have no attribute-level diffing.
8
+ export const DIFFABLE_ATTRS_BY_NODE_TYPE = {
9
+ tableCell: {
10
+ include: ['background', 'colspan', 'rowspan']
11
+ },
12
+ tableHeader: {
13
+ include: ['background', 'colspan', 'rowspan']
14
+ },
15
+ // Inline nodes whose identity is their attrs.
16
+ date: {
17
+ include: ['timestamp']
18
+ },
19
+ emoji: {
20
+ include: ['shortName', 'id', 'text']
21
+ },
22
+ mention: {
23
+ include: ['id', 'text']
24
+ },
25
+ status: {
26
+ include: ['text', 'color']
27
+ },
28
+ taskItem: {
29
+ include: ['state']
30
+ },
31
+ // Media: id/collection/url identify the image.
32
+ media: {
33
+ include: ['id', 'collection', 'url']
34
+ },
35
+ // Extensions: any attribute change is meaningful except localId.
36
+ extension: {
37
+ exclude: ['localId']
38
+ },
39
+ inlineExtension: {
40
+ exclude: ['localId']
41
+ },
42
+ bodiedExtension: {
43
+ exclude: ['localId']
44
+ }
45
+ };
46
+
47
+ // Whether `attrName` is a meaningful change for `nodeTypeName`. False if no rule.
48
+ export const isDiffableAttr = (nodeTypeName, attrName) => {
49
+ const rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
50
+ if (!rule) {
51
+ return false;
52
+ }
53
+ return 'include' in rule ? rule.include.includes(attrName) : !rule.exclude.includes(attrName);
54
+ };
55
+
56
+ // The fixed include list for a node type, or `undefined` for no/exclude rule.
57
+ // Use where a node-independent attr list is needed (e.g. the inline node map).
58
+ export const getIncludedDiffableAttrs = nodeTypeName => {
59
+ const rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
60
+ return rule && 'include' in rule ? rule.include : undefined;
61
+ };
62
+
63
+ // Like `getIncludedDiffableAttrs` but asserts the include rule exists, so a
64
+ // consumer relying on a fixed list fails loudly if the rule is removed or
65
+ // switched to an exclude rule rather than silently drifting.
66
+ export const getRequiredIncludedDiffableAttrs = nodeTypeName => {
67
+ const attrs = getIncludedDiffableAttrs(nodeTypeName);
68
+ if (!attrs) {
69
+ throw new Error(`diffableAttrs: expected an include rule for "${nodeTypeName}"`);
70
+ }
71
+ return attrs;
72
+ };
73
+
74
+ /**
75
+ * Concrete attr names to diff for a node instance, honouring include and exclude
76
+ * rules; `undefined` when the type has no rule. Exclude rules read the node's
77
+ * attrs and sort them, since key order isn't stable across the two diffed docs.
78
+ */
79
+ export const getDiffableAttrNames = (nodeTypeName, attrs) => {
80
+ const rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
81
+ if (!rule) {
82
+ return undefined;
83
+ }
84
+ if ('include' in rule) {
85
+ return rule.include;
86
+ }
87
+ return Object.keys(attrs).filter(name => !rule.exclude.includes(name)).sort();
88
+ };
@@ -2,25 +2,16 @@ import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
2
2
  import { isExperimentEnabled } from '@atlaskit/editor-common/deprecated-platform-feature-experiments';
3
3
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
4
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
+ import { getRequiredIncludedDiffableAttrs, isDiffableAttr } from './diffableAttrs';
5
6
  const filterUndefined = x => !!x;
6
7
 
7
- // Attributes that indicate a change in media image
8
- const mediaAttrs = ['id', 'collection', 'url'];
9
-
10
- // Attribute that indicates a date change
11
- const dateAttrs = ['timestamp'];
12
-
13
- // Attribute that indicates a task item state change
14
- const taskItemAttrs = ['state'];
15
-
16
- // Attributes that indicate an emoji change
17
- const emojiAttrs = ['shortName', 'id', 'text'];
18
-
19
- // Attributes that indicate a mention change (who is mentioned)
20
- const mentionAttrs = ['id', 'text'];
21
-
22
- // Attributes that indicate a status change (label or colour)
23
- const statusAttrs = ['text', 'color'];
8
+ // Attr lists sourced from the shared `diffableAttrs` map (shared with the encoder).
9
+ const mediaAttrs = getRequiredIncludedDiffableAttrs('media');
10
+ const dateAttrs = getRequiredIncludedDiffableAttrs('date');
11
+ const taskItemAttrs = getRequiredIncludedDiffableAttrs('taskItem');
12
+ const emojiAttrs = getRequiredIncludedDiffableAttrs('emoji');
13
+ const mentionAttrs = getRequiredIncludedDiffableAttrs('mention');
14
+ const statusAttrs = getRequiredIncludedDiffableAttrs('status');
24
15
 
25
16
  // Map of node type name → the attrs that represent a meaningful content change for that node
26
17
  const inlineNodeAttrMap = {
@@ -31,10 +22,8 @@ const inlineNodeAttrMap = {
31
22
  };
32
23
  const isInlineAttrChangeNodeName = nodeName => nodeName in inlineNodeAttrMap;
33
24
 
34
- // Attributes excluded from extension change detection (not meaningful content changes)
35
- const extensionExcludedAttrs = ['localId'];
36
-
37
- // Extension node type names
25
+ // Extension node type names. Their "any attr except localId" exclude rule lives
26
+ // in the shared `diffableAttrs` map and is applied via `isDiffableAttr` below.
38
27
  const extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
39
28
  const getStepAttrs = step => {
40
29
  if (step instanceof AttrStep) {
@@ -95,8 +84,8 @@ export const getAttrChangeRanges = (doc, steps, originalDoc) => {
95
84
  };
96
85
  }
97
86
 
98
- // extension nodes: any attribute change except localId — highlight the node
99
- if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => !extensionExcludedAttrs.includes(v))) {
87
+ // extension nodes: highlight on any diffable attr change (exclude rule in shared map)
88
+ if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => isDiffableAttr(nodeAtPos.type.name, v))) {
100
89
  const isInline = nodeAtPos.type.name === 'inlineExtension';
101
90
  return {
102
91
  fromB: step.pos,
@@ -3,8 +3,8 @@ import { trackChangesMessages } from '@atlaskit/editor-common/messages';
3
3
  import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
4
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
5
  import { isExtendedEnabled } from '../../isExtendedEnabled';
6
- import { deletedBlockOutline, deletedBlockOutlineActive, deletedBlockOutlineRounded, deletedBlockOutlineRoundedActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleUnbounded, deletedInlineContentStyleExtended, deletedStyleQuoteNodeWithLozenge, deletedStyleQuoteNodeWithLozengeActive, editingContentStyleInBlockExtended, editingContentStyleInBlockExtendedNoUnderline, editingStyleExtended, editingStyleExtendedNoUnderline, editingStyleActiveExtended, editingStyleActiveExtendedNoUnderline, editingStyleNode, addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedCellOverlayStyle } from '../colorSchemes/standard';
7
- import { deletedTraditionalBlockOutlineActive, deletedTraditionalBlockOutlineNew, deletedTraditionalBlockOutlineRoundedActive, deletedTraditionalBlockOutlineRoundedNew, deletedTraditionalContentStyleUnbounded, deletedTraditionalContentStyleUnboundedActive, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, deletedTraditionalStyleQuoteNodeActive, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNodeActive, traditionalStyleNodeNew, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyleNew, deletedTraditionalCellOverlayStyle } from '../colorSchemes/traditional';
6
+ import { deletedBlockOutline, deletedBlockOutlineActive, deletedBlockOutlineRounded, deletedBlockOutlineRoundedActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleUnbounded, deletedInlineContentStyleExtended, deletedStyleQuoteNodeWithLozenge, deletedStyleQuoteNodeWithLozengeActive, editingContentStyleInBlockExtended, editingContentStyleInBlockExtendedNoUnderline, editingStyleExtended, editingStyleExtendedNoUnderline, editingStyleActiveExtended, editingStyleActiveExtendedNoUnderline, editingStyleNode, addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from '../colorSchemes/standard';
7
+ import { deletedTraditionalBlockOutlineActive, deletedTraditionalBlockOutlineNew, deletedTraditionalBlockOutlineRoundedActive, deletedTraditionalBlockOutlineRoundedNew, deletedTraditionalContentStyleUnbounded, deletedTraditionalContentStyleUnboundedActive, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, deletedTraditionalStyleQuoteNodeActive, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNodeActive, traditionalStyleNodeNew, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyleNew, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from '../colorSchemes/traditional';
8
8
  import { applyTableCellEdgeAttrs } from './tableCellEdgeAttrs';
9
9
  const lozengeStyle = convertToInlineCss({
10
10
  display: 'inline-flex',
@@ -168,7 +168,7 @@ const applyCellOverlayStyles = ({
168
168
  element.querySelectorAll('td, th').forEach(cell => {
169
169
  const overlay = document.createElement('span');
170
170
  const isTraditional = colorScheme === 'traditional';
171
- const deletedCellStyle = isTraditional ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
171
+ const deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
172
172
  const addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyleNew : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
173
173
  const overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
174
174
  overlay.setAttribute('style', overlayStyle);
@@ -1,30 +1,17 @@
1
- /**
2
- * Attribute-aware token encoder for `prosemirror-changeset`.
3
- *
4
- * The library's default encoder reduces a node's open token to `node.type.name`,
5
- * ignoring attributes. An attribute-only change (e.g. recolouring a table cell's
6
- * `background`) therefore tokenises identically on both sides and reports no
7
- * change at all.
8
- *
9
- * This encoder folds an allow-listed set of attributes into the open token so
10
- * such changes register. Everything else encodes exactly as the default. The
11
- * allow-list is deliberately narrow — folding in ephemeral attrs like `localId`
12
- * would produce phantom diffs.
13
- */
14
-
15
- var DIFFED_ATTRS_BY_NODE_TYPE = {
16
- tableCell: ['background', 'colspan', 'rowspan'],
17
- tableHeader: ['background', 'colspan', 'rowspan']
18
- };
1
+ import { getDiffableAttrNames } from '../decorations/utils/diffableAttrs';
19
2
 
20
3
  /**
21
- * Builds a stable composite token. Iterates the allow-list rather than
22
- * `Object.keys` so ordering is deterministic for string comparison.
4
+ * Attribute-aware token encoder for `prosemirror-changeset`. The default encoder
5
+ * reduces a node to `node.type.name`, so an attribute-only change (e.g. a table
6
+ * cell recolour) tokenises identically on both sides and goes undetected. This
7
+ * folds the diffable attributes (from the shared `diffableAttrs` map) into the
8
+ * open token so such changes register.
23
9
  */
10
+
11
+ // Stable composite token; attr names are pre-ordered by the caller.
24
12
  var encodeNodeWithAttrs = function encodeNodeWithAttrs(node, attrNames) {
25
13
  var _node$attrs;
26
14
  var attrs = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {};
27
- // Deterministic order: iterate the allow-list, not `Object.keys(attrs)`.
28
15
  var parts = attrNames.map(function (name) {
29
16
  var _attrs$name;
30
17
  return "".concat(name, "=").concat(JSON.stringify((_attrs$name = attrs[name]) !== null && _attrs$name !== void 0 ? _attrs$name : null));
@@ -32,18 +19,16 @@ var encodeNodeWithAttrs = function encodeNodeWithAttrs(node, attrNames) {
32
19
  return "".concat(node.type.name, "|").concat(parts.join('|'));
33
20
  };
34
21
 
35
- /**
36
- * Identical to the library default except that allow-listed node types encode
37
- * their allow-listed attributes into the open token. Characters and node-end
38
- * tokens are left as-is, hence the `string | number` token type.
39
- */
22
+ // Like the library default, but node types with a `diffableAttrs` rule fold their
23
+ // attrs into the open token. Chars/node-end are unchanged, hence `string | number`.
40
24
  export var attrAwareTokenEncoder = {
41
25
  encodeCharacter: function encodeCharacter(char, _marks) {
42
26
  return char;
43
27
  },
44
28
  encodeNodeStart: function encodeNodeStart(node) {
45
- var attrNames = DIFFED_ATTRS_BY_NODE_TYPE[node.type.name];
46
- if (attrNames) {
29
+ var _node$attrs2;
30
+ var attrNames = getDiffableAttrNames(node.type.name, (_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 ? _node$attrs2 : {});
31
+ if (attrNames && attrNames.length > 0) {
47
32
  return encodeNodeWithAttrs(node, attrNames);
48
33
  }
49
34
  return node.type.name;
@@ -56,10 +41,7 @@ export var attrAwareTokenEncoder = {
56
41
  }
57
42
  };
58
43
 
59
- /**
60
- * Mirrors the library's private `typeID` so node-end tokens match the default
61
- * encoding exactly. Reimplemented here because it is not exported.
62
- */
44
+ // Mirrors the library's private (unexported) `typeID` so node-end tokens match.
63
45
  function typeID(type) {
64
46
  var cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
65
47
  var id = cache[type.name];
@@ -3,8 +3,8 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
3
3
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
5
  import { isExtendedEnabled } from '../isExtendedEnabled';
6
- import { standardDecorationMarkerVariable, deletedDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
7
- import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from './colorSchemes/traditional';
6
+ import { standardDecorationMarkerVariable, deletedDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayRoundedStyle, addedCellOverlayStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
7
+ import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from './colorSchemes/traditional';
8
8
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
9
9
  import { buildDiffDecorationSpec } from './decorationKeys';
10
10
  var displayNoneStyle = convertToInlineCss({
@@ -177,7 +177,7 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
177
177
  var cellOverlay = document.createElement('div');
178
178
  var isTraditional = colorScheme === 'traditional';
179
179
  var isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
180
- var addedCellStyle = isTraditional ? traditionalAddedCellOverlayStyle : addedCellOverlayStyle;
180
+ var addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyle : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
181
181
  var deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
182
182
 
183
183
  // On an inverted diff, an empty cell being filled is an addition, so give it the
@@ -11,8 +11,8 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
11
11
  import { TableMap } from '@atlaskit/editor-tables/table-map';
12
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
13
  import { isExtendedEnabled } from '../isExtendedEnabled';
14
- import { addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedRowStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
15
- import { deletedTraditionalRowStyle, deletedTraditionalCellOverlayStyle, traditionalAddedCellOverlayStyle, traditionalAddedCellOverlayRoundedStyle } from './colorSchemes/traditional';
14
+ import { addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedRowStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from './colorSchemes/standard';
15
+ import { deletedTraditionalRowStyle, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle, traditionalAddedCellOverlayStyle, traditionalAddedCellOverlayRoundedStyle } from './colorSchemes/traditional';
16
16
  import { buildDiffDecorationSpec } from './decorationKeys';
17
17
  import { findSafeInsertPos } from './utils/findSafeInsertPos';
18
18
  import { applyCellEdgeAttrs, getRowCellEdgeAttrs } from './utils/tableCellEdgeAttrs';
@@ -147,7 +147,7 @@ var createChangedRowDOM = function createChangedRowDOM(rowNode, cellEdgeAttrs, n
147
147
  var overlay = document.createElement('span');
148
148
  var isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
149
149
  var isTraditional = colorScheme === 'traditional';
150
- var deletedCellStyle = isTraditional ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
150
+ var deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
151
151
  var addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyle : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
152
152
  var overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
153
153
  overlay.setAttribute('style', overlayStyle);
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Single source of truth for which attributes of a node type represent a
3
+ * meaningful change. Shared by `attrAwareTokenEncoder` (changeset detection)
4
+ * and `getAttrChangeRanges` (decoration rendering) so the two stages agree.
5
+ */
6
+
7
+ // Node types absent from this map have no attribute-level diffing.
8
+ export var DIFFABLE_ATTRS_BY_NODE_TYPE = {
9
+ tableCell: {
10
+ include: ['background', 'colspan', 'rowspan']
11
+ },
12
+ tableHeader: {
13
+ include: ['background', 'colspan', 'rowspan']
14
+ },
15
+ // Inline nodes whose identity is their attrs.
16
+ date: {
17
+ include: ['timestamp']
18
+ },
19
+ emoji: {
20
+ include: ['shortName', 'id', 'text']
21
+ },
22
+ mention: {
23
+ include: ['id', 'text']
24
+ },
25
+ status: {
26
+ include: ['text', 'color']
27
+ },
28
+ taskItem: {
29
+ include: ['state']
30
+ },
31
+ // Media: id/collection/url identify the image.
32
+ media: {
33
+ include: ['id', 'collection', 'url']
34
+ },
35
+ // Extensions: any attribute change is meaningful except localId.
36
+ extension: {
37
+ exclude: ['localId']
38
+ },
39
+ inlineExtension: {
40
+ exclude: ['localId']
41
+ },
42
+ bodiedExtension: {
43
+ exclude: ['localId']
44
+ }
45
+ };
46
+
47
+ // Whether `attrName` is a meaningful change for `nodeTypeName`. False if no rule.
48
+ export var isDiffableAttr = function isDiffableAttr(nodeTypeName, attrName) {
49
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
50
+ if (!rule) {
51
+ return false;
52
+ }
53
+ return 'include' in rule ? rule.include.includes(attrName) : !rule.exclude.includes(attrName);
54
+ };
55
+
56
+ // The fixed include list for a node type, or `undefined` for no/exclude rule.
57
+ // Use where a node-independent attr list is needed (e.g. the inline node map).
58
+ export var getIncludedDiffableAttrs = function getIncludedDiffableAttrs(nodeTypeName) {
59
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
60
+ return rule && 'include' in rule ? rule.include : undefined;
61
+ };
62
+
63
+ // Like `getIncludedDiffableAttrs` but asserts the include rule exists, so a
64
+ // consumer relying on a fixed list fails loudly if the rule is removed or
65
+ // switched to an exclude rule rather than silently drifting.
66
+ export var getRequiredIncludedDiffableAttrs = function getRequiredIncludedDiffableAttrs(nodeTypeName) {
67
+ var attrs = getIncludedDiffableAttrs(nodeTypeName);
68
+ if (!attrs) {
69
+ throw new Error("diffableAttrs: expected an include rule for \"".concat(nodeTypeName, "\""));
70
+ }
71
+ return attrs;
72
+ };
73
+
74
+ /**
75
+ * Concrete attr names to diff for a node instance, honouring include and exclude
76
+ * rules; `undefined` when the type has no rule. Exclude rules read the node's
77
+ * attrs and sort them, since key order isn't stable across the two diffed docs.
78
+ */
79
+ export var getDiffableAttrNames = function getDiffableAttrNames(nodeTypeName, attrs) {
80
+ var rule = DIFFABLE_ATTRS_BY_NODE_TYPE[nodeTypeName];
81
+ if (!rule) {
82
+ return undefined;
83
+ }
84
+ if ('include' in rule) {
85
+ return rule.include;
86
+ }
87
+ return Object.keys(attrs).filter(function (name) {
88
+ return !rule.exclude.includes(name);
89
+ }).sort();
90
+ };
@@ -5,27 +5,18 @@ import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
5
5
  import { isExperimentEnabled } from '@atlaskit/editor-common/deprecated-platform-feature-experiments';
6
6
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
7
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
+ import { getRequiredIncludedDiffableAttrs, isDiffableAttr } from './diffableAttrs';
8
9
  var filterUndefined = function filterUndefined(x) {
9
10
  return !!x;
10
11
  };
11
12
 
12
- // Attributes that indicate a change in media image
13
- var mediaAttrs = ['id', 'collection', 'url'];
14
-
15
- // Attribute that indicates a date change
16
- var dateAttrs = ['timestamp'];
17
-
18
- // Attribute that indicates a task item state change
19
- var taskItemAttrs = ['state'];
20
-
21
- // Attributes that indicate an emoji change
22
- var emojiAttrs = ['shortName', 'id', 'text'];
23
-
24
- // Attributes that indicate a mention change (who is mentioned)
25
- var mentionAttrs = ['id', 'text'];
26
-
27
- // Attributes that indicate a status change (label or colour)
28
- var statusAttrs = ['text', 'color'];
13
+ // Attr lists sourced from the shared `diffableAttrs` map (shared with the encoder).
14
+ var mediaAttrs = getRequiredIncludedDiffableAttrs('media');
15
+ var dateAttrs = getRequiredIncludedDiffableAttrs('date');
16
+ var taskItemAttrs = getRequiredIncludedDiffableAttrs('taskItem');
17
+ var emojiAttrs = getRequiredIncludedDiffableAttrs('emoji');
18
+ var mentionAttrs = getRequiredIncludedDiffableAttrs('mention');
19
+ var statusAttrs = getRequiredIncludedDiffableAttrs('status');
29
20
 
30
21
  // Map of node type name → the attrs that represent a meaningful content change for that node
31
22
  var inlineNodeAttrMap = {
@@ -38,10 +29,8 @@ var isInlineAttrChangeNodeName = function isInlineAttrChangeNodeName(nodeName) {
38
29
  return nodeName in inlineNodeAttrMap;
39
30
  };
40
31
 
41
- // Attributes excluded from extension change detection (not meaningful content changes)
42
- var extensionExcludedAttrs = ['localId'];
43
-
44
- // Extension node type names
32
+ // Extension node type names. Their "any attr except localId" exclude rule lives
33
+ // in the shared `diffableAttrs` map and is applied via `isDiffableAttr` below.
45
34
  var extensionNodeNames = ['extension', 'inlineExtension', 'bodiedExtension'];
46
35
  var getStepAttrs = function getStepAttrs(step) {
47
36
  if (step instanceof AttrStep) {
@@ -111,9 +100,9 @@ export var getAttrChangeRanges = function getAttrChangeRanges(doc, steps, origin
111
100
  };
112
101
  }
113
102
 
114
- // extension nodes: any attribute change except localId — highlight the node
103
+ // extension nodes: highlight on any diffable attr change (exclude rule in shared map)
115
104
  if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(function (v) {
116
- return !extensionExcludedAttrs.includes(v);
105
+ return isDiffableAttr(nodeAtPos.type.name, v);
117
106
  })) {
118
107
  var isInline = nodeAtPos.type.name === 'inlineExtension';
119
108
  return {
@@ -4,8 +4,8 @@ import { trackChangesMessages } from '@atlaskit/editor-common/messages';
4
4
  import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
6
  import { isExtendedEnabled } from '../../isExtendedEnabled';
7
- import { deletedBlockOutline, deletedBlockOutlineActive, deletedBlockOutlineRounded, deletedBlockOutlineRoundedActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleUnbounded, deletedInlineContentStyleExtended, deletedStyleQuoteNodeWithLozenge, deletedStyleQuoteNodeWithLozengeActive, editingContentStyleInBlockExtended, editingContentStyleInBlockExtendedNoUnderline, editingStyleExtended, editingStyleExtendedNoUnderline, editingStyleActiveExtended, editingStyleActiveExtendedNoUnderline, editingStyleNode, addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedCellOverlayStyle } from '../colorSchemes/standard';
8
- import { deletedTraditionalBlockOutlineActive, deletedTraditionalBlockOutlineNew, deletedTraditionalBlockOutlineRoundedActive, deletedTraditionalBlockOutlineRoundedNew, deletedTraditionalContentStyleUnbounded, deletedTraditionalContentStyleUnboundedActive, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, deletedTraditionalStyleQuoteNodeActive, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNodeActive, traditionalStyleNodeNew, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyleNew, deletedTraditionalCellOverlayStyle } from '../colorSchemes/traditional';
7
+ import { deletedBlockOutline, deletedBlockOutlineActive, deletedBlockOutlineRounded, deletedBlockOutlineRoundedActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleUnbounded, deletedInlineContentStyleExtended, deletedStyleQuoteNodeWithLozenge, deletedStyleQuoteNodeWithLozengeActive, editingContentStyleInBlockExtended, editingContentStyleInBlockExtendedNoUnderline, editingStyleExtended, editingStyleExtendedNoUnderline, editingStyleActiveExtended, editingStyleActiveExtendedNoUnderline, editingStyleNode, addedCellOverlayStyle, addedCellOverlayRoundedStyle, deletedCellOverlayStyle, deletedCellOverlayRoundedStyle } from '../colorSchemes/standard';
8
+ import { deletedTraditionalBlockOutlineActive, deletedTraditionalBlockOutlineNew, deletedTraditionalBlockOutlineRoundedActive, deletedTraditionalBlockOutlineRoundedNew, deletedTraditionalContentStyleUnbounded, deletedTraditionalContentStyleUnboundedActive, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, deletedTraditionalStyleQuoteNodeActive, traditionalInsertStyle, traditionalInsertStyleActive, traditionalStyleNodeActive, traditionalStyleNodeNew, traditionalAddedCellOverlayRoundedStyle, traditionalAddedCellOverlayStyleNew, deletedTraditionalCellOverlayStyle, deletedTraditionalCellOverlayRoundedStyle } from '../colorSchemes/traditional';
9
9
  import { applyTableCellEdgeAttrs } from './tableCellEdgeAttrs';
10
10
  var lozengeStyle = convertToInlineCss({
11
11
  display: 'inline-flex',
@@ -176,7 +176,7 @@ var applyCellOverlayStyles = function applyCellOverlayStyles(_ref2) {
176
176
  element.querySelectorAll('td, th').forEach(function (cell) {
177
177
  var overlay = document.createElement('span');
178
178
  var isTraditional = colorScheme === 'traditional';
179
- var deletedCellStyle = isTraditional ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
179
+ var deletedCellStyle = isTraditional ? isRoundedTable ? deletedTraditionalCellOverlayRoundedStyle : deletedTraditionalCellOverlayStyle : isRoundedTable ? deletedCellOverlayRoundedStyle : deletedCellOverlayStyle;
180
180
  var addedCellStyle = isTraditional ? isRoundedTable ? traditionalAddedCellOverlayRoundedStyle : traditionalAddedCellOverlayStyleNew : isRoundedTable ? addedCellOverlayRoundedStyle : addedCellOverlayStyle;
181
181
  var overlayStyle = isInserted ? addedCellStyle : deletedCellStyle;
182
182
  overlay.setAttribute('style', overlayStyle);
@@ -1,7 +1,2 @@
1
1
  import type { TokenEncoder } from 'prosemirror-changeset';
2
- /**
3
- * Identical to the library default except that allow-listed node types encode
4
- * their allow-listed attributes into the open token. Characters and node-end
5
- * tokens are left as-is, hence the `string | number` token type.
6
- */
7
2
  export declare const attrAwareTokenEncoder: TokenEncoder<string | number>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Single source of truth for which attributes of a node type represent a
3
+ * meaningful change. Shared by `attrAwareTokenEncoder` (changeset detection)
4
+ * and `getAttrChangeRanges` (decoration rendering) so the two stages agree.
5
+ */
6
+ export type DiffableAttrsRule = {
7
+ include: readonly string[];
8
+ } | {
9
+ exclude: readonly string[];
10
+ };
11
+ export declare const DIFFABLE_ATTRS_BY_NODE_TYPE: Record<string, DiffableAttrsRule>;
12
+ export declare const isDiffableAttr: (nodeTypeName: string, attrName: string) => boolean;
13
+ export declare const getIncludedDiffableAttrs: (nodeTypeName: string) => readonly string[] | undefined;
14
+ export declare const getRequiredIncludedDiffableAttrs: (nodeTypeName: string) => readonly string[];
15
+ /**
16
+ * Concrete attr names to diff for a node instance, honouring include and exclude
17
+ * rules; `undefined` when the type has no rule. Exclude rules read the node's
18
+ * attrs and sort them, since key order isn't stable across the two diffed docs.
19
+ */
20
+ export declare const getDiffableAttrNames: (nodeTypeName: string, attrs: Record<string, unknown>) => readonly string[] | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "13.0.5",
3
+ "version": "13.0.7",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  "@atlaskit/editor-prosemirror": "^8.0.0",
28
28
  "@atlaskit/editor-tables": "^3.0.0",
29
29
  "@atlaskit/platform-feature-flags": "^2.1.0",
30
- "@atlaskit/tmp-editor-statsig": "^148.0.0",
30
+ "@atlaskit/tmp-editor-statsig": "^149.0.0",
31
31
  "@atlaskit/tokens": "^16.7.0",
32
32
  "@babel/runtime": "^7.0.0",
33
33
  "@compiled/react": "^1.0.2",
@@ -40,7 +40,7 @@
40
40
  "@atlaskit/button": "^25.0.0",
41
41
  "@atlaskit/css": "^1.0.0",
42
42
  "@atlaskit/dropdown-menu": "^18.0.0",
43
- "@atlaskit/editor-core": "^224.0.0",
43
+ "@atlaskit/editor-core": "^224.1.0",
44
44
  "@atlaskit/editor-json-transformer": "^9.4.0",
45
45
  "@atlaskit/form": "^17.0.0",
46
46
  "@atlaskit/primitives": "^22.2.0",
@@ -53,7 +53,7 @@
53
53
  "react-intl": "^7.0.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@atlaskit/editor-common": "^119.5.0",
56
+ "@atlaskit/editor-common": "^119.7.0",
57
57
  "react": "^18.2.0 || ^19.2.0"
58
58
  },
59
59
  "techstack": {