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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 13.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 13.0.9
10
+
11
+ ### Patch Changes
12
+
13
+ - [`0123de9acf8b1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0123de9acf8b1) -
14
+ [CCI-18588] Fix multi-second browser freeze when opening the AI Review moment on a large generated
15
+ table. For big inserted tables the diff previously emitted one decoration per cell (O(cells)) plus
16
+ redundant inner-block decorations, forcing a full synchronous table relayout. It now skips the
17
+ per-cell inline and inner-block (table/row/paragraph) decorations while keeping the cell overlay
18
+ highlight. Gated behind `platform_editor_ai_new_aifc_editor_experience`; behaviour is unchanged
19
+ for small tables and when the experiment is off.
20
+
3
21
  ## 13.0.8
4
22
 
5
23
  ### Patch Changes
@@ -95,6 +95,26 @@ var leafTextblockRanges = function leafTextblockRanges(doc, from, to) {
95
95
  });
96
96
  return ranges;
97
97
  };
98
+
99
+ // A large inserted table is treated with a coarse decoration path (see below)
100
+ // once it has more leaf textblocks (~cells) than this threshold. Small tables
101
+ // keep the precise per-cell path.
102
+ var LARGE_TABLE_LEAF_THRESHOLD = 40;
103
+ var isLargeInsertedTableRange = function isLargeInsertedTableRange(doc, from, to) {
104
+ var containsTable = false;
105
+ var leafCount = 0;
106
+ doc.nodesBetween(from, to, function (node) {
107
+ if (node.type.name === 'table') {
108
+ containsTable = true;
109
+ }
110
+ if (node.isTextblock) {
111
+ leafCount += 1;
112
+ return false;
113
+ }
114
+ return true;
115
+ });
116
+ return containsTable && leafCount > LARGE_TABLE_LEAF_THRESHOLD;
117
+ };
98
118
  var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration(_ref2) {
99
119
  var doc = _ref2.doc,
100
120
  from = _ref2.from,
@@ -107,10 +127,20 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
107
127
  shouldHideDeleted = _ref2$shouldHideDelet === void 0 ? false : _ref2$shouldHideDelet,
108
128
  _ref2$showIndicators = _ref2.showIndicators,
109
129
  showIndicators = _ref2$showIndicators === void 0 ? false : _ref2$showIndicators,
110
- diffType = _ref2.diffType;
130
+ diffType = _ref2.diffType,
131
+ _ref2$coarseTableCell = _ref2.coarseTableCellsOnly,
132
+ coarseTableCellsOnly = _ref2$coarseTableCell === void 0 ? false : _ref2$coarseTableCell;
111
133
  var decorations = [];
112
134
  // Iterate over the document nodes within the range
113
135
  doc.nodesBetween(from, to, function (node, pos) {
136
+ // Coarse path: only cell/header overlays are visible; table/row/paragraph
137
+ // decorations are redundant and cause expensive per-cell re-render.
138
+ if (coarseTableCellsOnly) {
139
+ var name = node.type.name;
140
+ if (name !== 'tableCell' && name !== 'tableHeader') {
141
+ return;
142
+ }
143
+ }
114
144
  if (node.isBlock && (!(0, _isExtendedEnabled.isExtendedEnabled)(diffType) || pos + node.nodeSize <= to)) {
115
145
  var nodeEnd = pos + node.nodeSize;
116
146
  var isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
@@ -290,12 +320,18 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
290
320
  // emit ONE inline decoration per leaf text-bearing block within the range — the text
291
321
  // gets highlighted, and structural gaps never do.
292
322
  var isSmartNodeLevel = diffType === 'smart' && (0, _platformFeatureFlags.fg)('platform_editor_ai_smart_diff') && (0, _helpers.smartChangeLevel)(change) === 'node';
323
+ // For a large inserted table, skip the per-cell inline decorations that
324
+ // freeze the browser on re-render. Only active in the AIFC experience.
325
+ var useCoarseTableDecoration = isSmartNodeLevel && (0, _expValEquals.expValEquals)('platform_editor_ai_new_aifc_editor_experience', 'isEnabled', true) && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
293
326
  // Whether the deleted-content widget will actually be rendered for this
294
327
  // change. Used to decide if indicator anchor positions should be adjusted
295
328
  // inward — when the widget is present the anchor must stay at the block
296
329
  // boundary to keep the indicator bar continuous with the deleted content.
297
330
  var willRenderDeletedWidget = change.deleted.length > 0 && !((0, _isExtendedEnabled.isExtendedEnabled)(diffType) && !isInverted && hideDeletedDiffs && change.inserted.length > 0);
298
- if (isSmartNodeLevel) {
331
+ if (useCoarseTableDecoration) {
332
+ // Skip the O(cells) inline decorations; the cell overlays (added below)
333
+ // provide the highlight without the expensive content re-render.
334
+ } else if (isSmartNodeLevel) {
299
335
  var _iterator2 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
300
336
  _step2;
301
337
  try {
@@ -354,7 +390,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
354
390
  }), {}, {
355
391
  activeIndexPos: activeIndexPos,
356
392
  intl: intl,
357
- diffType: diffType
393
+ diffType: diffType,
394
+ coarseTableCellsOnly: useCoarseTableDecoration
358
395
  }))));
359
396
  }
360
397
  if (change.deleted.length > 0) {
@@ -81,6 +81,26 @@ const leafTextblockRanges = (doc, from, to) => {
81
81
  });
82
82
  return ranges;
83
83
  };
84
+
85
+ // A large inserted table is treated with a coarse decoration path (see below)
86
+ // once it has more leaf textblocks (~cells) than this threshold. Small tables
87
+ // keep the precise per-cell path.
88
+ const LARGE_TABLE_LEAF_THRESHOLD = 40;
89
+ const isLargeInsertedTableRange = (doc, from, to) => {
90
+ let containsTable = false;
91
+ let leafCount = 0;
92
+ doc.nodesBetween(from, to, node => {
93
+ if (node.type.name === 'table') {
94
+ containsTable = true;
95
+ }
96
+ if (node.isTextblock) {
97
+ leafCount += 1;
98
+ return false;
99
+ }
100
+ return true;
101
+ });
102
+ return containsTable && leafCount > LARGE_TABLE_LEAF_THRESHOLD;
103
+ };
84
104
  const calculateNodesForBlockDecoration = ({
85
105
  doc,
86
106
  from,
@@ -90,11 +110,20 @@ const calculateNodesForBlockDecoration = ({
90
110
  activeIndexPos,
91
111
  shouldHideDeleted = false,
92
112
  showIndicators = false,
93
- diffType
113
+ diffType,
114
+ coarseTableCellsOnly = false
94
115
  }) => {
95
116
  const decorations = [];
96
117
  // Iterate over the document nodes within the range
97
118
  doc.nodesBetween(from, to, (node, pos) => {
119
+ // Coarse path: only cell/header overlays are visible; table/row/paragraph
120
+ // decorations are redundant and cause expensive per-cell re-render.
121
+ if (coarseTableCellsOnly) {
122
+ const name = node.type.name;
123
+ if (name !== 'tableCell' && name !== 'tableHeader') {
124
+ return;
125
+ }
126
+ }
98
127
  if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
99
128
  const nodeEnd = pos + node.nodeSize;
100
129
  const isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
@@ -264,12 +293,18 @@ const calculateDiffDecorationsInner = ({
264
293
  // emit ONE inline decoration per leaf text-bearing block within the range — the text
265
294
  // gets highlighted, and structural gaps never do.
266
295
  const isSmartNodeLevel = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && smartChangeLevel(change) === 'node';
296
+ // For a large inserted table, skip the per-cell inline decorations that
297
+ // freeze the browser on re-render. Only active in the AIFC experience.
298
+ const useCoarseTableDecoration = isSmartNodeLevel && expValEquals('platform_editor_ai_new_aifc_editor_experience', 'isEnabled', true) && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
267
299
  // Whether the deleted-content widget will actually be rendered for this
268
300
  // change. Used to decide if indicator anchor positions should be adjusted
269
301
  // inward — when the widget is present the anchor must stay at the block
270
302
  // boundary to keep the indicator bar continuous with the deleted content.
271
303
  const willRenderDeletedWidget = change.deleted.length > 0 && !(isExtendedEnabled(diffType) && !isInverted && hideDeletedDiffs && change.inserted.length > 0);
272
- if (isSmartNodeLevel) {
304
+ if (useCoarseTableDecoration) {
305
+ // Skip the O(cells) inline decorations; the cell overlays (added below)
306
+ // provide the highlight without the expensive content re-render.
307
+ } else if (isSmartNodeLevel) {
273
308
  for (const [from, to] of leafTextblockRanges(tr.doc, change.fromB, change.toB)) {
274
309
  decorations.push(...createInlineChangedDecoration({
275
310
  change: {
@@ -319,7 +354,8 @@ const calculateDiffDecorationsInner = ({
319
354
  }),
320
355
  activeIndexPos,
321
356
  intl,
322
- diffType
357
+ diffType,
358
+ coarseTableCellsOnly: useCoarseTableDecoration
323
359
  }));
324
360
  }
325
361
  if (change.deleted.length > 0) {
@@ -88,6 +88,26 @@ var leafTextblockRanges = function leafTextblockRanges(doc, from, to) {
88
88
  });
89
89
  return ranges;
90
90
  };
91
+
92
+ // A large inserted table is treated with a coarse decoration path (see below)
93
+ // once it has more leaf textblocks (~cells) than this threshold. Small tables
94
+ // keep the precise per-cell path.
95
+ var LARGE_TABLE_LEAF_THRESHOLD = 40;
96
+ var isLargeInsertedTableRange = function isLargeInsertedTableRange(doc, from, to) {
97
+ var containsTable = false;
98
+ var leafCount = 0;
99
+ doc.nodesBetween(from, to, function (node) {
100
+ if (node.type.name === 'table') {
101
+ containsTable = true;
102
+ }
103
+ if (node.isTextblock) {
104
+ leafCount += 1;
105
+ return false;
106
+ }
107
+ return true;
108
+ });
109
+ return containsTable && leafCount > LARGE_TABLE_LEAF_THRESHOLD;
110
+ };
91
111
  var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration(_ref2) {
92
112
  var doc = _ref2.doc,
93
113
  from = _ref2.from,
@@ -100,10 +120,20 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
100
120
  shouldHideDeleted = _ref2$shouldHideDelet === void 0 ? false : _ref2$shouldHideDelet,
101
121
  _ref2$showIndicators = _ref2.showIndicators,
102
122
  showIndicators = _ref2$showIndicators === void 0 ? false : _ref2$showIndicators,
103
- diffType = _ref2.diffType;
123
+ diffType = _ref2.diffType,
124
+ _ref2$coarseTableCell = _ref2.coarseTableCellsOnly,
125
+ coarseTableCellsOnly = _ref2$coarseTableCell === void 0 ? false : _ref2$coarseTableCell;
104
126
  var decorations = [];
105
127
  // Iterate over the document nodes within the range
106
128
  doc.nodesBetween(from, to, function (node, pos) {
129
+ // Coarse path: only cell/header overlays are visible; table/row/paragraph
130
+ // decorations are redundant and cause expensive per-cell re-render.
131
+ if (coarseTableCellsOnly) {
132
+ var name = node.type.name;
133
+ if (name !== 'tableCell' && name !== 'tableHeader') {
134
+ return;
135
+ }
136
+ }
107
137
  if (node.isBlock && (!isExtendedEnabled(diffType) || pos + node.nodeSize <= to)) {
108
138
  var nodeEnd = pos + node.nodeSize;
109
139
  var isActive = activeIndexPos && pos === activeIndexPos.from && nodeEnd === activeIndexPos.to;
@@ -283,12 +313,18 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
283
313
  // emit ONE inline decoration per leaf text-bearing block within the range — the text
284
314
  // gets highlighted, and structural gaps never do.
285
315
  var isSmartNodeLevel = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && smartChangeLevel(change) === 'node';
316
+ // For a large inserted table, skip the per-cell inline decorations that
317
+ // freeze the browser on re-render. Only active in the AIFC experience.
318
+ var useCoarseTableDecoration = isSmartNodeLevel && expValEquals('platform_editor_ai_new_aifc_editor_experience', 'isEnabled', true) && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
286
319
  // Whether the deleted-content widget will actually be rendered for this
287
320
  // change. Used to decide if indicator anchor positions should be adjusted
288
321
  // inward — when the widget is present the anchor must stay at the block
289
322
  // boundary to keep the indicator bar continuous with the deleted content.
290
323
  var willRenderDeletedWidget = change.deleted.length > 0 && !(isExtendedEnabled(diffType) && !isInverted && hideDeletedDiffs && change.inserted.length > 0);
291
- if (isSmartNodeLevel) {
324
+ if (useCoarseTableDecoration) {
325
+ // Skip the O(cells) inline decorations; the cell overlays (added below)
326
+ // provide the highlight without the expensive content re-render.
327
+ } else if (isSmartNodeLevel) {
292
328
  var _iterator2 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
293
329
  _step2;
294
330
  try {
@@ -347,7 +383,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
347
383
  }), {}, {
348
384
  activeIndexPos: activeIndexPos,
349
385
  intl: intl,
350
- diffType: diffType
386
+ diffType: diffType,
387
+ coarseTableCellsOnly: useCoarseTableDecoration
351
388
  }))));
352
389
  }
353
390
  if (change.deleted.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "13.0.8",
3
+ "version": "13.0.10",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  "@atlaskit/editor-prosemirror": "^8.0.0",
28
28
  "@atlaskit/editor-tables": "^3.0.0",
29
29
  "@atlaskit/platform-feature-flags": "^2.1.0",
30
- "@atlaskit/tmp-editor-statsig": "^149.0.0",
30
+ "@atlaskit/tmp-editor-statsig": "^150.0.0",
31
31
  "@atlaskit/tokens": "^16.7.0",
32
32
  "@babel/runtime": "^7.0.0",
33
33
  "@compiled/react": "^1.0.2",
@@ -36,7 +36,7 @@
36
36
  "prosemirror-changeset": "^2.3.1"
37
37
  },
38
38
  "devDependencies": {
39
- "@atlaskit/adf-utils": "^20.6.0",
39
+ "@atlaskit/adf-utils": "^20.7.0",
40
40
  "@atlaskit/button": "^25.1.0",
41
41
  "@atlaskit/css": "^1.0.0",
42
42
  "@atlaskit/dropdown-menu": "^18.0.0",
@@ -53,7 +53,7 @@
53
53
  "react-intl": "^7.0.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@atlaskit/editor-common": "^119.8.0",
56
+ "@atlaskit/editor-common": "^119.9.0",
57
57
  "react": "^18.2.0 || ^19.2.0"
58
58
  },
59
59
  "techstack": {