@atlaskit/editor-plugin-show-diff 8.4.5 → 8.4.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 (29) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/pm-plugins/areDocsEqualByBlockStructureAndText.js +6 -10
  3. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +13 -20
  4. package/dist/cjs/pm-plugins/calculateDiff/diffBySteps.js +141 -4
  5. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +13 -27
  6. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +12 -34
  7. package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +30 -32
  8. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +5 -9
  9. package/dist/cjs/pm-plugins/main.js +13 -18
  10. package/dist/cjs/showDiffPlugin.js +1 -3
  11. package/dist/es2019/pm-plugins/areDocsEqualByBlockStructureAndText.js +6 -10
  12. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +13 -20
  13. package/dist/es2019/pm-plugins/calculateDiff/diffBySteps.js +137 -4
  14. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +15 -27
  15. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +10 -32
  16. package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +24 -26
  17. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +6 -10
  18. package/dist/es2019/pm-plugins/main.js +13 -18
  19. package/dist/es2019/showDiffPlugin.js +1 -3
  20. package/dist/esm/pm-plugins/areDocsEqualByBlockStructureAndText.js +6 -10
  21. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +13 -20
  22. package/dist/esm/pm-plugins/calculateDiff/diffBySteps.js +141 -4
  23. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +14 -27
  24. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +12 -34
  25. package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +30 -32
  26. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +6 -10
  27. package/dist/esm/pm-plugins/main.js +13 -18
  28. package/dist/esm/showDiffPlugin.js +1 -3
  29. package/package.json +7 -14
@@ -11,7 +11,6 @@ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
11
  var _state2 = require("@atlaskit/editor-prosemirror/state");
12
12
  var _transform = require("@atlaskit/editor-prosemirror/transform");
13
13
  var _view = require("@atlaskit/editor-prosemirror/view");
14
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
14
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
15
  var _calculateDiffDecorations = require("./calculateDiff/calculateDiffDecorations");
17
16
  var _enforceCustomStepRegisters = require("./enforceCustomStepRegisters");
@@ -22,9 +21,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
22
21
  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; }
23
22
  var showDiffPluginKey = exports.showDiffPluginKey = new _state2.PluginKey('showDiffPlugin');
