@atlaskit/editor-plugin-show-diff 13.0.6 → 13.0.8

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,23 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 13.0.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [`d4fbe7dee465e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/d4fbe7dee465e) -
8
+ Consolidate diff decoration scrolling into a single internal helper.
9
+ - Updated dependencies
10
+
11
+ ## 13.0.7
12
+
13
+ ### Patch Changes
14
+
15
+ - [`2c712e57d3793`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2c712e57d3793) -
16
+ Consolidate diffable-attribute rules into a single shared source of truth (`diffableAttrs`) used
17
+ by both the changeset token encoder (detection) and `getAttrChangeRanges` (rendering), and extend
18
+ the encoder to fold all mapped node types.
19
+ - Updated dependencies
20
+
3
21
  ## 13.0.6
4
22
 
5
23
  ### 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];
@@ -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 {
@@ -187,7 +187,7 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
187
187
  if (pluginState !== null && pluginState !== void 0 && pluginState.scrollIntoView && (0, _isExtendedEnabled.isExtendedEnabled)(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
188
188
  var _cancelPendingScrollT;
189
189
  (_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 || _cancelPendingScrollT();
190
- cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToFirstDecoration)(view, (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
190
+ cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToDecoration)(view, (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
191
191
 
192
192
  // Reset the flag so we don't scroll again on subsequent updates
193
193
  view.dispatch(view.state.tr.setMeta(showDiffPluginKey, {
@@ -208,7 +208,7 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
208
208
  // avoid a circular dependency; expand picks up the meta if loaded (no-op if not).
209
209
  api === null || api === void 0 || api.core.actions.execute((0, _expand.toggleExpandRange)(activeDecoration.from, activeDecoration.to, true));
210
210
  }
211
- cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToActiveDecoration)(view, scrollableDecorations, pluginState.activeIndex);
211
+ cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToDecoration)(view, scrollableDecorations, pluginState.activeIndex);
212
212
  }
213
213
  },
214
214
  destroy: function destroy() {
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.scrollToFirstDecoration = exports.scrollToActiveDecoration = void 0;
6
+ exports.scrollToDecoration = void 0;
7
7
  var _decorationKeys = require("./decorations/decorationKeys");
8
8
  /**
9
9
  * Extra space above the scrolled-to element so it does not sit flush under the
@@ -40,29 +40,13 @@ function scrollToSelection(node) {
40
40
  }
41
41
 
42
42
  /**
43
- * Schedules scrolling to the first diff decoration after the next frame.
44
- * Unlike `scrollToActiveDecoration`, this does not require an active index —
45
- * it simply scrolls to bring the first decoration into view.
46
- *
47
- * The caller must pass the list of scrollable decorations as produced by
48
- * `getScrollableDecorations`, which is filtered (non-scrollable decorations
49
- * removed) and sorted by document position. This guarantees "first" means the
50
- * topmost scrollable diff in the document rather than an arbitrary decoration
51
- * from the raw `DecorationSet` ordering. This is delegated to
52
- * `scrollToActiveDecoration` at index 0 so both scroll paths behave identically.
53
- *
54
- * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
55
- */
56
- var scrollToFirstDecoration = exports.scrollToFirstDecoration = function scrollToFirstDecoration(view, scrollableDecorations) {
57
- return scrollToActiveDecoration(view, scrollableDecorations, 0);
58
- };
59
-
60
- /**
61
- * Schedules scrolling to the decoration at the given index after the next frame.
43
+ * Schedules scrolling to the decoration at the given index after the next frame. Defaults to the
44
+ * first decoration when no index is provided.
62
45
  *
63
46
  * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
64
47
  */
65
- var scrollToActiveDecoration = exports.scrollToActiveDecoration = function scrollToActiveDecoration(view, decorations, activeIndex) {
48
+ var scrollToDecoration = exports.scrollToDecoration = function scrollToDecoration(view, decorations) {
49
+ var activeIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
66
50
  var decoration = decorations[activeIndex];
67
51
  if (!decoration) {
68
52
  return function () {};
@@ -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];
@@ -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,
@@ -9,7 +9,7 @@ import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
9
9
  import { getScrollableDecorations } from './getScrollableDecorations';
10
10
  import { isExtendedEnabled } from './isExtendedEnabled';
11
11
  import { NodeViewSerializer } from './NodeViewSerializer';
12
- import { scrollToActiveDecoration, scrollToFirstDecoration } from './scrollToDiff';
12
+ import { scrollToDecoration } from './scrollToDiff';
13
13
  export const showDiffPluginKey = new PluginKey('showDiffPlugin');
14
14
  export const createPlugin = (config, getIntl, api, onEditorView) => {
15
15
  enforceCustomStepRegisters();
@@ -194,7 +194,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
194
194
  if (pluginState !== null && pluginState !== void 0 && pluginState.scrollIntoView && isExtendedEnabled(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
195
195
  var _cancelPendingScrollT;
196
196
  (_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 ? void 0 : _cancelPendingScrollT();
197
- cancelPendingScrollToDecoration = scrollToFirstDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
197
+ cancelPendingScrollToDecoration = scrollToDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
198
198
 
199
199
  // Reset the flag so we don't scroll again on subsequent updates
200
200
  view.dispatch(view.state.tr.setMeta(showDiffPluginKey, {
@@ -215,7 +215,7 @@ export const createPlugin = (config, getIntl, api, onEditorView) => {
215
215
  // avoid a circular dependency; expand picks up the meta if loaded (no-op if not).
216
216
  api === null || api === void 0 ? void 0 : api.core.actions.execute(toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
217
217
  }
218
- cancelPendingScrollToDecoration = scrollToActiveDecoration(view, scrollableDecorations, pluginState.activeIndex);
218
+ cancelPendingScrollToDecoration = scrollToDecoration(view, scrollableDecorations, pluginState.activeIndex);
219
219
  }
220
220
  },
221
221
  destroy() {
@@ -35,27 +35,12 @@ function scrollToSelection(node) {
35
35
  }
36
36
 
37
37
  /**
38
- * Schedules scrolling to the first diff decoration after the next frame.
39
- * Unlike `scrollToActiveDecoration`, this does not require an active index —
40
- * it simply scrolls to bring the first decoration into view.
41
- *
42
- * The caller must pass the list of scrollable decorations as produced by
43
- * `getScrollableDecorations`, which is filtered (non-scrollable decorations
44
- * removed) and sorted by document position. This guarantees "first" means the
45
- * topmost scrollable diff in the document rather than an arbitrary decoration
46
- * from the raw `DecorationSet` ordering. This is delegated to
47
- * `scrollToActiveDecoration` at index 0 so both scroll paths behave identically.
48
- *
49
- * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
50
- */
51
- export const scrollToFirstDecoration = (view, scrollableDecorations) => scrollToActiveDecoration(view, scrollableDecorations, 0);
52
-
53
- /**
54
- * Schedules scrolling to the decoration at the given index after the next frame.
38
+ * Schedules scrolling to the decoration at the given index after the next frame. Defaults to the
39
+ * first decoration when no index is provided.
55
40
  *
56
41
  * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
57
42
  */
58
- export const scrollToActiveDecoration = (view, decorations, activeIndex) => {
43
+ export const scrollToDecoration = (view, decorations, activeIndex = 0) => {
59
44
  const decoration = decorations[activeIndex];
60
45
  if (!decoration) {
61
46
  return () => {};
@@ -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];
@@ -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 {
@@ -12,7 +12,7 @@ import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
12
12
  import { getScrollableDecorations } from './getScrollableDecorations';
13
13
  import { isExtendedEnabled } from './isExtendedEnabled';
14
14
  import { NodeViewSerializer } from './NodeViewSerializer';
15
- import { scrollToActiveDecoration, scrollToFirstDecoration } from './scrollToDiff';
15
+ import { scrollToDecoration } from './scrollToDiff';
16
16
  export var showDiffPluginKey = new PluginKey('showDiffPlugin');
17
17
  export var createPlugin = function createPlugin(config, getIntl, api, onEditorView) {
18
18
  enforceCustomStepRegisters();
@@ -180,7 +180,7 @@ export var createPlugin = function createPlugin(config, getIntl, api, onEditorVi
180
180
  if (pluginState !== null && pluginState !== void 0 && pluginState.scrollIntoView && isExtendedEnabled(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
181
181
  var _cancelPendingScrollT;
182
182
  (_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 || _cancelPendingScrollT();
183
- cancelPendingScrollToDecoration = scrollToFirstDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
183
+ cancelPendingScrollToDecoration = scrollToDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
184
184
 
185
185
  // Reset the flag so we don't scroll again on subsequent updates
186
186
  view.dispatch(view.state.tr.setMeta(showDiffPluginKey, {
@@ -201,7 +201,7 @@ export var createPlugin = function createPlugin(config, getIntl, api, onEditorVi
201
201
  // avoid a circular dependency; expand picks up the meta if loaded (no-op if not).
202
202
  api === null || api === void 0 || api.core.actions.execute(toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
203
203
  }
204
- cancelPendingScrollToDecoration = scrollToActiveDecoration(view, scrollableDecorations, pluginState.activeIndex);
204
+ cancelPendingScrollToDecoration = scrollToDecoration(view, scrollableDecorations, pluginState.activeIndex);
205
205
  }
206
206
  },
207
207
  destroy: function destroy() {
@@ -35,29 +35,13 @@ function scrollToSelection(node) {
35
35
  }
36
36
 
37
37
  /**
38
- * Schedules scrolling to the first diff decoration after the next frame.
39
- * Unlike `scrollToActiveDecoration`, this does not require an active index —
40
- * it simply scrolls to bring the first decoration into view.
41
- *
42
- * The caller must pass the list of scrollable decorations as produced by
43
- * `getScrollableDecorations`, which is filtered (non-scrollable decorations
44
- * removed) and sorted by document position. This guarantees "first" means the
45
- * topmost scrollable diff in the document rather than an arbitrary decoration
46
- * from the raw `DecorationSet` ordering. This is delegated to
47
- * `scrollToActiveDecoration` at index 0 so both scroll paths behave identically.
48
- *
49
- * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
50
- */
51
- export var scrollToFirstDecoration = function scrollToFirstDecoration(view, scrollableDecorations) {
52
- return scrollToActiveDecoration(view, scrollableDecorations, 0);
53
- };
54
-
55
- /**
56
- * Schedules scrolling to the decoration at the given index after the next frame.
38
+ * Schedules scrolling to the decoration at the given index after the next frame. Defaults to the
39
+ * first decoration when no index is provided.
57
40
  *
58
41
  * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
59
42
  */
60
- export var scrollToActiveDecoration = function scrollToActiveDecoration(view, decorations, activeIndex) {
43
+ export var scrollToDecoration = function scrollToDecoration(view, decorations) {
44
+ var activeIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
61
45
  var decoration = decorations[activeIndex];
62
46
  if (!decoration) {
63
47
  return function () {};
@@ -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;
@@ -1,22 +1,8 @@
1
1
  import type { EditorView, Decoration } from '@atlaskit/editor-prosemirror/view';
2
2
  /**
3
- * Schedules scrolling to the first diff decoration after the next frame.
4
- * Unlike `scrollToActiveDecoration`, this does not require an active index —
5
- * it simply scrolls to bring the first decoration into view.
6
- *
7
- * The caller must pass the list of scrollable decorations as produced by
8
- * `getScrollableDecorations`, which is filtered (non-scrollable decorations
9
- * removed) and sorted by document position. This guarantees "first" means the
10
- * topmost scrollable diff in the document rather than an arbitrary decoration
11
- * from the raw `DecorationSet` ordering. This is delegated to
12
- * `scrollToActiveDecoration` at index 0 so both scroll paths behave identically.
13
- *
14
- * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
15
- */
16
- export declare const scrollToFirstDecoration: (view: EditorView, scrollableDecorations: Decoration[]) => (() => void);
17
- /**
18
- * Schedules scrolling to the decoration at the given index after the next frame.
3
+ * Schedules scrolling to the decoration at the given index after the next frame. Defaults to the
4
+ * first decoration when no index is provided.
19
5
  *
20
6
  * @returns A function that cancels the scheduled `requestAnimationFrame` if it has not run yet.
21
7
  */
22
- export declare const scrollToActiveDecoration: (view: EditorView, decorations: Decoration[], activeIndex: number) => (() => void);
8
+ export declare const scrollToDecoration: (view: EditorView, decorations: Decoration[], activeIndex?: number) => (() => void);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "13.0.6",
3
+ "version": "13.0.8",
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.1.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",
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@atlaskit/adf-utils": "^20.6.0",
40
- "@atlaskit/button": "^25.0.0",
40
+ "@atlaskit/button": "^25.1.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.6.0",
56
+ "@atlaskit/editor-common": "^119.8.0",
57
57
  "react": "^18.2.0 || ^19.2.0"
58
58
  },
59
59
  "techstack": {