@atlaskit/editor-plugin-show-diff 27.1.3 → 28.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 28.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 27.1.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [`a13cf337bdc67`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a13cf337bdc67) -
14
+ Fix a suggested table cell change showing a blank cell when the changeset trims it to the cell's
15
+ open tokens, and keep its indicator bar on the suggested content. Behind
16
+ `platform_editor_ai_show_diff_patch_2_fix`.
17
+
3
18
  ## 27.1.3
4
19
 
5
20
  ### Patch Changes
@@ -234,7 +234,8 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
234
234
  intl: intl,
235
235
  isActive: isActive,
236
236
  isInserted: isInserted,
237
- leftAnchorId: leftAnchorId
237
+ leftAnchorId: leftAnchorId,
238
+ showIndicators: showIndicators
238
239
  });
239
240
  }
240
241
  return createTableCellContentWidgets({
@@ -14,6 +14,8 @@ var _tableNodes = require("@atlaskit/adf-schema/tableNodes");
14
14
  var _cssHelper = require("@atlaskit/editor-common/css-helper");
15
15
  var _document = require("@atlaskit/editor-common/utils/document");
16
16
  var _view = require("@atlaskit/editor-prosemirror/view");
17
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
18
+ var _createAnchorDecorationWidgets = require("./createAnchorDecorationWidgets");
17
19
  var _createBlockChangedDecoration = require("./createBlockChangedDecoration");
18
20
  var _createInlineChangedDecoration = require("./createInlineChangedDecoration");
19
21
  var _decorationKeys = require("./decorationKeys");
@@ -22,6 +24,26 @@ var _wrapBlockNodeView = require("./utils/wrapBlockNodeView");
22
24
  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; }
23
25
  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; }
24
26
  var CELL_TYPE_NAMES = ['tableCell', 'tableHeader'];
