@atlaskit/editor-plugin-show-diff 6.1.3 → 6.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 6.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`9df4b10b5f0f8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9df4b10b5f0f8) -
8
+ Improve edge cases when showing diff by using a looser equality structure for steps.
9
+ - Updated dependencies
10
+
3
11
  ## 6.1.3
4
12
 
5
13
  ### Patch Changes
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.areDocsEqualByBlockStructureAndText = areDocsEqualByBlockStructureAndText;
7
+ /**
8
+ * Returns true if both nodes have the same tree structure (type and child count at every level).
9
+ */
10
+ function isBlockStructureEqual(node1, node2) {
11
+ if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
12
+ return false;
13
+ }
14
+ for (var i = 0; i < node1.childCount; i++) {
15
+ if (!isBlockStructureEqual(node1.child(i), node2.child(i))) {
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ }
21
+
22
+ /**
23
+ * Looser equality for "safe diff" cases: same full text content and same block structure
24
+ * (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
25
+ * This is safe because we ensure decorations get applied to valid positions.
26
+ */
27
+ function areDocsEqualByBlockStructureAndText(doc1, doc2) {
28
+ return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
29
+ }
@@ -13,7 +13,9 @@ var _memoizeOne = _interopRequireDefault(require("memoize-one"));
13
13
  var _prosemirrorChangeset = require("prosemirror-changeset");
14
14
  var _document = require("@atlaskit/editor-common/utils/document");
15
15
  var _view = require("@atlaskit/editor-prosemirror/view");
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
17
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
18
+ var _areDocsEqualByBlockStructureAndText = require("./areDocsEqualByBlockStructureAndText");
17
19
  var _createBlockChangedDecoration = require("./decorations/createBlockChangedDecoration");
18
20
  var _createInlineChangedDecoration = require("./decorations/createInlineChangedDecoration");
19
21
  var _createNodeChangedDecorationWidget = require("./decorations/createNodeChangedDecorationWidget");
@@ -132,6 +134,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref2
132
134
  _iterator.f();
133
135
  }
134
136
  if (!(0, _document.areNodesEqualIgnoreAttrs)(steppedDoc, tr.doc)) {
137
+ var recoveredViaContentEquality = (0, _platformFeatureFlags.fg)('platform_editor_show_diff_equality_fallback') ? (0, _areDocsEqualByBlockStructureAndText.areDocsEqualByBlockStructureAndText)(steppedDoc, tr.doc) : undefined;
135
138
  if ((0, _expValEquals.expValEquals)('platform_editor_are_nodes_equal_ignore_mark_order', 'isEnabled', true)) {
136
139
  var _api$analytics;
137
140
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
@@ -140,11 +143,18 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref2
140
143
  actionSubject: 'showDiff',
141
144
  attributes: {
142
145
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
143
- colorScheme: colorScheme
146
+ colorScheme: colorScheme,
147
+ recoveredViaContentEquality: recoveredViaContentEquality
144
148
  }
145
149
  });
146
150
  }
147
- return _view.DecorationSet.empty;
151
+ if ((0, _platformFeatureFlags.fg)('platform_editor_show_diff_equality_fallback')) {
152
+ if (!recoveredViaContentEquality) {
153
+ return _view.DecorationSet.empty;
154
+ }
155
+ } else {
156
+ return _view.DecorationSet.empty;
157
+ }
148
158
  }
149
159
  var changeset = _prosemirrorChangeset.ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
150
160
  var changes = (0, _prosemirrorChangeset.simplifyChanges)(changeset.changes, tr.doc);
@@ -119,7 +119,8 @@ var createBlockChangedDecoration = exports.createBlockChangedDecoration = functi
119
119
  'data-testid': 'show-diff-changed-decoration-node',
120
120
  class: className
121
121
  }, {
122
- key: 'diff-block'
122
+ key: 'diff-block',
123
+ nodeName: change.name
123
124
  });
124
125
  } else {
125
126
  return undefined;
@@ -130,6 +131,7 @@ var createBlockChangedDecoration = exports.createBlockChangedDecoration = functi
130
131
  'data-testid': 'show-diff-changed-decoration-node',
131
132
  class: className
132
133
  }, {
133
- key: 'diff-block'
134
+ key: 'diff-block',
135
+ nodeName: change.name
134
136
  });
135
137
  };
@@ -20,13 +20,26 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
20
20
  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; }
21
21
  var showDiffPluginKey = exports.showDiffPluginKey = new _state2.PluginKey('showDiffPlugin');
22
22
  var getScrollableDecorations = exports.getScrollableDecorations = function getScrollableDecorations(set) {
23
- var _set$find$sort;
24
- return (_set$find$sort = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, function (spec) {
23
+ var _set$find;
24
+ var seenBlockKeys = new Set();
25
+ return ((_set$find = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, function (spec) {
25
26
  var _spec$key;
26
27
  return spec.key === 'diff-inline' || ((_spec$key = spec.key) === null || _spec$key === void 0 ? void 0 : _spec$key.startsWith('diff-widget')) || spec.key === 'diff-block';
28
+ })) !== null && _set$find !== void 0 ? _set$find : []).filter(function (dec) {
29
+ var _dec$spec;
30
+ if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.key) === 'diff-block') {
31
+ var _dec$spec2, _dec$spec$nodeName, _dec$spec3;
32
+ // Skip listItem blocks as they are not scrollable
33
+ if (((_dec$spec2 = dec.spec) === null || _dec$spec2 === void 0 ? void 0 : _dec$spec2.nodeName) === 'listItem') return false;
34
+ var key = "".concat(dec.from, "-").concat(dec.to, "-").concat((_dec$spec$nodeName = (_dec$spec3 = dec.spec) === null || _dec$spec3 === void 0 ? void 0 : _dec$spec3.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : '');
35
+ // Skip blocks that have already been seen
36
+ if (seenBlockKeys.has(key)) return false;
37
+ seenBlockKeys.add(key);
38
+ }
39
+ return true;
27
40
  }).sort(function (a, b) {
28
- return a.from - b.from;
29
- })) !== null && _set$find$sort !== void 0 ? _set$find$sort : [];
41
+ return a.from === b.from ? a.to - b.to : a.from - b.from;
42
+ });
30
43
  };
31
44
  var createPlugin = exports.createPlugin = function createPlugin(config, getIntl, api) {
32
45
  var nodeViewSerializer = new _NodeViewSerializer.NodeViewSerializer({});
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Returns true if both nodes have the same tree structure (type and child count at every level).
3
+ */
4
+ function isBlockStructureEqual(node1, node2) {
5
+ if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
6
+ return false;
7
+ }
8
+ for (let i = 0; i < node1.childCount; i++) {
9
+ if (!isBlockStructureEqual(node1.child(i), node2.child(i))) {
10
+ return false;
11
+ }
12
+ }
13
+ return true;
14
+ }
15
+
16
+ /**
17
+ * Looser equality for "safe diff" cases: same full text content and same block structure
18
+ * (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
19
+ * This is safe because we ensure decorations get applied to valid positions.
20
+ */
21
+ export function areDocsEqualByBlockStructureAndText(doc1, doc2) {
22
+ return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
23
+ }
@@ -4,7 +4,9 @@ import memoizeOne from 'memoize-one';
4
4
  import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
5
5
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
6
6
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
+ import { areDocsEqualByBlockStructureAndText } from './areDocsEqualByBlockStructureAndText';
8
10
  import { createBlockChangedDecoration } from './decorations/createBlockChangedDecoration';
9
11
  import { createInlineChangedDecoration } from './decorations/createInlineChangedDecoration';
10
12
  import { createNodeChangedDecorationWidget } from './decorations/createNodeChangedDecorationWidget';
@@ -117,6 +119,7 @@ const calculateDiffDecorationsInner = ({
117
119
  // Rather than using .eq() we use a custom function that only checks for structural
118
120
  // changes and ignores differences in attributes which don't affect decoration positions
119
121
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
122
+ const recoveredViaContentEquality = fg('platform_editor_show_diff_equality_fallback') ? areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc) : undefined;
120
123
  if (expValEquals('platform_editor_are_nodes_equal_ignore_mark_order', 'isEnabled', true)) {
121
124
  var _api$analytics;
122
125
  api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
@@ -125,11 +128,18 @@ const calculateDiffDecorationsInner = ({
125
128
  actionSubject: 'showDiff',
126
129
  attributes: {
127
130
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
128
- colorScheme
131
+ colorScheme,
132
+ recoveredViaContentEquality
129
133
  }
130
134
  });
131
135
  }
132
- return DecorationSet.empty;
136
+ if (fg('platform_editor_show_diff_equality_fallback')) {
137
+ if (!recoveredViaContentEquality) {
138
+ return DecorationSet.empty;
139
+ }
140
+ } else {
141
+ return DecorationSet.empty;
142
+ }
133
143
  }
134
144
  const changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
135
145
  const changes = simplifyChanges(changeset.changes, tr.doc);
@@ -111,7 +111,8 @@ export const createBlockChangedDecoration = ({
111
111
  'data-testid': 'show-diff-changed-decoration-node',
112
112
  class: className
113
113
  }, {
114
- key: 'diff-block'
114
+ key: 'diff-block',
115
+ nodeName: change.name
115
116
  });
116
117
  } else {
117
118
  return undefined;
@@ -122,6 +123,7 @@ export const createBlockChangedDecoration = ({
122
123
  'data-testid': 'show-diff-changed-decoration-node',
123
124
  class: className
124
125
  }, {
125
- key: 'diff-block'
126
+ key: 'diff-block',
127
+ nodeName: change.name
126
128
  });
127
129
  };
@@ -10,11 +10,24 @@ import { NodeViewSerializer } from './NodeViewSerializer';
10
10
  import { scrollToActiveDecoration } from './scrollToActiveDecoration';
11
11
  export const showDiffPluginKey = new PluginKey('showDiffPlugin');
12
12
  export const getScrollableDecorations = set => {
13
- var _set$find$sort;
14
- return (_set$find$sort = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, spec => {
13
+ var _set$find;
14
+ const seenBlockKeys = new Set();
15
+ return ((_set$find = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, spec => {
15
16
  var _spec$key;
16
17
  return spec.key === 'diff-inline' || ((_spec$key = spec.key) === null || _spec$key === void 0 ? void 0 : _spec$key.startsWith('diff-widget')) || spec.key === 'diff-block';
17
- }).sort((a, b) => a.from - b.from)) !== null && _set$find$sort !== void 0 ? _set$find$sort : [];
18
+ })) !== null && _set$find !== void 0 ? _set$find : []).filter(dec => {
19
+ var _dec$spec;
20
+ if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.key) === 'diff-block') {
21
+ var _dec$spec2, _dec$spec$nodeName, _dec$spec3;
22
+ // Skip listItem blocks as they are not scrollable
23
+ if (((_dec$spec2 = dec.spec) === null || _dec$spec2 === void 0 ? void 0 : _dec$spec2.nodeName) === 'listItem') return false;
24
+ const key = `${dec.from}-${dec.to}-${(_dec$spec$nodeName = (_dec$spec3 = dec.spec) === null || _dec$spec3 === void 0 ? void 0 : _dec$spec3.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : ''}`;
25
+ // Skip blocks that have already been seen
26
+ if (seenBlockKeys.has(key)) return false;
27
+ seenBlockKeys.add(key);
28
+ }
29
+ return true;
30
+ }).sort((a, b) => a.from === b.from ? a.to - b.to : a.from - b.from);
18
31
  };
19
32
  export const createPlugin = (config, getIntl, api) => {
20
33
  const nodeViewSerializer = new NodeViewSerializer({});
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Returns true if both nodes have the same tree structure (type and child count at every level).
3
+ */
4
+ function isBlockStructureEqual(node1, node2) {
5
+ if (node1.type !== node2.type || node1.childCount !== node2.childCount) {
6
+ return false;
7
+ }
8
+ for (var i = 0; i < node1.childCount; i++) {
9
+ if (!isBlockStructureEqual(node1.child(i), node2.child(i))) {
10
+ return false;
11
+ }
12
+ }
13
+ return true;
14
+ }
15
+
16
+ /**
17
+ * Looser equality for "safe diff" cases: same full text content and same block structure
18
+ * (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
19
+ * This is safe because we ensure decorations get applied to valid positions.
20
+ */
21
+ export function areDocsEqualByBlockStructureAndText(doc1, doc2) {
22
+ return doc1.textContent === doc2.textContent && doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
23
+ }
@@ -12,7 +12,9 @@ import memoizeOne from 'memoize-one';
12
12
  import { ChangeSet, simplifyChanges } from 'prosemirror-changeset';
13
13
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
14
14
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
15
+ import { fg } from '@atlaskit/platform-feature-flags';
15
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
17
+ import { areDocsEqualByBlockStructureAndText } from './areDocsEqualByBlockStructureAndText';
16
18
  import { createBlockChangedDecoration } from './decorations/createBlockChangedDecoration';
17
19
  import { createInlineChangedDecoration } from './decorations/createInlineChangedDecoration';
18
20
  import { createNodeChangedDecorationWidget } from './decorations/createNodeChangedDecorationWidget';
@@ -126,6 +128,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref2
126
128
  _iterator.f();
127
129
  }
128
130
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
131
+ var recoveredViaContentEquality = fg('platform_editor_show_diff_equality_fallback') ? areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc) : undefined;
129
132
  if (expValEquals('platform_editor_are_nodes_equal_ignore_mark_order', 'isEnabled', true)) {
130
133
  var _api$analytics;
131
134
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
@@ -134,11 +137,18 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref2
134
137
  actionSubject: 'showDiff',
135
138
  attributes: {
136
139
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
137
- colorScheme: colorScheme
140
+ colorScheme: colorScheme,
141
+ recoveredViaContentEquality: recoveredViaContentEquality
138
142
  }
139
143
  });
140
144
  }
141
- return DecorationSet.empty;
145
+ if (fg('platform_editor_show_diff_equality_fallback')) {
146
+ if (!recoveredViaContentEquality) {
147
+ return DecorationSet.empty;
148
+ }
149
+ } else {
150
+ return DecorationSet.empty;
151
+ }
142
152
  }
143
153
  var changeset = ChangeSet.create(originalDoc).addSteps(steppedDoc, stepMaps, tr.doc);
144
154
  var changes = simplifyChanges(changeset.changes, tr.doc);
@@ -113,7 +113,8 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
113
113
  'data-testid': 'show-diff-changed-decoration-node',
114
114
  class: className
115
115
  }, {
116
- key: 'diff-block'
116
+ key: 'diff-block',
117
+ nodeName: change.name
117
118
  });
118
119
  } else {
119
120
  return undefined;
@@ -124,6 +125,7 @@ export var createBlockChangedDecoration = function createBlockChangedDecoration(
124
125
  'data-testid': 'show-diff-changed-decoration-node',
125
126
  class: className
126
127
  }, {
127
- key: 'diff-block'
128
+ key: 'diff-block',
129
+ nodeName: change.name
128
130
  });
129
131
  };
@@ -13,13 +13,26 @@ import { NodeViewSerializer } from './NodeViewSerializer';
13
13
  import { scrollToActiveDecoration } from './scrollToActiveDecoration';
14
14
  export var showDiffPluginKey = new PluginKey('showDiffPlugin');
15
15
  export var getScrollableDecorations = function getScrollableDecorations(set) {
16
- var _set$find$sort;
17
- return (_set$find$sort = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, function (spec) {
16
+ var _set$find;
17
+ var seenBlockKeys = new Set();
18
+ return ((_set$find = set === null || set === void 0 ? void 0 : set.find(undefined, undefined, function (spec) {
18
19
  var _spec$key;
19
20
  return spec.key === 'diff-inline' || ((_spec$key = spec.key) === null || _spec$key === void 0 ? void 0 : _spec$key.startsWith('diff-widget')) || spec.key === 'diff-block';
21
+ })) !== null && _set$find !== void 0 ? _set$find : []).filter(function (dec) {
22
+ var _dec$spec;
23
+ if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.key) === 'diff-block') {
24
+ var _dec$spec2, _dec$spec$nodeName, _dec$spec3;
25
+ // Skip listItem blocks as they are not scrollable
26
+ if (((_dec$spec2 = dec.spec) === null || _dec$spec2 === void 0 ? void 0 : _dec$spec2.nodeName) === 'listItem') return false;
27
+ var key = "".concat(dec.from, "-").concat(dec.to, "-").concat((_dec$spec$nodeName = (_dec$spec3 = dec.spec) === null || _dec$spec3 === void 0 ? void 0 : _dec$spec3.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : '');
28
+ // Skip blocks that have already been seen
29
+ if (seenBlockKeys.has(key)) return false;
30
+ seenBlockKeys.add(key);
31
+ }
32
+ return true;
20
33
  }).sort(function (a, b) {
21
- return a.from - b.from;
22
- })) !== null && _set$find$sort !== void 0 ? _set$find$sort : [];
34
+ return a.from === b.from ? a.to - b.to : a.from - b.from;
35
+ });
23
36
  };
24
37
  export var createPlugin = function createPlugin(config, getIntl, api) {
25
38
  var nodeViewSerializer = new NodeViewSerializer({});
@@ -0,0 +1,7 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Looser equality for "safe diff" cases: same full text content and same block structure
4
+ * (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
5
+ * This is safe because we ensure decorations get applied to valid positions.
6
+ */
7
+ export declare function areDocsEqualByBlockStructureAndText(doc1: PMNode, doc2: PMNode): boolean;
@@ -0,0 +1,7 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Looser equality for "safe diff" cases: same full text content and same block structure
4
+ * (e.g. text moved across text-node boundaries). Used when strict areNodesEqualIgnoreAttrs fails.
5
+ * This is safe because we ensure decorations get applied to valid positions.
6
+ */
7
+ export declare function areDocsEqualByBlockStructureAndText(doc1: PMNode, doc2: PMNode): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "6.1.3",
3
+ "version": "6.1.4",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,7 +33,7 @@
33
33
  "@atlaskit/editor-prosemirror": "^7.3.0",
34
34
  "@atlaskit/editor-tables": "^2.9.0",
35
35
  "@atlaskit/platform-feature-flags": "^1.1.0",
36
- "@atlaskit/tmp-editor-statsig": "^40.0.0",
36
+ "@atlaskit/tmp-editor-statsig": "^40.3.0",
37
37
  "@atlaskit/tokens": "^11.1.0",
38
38
  "@babel/runtime": "^7.0.0",
39
39
  "lodash": "^4.17.21",
@@ -85,6 +85,9 @@
85
85
  }
86
86
  },
87
87
  "platform-feature-flags": {
88
+ "platform_editor_show_diff_equality_fallback": {
89
+ "type": "boolean"
90
+ },
88
91
  "platform_editor_show_diff_scroll_navigation": {
89
92
  "type": "boolean"
90
93
  }