24
23
  var createPlugin = exports.createPlugin = function createPlugin(config, getIntl, api) {
25
- if ((0, _platformFeatureFlags.fg)('platform_editor_show_diff_equality_fallback')) {
26
- (0, _enforceCustomStepRegisters.enforceCustomStepRegisters)();
27
- }
24
+ (0, _enforceCustomStepRegisters.enforceCustomStepRegisters)();
28
25
  var nodeViewSerializer = new _NodeViewSerializer.NodeViewSerializer({});
29
26
  var setNodeViewSerializer = function setNodeViewSerializer(editorView) {
30
27
  nodeViewSerializer.init({
@@ -65,7 +62,7 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
65
62
  nodeViewSerializer: nodeViewSerializer,
66
63
  colorScheme: config === null || config === void 0 ? void 0 : config.colorScheme,
67
64
  intl: getIntl(),
68
- activeIndexPos: (0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation') ? newPluginState.activeIndexPos : undefined,
65
+ activeIndexPos: newPluginState.activeIndexPos,
69
66
  api: api
70
67
  }, (0, _expValEquals.expValEquals)('platform_editor_diff_plugin_extended', 'isEnabled', true) ? {
71
68
  isInverted: (_newPluginState = newPluginState) === null || _newPluginState === void 0 ? void 0 : _newPluginState.isInverted,
@@ -84,7 +81,7 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
84
81
  diffType: 'inline',
85
82
  hideDeletedDiffs: false
86
83
  } : {});
87
- } else if (((meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_NEXT' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_PREVIOUS') && (0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation')) {
84
+ } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_NEXT' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_PREVIOUS') {
88
85
  // Update the active index in plugin state and recalculate decorations
89
86
  var _decorations = (0, _getScrollableDecorations.getScrollableDecorations)(currentPluginState.decorations, newState.doc);
90
87
  if (_decorations.length > 0) {
@@ -170,19 +167,17 @@ var createPlugin = exports.createPlugin = function createPlugin(config, getIntl,
170
167
  }
171
168
 
172
169
  // Check for any potential scroll side-effects
173
- if ((0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation')) {
174
- var activeIndexChanged = (pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && pluginState.activeIndex !== previousActiveIndex;
175
- previousActiveIndex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex;
176
- if ((pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && activeIndexChanged) {
177
- var _cancelPendingScrollT2, _api$expand;
178
- (_cancelPendingScrollT2 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT2 === void 0 || _cancelPendingScrollT2();
179
- var scrollableDecorations = (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc);
180
- var activeDecoration = scrollableDecorations[pluginState.activeIndex];
181
- if (activeDecoration && api !== null && api !== void 0 && (_api$expand = api.expand) !== null && _api$expand !== void 0 && (_api$expand = _api$expand.commands) !== null && _api$expand !== void 0 && _api$expand.toggleExpandRange && (0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation')) {
182
- api === null || api === void 0 || api.core.actions.execute(api.expand.commands.toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
183
- }
184
- cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToActiveDecoration)(view, scrollableDecorations, pluginState.activeIndex);
170
+ var activeIndexChanged = (pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && pluginState.activeIndex !== previousActiveIndex;
171
+ previousActiveIndex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex;
172
+ if ((pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && activeIndexChanged) {
173
+ var _cancelPendingScrollT2, _api$expand;
174
+ (_cancelPendingScrollT2 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT2 === void 0 || _cancelPendingScrollT2();
175
+ var scrollableDecorations = (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc);
176
+ var activeDecoration = scrollableDecorations[pluginState.activeIndex];
177
+ if (activeDecoration && api !== null && api !== void 0 && (_api$expand = api.expand) !== null && _api$expand !== void 0 && (_api$expand = _api$expand.commands) !== null && _api$expand !== void 0 && _api$expand.toggleExpandRange) {
178
+ api === null || api === void 0 || api.core.actions.execute(api.expand.commands.toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
185
179
  }
180
+ cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToActiveDecoration)(view, scrollableDecorations, pluginState.activeIndex);
186
181
  }
187
182
  },
188
183
  destroy: function destroy() {
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.showDiffPlugin = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
10
9
  var _getScrollableDecorations = require("./pm-plugins/getScrollableDecorations");
11
10
  var _main = require("./pm-plugins/main");
12
11
  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; }
@@ -55,7 +54,6 @@ var showDiffPlugin = exports.showDiffPlugin = function showDiffPlugin(_ref) {
55
54
  }];
56
55
  },
57
56
  getSharedState: function getSharedState(editorState) {
58
- var _pluginState$decorati;
59
57
  if (!editorState) {
60
58
  return {
61
59
  isDisplayingChanges: false,
@@ -63,7 +61,7 @@ var showDiffPlugin = exports.showDiffPlugin = function showDiffPlugin(_ref) {
63
61
  };
64
62
  }
65
63
  var pluginState = _main.showDiffPluginKey.getState(editorState);
66
- var decorationCount = (0, _platformFeatureFlags.fg)('platform_editor_show_diff_scroll_navigation') ? (0, _getScrollableDecorations.getScrollableDecorations)(pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations, editorState.doc) : (pluginState === null || pluginState === void 0 || (_pluginState$decorati = pluginState.decorations) === null || _pluginState$decorati === void 0 ? void 0 : _pluginState$decorati.find()) || [];
64
+ var decorationCount = (0, _getScrollableDecorations.getScrollableDecorations)(pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations, editorState.doc);
67
65
  return {
68
66
  isDisplayingChanges: decorationCount.length > 0,
69
67
  activeIndex: pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex,
@@ -1,5 +1,4 @@
1
1
  import { Transform } from '@atlaskit/editor-prosemirror/transform';
2
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
3
2
 
4
3
  /**
5
4
  * Returns a copy of the document with all marks removed from all text.
@@ -42,13 +41,10 @@ export function areDocsEqualByBlockStructureAndText(doc1, doc2) {
42
41
  if (doc1.textContent !== doc2.textContent) {
43
42
  return false;
44
43
  }
45
- if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
46
- // Strip marks before comparing so that mark-driven text fragmentation
47
- // (e.g. annotation mark reordering producing different childCounts) does not
48
- // cause false inequalities.
49
- const stripped1 = stripMarks(doc1);
50
- const stripped2 = stripMarks(doc2);
51
- return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(stripped1, stripped2);
52
- }
53
- return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(doc1, doc2);
44
+ // Strip marks before comparing so that mark-driven text fragmentation
45
+ // (e.g. annotation mark reordering producing different childCounts) does not
46
+ // cause false inequalities.
47
+ const stripped1 = stripMarks(doc1);
48
+ const stripped2 = stripMarks(doc2);
49
+ return doc1.nodeSize === doc2.nodeSize && isBlockStructureEqual(stripped1, stripped2);
54
50
  }
@@ -4,7 +4,6 @@ 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';
8
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
8
  import { areDocsEqualByBlockStructureAndText } from '../areDocsEqualByBlockStructureAndText';
10
9
  import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
@@ -110,25 +109,19 @@ const calculateDiffDecorationsInner = ({
110
109
  // Rather than using .eq() we use a custom function that only checks for structural
111
110
  // changes and ignores differences in attributes which don't affect decoration positions
112
111
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
113
- const recoveredViaContentEquality = fg('platform_editor_show_diff_equality_fallback') ? areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc) : undefined;
114
- if (expValEquals('platform_editor_are_nodes_equal_ignore_mark_order', 'isEnabled', true)) {
115
- var _api$analytics;
116
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
117
- eventType: 'track',
118
- action: 'nodesNotEqual',
119
- actionSubject: 'showDiff',
120
- attributes: {
121
- docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
122
- colorScheme,
123
- recoveredViaContentEquality
124
- }
125
- });
126
- }
127
- if (fg('platform_editor_show_diff_equality_fallback')) {
128
- if (!recoveredViaContentEquality) {
129
- return DecorationSet.empty;
112
+ var _api$analytics;
113
+ const recoveredViaContentEquality = areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc);
114
+ api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
115
+ eventType: 'track',
116
+ action: 'nodesNotEqual',
117
+ actionSubject: 'showDiff',
118
+ attributes: {
119
+ docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
120
+ colorScheme,
121
+ recoveredViaContentEquality
130
122
  }
131
- } else {
123
+ });
124
+ if (!recoveredViaContentEquality) {
132
125
  return DecorationSet.empty;
133
126
  }
134
127
  }
@@ -202,7 +195,7 @@ const calculateDiffDecorationsInner = ({
202
195
  }));
203
196
  });
204
197
  getAttrChangeRanges(tr.doc, attrSteps).forEach(change => {
205
- if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) && change.isInline) {
198
+ if (change.isInline) {
206
199
  // Inline nodes (e.g. date) need an inline decoration rather than a block decoration
207
200
  const isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
208
201
  decorations.push(createInlineChangedDecoration({
@@ -4,6 +4,103 @@ import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
4
4
  import { optimizeChanges } from './optimizeChanges';
5
5
  const mapPosition = (mapping, pos) => mapping.map(pos);
6
6
 
7
+ /**
8
+ * Given a ProseMirror doc and a position range [from, to], expand
9
+ * both endpoints outward to the nearest word boundaries.
10
+ *
11
+ * A "word character" is any non-whitespace, non-punctuation character, plus hyphen.
12
+ * This covers accented/Unicode letters (e.g. é, ñ, ü) while still stopping at
13
+ * commas, periods, and other punctuation. Hyphens are explicitly included so that
14
+ * hyphenated words (e.g. "deep-sea") are treated as a single word.
15
+ * Expansion stops at whitespace, punctuation (excluding hyphen), or the textblock edges.
16
+ *
17
+ * If `from` and `to` resolve into different parent nodes, or if the
18
+ * parent is not a textblock, the range is returned unchanged.
19
+ */
20
+ const expandToWordBoundaries = (doc, from, to) => {
21
+ const $from = doc.resolve(from);
22
+
23
+ // Only expand inside a textblock.
24
+ if (!$from.parent.isTextblock) {
25
+ return {
26
+ from,
27
+ to
28
+ };
29
+ }
30
+
31
+ // When `from !== to`, verify both ends are in the same textblock.
32
+ if (from !== to) {
33
+ const $to = doc.resolve(to);
34
+ if ($from.parent !== $to.parent) {
35
+ return {
36
+ from,
37
+ to
38
+ };
39
+ }
40
+ }
41
+ const parent = $from.parent;
42
+ const text = parent.textContent;
43
+ const parentStart = $from.start(); // absolute position of the first character in the textblock
44
+
45
+ // Convert absolute doc positions to zero-based string indices.
46
+ let fromIdx = from - parentStart;
47
+ let toIdx = to - parentStart;
48
+
49
+ // any non whitespace and non punctuation chars
50
+ // @ts-ignore TS1501: This regular expression flag is only available when targeting 'es6' or later.
51
+ const isWordChar = ch => /[\p{L}\p{N}_-]/u.test(ch);
52
+
53
+ // Detect whether the position sits mid-word: there is a word character
54
+ // on both sides of the position (or, for a non-empty range, on the
55
+ // outer side of each endpoint).
56
+ const isMidWord = idx => idx > 0 && idx < text.length && isWordChar(text[idx - 1]) && isWordChar(text[idx]);
57
+
58
+ // For a zero-width range (pure insertion / deletion point), only expand
59
+ // if the point is mid-word — i.e. both the char before and after are
60
+ // word characters. Otherwise the point is already at a word boundary.
61
+ if (from === to) {
62
+ if (!isMidWord(fromIdx)) {
63
+ return {
64
+ from,
65
+ to
66
+ };
67
+ }
68
+ // Expand both directions from the mid-word point.
69
+ while (fromIdx > 0 && isWordChar(text[fromIdx - 1])) {
70
+ fromIdx--;
71
+ }
72
+ while (toIdx < text.length && isWordChar(text[toIdx])) {
73
+ toIdx++;
74
+ }
75
+ return {
76
+ from: parentStart + fromIdx,
77
+ to: parentStart + toIdx
78
+ };
79
+ }
80
+
81
+ // Non-empty range: expand each endpoint outward if it is mid-word.
82
+
83
+ // Expand left only if `from` is truly mid-word: the character at `from`
84
+ // (inside the range) and the character before `from` are both word chars.
85
+ if (fromIdx > 0 && fromIdx < text.length && isWordChar(text[fromIdx]) && isWordChar(text[fromIdx - 1])) {
86
+ while (fromIdx > 0 && isWordChar(text[fromIdx - 1])) {
87
+ fromIdx--;
88
+ }
89
+ }
90
+
91
+ // Expand right only if `to` is truly mid-word: the character just before
92
+ // `to` (last char of the range) and the character at `to` are both word chars.
93
+ if (toIdx > 0 && toIdx < text.length && isWordChar(text[toIdx - 1]) && isWordChar(text[toIdx])) {
94
+ while (toIdx < text.length && isWordChar(text[toIdx])) {
95
+ toIdx++;
96
+ }
97
+ }
98
+ return {
99
+ from: parentStart + fromIdx,
100
+ to: parentStart + toIdx
101
+ };
102
+ };
103
+
7
104
  /**
8
105
  * Compare marks between two nodes
9
106
  * We have to check each child because adding a mark splits text into multiple nodes
@@ -147,10 +244,46 @@ export const diffBySteps = (originalDoc, steps) => {
147
244
  // wrong text/positions. Use the post-step doc here.
148
245
  const optimizedGranularStepChanges = optimizeChanges(simplifyChanges(granularStepChanges.changes, rangedStep.doc));
149
246
  for (const granularChange of optimizedGranularStepChanges) {
150
- const granularFromA = mapPosition(beforeStepToOriginal, granularChange.fromA);
151
- const granularToA = mapPosition(beforeStepToOriginal, granularChange.toA);
152
- const granularFromB = mapPosition(afterStepToFinal, granularChange.fromB);
153
- const granularToB = mapPosition(afterStepToFinal, granularChange.toB);
247
+ // Expand each granular change to the nearest word boundaries in
248
+ // both the pre-step doc (A-side) and the post-step doc (B-side).
249
+ // This ensures that a mid-word edit like "sanitised" → "sanitized"
250
+ // shows as deleting the whole original word and inserting the whole
251
+ // new word, rather than a single-character swap.
252
+ const expandedA = expandToWordBoundaries(rangedStep.before, granularChange.fromA, granularChange.toA);
253
+ const expandedB = expandToWordBoundaries(rangedStep.doc, granularChange.fromB, granularChange.toB);
254
+
255
+ // When one side expanded further than the other (e.g. a space
256
+ // was inserted mid-word: "altogether" → "all together"), the
257
+ // less-expanded side must grow to match — otherwise the renderer
258
+ // shows a partial word as plain text next to a deletion/insertion.
259
+ // We compare left and right deltas independently so partial
260
+ // expansion on one side doesn't prevent the other side from
261
+ // being pulled out further.
262
+ const aLeftDelta = granularChange.fromA - expandedA.from;
263
+ const aRightDelta = expandedA.to - granularChange.toA;
264
+ const bLeftDelta = granularChange.fromB - expandedB.from;
265
+ const bRightDelta = expandedB.to - granularChange.toB;
266
+ let finalA = expandedA;
267
+ let finalB = expandedB;
268
+
269
+ // If A expanded further on either side, nudge B outward
270
+ // by the excess and re-expand to snap to word boundaries.
271
+ if (aLeftDelta > bLeftDelta || aRightDelta > bRightDelta) {
272
+ const extraLeft = Math.max(0, aLeftDelta - bLeftDelta);
273
+ const extraRight = Math.max(0, aRightDelta - bRightDelta);
274
+ finalB = expandToWordBoundaries(rangedStep.doc, Math.max(expandedB.from - extraLeft, 0), expandedB.to + extraRight);
275
+ }
276
+
277
+ // If B expanded further on either side, nudge A outward.
278
+ if (bLeftDelta > aLeftDelta || bRightDelta > aRightDelta) {
279
+ const extraLeft = Math.max(0, bLeftDelta - aLeftDelta);
280
+ const extraRight = Math.max(0, bRightDelta - aRightDelta);
281
+ finalA = expandToWordBoundaries(rangedStep.before, Math.max(expandedA.from - extraLeft, 0), expandedA.to + extraRight);
282
+ }
283
+ const granularFromA = mapPosition(beforeStepToOriginal, finalA.from);
284
+ const granularToA = mapPosition(beforeStepToOriginal, finalA.to);
285
+ const granularFromB = mapPosition(afterStepToFinal, finalB.from);
286
+ const granularToB = mapPosition(afterStepToFinal, finalB.to);
154
287
  changes.push({
155
288
  fromA: granularFromA,
156
289
  toA: granularToA,
@@ -1,9 +1,8 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
- import { fg } from '@atlaskit/platform-feature-flags';
4
3
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
4
  import { standardDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
6
- import { traditionalDecorationMarkerVariable, traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariable, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNode, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNode, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNode, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNode, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from './colorSchemes/traditional';
5
+ import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from './colorSchemes/traditional';
7
6
  const displayNoneStyle = convertToInlineCss({
8
7
  display: 'none'
9
8
  });
@@ -26,7 +25,7 @@ const getBlockNodeStyle = ({
26
25
  // Handle table separately to avoid border issues
27
26
  'tableRow', 'paragraph',
28
27
  // Paragraph and heading nodes do not need special styling
29
- 'heading', 'hardBreak', 'decisionList', 'taskList', ...(expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true) ? [] : ['taskItem']), 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
28
+ 'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'].includes(nodeName)) {
30
29
  // Layout nodes do not need special styling
31
30
  return undefined;
32
31
  }
@@ -43,51 +42,51 @@ const getBlockNodeStyle = ({
43
42
  if (['extension', 'embedCard', 'listItem'].includes(nodeName)) {
44
43
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
45
44
  if (isInserted) {
46
- return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? fg('platform_editor_show_diff_scroll_navigation') ? traditionalDecorationMarkerVariableNew : traditionalDecorationMarkerVariable : standardDecorationMarkerVariable;
45
+ return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? traditionalDecorationMarkerVariableNew : standardDecorationMarkerVariable;
47
46
  } else {
48
- return isTraditional && isActive ? traditionalDeletedDecorationMarkerVariableActive : isTraditional ? fg('platform_editor_show_diff_scroll_navigation') ? traditionalDeletedDecorationMarkerVariableNew : traditionalDeletedDecorationMarkerVariable : deletedContentStyleNew;
47
+ return isTraditional && isActive ? traditionalDeletedDecorationMarkerVariableActive : isTraditional ? traditionalDeletedDecorationMarkerVariableNew : deletedContentStyleNew;
49
48
  }
50
49
  }
51
- return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? fg('platform_editor_show_diff_scroll_navigation') ? traditionalDecorationMarkerVariableNew : traditionalDecorationMarkerVariable : standardDecorationMarkerVariable;
50
+ return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? traditionalDecorationMarkerVariableNew : standardDecorationMarkerVariable;
52
51
  }
53
52
  if (nodeName === 'blockquote') {
54
53
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
55
54
  if (isInserted) {
56
- return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleQuoteNodeNew : traditionalStyleQuoteNode : editingStyleQuoteNode;
55
+ return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : traditionalStyleQuoteNodeNew : editingStyleQuoteNode;
57
56
  } else {
58
57
  return isTraditional ? deletedTraditionalStyleQuoteNode : deletedStyleQuoteNode;
59
58
  }
60
59
  }
61
- return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleQuoteNodeNew : traditionalStyleQuoteNode : editingStyleQuoteNode;
60
+ return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : traditionalStyleQuoteNodeNew : editingStyleQuoteNode;
62
61
  }
63
62
  if (nodeName === 'rule') {
64
63
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
65
64
  if (isInserted) {
66
- return isTraditional ? isActive ? traditionalStyleRuleNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleRuleNodeNew : traditionalStyleRuleNode : editingStyleRuleNode;
65
+ return isTraditional ? isActive ? traditionalStyleRuleNodeActive : traditionalStyleRuleNodeNew : editingStyleRuleNode;
67
66
  } else {
68
67
  return isTraditional ? getDeletedTraditionalInlineStyle(false) : deletedContentStyleNew;
69
68
  }
70
69
  }
71
- return isTraditional ? isActive ? traditionalStyleRuleNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleRuleNodeNew : traditionalStyleRuleNode : editingStyleRuleNode;
70
+ return isTraditional ? isActive ? traditionalStyleRuleNodeActive : traditionalStyleRuleNodeNew : editingStyleRuleNode;
72
71
  }
73
72
  if (nodeName === 'blockCard') {
74
73
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
75
74
  if (isInserted) {
76
- return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleCardBlockNodeNew : traditionalStyleCardBlockNode : editingStyleCardBlockNode;
75
+ return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : traditionalStyleCardBlockNodeNew : editingStyleCardBlockNode;
77
76
  } else {
78
77
  return isTraditional ? getDeletedTraditionalInlineStyle(false) : deletedContentStyleNew;
79
78
  }
80
79
  }
81
- return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleCardBlockNodeNew : traditionalStyleCardBlockNode : editingStyleCardBlockNode;
80
+ return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : traditionalStyleCardBlockNodeNew : editingStyleCardBlockNode;
82
81
  }
83
82
  if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
84
83
  if (isInserted) {
85
- return isTraditional ? isActive ? traditionalStyleNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleNodeNew : traditionalStyleNode : editingStyleNode;
84
+ return isTraditional ? isActive ? traditionalStyleNodeActive : traditionalStyleNodeNew : editingStyleNode;
86
85
  } else {
87
86
  return isTraditional ? getDeletedTraditionalInlineStyle(false) : deletedContentStyleNew;
88
87
  }
89
88
  }
90
- return isTraditional ? isActive ? traditionalStyleNodeActive : fg('platform_editor_show_diff_scroll_navigation') ? traditionalStyleNodeNew : traditionalStyleNode : editingStyleNode;
89
+ return isTraditional ? isActive ? traditionalStyleNodeActive : traditionalStyleNodeNew : editingStyleNode;
91
90
  };
92
91
 
93
92
  /**
@@ -140,20 +139,9 @@ export const createBlockChangedDecoration = ({
140
139
  });
141
140
  }
142
141
  const className = getNodeClass(change.name);
143
- if (fg('platform_editor_show_diff_scroll_navigation')) {
144
- if (style || className) {
145
- decorations.push(Decoration.node(change.from, change.to, {
146
- style: style,
147
- 'data-testid': 'show-diff-changed-decoration-node',
148
- class: className
149
- }, {
150
- key: 'diff-block',
151
- nodeName: change.name
152
- }));
153
- }
154
- } else {
142
+ if (style || className) {
155
143
  decorations.push(Decoration.node(change.from, change.to, {
156
- style,
144
+ style: style,
157
145
  'data-testid': 'show-diff-changed-decoration-node',
158
146
  class: className
159
147
  }, {
@@ -1,6 +1,5 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
- import { fg } from '@atlaskit/platform-feature-flags';
4
3
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
4
  import { editingStyle, editingStyleActive, deletedContentStyle, deletedContentStyleActive, deletedContentStyleNew, deletedContentStyleUnbounded, deletedInlineContentBackground } from './colorSchemes/standard';
6
5
  import { traditionalInsertStyle, traditionalInsertStyleActive, getDeletedTraditionalInlineStyle, deletedTraditionalContentStyleUnbounded, deletedTraditionalContentStyleUnboundedActive } from './colorSchemes/traditional';
@@ -42,21 +41,6 @@ const getDeletedContentStyle = (colorScheme, isActive = false) => {
42
41
  return expValEquals('platform_editor_enghealth_a11y_jan_fixes', 'isEnabled', true) ? deletedContentStyleNew : deletedContentStyle;
43
42
  };
44
43
 
45
- /**
46
- * Wraps content with deleted styling without opacity (for use when content is a direct child of dom)
47
- */
48
- const createDeletedStyleWrapperWithoutOpacity = (colorScheme, isActive) => {
49
- const wrapper = document.createElement('span');
50
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
51
- wrapper.setAttribute('style',
52
- // Merge into existing styles when cleaning up
53
- getDeletedContentStyle(colorScheme, isActive) + deletedInlineContentBackground);
54
- } else {
55
- wrapper.setAttribute('style', getDeletedContentStyle(colorScheme, isActive));
56
- }
57
- return wrapper;
58
- };
59
-
60
44
  /**
61
45
  * CSS backgrounds don't work when applied to a wrapper around a paragraph, so
62
46
  * the wrapper needs to be injected inside the node around the child content
@@ -117,7 +101,7 @@ export const createNodeChangedDecorationWidget = ({
117
101
  }) => {
118
102
  var _slice$content, _slice$content2, _slice$content2$first, _slice$content3, _slice$content3$first;
119
103
  const slice = doc.slice(change.fromA, change.toA);
120
- const shouldSkipDeletedEmptyParagraphDecoration = !isInserted && (slice === null || slice === void 0 ? void 0 : (_slice$content = slice.content) === null || _slice$content === void 0 ? void 0 : _slice$content.childCount) === 1 && (slice === null || slice === void 0 ? void 0 : (_slice$content2 = slice.content) === null || _slice$content2 === void 0 ? void 0 : (_slice$content2$first = _slice$content2.firstChild) === null || _slice$content2$first === void 0 ? void 0 : _slice$content2$first.type.name) === 'paragraph' && (slice === null || slice === void 0 ? void 0 : (_slice$content3 = slice.content) === null || _slice$content3 === void 0 ? void 0 : (_slice$content3$first = _slice$content3.firstChild) === null || _slice$content3$first === void 0 ? void 0 : _slice$content3$first.content.size) === 0 && fg('platform_editor_show_diff_scroll_navigation');
104
+ const shouldSkipDeletedEmptyParagraphDecoration = !isInserted && (slice === null || slice === void 0 ? void 0 : (_slice$content = slice.content) === null || _slice$content === void 0 ? void 0 : _slice$content.childCount) === 1 && (slice === null || slice === void 0 ? void 0 : (_slice$content2 = slice.content) === null || _slice$content2 === void 0 ? void 0 : (_slice$content2$first = _slice$content2.firstChild) === null || _slice$content2$first === void 0 ? void 0 : _slice$content2$first.type.name) === 'paragraph' && (slice === null || slice === void 0 ? void 0 : (_slice$content3 = slice.content) === null || _slice$content3 === void 0 ? void 0 : (_slice$content3$first = _slice$content3.firstChild) === null || _slice$content3$first === void 0 ? void 0 : _slice$content3$first.content.size) === 0;
121
105
  // Widget decoration used for deletions as the content is not in the document
122
106
  // and we want to display the deleted content with a style.
123
107
  const safeInsertPos = findSafeInsertPos(newDoc, change.fromB, slice);
@@ -228,22 +212,16 @@ export const createNodeChangedDecorationWidget = ({
228
212
  } else {
229
213
  const fallbackNode = fallbackSerialization();
230
214
  if (fallbackNode) {
231
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || fg('platform_editor_show_diff_scroll_navigation')) {
232
- if (fallbackNode instanceof HTMLElement) {
233
- const injectedNode = injectInnerWrapper({
234
- node: fallbackNode,
235
- colorScheme,
236
- isActive,
237
- isInserted
238
- });
239
- dom.append(injectedNode);
240
- } else {
241
- const wrapper = createContentWrapper(colorScheme, isActive, isInserted);
242
- wrapper.append(fallbackNode);
243
- dom.append(wrapper);
244
- }
215
+ if (fallbackNode instanceof HTMLElement) {
216
+ const injectedNode = injectInnerWrapper({
217
+ node: fallbackNode,
218
+ colorScheme,
219
+ isActive,
220
+ isInserted
221
+ });
222
+ dom.append(injectedNode);
245
223
  } else {
246
- const wrapper = createDeletedStyleWrapperWithoutOpacity(colorScheme, isActive);
224
+ const wrapper = createContentWrapper(colorScheme, isActive, isInserted);
247
225
  wrapper.append(fallbackNode);
248
226
  dom.append(wrapper);
249
227
  }
@@ -1,6 +1,5 @@
1
1
  import { SetAttrsStep } from '@atlaskit/adf-schema/steps';
2
2
  import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
3
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
3
  const filterUndefined = x => !!x;
5
4
 
6
5
  // Attributes that indicate a change in media image
@@ -34,33 +33,32 @@ export const getAttrChangeRanges = (doc, steps) => {
34
33
  const stepAttrs = getStepAttrs(step);
35
34
  const $pos = doc.resolve(step.pos);
36
35
  const nodeAtPos = doc.nodeAt(step.pos);
37
- if (expValEquals('platform_editor_show_diff_improvements', 'isEnabled', true)) {
38
- // date node: timestamp attribute change — highlight the date node itself (inline)
39
- if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
40
- return {
41
- fromB: step.pos,
42
- toB: step.pos + nodeAtPos.nodeSize,
43
- isInline: true
44
- };
45
- }
46
36
 
47
- // taskItem node: state attribute change — highlight the taskItem node
48
- if (stepAttrs.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
49
- return {
50
- fromB: step.pos,
51
- toB: step.pos + nodeAtPos.nodeSize
52
- };
53
- }
37
+ // date node: timestamp attribute change — highlight the date node itself (inline)
38
+ if (stepAttrs.some(v => dateAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'date') {
39
+ return {
40
+ fromB: step.pos,
41
+ toB: step.pos + nodeAtPos.nodeSize,
42
+ isInline: true
43
+ };
44
+ }
54
45
 
55
- // extension nodes: any attribute change except localId — highlight the node
56
- if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => !extensionExcludedAttrs.includes(v))) {
57
- const isInline = nodeAtPos.type.name === 'inlineExtension';
58
- return {
59
- fromB: step.pos,
60
- toB: step.pos + nodeAtPos.nodeSize,
61
- isInline
62
- };
63
- }
46
+ // taskItem node: state attribute change — highlight the taskItem node
47
+ if (stepAttrs.some(v => taskItemAttrs.includes(v)) && (nodeAtPos === null || nodeAtPos === void 0 ? void 0 : nodeAtPos.type.name) === 'taskItem') {
48
+ return {
49
+ fromB: step.pos,
50
+ toB: step.pos + nodeAtPos.nodeSize
51
+ };
52
+ }
53
+
54
+ // extension nodes: any attribute change except localId — highlight the node
55
+ if (nodeAtPos && extensionNodeNames.includes(nodeAtPos.type.name) && stepAttrs.some(v => !extensionExcludedAttrs.includes(v))) {
56
+ const isInline = nodeAtPos.type.name === 'inlineExtension';
57
+ return {
58
+ fromB: step.pos,
59
+ toB: step.pos + nodeAtPos.nodeSize,
60
+ isInline
61
+ };
64
62
  }
65
63
 
66
64
  // media node: id/collection/url attribute change — highlight the mediaSingle parent