@atlaskit/editor-plugin-show-diff 10.1.23 → 10.2.0

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 (65) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +132 -31
  3. package/dist/cjs/pm-plugins/calculateDiff/groupChangesByBlock.js +4 -2
  4. package/dist/cjs/pm-plugins/calculateDiff/smart/classifySmartChanges.js +841 -0
  5. package/dist/cjs/pm-plugins/calculateDiff/smart/helpers.js +135 -0
  6. package/dist/cjs/pm-plugins/calculateDiff/smart/segmentText.js +331 -0
  7. package/dist/cjs/pm-plugins/calculateDiff/smart/thresholds.js +46 -0
  8. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +24 -18
  9. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +18 -12
  10. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +6 -5
  11. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +30 -17
  12. package/dist/cjs/pm-plugins/decorations/decorationKeys.js +8 -5
  13. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +52 -30
  14. package/dist/cjs/pm-plugins/getScrollableDecorations.js +6 -6
  15. package/dist/cjs/pm-plugins/isExtendedEnabled.js +20 -0
  16. package/dist/cjs/pm-plugins/main.js +21 -16
  17. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +119 -29
  18. package/dist/es2019/pm-plugins/calculateDiff/groupChangesByBlock.js +4 -3
  19. package/dist/es2019/pm-plugins/calculateDiff/smart/classifySmartChanges.js +689 -0
  20. package/dist/es2019/pm-plugins/calculateDiff/smart/helpers.js +106 -0
  21. package/dist/es2019/pm-plugins/calculateDiff/smart/segmentText.js +283 -0
  22. package/dist/es2019/pm-plugins/calculateDiff/smart/thresholds.js +45 -0
  23. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +24 -18
  24. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +18 -12
  25. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +6 -5
  26. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +31 -16
  27. package/dist/es2019/pm-plugins/decorations/decorationKeys.js +9 -5
  28. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +52 -34
  29. package/dist/es2019/pm-plugins/getScrollableDecorations.js +6 -6
  30. package/dist/es2019/pm-plugins/isExtendedEnabled.js +12 -0
  31. package/dist/es2019/pm-plugins/main.js +21 -16
  32. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +132 -31
  33. package/dist/esm/pm-plugins/calculateDiff/groupChangesByBlock.js +4 -3
  34. package/dist/esm/pm-plugins/calculateDiff/smart/classifySmartChanges.js +834 -0
  35. package/dist/esm/pm-plugins/calculateDiff/smart/helpers.js +128 -0
  36. package/dist/esm/pm-plugins/calculateDiff/smart/segmentText.js +325 -0
  37. package/dist/esm/pm-plugins/calculateDiff/smart/thresholds.js +39 -0
  38. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +24 -18
  39. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +18 -12
  40. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +6 -5
  41. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +30 -17
  42. package/dist/esm/pm-plugins/decorations/decorationKeys.js +9 -5
  43. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +52 -30
  44. package/dist/esm/pm-plugins/getScrollableDecorations.js +6 -6
  45. package/dist/esm/pm-plugins/isExtendedEnabled.js +14 -0
  46. package/dist/esm/pm-plugins/main.js +21 -16
  47. package/dist/types/entry-points/show-diff-plugin-type.d.ts +1 -1
  48. package/dist/types/pm-plugins/calculateDiff/calculateDiffDecorations.d.ts +3 -1
  49. package/dist/types/pm-plugins/calculateDiff/groupChangesByBlock.d.ts +9 -0
  50. package/dist/types/pm-plugins/calculateDiff/smart/classifySmartChanges.d.ts +26 -0
  51. package/dist/types/pm-plugins/calculateDiff/smart/helpers.d.ts +44 -0
  52. package/dist/types/pm-plugins/calculateDiff/smart/segmentText.d.ts +32 -0
  53. package/dist/types/pm-plugins/calculateDiff/smart/thresholds.d.ts +44 -0
  54. package/dist/types/pm-plugins/decorations/createBlockChangedDecoration.d.ts +3 -2
  55. package/dist/types/pm-plugins/decorations/createChangedRowDecorationWidgets.d.ts +3 -2
  56. package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +3 -2
  57. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +4 -2
  58. package/dist/types/pm-plugins/decorations/decorationKeys.d.ts +5 -3
  59. package/dist/types/pm-plugins/decorations/utils/wrapBlockNodeView.d.ts +6 -4
  60. package/dist/types/pm-plugins/getScrollableDecorations.d.ts +2 -1
  61. package/dist/types/pm-plugins/isExtendedEnabled.d.ts +11 -0
  62. package/dist/types/pm-plugins/main.d.ts +11 -1
  63. package/dist/types/showDiffPluginType.d.ts +20 -1
  64. package/docs/smart-diff-design.md +240 -0
  65. package/package.json +7 -2
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The granularity level a `smart` change was promoted to. Stored on span `data` so the
3
+ * decoration pipeline can render each level differently (e.g. node-level deletions shown
4
+ * below the new content). `null` for non-promoted (inline) changes.
5
+ */
6
+
7
+ /** Span `data` payload carried by promoted `smart` changes. */
8
+
9
+ /**
10
+ * Build the `deleted`/`inserted` span arrays a `Change` needs. A length of 0 yields an
11
+ * empty array (no change on that side).
12
+ */
13
+ export const createSpans = (length, data = null) => length > 0 ? [{
14
+ length,
15
+ data
16
+ }] : [];
17
+
18
+ /**
19
+ * Build a promoted `Change` spanning `[fromA,toA)` in the original doc and `[fromB,toB)`
20
+ * in the new doc, with both sides marked changed (mirrors `groupChangesByBlock` so the
21
+ * decoration pipeline renders both the inserted-block decoration and the deleted-node
22
+ * widget).
23
+ *
24
+ * `level` tags the change's spans so downstream decoration logic can treat node-level
25
+ * promotions specially (see `smartChangeLevel`).
26
+ */
27
+ export const makePromotedChange = (fromA, toA, fromB, toB, level) => {
28
+ const data = level ? {
29
+ smartLevel: level
30
+ } : null;
31
+ // `deleted` and `inserted` must be independent arrays: downstream code may mutate one
32
+ // side (push/splice), which would silently corrupt the other if they shared a reference.
33
+ return {
34
+ fromA,
35
+ toA,
36
+ fromB,
37
+ toB,
38
+ deleted: createSpans(Math.max(0, toA - fromA), data),
39
+ inserted: createSpans(Math.max(0, toB - fromB), data)
40
+ };
41
+ };
42
+
43
+ /**
44
+ * Read the `smartLevel` tag off a change's spans, if present. Returns `undefined` for
45
+ * changes not produced by the `smart` classifier (e.g. inline/block/step diff types).
46
+ */
47
+ export const smartChangeLevel = change => {
48
+ const spans = [...change.inserted, ...change.deleted];
49
+ for (const span of spans) {
50
+ const data = span.data;
51
+ if (data && typeof data === 'object' && 'smartLevel' in data) {
52
+ return data.smartLevel;
53
+ }
54
+ }
55
+ return undefined;
56
+ };
57
+
58
+ /** True when a change has no original-side (deleted) content — a pure insertion. */
59
+ const isPureInsertion = change => change.toA <= change.fromA;
60
+
61
+ /**
62
+ * Coalesce changes that overlap in new-doc (B) coordinates.
63
+ *
64
+ * Merging two changes takes the UNION of their A (original-doc) ranges. That is only safe when
65
+ * their A ranges actually touch/overlap — OR when at least one is a pure insertion (empty A).
66
+ * Otherwise, merging a deletion at A[110,152] with a later edit at A[162,168] would fabricate an
67
+ * A span [110,168] covering the untouched gap, which then overlaps a *different* change's A range
68
+ * (making the same original content appear deleted twice). So we require B-overlap AND
69
+ * (A-overlap OR a pure insertion) before coalescing.
70
+ */
71
+ export const mergeOverlappingByNewDocRange = changes => {
72
+ if (changes.length <= 1) {
73
+ return changes;
74
+ }
75
+ const sorted = [...changes].sort((l, r) => l.fromB - r.fromB);
76
+ const merged = [];
77
+ let current = {
78
+ ...sorted[0]
79
+ };
80
+ for (let i = 1; i < sorted.length; i++) {
81
+ const next = sorted[i];
82
+ const bOverlaps = next.fromB <= current.toB;
83
+ // A ranges may be unioned only if they touch, or if either side contributes no A content.
84
+ const aMergeable = isPureInsertion(current) || isPureInsertion(next) || rangesOverlap(current.fromA, current.toA, next.fromA, next.toA) || next.fromA <= current.toA; // adjacency (sorted by B, A usually monotonic)
85
+ if (bOverlaps && aMergeable) {
86
+ current = {
87
+ fromA: Math.min(current.fromA, next.fromA),
88
+ toA: Math.max(current.toA, next.toA),
89
+ fromB: Math.min(current.fromB, next.fromB),
90
+ toB: Math.max(current.toB, next.toB),
91
+ deleted: [...current.deleted, ...next.deleted],
92
+ inserted: [...current.inserted, ...next.inserted]
93
+ };
94
+ } else {
95
+ merged.push(current);
96
+ current = {
97
+ ...next
98
+ };
99
+ }
100
+ }
101
+ merged.push(current);
102
+ return merged;
103
+ };
104
+
105
+ /** True when two half-open ranges overlap. */
106
+ export const rangesOverlap = (aFrom, aTo, bFrom, bTo) => aFrom < bTo && bFrom < aTo;
@@ -0,0 +1,283 @@
1
+ /**
2
+ * A half-open offset span `[from, to)` measured in *content offsets* relative to the
3
+ * start of a textblock (i.e. the same coordinate space as `buildCharsByOffset`).
4
+ */
5
+
6
+ /**
7
+ * Minimal local typing for `Intl.Segmenter`. The TS lib target configured for this
8
+ * package does not include the `Intl.Segmenter` declarations, so we declare just the
9
+ * surface we use and access it via a runtime-checked cast. Remove when the lib target is
10
+ * bumped to include `es2022.intl`/`esnext.intl`.
11
+ */
12
+
13
+ // A character is treated as part of a word unless it is whitespace or common punctuation.
14
+ // We deliberately avoid Unicode property escapes (`\p{...}`) and the `u` flag here: the `u`
15
+ // flag is only valid when targeting es6+, and the declaration-generation build config for
16
+ // this package targets an older lib, which rejects it with TS1501. This negated approach
17
+ // keeps word segmentation reasonable across scripts (any non-punctuation, non-space glyph
18
+ // counts as a word char) without needing Unicode-aware regex.
19
+ const NON_WORD_CHARS = ' \t\n\r\f\v.,;:!?…。!?、"\'`“”‘’()[]{}<>/\\|@#$%^&*-+=~';
20
+ const NON_WORD_CHAR_SET = new Set(NON_WORD_CHARS.split(''));
21
+ const isWordChar = ch => !NON_WORD_CHAR_SET.has(ch);
22
+ // Whitespace is checked via a Set rather than a regex to avoid both the `require-unicode-regexp`
23
+ // lint rule and the TS1501 error the `u` flag triggers under this package's build lib target.
24
+ const WHITESPACE_CHAR_SET = new Set([' ', '\t', '\n', '\r', '\f', '\v', '\u00a0']);
25
+ const isWhitespaceChar = ch => WHITESPACE_CHAR_SET.has(ch);
26
+ // Sentence terminators used by the regex fallback. We intentionally keep this small and
27
+ // conservative — the Intl.Segmenter path is the source of truth when available.
28
+ const SENTENCE_TERMINATORS = new Set(['.', '!', '?', '…', '。', '!', '?']);
29
+
30
+ /**
31
+ * Build a per-content-offset view of a textblock's characters.
32
+ *
33
+ * `chars[i]` is the character at content offset `i`, or `null` when that offset lies
34
+ * inside a non-text inline node (mention, date, emoji, hardBreak, …). A `null` acts as an
35
+ * *opaque single token*: it counts as one word and never terminates a sentence.
36
+ */
37
+ export const buildCharsByOffset = parent => {
38
+ const chars = Array.from({
39
+ length: parent.content.size
40
+ }, () => null);
41
+ parent.content.forEach((child, offset) => {
42
+ var _child$text;
43
+ if (!child.isText) {
44
+ return;
45
+ }
46
+ const text = (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : '';
47
+ for (let i = 0; i < text.length; i++) {
48
+ chars[offset + i] = text[i];
49
+ }
50
+ });
51
+ return chars;
52
+ };
53
+ const getSegmenterCtor = () => {
54
+ if (typeof Intl === 'undefined') {
55
+ return undefined;
56
+ }
57
+ const ctor = Intl.Segmenter;
58
+ return typeof ctor === 'function' ? ctor : undefined;
59
+ };
60
+ let cachedLocale;
61
+ let cachedSentenceSegmenter;
62
+ let cachedWordSegmenter;
63
+ const getSegmenters = locale => {
64
+ const Ctor = getSegmenterCtor();
65
+ if (!Ctor) {
66
+ return undefined;
67
+ }
68
+ if (cachedLocale !== locale || !cachedSentenceSegmenter || !cachedWordSegmenter) {
69
+ cachedLocale = locale;
70
+ cachedSentenceSegmenter = new Ctor(locale, {
71
+ granularity: 'sentence'
72
+ });
73
+ cachedWordSegmenter = new Ctor(locale, {
74
+ granularity: 'word'
75
+ });
76
+ }
77
+ return {
78
+ sentence: cachedSentenceSegmenter,
79
+ word: cachedWordSegmenter
80
+ };
81
+ };
82
+
83
+ /**
84
+ * Convert a char-view into a plain string plus an index map back to content offsets.
85
+ *
86
+ * Opaque inline tokens (`null`) are represented in the string by a single sentinel
87
+ * character (Unicode Object Replacement Character, U+FFFC) so that:
88
+ * - the segmenter treats them as a word-like glyph rather than whitespace/punctuation;
89
+ * - they never look like a sentence terminator.
90
+ * `indexMap[j]` gives the content offset of string index `j`.
91
+ */
92
+ const OBJECT_REPLACEMENT = '\uFFFC';
93
+ const flattenChars = chars => {
94
+ let text = '';
95
+ const indexMap = [];
96
+ for (let offset = 0; offset < chars.length; offset++) {
97
+ const ch = chars[offset];
98
+ text += ch === null ? OBJECT_REPLACEMENT : ch;
99
+ indexMap.push(offset);
100
+ }
101
+ return {
102
+ text,
103
+ indexMap
104
+ };
105
+ };
106
+ const stringIndexToOffset = (indexMap, strIdx, fallback) => {
107
+ if (strIdx < 0) {
108
+ return fallback;
109
+ }
110
+ if (strIdx >= indexMap.length) {
111
+ // One-past-the-end maps to the offset after the last char.
112
+ return indexMap.length > 0 ? indexMap[indexMap.length - 1] + 1 : fallback;
113
+ }
114
+ return indexMap[strIdx];
115
+ };
116
+
117
+ /**
118
+ * Segment a textblock char-view into sentence spans (content-offset coordinates).
119
+ * Uses Intl.Segmenter when available, otherwise a conservative regex fallback.
120
+ */
121
+ export const segmentSentences = (chars, locale) => {
122
+ if (chars.length === 0) {
123
+ return [];
124
+ }
125
+ const {
126
+ text,
127
+ indexMap
128
+ } = flattenChars(chars);
129
+ const segmenters = getSegmenters(locale);
130
+ if (segmenters) {
131
+ const spans = [];
132
+ for (const seg of segmenters.sentence.segment(text)) {
133
+ const startOffset = stringIndexToOffset(indexMap, seg.index, 0);
134
+ const endOffset = stringIndexToOffset(indexMap, seg.index + seg.segment.length, chars.length);
135
+ if (endOffset > startOffset) {
136
+ spans.push({
137
+ from: startOffset,
138
+ to: endOffset
139
+ });
140
+ }
141
+ }
142
+ return spans.length > 0 ? spans : [{
143
+ from: 0,
144
+ to: chars.length
145
+ }];
146
+ }
147
+
148
+ // Regex fallback: split after a sentence terminator followed by whitespace/EOL.
149
+ // A `null` (OBJECT_REPLACEMENT) is never a terminator.
150
+ const spans = [];
151
+ let start = 0;
152
+ for (let i = 0; i < chars.length; i++) {
153
+ const ch = chars[i];
154
+ if (ch !== null && SENTENCE_TERMINATORS.has(ch)) {
155
+ // Consume trailing spaces as part of this sentence.
156
+ let end = i + 1;
157
+ while (end < chars.length && chars[end] !== null && isWhitespaceChar(chars[end])) {
158
+ end++;
159
+ }
160
+ spans.push({
161
+ from: start,
162
+ to: end
163
+ });
164
+ start = end;
165
+ }
166
+ }
167
+ if (start < chars.length) {
168
+ spans.push({
169
+ from: start,
170
+ to: chars.length
171
+ });
172
+ }
173
+ return spans.length > 0 ? spans : [{
174
+ from: 0,
175
+ to: chars.length
176
+ }];
177
+ };
178
+
179
+ /**
180
+ * Count word-like tokens within a content-offset range of the char-view.
181
+ * Each opaque inline token (`null`) counts as exactly one word.
182
+ */
183
+ export const countWords = (chars, span, locale) => {
184
+ const slice = chars.slice(span.from, span.to);
185
+ if (slice.length === 0) {
186
+ return 0;
187
+ }
188
+ const segmenters = getSegmenters(locale);
189
+ const {
190
+ text
191
+ } = flattenChars(slice);
192
+ if (segmenters) {
193
+ let count = 0;
194
+ for (const seg of segmenters.word.segment(text)) {
195
+ // Object-replacement sentinel is word-like; count it.
196
+ if (seg.isWordLike || seg.segment.includes(OBJECT_REPLACEMENT)) {
197
+ count++;
198
+ }
199
+ }
200
+ return count;
201
+ }
202
+
203
+ // Regex fallback: a run of word chars OR a single opaque token is one word.
204
+ let count = 0;
205
+ let inWord = false;
206
+ for (const ch of slice) {
207
+ if (ch === null) {
208
+ count++;
209
+ inWord = false;
210
+ continue;
211
+ }
212
+ if (isWordChar(ch)) {
213
+ if (!inWord) {
214
+ count++;
215
+ inWord = true;
216
+ }
217
+ } else {
218
+ inWord = false;
219
+ }
220
+ }
221
+ return count;
222
+ };
223
+
224
+ /**
225
+ * Return the word-token spans (content-offset coordinates) within a range. Used to decide
226
+ * which words a change overlaps. Each opaque inline token is its own span.
227
+ */
228
+ export const segmentWordSpans = (chars, span, locale) => {
229
+ const segmenters = getSegmenters(locale);
230
+ const slice = chars.slice(span.from, span.to);
231
+ const {
232
+ text,
233
+ indexMap
234
+ } = flattenChars(slice);
235
+ const spans = [];
236
+ if (segmenters) {
237
+ for (const seg of segmenters.word.segment(text)) {
238
+ if (seg.isWordLike || seg.segment.includes(OBJECT_REPLACEMENT)) {
239
+ const from = span.from + stringIndexToOffset(indexMap, seg.index, 0);
240
+ const to = span.from + stringIndexToOffset(indexMap, seg.index + seg.segment.length, slice.length);
241
+ spans.push({
242
+ from,
243
+ to
244
+ });
245
+ }
246
+ }
247
+ return spans;
248
+ }
249
+ let wordStart = -1;
250
+ for (let i = 0; i < slice.length; i++) {
251
+ const ch = slice[i];
252
+ if (ch === null) {
253
+ if (wordStart !== -1) {
254
+ spans.push({
255
+ from: span.from + wordStart,
256
+ to: span.from + i
257
+ });
258
+ wordStart = -1;
259
+ }
260
+ spans.push({
261
+ from: span.from + i,
262
+ to: span.from + i + 1
263
+ });
264
+ } else if (isWordChar(ch)) {
265
+ if (wordStart === -1) {
266
+ wordStart = i;
267
+ }
268
+ } else if (wordStart !== -1) {
269
+ spans.push({
270
+ from: span.from + wordStart,
271
+ to: span.from + i
272
+ });
273
+ wordStart = -1;
274
+ }
275
+ }
276
+ if (wordStart !== -1) {
277
+ spans.push({
278
+ from: span.from + wordStart,
279
+ to: span.from + slice.length
280
+ });
281
+ }
282
+ return spans;
283
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Configurable thresholds for the `smart` diffType.
3
+ *
4
+ * See docs/smart-diff-design.md §3 and §6 for the exact semantics of each level.
5
+ */
6
+
7
+ export const DEFAULT_SMART_THRESHOLDS = {
8
+ sentence: {
9
+ ratio: 0.4,
10
+ minChanged: 2
11
+ },
12
+ paragraph: {
13
+ ratio: 0.4,
14
+ minChanged: 2
15
+ },
16
+ node: {
17
+ ratio: 0.6,
18
+ textBearingRatio: 0.6
19
+ }
20
+ };
21
+
22
+ /**
23
+ * Merge caller overrides onto the defaults. Overrides are shallow-merged per level so a
24
+ * caller can tune a single field (e.g. only `sentence.ratio`) without re-specifying the
25
+ * rest.
26
+ */
27
+ export const resolveThresholds = overrides => {
28
+ if (!overrides) {
29
+ return DEFAULT_SMART_THRESHOLDS;
30
+ }
31
+ return {
32
+ sentence: {
33
+ ...DEFAULT_SMART_THRESHOLDS.sentence,
34
+ ...overrides.sentence
35
+ },
36
+ paragraph: {
37
+ ...DEFAULT_SMART_THRESHOLDS.paragraph,
38
+ ...overrides.paragraph
39
+ },
40
+ node: {
41
+ ...DEFAULT_SMART_THRESHOLDS.node,
42
+ ...overrides.node
43
+ }
44
+ };
45
+ };
@@ -1,6 +1,6 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
3
+ import { isExtendedEnabled } from '../isExtendedEnabled';
4
4
  import { standardDecorationMarkerVariable, deletedDecorationMarkerVariable, editingStyleQuoteNode, editingStyleRuleNode, editingStyleCardBlockNode, editingStyleNode, deletedContentStyleNew, deletedStyleQuoteNode, addedCellOverlayStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
5
5
  import { traditionalDecorationMarkerVariableActive, traditionalDecorationMarkerVariableNew, traditionalDeletedDecorationMarkerVariableActive, traditionalDeletedDecorationMarkerVariableNew, traditionalStyleQuoteNodeActive, traditionalStyleQuoteNodeNew, traditionalStyleRuleNodeActive, traditionalStyleRuleNodeNew, traditionalStyleCardBlockNodeActive, traditionalStyleCardBlockNodeNew, traditionalStyleNodeActive, traditionalStyleNodeNew, getDeletedTraditionalInlineStyle, deletedTraditionalStyleQuoteNode, traditionalAddedCellOverlayStyle, deletedTraditionalCellOverlayStyle } from './colorSchemes/traditional';
6
6
  import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
@@ -20,7 +20,8 @@ const getBlockNodeStyle = ({
20
20
  nodeName,
21
21
  colorScheme,
22
22
  isInserted = true,
23
- isActive = false
23
+ isActive = false,
24
+ diffType
24
25
  }) => {
25
26
  const isTraditional = colorScheme === 'traditional';
26
27
  if (['mediaSingle', 'mediaGroup', 'table',
@@ -34,13 +35,13 @@ const getBlockNodeStyle = ({
34
35
  // Media nodes inside mediaSingle should not get position:relative
35
36
  // as it shifts the image outside its parent container (e.g. panel)
36
37
  if (nodeName === 'media') {
37
- if (!isInserted && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
38
+ if (!isInserted && isExtendedEnabled(diffType)) {
38
39
  return isTraditional ? getDeletedTraditionalInlineStyle(false) : deletedDecorationMarkerVariable;
39
40
  }
40
41
  return isTraditional ? isActive ? traditionalStyleNodeActive : traditionalStyleNodeNew : editingStyleNode;
41
42
  }
42
43
  if (['tableCell', 'tableHeader'].includes(nodeName)) {
43
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
44
+ if (isExtendedEnabled(diffType)) {
44
45
  // This is used for positioning the cell overlay widget decorations
45
46
  return convertToInlineCss({
46
47
  position: 'relative'
@@ -50,13 +51,13 @@ const getBlockNodeStyle = ({
50
51
  return undefined;
51
52
  }
52
53
  if (nodeName === 'panel') {
53
- if (!isInserted && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
54
+ if (!isInserted && isExtendedEnabled(diffType)) {
54
55
  return isTraditional ? getDeletedTraditionalInlineStyle(false) : deletedDecorationMarkerVariable;
55
56
  }
56
57
  return isTraditional ? isActive ? traditionalStyleNodeActive : traditionalStyleNodeNew : editingStyleNode;
57
58
  }
58
59
  if (['extension', 'embedCard', 'listItem'].includes(nodeName)) {
59
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
60
+ if (isExtendedEnabled(diffType)) {
60
61
  if (isInserted) {
61
62
  return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? traditionalDecorationMarkerVariableNew : standardDecorationMarkerVariable;
62
63
  } else {
@@ -69,7 +70,7 @@ const getBlockNodeStyle = ({
69
70
  return isTraditional && isActive ? traditionalDecorationMarkerVariableActive : isTraditional ? traditionalDecorationMarkerVariableNew : standardDecorationMarkerVariable;
70
71
  }
71
72
  if (nodeName === 'blockquote') {
72
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
73
+ if (isExtendedEnabled(diffType)) {
73
74
  if (isInserted) {
74
75
  return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : traditionalStyleQuoteNodeNew : editingStyleQuoteNode;
75
76
  } else {
@@ -79,7 +80,7 @@ const getBlockNodeStyle = ({
79
80
  return isTraditional ? isActive ? traditionalStyleQuoteNodeActive : traditionalStyleQuoteNodeNew : editingStyleQuoteNode;
80
81
  }
81
82
  if (nodeName === 'rule') {
82
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
83
+ if (isExtendedEnabled(diffType)) {
83
84
  if (isInserted) {
84
85
  return isTraditional ? isActive ? traditionalStyleRuleNodeActive : traditionalStyleRuleNodeNew : editingStyleRuleNode;
85
86
  } else {
@@ -89,7 +90,7 @@ const getBlockNodeStyle = ({
89
90
  return isTraditional ? isActive ? traditionalStyleRuleNodeActive : traditionalStyleRuleNodeNew : editingStyleRuleNode;
90
91
  }
91
92
  if (nodeName === 'blockCard') {
92
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
93
+ if (isExtendedEnabled(diffType)) {
93
94
  if (isInserted) {
94
95
  return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : traditionalStyleCardBlockNodeNew : editingStyleCardBlockNode;
95
96
  } else {
@@ -98,7 +99,7 @@ const getBlockNodeStyle = ({
98
99
  }
99
100
  return isTraditional ? isActive ? traditionalStyleCardBlockNodeActive : traditionalStyleCardBlockNodeNew : editingStyleCardBlockNode;
100
101
  }
101
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
102
+ if (isExtendedEnabled(diffType)) {
102
103
  if (isInserted) {
103
104
  return isTraditional ? isActive ? traditionalStyleNodeActive : traditionalStyleNodeNew : editingStyleNode;
104
105
  } else {
@@ -123,7 +124,8 @@ export const createBlockChangedDecoration = ({
123
124
  isActive = false,
124
125
  shouldHideDeleted = false,
125
126
  showIndicators = false,
126
- doc
127
+ doc,
128
+ diffType
127
129
  }) => {
128
130
  const decorations = [];
129
131
  const diffId = crypto.randomUUID();
@@ -134,11 +136,12 @@ export const createBlockChangedDecoration = ({
134
136
  decorationType: 'block',
135
137
  diffId,
136
138
  isActive,
137
- nodeName: change.name
139
+ nodeName: change.name,
140
+ diffType
138
141
  }))];
139
142
  }
140
143
  let style;
141
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) && ['tableCell', 'tableHeader'].includes(change.name)) {
144
+ if (isExtendedEnabled(diffType) && ['tableCell', 'tableHeader'].includes(change.name)) {
142
145
  const cellOverlay = document.createElement('div');
143
146
  const cellOverlayStyle = isInserted ? colorScheme === 'traditional' ? traditionalAddedCellOverlayStyle : addedCellOverlayStyle : colorScheme === 'traditional' ? deletedTraditionalCellOverlayStyle : deletedCellOverlayStyle;
144
147
  cellOverlay.setAttribute('style', cellOverlayStyle);
@@ -150,18 +153,20 @@ export const createBlockChangedDecoration = ({
150
153
  key: 'cell-overlay-decoration'
151
154
  }));
152
155
  }
153
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
156
+ if (isExtendedEnabled(diffType)) {
154
157
  style = getBlockNodeStyle({
155
158
  nodeName: change.name,
156
159
  colorScheme,
157
160
  isInserted,
158
- isActive
161
+ isActive,
162
+ diffType
159
163
  });
160
164
  } else {
161
165
  style = getBlockNodeStyle({
162
166
  nodeName: change.name,
163
167
  colorScheme,
164
- isActive
168
+ isActive,
169
+ diffType
165
170
  });
166
171
  }
167
172
  const className = getNodeClass(change.name);
@@ -174,10 +179,11 @@ export const createBlockChangedDecoration = ({
174
179
  decorationType: 'block',
175
180
  diffId,
176
181
  isActive,
177
- nodeName: change.name
182
+ nodeName: change.name,
183
+ diffType
178
184
  })));
179
185
  }
180
- if (decorations.length > 0 && showIndicators && doc && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
186
+ if (decorations.length > 0 && showIndicators && doc && isExtendedEnabled(diffType)) {
181
187
  decorations.push(...createBlockIndicatorAnchorWidgets({
182
188
  doc,
183
189
  from: change.from,
@@ -2,7 +2,7 @@ import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document
2
2
  import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
3
3
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
4
4
  import { TableMap } from '@atlaskit/editor-tables/table-map';
5
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
+ import { isExtendedEnabled } from '../isExtendedEnabled';
6
6
  import { addedCellOverlayStyle, deletedRowStyle, deletedCellOverlayStyle } from './colorSchemes/standard';
7
7
  import { deletedTraditionalRowStyle, deletedTraditionalCellOverlayStyle, traditionalAddedCellOverlayStyle } from './colorSchemes/traditional';
8
8
  import { buildDiffDecorationSpec } from './decorationKeys';
@@ -13,7 +13,8 @@ import { findSafeInsertPos } from './utils/findSafeInsertPos';
13
13
  const extractChangedRows = ({
14
14
  change,
15
15
  originalDoc,
16
- newDoc
16
+ newDoc,
17
+ diffType
17
18
  }) => {
18
19
  const changedRows = [];
19
20
 
@@ -50,7 +51,7 @@ const extractChangedRows = ({
50
51
 
51
52
  // Check if this row overlaps with the deletion range
52
53
  const rowOverlapsChange = rowStart >= changeStartInTable && rowStart < changeEndInTable || rowEnd > changeStartInTable && rowEnd <= changeEndInTable || rowStart < changeStartInTable && rowEnd > changeEndInTable;
53
- if (rowOverlapsChange && rowNode.type.name === 'tableRow' && (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true) || !isEmptyRow(rowNode))) {
54
+ if (rowOverlapsChange && rowNode.type.name === 'tableRow' && (isExtendedEnabled(diffType) || !isEmptyRow(rowNode))) {
54
55
  const startOfRow = newTableMap.mapByRow.slice().reverse().find(row => row[0] + tableNew.pos < change.fromB && change.fromB < row[row.length - 1] + tableNew.pos);
55
56
  changedRows.push({
56
57
  rowIndex,
@@ -101,9 +102,9 @@ const isEmptyRow = rowNode => {
101
102
  /**
102
103
  * Creates a DOM representation of a deleted table row
103
104
  */
104
- const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserted) => {
105
+ const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserted, diffType) => {
105
106
  const tr = document.createElement('tr');
106
- if (expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
107
+ if (isExtendedEnabled(diffType)) {
107
108
  if (!isInserted) {
108
109
  tr.setAttribute('style', colorScheme === 'traditional' ? deletedTraditionalRowStyle : deletedRowStyle);
109
110
  }
@@ -117,7 +118,7 @@ const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserte
117
118
  if (cellNode.type.name === 'tableCell' || cellNode.type.name === 'tableHeader') {
118
119
  const nodeView = nodeViewSerializer.tryCreateNodeView(cellNode);
119
120
  if (nodeView) {
120
- if (nodeView instanceof HTMLElement && expValEquals('platform_editor_diff_plugin_extended', 'isEnabled', true)) {
121
+ if (nodeView instanceof HTMLElement && isExtendedEnabled(diffType)) {
121
122
  const overlay = document.createElement('span');
122
123
  const overlayStyle = colorScheme === 'traditional' ? isInserted ? traditionalAddedCellOverlayStyle : deletedTraditionalCellOverlayStyle : isInserted ? addedCellOverlayStyle : deletedCellOverlayStyle;
123
124
  overlay.setAttribute('style', overlayStyle);
@@ -142,7 +143,8 @@ const createChangedRowDOM = (rowNode, nodeViewSerializer, colorScheme, isInserte
142
143
  const expandDiffForChangedRows = ({
143
144
  changes,
144
145
  originalDoc,
145
- newDoc
146
+ newDoc,
147
+ diffType
146
148
  }) => {
147
149
  const rowInfo = [];
148
150
  for (const change of changes) {
@@ -150,7 +152,8 @@ const expandDiffForChangedRows = ({
150
152
  const changedRows = extractChangedRows({
151
153
  change,
152
154
  originalDoc,
153
- newDoc
155
+ newDoc,
156
+ diffType
154
157
  });
155
158
  if (changedRows.length > 0) {
156
159
  rowInfo.push(...changedRows);
@@ -168,16 +171,18 @@ export const createChangedRowDecorationWidgets = ({
168
171
  newDoc,
169
172
  nodeViewSerializer,
170
173
  colorScheme,
171
- isInserted = false
174
+ isInserted = false,
175
+ diffType
172
176
  }) => {
173
177
  // First, expand the changes to include complete deleted rows
174
178
  const changedRows = expandDiffForChangedRows({
175
179
  changes: changes.filter(change => change.deleted.length > 0),
176
180
  originalDoc,
177
- newDoc
181
+ newDoc,
182
+ diffType
178
183
  });
179
184
  return changedRows.map(changedRow => {
180
- const rowDOM = createChangedRowDOM(changedRow.rowNode, nodeViewSerializer, colorScheme, isInserted);
185
+ const rowDOM = createChangedRowDOM(changedRow.rowNode, nodeViewSerializer, colorScheme, isInserted, diffType);
181
186
 
182
187
  // Find safe insertion position for the deleted row
183
188
  const safeInsertPos = findSafeInsertPos(newDoc, changedRow.fromB - 1,
@@ -188,7 +193,8 @@ export const createChangedRowDecorationWidgets = ({
188
193
  ...buildDiffDecorationSpec({
189
194
  decorationType: 'widget',
190
195
  diffId,
191
- isActive: false
196
+ isActive: false,
197
+ diffType
192
198
  })
193
199
  });
194
200
  });