27
+ /**
28
+ * Whether only close tokens follow `pos` in its cell: it is at the end of its parent, and so is each
29
+ * ancestor up to the cell.
30
+ */
31
+ var isAtEndOfCell = function isAtEndOfCell(doc, pos) {
32
+ var $pos = doc.resolve(pos);
33
+ if ($pos.parentOffset !== $pos.parent.content.size) {
34
+ return false;
35
+ }
36
+ for (var depth = $pos.depth; depth > 0; depth--) {
37
+ if (CELL_TYPE_NAMES.includes($pos.node(depth).type.name)) {
38
+ return true;
39
+ }
40
+ if ($pos.indexAfter(depth - 1) !== $pos.node(depth - 1).childCount) {
41
+ return false;
42
+ }
43
+ }
44
+ return false;
45
+ };
46
+
25
47
  /**
26
48
  * Pairs each cell of a whole-cell replacement with the live cell it replaces, or `[]` when the slice
27
49
  * is not a run of whole cells matching the live ones. Whole-cell replacement preserves cell count and
@@ -33,7 +55,7 @@ var getReplacedCells = exports.getReplacedCells = function getReplacedCells(_ref
33
55
  fromB = _ref.fromB,
34
56
  toB = _ref.toB;
35
57
  var otherCells = slice.content.content;
36
- if (slice.openStart !== 0 || slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(function (node) {
58
+ if (slice.openStart !== 0 || (!(0, _fg.fg)('platform_editor_ai_show_diff_patch_2_fix') || !isAtEndOfCell(doc, toB)) && slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(function (node) {
37
59
  return CELL_TYPE_NAMES.includes(node.type.name);
38
60
  })) {
39
61
  return [];
@@ -134,16 +156,18 @@ var createCellBackgroundLayer = function createCellBackgroundLayer(color) {
134
156
  return layer;
135
157
  };
136
158
 
137
- /** The other side's cell content, as blocks, for the top of the live cell. */
138
- var createCellContentWidget = function createCellContentWidget(_ref2) {
159
+ /** The other side's cell content, as blocks, for the top of the live cell, with its indicator anchor. */
160
+ var createCellContentWidgets = function createCellContentWidgets(_ref2) {
139
161
  var cell = _ref2.cell,
162
+ doc = _ref2.doc,
140
163
  pos = _ref2.pos,
141
164
  nodeViewSerializer = _ref2.nodeViewSerializer,
142
165
  colorScheme = _ref2.colorScheme,
143
166
  intl = _ref2.intl,
144
167
  isActive = _ref2.isActive,
145
168
  isInserted = _ref2.isInserted,
146
- leftAnchorId = _ref2.leftAnchorId;
169
+ leftAnchorId = _ref2.leftAnchorId,
170
+ showIndicators = _ref2.showIndicators;
147
171
  var dom = document.createElement('div');
148
172
  cell.content.forEach(function (block) {
149
173
  var nodeView = nodeViewSerializer.tryCreateNodeView(block, 0, undefined, isInserted, colorScheme);
@@ -170,16 +194,24 @@ var createCellContentWidget = function createCellContentWidget(_ref2) {
170
194
  }
171
195
  });
172
196
  if (dom.childNodes.length === 0) {
173
- return undefined;
197
+ return [];
174
198
  }
175
199
  dom.setAttribute('data-testid', isInserted ? 'show-diff-changed-decoration' : 'show-diff-deleted-decoration');
200
+ var diffId = crypto.randomUUID();
201
+ if (showIndicators && (0, _fg.fg)('platform_editor_ai_show_diff_patch_2_fix')) {
202
+ // The indicator bar of a widget anchors its top and bottom to the widget itself. Without this
203
+ // only the live cell's own bar resolves, which starts below the suggested content.
204
+ dom.style.setProperty('anchor-name', "--".concat((0, _decorationKeys.buildAnchorDecorationKey)({
205
+ diffId: diffId
206
+ })));
207
+ }
176
208
 
177
209
  // Before the cell's own blocks, as a block of its own, so it never lands inside a paragraph the
178
210
  // displayed side may hide.
179
- return _view.Decoration.widget(pos + 1, dom, _objectSpread(_objectSpread({}, (0, _decorationKeys.buildDiffDecorationSpec)({
211
+ var widget = _view.Decoration.widget(pos + 1, dom, _objectSpread(_objectSpread({}, (0, _decorationKeys.buildDiffDecorationSpec)({
180
212
  colorScheme: colorScheme,
181
213
  decorationType: 'widget',
182
- diffId: crypto.randomUUID(),
214
+ diffId: diffId,
183
215
  leftAnchorId: leftAnchorId,
184
216
  isActive: isActive,
185
217
  isInserted: isInserted,
@@ -187,6 +219,16 @@ var createCellContentWidget = function createCellContentWidget(_ref2) {
187
219
  })), {}, {
188
220
  marks: []
189
221
  }));
222
+
223
+ // The bar aligns its left edge to this anchor. The live cell's inline decoration makes one too,
224
+ // but not when the clean view hides it, and the widget is still showing then.
225
+ var leftAnchor = showIndicators && (0, _fg.fg)('platform_editor_ai_show_diff_patch_2_fix') ? (0, _createAnchorDecorationWidgets.createLeftAnchorWidget)({
226
+ doc: doc,
227
+ from: pos,
228
+ diffId: diffId,
229
+ leftAnchorId: leftAnchorId
230
+ }) : undefined;
231
+ return leftAnchor ? [widget, leftAnchor] : [widget];
190
232
  };
191
233
 
192
234
  /**
@@ -203,7 +245,8 @@ var createReplacedCellWidgets = exports.createReplacedCellWidgets = function cre
203
245
  intl = _ref3.intl,
204
246
  isActive = _ref3.isActive,
205
247
  isInserted = _ref3.isInserted,
206
- leftAnchorId = _ref3.leftAnchorId;
248
+ leftAnchorId = _ref3.leftAnchorId,
249
+ showIndicators = _ref3.showIndicators;
207
250
  return getReplacedCells({
208
251
  slice: slice,
209
252
  doc: newDoc,
@@ -240,24 +283,31 @@ var createReplacedCellWidgets = exports.createReplacedCellWidgets = function cre
240
283
  }));
241
284
  }
242
285
  if (!isCellContentEqual(otherCell, liveCell)) {
243
- var contentWidget = createCellContentWidget({
286
+ var contentWidgets = createCellContentWidgets({
244
287
  cell: otherCell,
288
+ doc: newDoc,
245
289
  pos: liveCellPos,
246
290
  nodeViewSerializer: nodeViewSerializer,
247
291
  colorScheme: colorScheme,
248
292
  intl: intl,
249
293
  isActive: isActive,
250
294
  isInserted: isInserted,
251
- leftAnchorId: leftAnchorId
295
+ leftAnchorId: leftAnchorId,
296
+ showIndicators: showIndicators
252
297
  });
253
- if (contentWidget) {
254
- decorations.push(contentWidget);
255
- }
298
+ decorations.push.apply(decorations, (0, _toConsumableArray2.default)(contentWidgets));
256
299
  }
257
300
  return decorations;
258
301
  });
259
302
  };
260
303
 
304
+ /** Whether a cell holds only blank lines: empty paragraphs or headings, with nothing to show. */
305
+ var isBlankCell = function isBlankCell(cell) {
306
+ return cell.childCount > 0 && cell.children.every(function (block) {
307
+ return block.isTextblock && block.content.size === 0;
308
+ });
309
+ };
310
+
261
311
  /**
262
312
  * The displayed side of a whole-cell replacement. A table cell is never removed on its own, only
263
313
  * replaced in place, so a live cell is never hidden — hiding it would leave the row a cell short. It
@@ -307,6 +357,10 @@ var createReplacedCellDecorations = exports.createReplacedCellDecorations = func
307
357
  if (isCellContentEqual(otherCell, liveCell)) {
308
358
  return decorations;
309
359
  }
360
+
361
+ // A blank live cell has no content to strike through, so the suggestion takes its place, as
362
+ // in the clean view, rather than leaving a blank line below it.
363
+ var hidesLiveCell = shouldHideDeleted || !isInserted && isBlankCell(liveCell) && (0, _fg.fg)('platform_editor_ai_show_diff_patch_2_fix');
310
364
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createInlineChangedDecoration.createInlineChangedDecoration)({
311
365
  attributionKey: attributionKey,
312
366
  change: _objectSpread(_objectSpread({}, change), {}, {
@@ -322,12 +376,12 @@ var createReplacedCellDecorations = exports.createReplacedCellDecorations = func
322
376
  isInserted: isInserted,
323
377
  leftAnchorId: leftAnchorId,
324
378
  reveal: reveal,
325
- shouldHideDeleted: shouldHideDeleted,
379
+ shouldHideDeleted: hidesLiveCell,
326
380
  showContributorTags: showContributorTags,
327
381
  showIndicators: showIndicators,
328
382
  tagMountContext: tagMountContext
329
383
  })));
330
- if (shouldHideDeleted) {
384
+ if (hidesLiveCell) {
331
385
  liveCell.forEach(function (block, offset) {
332
386
  var blockPos = liveCellPos + 1 + offset;
333
387
  decorations.push.apply(decorations, (0, _toConsumableArray2.default)((0, _createBlockChangedDecoration.createBlockChangedDecoration)({
@@ -341,7 +395,7 @@ var createReplacedCellDecorations = exports.createReplacedCellDecorations = func
341
395
  isActive: isActive,
342
396
  isInserted: isInserted,
343
397
  leftAnchorId: leftAnchorId,
344
- shouldHideDeleted: shouldHideDeleted
398
+ shouldHideDeleted: hidesLiveCell
345
399
  })));
346
400
  });
347
401
  }
@@ -226,7 +226,8 @@ export const createNodeChangedDecorationWidget = ({
226
226
  intl,
227
227
  isActive,
228
228
  isInserted,
229
- leftAnchorId
229
+ leftAnchorId,
230
+ showIndicators
230
231
  });
231
232
  }
232
233
  return createTableCellContentWidgets({
@@ -5,12 +5,34 @@ import { getCellDomAttrs } from '@atlaskit/adf-schema/tableNodes';
5
5
  import { convertToInlineCss } from '@atlaskit/editor-common/css-helper';
6
6
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
7
7
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
8
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
9
+ import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
8
10
  import { createBlockChangedDecoration } from './createBlockChangedDecoration';
9
11
  import { createInlineChangedDecoration } from './createInlineChangedDecoration';
10
- import { buildDiffDecorationSpec } from './decorationKeys';
12
+ import { buildAnchorDecorationKey, buildDiffDecorationSpec } from './decorationKeys';
11
13
  import { isCellAttrChanged, isCellBackgroundChanged, normalizeCellBackground } from './utils/cellAttrs';
12
14
  import { injectInnerWrapper, wrapBlockNodeView } from './utils/wrapBlockNodeView';
13
15
  const CELL_TYPE_NAMES = ['tableCell', 'tableHeader'];
16
+ /**
17
+ * Whether only close tokens follow `pos` in its cell: it is at the end of its parent, and so is each
18
+ * ancestor up to the cell.
19
+ */
20
+ const isAtEndOfCell = (doc, pos) => {
21
+ const $pos = doc.resolve(pos);
22
+ if ($pos.parentOffset !== $pos.parent.content.size) {
23
+ return false;
24
+ }
25
+ for (let depth = $pos.depth; depth > 0; depth--) {
26
+ if (CELL_TYPE_NAMES.includes($pos.node(depth).type.name)) {
27
+ return true;
28
+ }
29
+ if ($pos.indexAfter(depth - 1) !== $pos.node(depth - 1).childCount) {
30
+ return false;
31
+ }
32
+ }
33
+ return false;
34
+ };
35
+
14
36
  /**
15
37
  * Pairs each cell of a whole-cell replacement with the live cell it replaces, or `[]` when the slice
16
38
  * is not a run of whole cells matching the live ones. Whole-cell replacement preserves cell count and
@@ -23,7 +45,7 @@ export const getReplacedCells = ({
23
45
  toB
24
46
  }) => {
25
47
  const otherCells = slice.content.content;
26
- if (slice.openStart !== 0 || slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(node => CELL_TYPE_NAMES.includes(node.type.name))) {
48
+ if (slice.openStart !== 0 || (!fg('platform_editor_ai_show_diff_patch_2_fix') || !isAtEndOfCell(doc, toB)) && slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(node => CELL_TYPE_NAMES.includes(node.type.name))) {
27
49
  return [];
28
50
  }
29
51
  const liveCells = [];
@@ -119,16 +141,18 @@ const createCellBackgroundLayer = color => {
119
141
  return layer;
120
142
  };
121
143
 
122
- /** The other side's cell content, as blocks, for the top of the live cell. */
123
- const createCellContentWidget = ({
144
+ /** The other side's cell content, as blocks, for the top of the live cell, with its indicator anchor. */
145
+ const createCellContentWidgets = ({
124
146
  cell,
147
+ doc,
125
148
  pos,
126
149
  nodeViewSerializer,
127
150
  colorScheme,
128
151
  intl,
129
152
  isActive,
130
153
  isInserted,
131
- leftAnchorId
154
+ leftAnchorId,
155
+ showIndicators
132
156
  }) => {
133
157
  const dom = document.createElement('div');
134
158
  cell.content.forEach(block => {
@@ -156,17 +180,25 @@ const createCellContentWidget = ({
156
180
  }
157
181
  });
158
182
  if (dom.childNodes.length === 0) {
159
- return undefined;
183
+ return [];
160
184
  }
161
185
  dom.setAttribute('data-testid', isInserted ? 'show-diff-changed-decoration' : 'show-diff-deleted-decoration');
186
+ const diffId = crypto.randomUUID();
187
+ if (showIndicators && fg('platform_editor_ai_show_diff_patch_2_fix')) {
188
+ // The indicator bar of a widget anchors its top and bottom to the widget itself. Without this
189
+ // only the live cell's own bar resolves, which starts below the suggested content.
190
+ dom.style.setProperty('anchor-name', `--${buildAnchorDecorationKey({
191
+ diffId
192
+ })}`);
193
+ }
162
194
 
163
195
  // Before the cell's own blocks, as a block of its own, so it never lands inside a paragraph the
164
196
  // displayed side may hide.
165
- return Decoration.widget(pos + 1, dom, {
197
+ const widget = Decoration.widget(pos + 1, dom, {
166
198
  ...buildDiffDecorationSpec({
167
199
  colorScheme,
168
200
  decorationType: 'widget',
169
- diffId: crypto.randomUUID(),
201
+ diffId,
170
202
  leftAnchorId,
171
203
  isActive,
172
204
  isInserted,
@@ -174,6 +206,16 @@ const createCellContentWidget = ({
174
206
  }),
175
207
  marks: []
176
208
  });
209
+
210
+ // The bar aligns its left edge to this anchor. The live cell's inline decoration makes one too,
211
+ // but not when the clean view hides it, and the widget is still showing then.
212
+ const leftAnchor = showIndicators && fg('platform_editor_ai_show_diff_patch_2_fix') ? createLeftAnchorWidget({
213
+ doc,
214
+ from: pos,
215
+ diffId,
216
+ leftAnchorId
217
+ }) : undefined;
218
+ return leftAnchor ? [widget, leftAnchor] : [widget];
177
219
  };
178
220
 
179
221
  /**
@@ -190,7 +232,8 @@ export const createReplacedCellWidgets = ({
190
232
  intl,
191
233
  isActive,
192
234
  isInserted,
193
- leftAnchorId
235
+ leftAnchorId,
236
+ showIndicators
194
237
  }) => getReplacedCells({
195
238
  slice,
196
239
  doc: newDoc,
@@ -228,23 +271,26 @@ export const createReplacedCellWidgets = ({
228
271
  }));
229
272
  }
230
273
  if (!isCellContentEqual(otherCell, liveCell)) {
231
- const contentWidget = createCellContentWidget({
274
+ const contentWidgets = createCellContentWidgets({
232
275
  cell: otherCell,
276
+ doc: newDoc,
233
277
  pos: liveCellPos,
234
278
  nodeViewSerializer,
235
279
  colorScheme,
236
280
  intl,
237
281
  isActive,
238
282
  isInserted,
239
- leftAnchorId
283
+ leftAnchorId,
284
+ showIndicators
240
285
  });
241
- if (contentWidget) {
242
- decorations.push(contentWidget);
243
- }
286
+ decorations.push(...contentWidgets);
244
287
  }
245
288
  return decorations;
246
289
  });
247
290
 
291
+ /** Whether a cell holds only blank lines: empty paragraphs or headings, with nothing to show. */
292
+ const isBlankCell = cell => cell.childCount > 0 && cell.children.every(block => block.isTextblock && block.content.size === 0);
293
+
248
294
  /**
249
295
  * The displayed side of a whole-cell replacement. A table cell is never removed on its own, only
250
296
  * replaced in place, so a live cell is never hidden — hiding it would leave the row a cell short. It
@@ -295,6 +341,10 @@ export const createReplacedCellDecorations = ({
295
341
  if (isCellContentEqual(otherCell, liveCell)) {
296
342
  return decorations;
297
343
  }
344
+
345
+ // A blank live cell has no content to strike through, so the suggestion takes its place, as
346
+ // in the clean view, rather than leaving a blank line below it.
347
+ const hidesLiveCell = shouldHideDeleted || !isInserted && isBlankCell(liveCell) && fg('platform_editor_ai_show_diff_patch_2_fix');
298
348
  decorations.push(...createInlineChangedDecoration({
299
349
  attributionKey,
300
350
  change: {
@@ -311,12 +361,12 @@ export const createReplacedCellDecorations = ({
311
361
  isInserted,
312
362
  leftAnchorId,
313
363
  reveal,
314
- shouldHideDeleted,
364
+ shouldHideDeleted: hidesLiveCell,
315
365
  showContributorTags,
316
366
  showIndicators,
317
367
  tagMountContext
318
368
  }));
319
- if (shouldHideDeleted) {
369
+ if (hidesLiveCell) {
320
370
  liveCell.forEach((block, offset) => {
321
371
  const blockPos = liveCellPos + 1 + offset;
322
372
  decorations.push(...createBlockChangedDecoration({
@@ -330,7 +380,7 @@ export const createReplacedCellDecorations = ({
330
380
  isActive,
331
381
  isInserted,
332
382
  leftAnchorId,
333
- shouldHideDeleted
383
+ shouldHideDeleted: hidesLiveCell
334
384
  }));
335
385
  });
336
386
  }
@@ -228,7 +228,8 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
228
228
  intl: intl,
229
229
  isActive: isActive,
230
230
  isInserted: isInserted,
231
- leftAnchorId: leftAnchorId
231
+ leftAnchorId: leftAnchorId,
232
+ showIndicators: showIndicators
232
233
  });
233
234
  }
234
235
  return createTableCellContentWidgets({
@@ -9,12 +9,34 @@ import { getCellDomAttrs } from '@atlaskit/adf-schema/tableNodes';
9
9
  import { convertToInlineCss } from '@atlaskit/editor-common/css-helper';
10
10
  import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document';
11
11
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
12
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
13
+ import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
12
14
  import { createBlockChangedDecoration } from './createBlockChangedDecoration';
13
15
  import { createInlineChangedDecoration } from './createInlineChangedDecoration';
14
- import { buildDiffDecorationSpec } from './decorationKeys';
16
+ import { buildAnchorDecorationKey, buildDiffDecorationSpec } from './decorationKeys';
15
17
  import { isCellAttrChanged, isCellBackgroundChanged, normalizeCellBackground } from './utils/cellAttrs';
16
18
  import { injectInnerWrapper, wrapBlockNodeView } from './utils/wrapBlockNodeView';
17
19
  var CELL_TYPE_NAMES = ['tableCell', 'tableHeader'];
20
+ /**
21
+ * Whether only close tokens follow `pos` in its cell: it is at the end of its parent, and so is each
22
+ * ancestor up to the cell.
23
+ */
24
+ var isAtEndOfCell = function isAtEndOfCell(doc, pos) {
25
+ var $pos = doc.resolve(pos);
26
+ if ($pos.parentOffset !== $pos.parent.content.size) {
27
+ return false;
28
+ }
29
+ for (var depth = $pos.depth; depth > 0; depth--) {
30
+ if (CELL_TYPE_NAMES.includes($pos.node(depth).type.name)) {
31
+ return true;
32
+ }
33
+ if ($pos.indexAfter(depth - 1) !== $pos.node(depth - 1).childCount) {
34
+ return false;
35
+ }
36
+ }
37
+ return false;
38
+ };
39
+
18
40
  /**
19
41
  * Pairs each cell of a whole-cell replacement with the live cell it replaces, or `[]` when the slice
20
42
  * is not a run of whole cells matching the live ones. Whole-cell replacement preserves cell count and
@@ -26,7 +48,7 @@ export var getReplacedCells = function getReplacedCells(_ref) {
26
48
  fromB = _ref.fromB,
27
49
  toB = _ref.toB;
28
50
  var otherCells = slice.content.content;
29
- if (slice.openStart !== 0 || slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(function (node) {
51
+ if (slice.openStart !== 0 || (!fg('platform_editor_ai_show_diff_patch_2_fix') || !isAtEndOfCell(doc, toB)) && slice.openEnd !== 0 || otherCells.length === 0 || !otherCells.every(function (node) {
30
52
  return CELL_TYPE_NAMES.includes(node.type.name);
31
53
  })) {
32
54
  return [];
@@ -127,16 +149,18 @@ var createCellBackgroundLayer = function createCellBackgroundLayer(color) {
127
149
  return layer;
128
150
  };
129
151
 
130
- /** The other side's cell content, as blocks, for the top of the live cell. */
131
- var createCellContentWidget = function createCellContentWidget(_ref2) {
152
+ /** The other side's cell content, as blocks, for the top of the live cell, with its indicator anchor. */
153
+ var createCellContentWidgets = function createCellContentWidgets(_ref2) {
132
154
  var cell = _ref2.cell,
155
+ doc = _ref2.doc,
133
156
  pos = _ref2.pos,
134
157
  nodeViewSerializer = _ref2.nodeViewSerializer,
135
158
  colorScheme = _ref2.colorScheme,
136
159
  intl = _ref2.intl,
137
160
  isActive = _ref2.isActive,
138
161
  isInserted = _ref2.isInserted,
139
- leftAnchorId = _ref2.leftAnchorId;
162
+ leftAnchorId = _ref2.leftAnchorId,
163
+ showIndicators = _ref2.showIndicators;
140
164
  var dom = document.createElement('div');
141
165
  cell.content.forEach(function (block) {
142
166
  var nodeView = nodeViewSerializer.tryCreateNodeView(block, 0, undefined, isInserted, colorScheme);
@@ -163,16 +187,24 @@ var createCellContentWidget = function createCellContentWidget(_ref2) {
163
187
  }
164
188
  });
165
189
  if (dom.childNodes.length === 0) {
166
- return undefined;
190
+ return [];
167
191
  }
168
192
  dom.setAttribute('data-testid', isInserted ? 'show-diff-changed-decoration' : 'show-diff-deleted-decoration');
193
+ var diffId = crypto.randomUUID();
194
+ if (showIndicators && fg('platform_editor_ai_show_diff_patch_2_fix')) {
195
+ // The indicator bar of a widget anchors its top and bottom to the widget itself. Without this
196
+ // only the live cell's own bar resolves, which starts below the suggested content.
197
+ dom.style.setProperty('anchor-name', "--".concat(buildAnchorDecorationKey({
198
+ diffId: diffId
199
+ })));
200
+ }
169
201
 
170
202
  // Before the cell's own blocks, as a block of its own, so it never lands inside a paragraph the
171
203
  // displayed side may hide.
172
- return Decoration.widget(pos + 1, dom, _objectSpread(_objectSpread({}, buildDiffDecorationSpec({
204
+ var widget = Decoration.widget(pos + 1, dom, _objectSpread(_objectSpread({}, buildDiffDecorationSpec({
173
205
  colorScheme: colorScheme,
174
206
  decorationType: 'widget',
175
- diffId: crypto.randomUUID(),
207
+ diffId: diffId,
176
208
  leftAnchorId: leftAnchorId,
177
209
  isActive: isActive,
178
210
  isInserted: isInserted,
@@ -180,6 +212,16 @@ var createCellContentWidget = function createCellContentWidget(_ref2) {
180
212
  })), {}, {
181
213
  marks: []
182
214
  }));
215
+
216
+ // The bar aligns its left edge to this anchor. The live cell's inline decoration makes one too,
217
+ // but not when the clean view hides it, and the widget is still showing then.
218
+ var leftAnchor = showIndicators && fg('platform_editor_ai_show_diff_patch_2_fix') ? createLeftAnchorWidget({
219
+ doc: doc,
220
+ from: pos,
221
+ diffId: diffId,
222
+ leftAnchorId: leftAnchorId
223
+ }) : undefined;
224
+ return leftAnchor ? [widget, leftAnchor] : [widget];
183
225
  };
184
226
 
185
227
  /**
@@ -196,7 +238,8 @@ export var createReplacedCellWidgets = function createReplacedCellWidgets(_ref3)
196
238
  intl = _ref3.intl,
197
239
  isActive = _ref3.isActive,
198
240
  isInserted = _ref3.isInserted,
199
- leftAnchorId = _ref3.leftAnchorId;
241
+ leftAnchorId = _ref3.leftAnchorId,
242
+ showIndicators = _ref3.showIndicators;
200
243
  return getReplacedCells({
201
244
  slice: slice,
202
245
  doc: newDoc,
@@ -233,24 +276,31 @@ export var createReplacedCellWidgets = function createReplacedCellWidgets(_ref3)
233
276
  }));
234
277
  }
235
278
  if (!isCellContentEqual(otherCell, liveCell)) {
236
- var contentWidget = createCellContentWidget({
279
+ var contentWidgets = createCellContentWidgets({
237
280
  cell: otherCell,
281
+ doc: newDoc,
238
282
  pos: liveCellPos,
239
283
  nodeViewSerializer: nodeViewSerializer,
240
284
  colorScheme: colorScheme,
241
285
  intl: intl,
242
286
  isActive: isActive,
243
287
  isInserted: isInserted,
244
- leftAnchorId: leftAnchorId
288
+ leftAnchorId: leftAnchorId,
289
+ showIndicators: showIndicators
245
290
  });
246
- if (contentWidget) {
247
- decorations.push(contentWidget);
248
- }
291
+ decorations.push.apply(decorations, _toConsumableArray(contentWidgets));
249
292
  }
250
293
  return decorations;
251
294
  });
252
295
  };
253
296
 
297
+ /** Whether a cell holds only blank lines: empty paragraphs or headings, with nothing to show. */
298
+ var isBlankCell = function isBlankCell(cell) {
299
+ return cell.childCount > 0 && cell.children.every(function (block) {
300
+ return block.isTextblock && block.content.size === 0;
301
+ });
302
+ };
303
+
254
304
  /**
255
305
  * The displayed side of a whole-cell replacement. A table cell is never removed on its own, only
256
306
  * replaced in place, so a live cell is never hidden — hiding it would leave the row a cell short. It
@@ -300,6 +350,10 @@ export var createReplacedCellDecorations = function createReplacedCellDecoration
300
350
  if (isCellContentEqual(otherCell, liveCell)) {
301
351
  return decorations;
302
352
  }
353
+
354
+ // A blank live cell has no content to strike through, so the suggestion takes its place, as
355
+ // in the clean view, rather than leaving a blank line below it.
356
+ var hidesLiveCell = shouldHideDeleted || !isInserted && isBlankCell(liveCell) && fg('platform_editor_ai_show_diff_patch_2_fix');
303
357
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration({
304
358
  attributionKey: attributionKey,
305
359
  change: _objectSpread(_objectSpread({}, change), {}, {
@@ -315,12 +369,12 @@ export var createReplacedCellDecorations = function createReplacedCellDecoration
315
369
  isInserted: isInserted,
316
370
  leftAnchorId: leftAnchorId,
317
371
  reveal: reveal,
318
- shouldHideDeleted: shouldHideDeleted,
372
+ shouldHideDeleted: hidesLiveCell,
319
373
  showContributorTags: showContributorTags,
320
374
  showIndicators: showIndicators,
321
375
  tagMountContext: tagMountContext
322
376
  })));
323
- if (shouldHideDeleted) {
377
+ if (hidesLiveCell) {
324
378
  liveCell.forEach(function (block, offset) {
325
379
  var blockPos = liveCellPos + 1 + offset;
326
380
  decorations.push.apply(decorations, _toConsumableArray(createBlockChangedDecoration({
@@ -334,7 +388,7 @@ export var createReplacedCellDecorations = function createReplacedCellDecoration
334
388
  isActive: isActive,
335
389
  isInserted: isInserted,
336
390
  leftAnchorId: leftAnchorId,
337
- shouldHideDeleted: shouldHideDeleted
391
+ shouldHideDeleted: hidesLiveCell
338
392
  })));
339
393
  });
340
394
  }
@@ -29,7 +29,7 @@ export declare const getReplacedCells: ({ slice, doc, fromB, toB, }: {
29
29
  * shape: each live cell takes the other cell's background when it differs, and its content when that
30
30
  * differs. A cell whose content did not change has nothing to diff.
31
31
  */
32
- export declare const createReplacedCellWidgets: ({ slice, newDoc, change, nodeViewSerializer, colorScheme, intl, isActive, isInserted, leftAnchorId, }: {
32
+ export declare const createReplacedCellWidgets: ({ slice, newDoc, change, nodeViewSerializer, colorScheme, intl, isActive, isInserted, leftAnchorId, showIndicators, }: {
33
33
  change: Pick<Change, 'fromB' | 'toB'>;
34
34
  colorScheme?: ColorScheme;
35
35
  intl: IntlShape;
@@ -38,6 +38,7 @@ export declare const createReplacedCellWidgets: ({ slice, newDoc, change, nodeVi
38
38
  leftAnchorId?: string;
39
39
  newDoc: PMNode;
40
40
  nodeViewSerializer: NodeViewSerializer;
41
+ showIndicators?: boolean;
41
42
  slice: Slice;
42
43
  }) => Decoration[];
43
44
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "27.1.3",
3
+ "version": "28.0.0",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -29,10 +29,10 @@
29
29
  "@atlaskit/agent-color": "^1.0.0",
30
30
  "@atlaskit/avatar": "^30.0.0",
31
31
  "@atlaskit/custom-steps": "^1.1.0",
32
- "@atlaskit/editor-plugin-accessibility-utils": "^29.0.0",
33
- "@atlaskit/editor-plugin-analytics": "^29.0.0",
34
- "@atlaskit/editor-plugin-limited-mode": "^26.0.0",
35
- "@atlaskit/editor-plugin-user-intent": "^27.0.0",
32
+ "@atlaskit/editor-plugin-accessibility-utils": "^30.0.0",
33
+ "@atlaskit/editor-plugin-analytics": "^30.0.0",
34
+ "@atlaskit/editor-plugin-limited-mode": "^27.0.0",
35
+ "@atlaskit/editor-plugin-user-intent": "^28.0.0",
36
36
  "@atlaskit/editor-prosemirror": "^9.0.0",
37
37
  "@atlaskit/editor-shared-styles": "^4.6.0",
38
38
  "@atlaskit/editor-tables": "^3.2.0",
@@ -53,25 +53,25 @@
53
53
  "@atlaskit/button": "^25.6.0",
54
54
  "@atlaskit/css": "^1.1.0",
55
55
  "@atlaskit/dropdown-menu": "^21.0.0",
56
- "@atlaskit/editor-core": "^240.1.0",
56
+ "@atlaskit/editor-core": "^241.0.0",
57
57
  "@atlaskit/editor-json-transformer": "^10.0.0",
58
58
  "@atlaskit/editor-plugins": "^16.2.0",
59
- "@atlaskit/form": "^18.2.0",
59
+ "@atlaskit/form": "^18.3.0",
60
60
  "@atlaskit/section-message": "^10.2.0",
61
61
  "@atlaskit/textarea": "^10.2.0",
62
62
  "@atlaskit/textfield": "^10.2.0",
63
63
  "@atlaskit/util-data-test": "^19.1.0",
64
64
  "@atlassian/confluence-presets": "workspace:^",
65
65
  "@atlassian/content-reconciliation": "^0.1.3506",
66
- "@atlassian/editor-ai-injected-editors": "^44.0.0",
67
- "@atlassian/editor-plugin-ai": "^83.7.0",
66
+ "@atlassian/editor-ai-injected-editors": "^45.0.0",
67
+ "@atlassian/editor-plugin-ai": "^84.0.0",
68
68
  "@atlassian/structured-docs-types": "workspace:^",
69
69
  "react": "^19.2.0",
70
70
  "react-dom": "^19.2.0",
71
71
  "react-intl": "^7.0.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@atlaskit/editor-common": "^133.7.0",
74
+ "@atlaskit/editor-common": "^134.0.0",
75
75
  "react": "^18.2.0 || ^19.2.0",
76
76
  "react-dom": "^18.2.0 || ^19.2.0",
77
77
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -93,6 +93,9 @@
93
93
  "platform_editor_ai_show_diff_patch_2": {
94
94
  "type": "boolean"
95
95
  },
96
+ "platform_editor_ai_show_diff_patch_2_fix": {
97
+ "type": "boolean"
98
+ },
96
99
  "platform_editor_ai_smart_diff": {
97
100
  "type": "boolean"
98
101
  },