@atlaskit/editor-plugin-show-diff 12.1.1 → 12.1.3

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.
@@ -28,7 +28,7 @@ export const createNodeChangedDecorationWidget = ({
28
28
  diffType,
29
29
  hideAddedDiffsUnderline = false
30
30
  }) => {
31
- var _slice$content, _slice$content2, _slice$content2$first, _slice$content3, _slice$content3$first, _slice$content$firstC;
31
+ var _slice$content, _slice$content2, _slice$content2$first, _slice$content3, _slice$content3$first, _$safeInsertPos$nodeB, _slice$content$firstC;
32
32
  const slice = doc.slice(change.fromA, change.toA);
33
33
  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;
34
34
  // Widget decoration used for deletions as the content is not in the document
@@ -61,6 +61,14 @@ export const createNodeChangedDecorationWidget = ({
61
61
 
62
62
  // For non-table content, use the existing span wrapper approach
63
63
  const dom = document.createElement('span');
64
+ const $safeInsertPos = newDoc.resolve(safeInsertPos);
65
+ const isTopLevelInsert = $safeInsertPos.depth === 0;
66
+ const hasPreviousBlock = ((_$safeInsertPos$nodeB = $safeInsertPos.nodeBefore) === null || _$safeInsertPos$nodeB === void 0 ? void 0 : _$safeInsertPos$nodeB.isBlock) === true;
67
+ if (isExtendedEnabled(diffType) && isTopLevelInsert && hasPreviousBlock) {
68
+ // Editor CSS removes the top margin from a block when it is its parent's first child.
69
+ // Keep the serialized block as a subsequent child so each block type retains its own margin.
70
+ dom.append(dom.ownerDocument.createElement('div'));
71
+ }
64
72
  // When mediaSingle nodes are rendered inside a widget decoration (e.g. as part of
65
73
  // a replaced panel), the centering CSS (margin-left: 50%; transform: translateX(-50%))
66
74
  // incorrectly applies because isNestedNode resolves to false (getPos returns 0).
@@ -18,14 +18,13 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
18
18
  import { areDocsEqualByBlockStructureAndText } from '../areDocsEqualByBlockStructureAndText';
19
19
  import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorationWidgets';
20
20
  import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
21
- import { createGranularBlockReferenceWidget } from '../decorations/createGranularBlockReferenceWidget';
22
21
  import { createInlineChangedDecoration } from '../decorations/createInlineChangedDecoration';
23
22
  import { createNodeChangedDecorationWidget } from '../decorations/createNodeChangedDecorationWidget';
24
23
  import { extractDiffDescriptors } from '../decorations/decorationKeys';
25
24
  import { getAttrChangeRanges, stepIsValidAttrChange } from '../decorations/utils/getAttrChangeRanges';
26
25
  import { getMarkChangeRanges } from '../decorations/utils/getMarkChangeRanges';
27
26
  import { isExtendedEnabled } from '../isExtendedEnabled';
28
- import { diffBySteps, getStepChanges } from './diffBySteps';
27
+ import { diffBySteps } from './diffBySteps';
29
28
  import { groupChangesByBlock } from './groupChangesByBlock';
30
29
  import { optimizeChanges } from './optimizeChanges';
31
30
  import { simplifySteps } from './simplifySteps';
@@ -254,7 +253,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
254
253
 
255
254
  // Our default operations are insertions, so it should match the opposite of isInverted.
256
255
  var isInserted = !isInverted;
257
- var createDecorationsForChange = function createDecorationsForChange(change, showGranularWithBlock) {
256
+ var createDecorationsForChange = function createDecorationsForChange(change) {
258
257
  var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
259
258
 
260
259
  // Hoisted because it decides BOTH where the deleted widget is anchored and — since the
@@ -266,10 +265,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
266
265
  inlineDeletedDiffPlacement: inlineDeletedDiffPlacement
267
266
  });
268
267
  if (change.inserted.length > 0) {
269
- // shouldHideDeleted for block/node decorations: suppressed when isInverted + hideDeletedDiffs,
270
- // or when showGranularWithBlock (block reference widget is shown instead).
271
- // isInverted gates both — on an inverted diff the inserted side is visually the deleted side.
272
- var shouldHideDeleted = isExtendedEnabled(diffType) ? isInverted && (hideDeletedDiffs || showGranularWithBlock && change.deleted.length > 0) : false;
268
+ // On an inverted diff the inserted side is visually the deleted side.
269
+ var shouldHideDeleted = isExtendedEnabled(diffType) ? isInverted && hideDeletedDiffs : false;
273
270
 
274
271
  // For `smart` NODE-level promotions the change range spans a whole container
275
272
  // (e.g. an entire list/table/layout, using outer node bounds). Applying a SINGLE
@@ -286,7 +283,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
286
283
  // change. Used to decide if indicator anchor positions should be adjusted
287
284
  // inward — when the widget is present the anchor must stay at the block
288
285
  // boundary to keep the indicator bar continuous with the deleted content.
289
- var willRenderDeletedWidget = change.deleted.length > 0 && !(isExtendedEnabled(diffType) && !isInverted && (hideDeletedDiffs || showGranularWithBlock) && change.inserted.length > 0);
286
+ var willRenderDeletedWidget = change.deleted.length > 0 && !(isExtendedEnabled(diffType) && !isInverted && hideDeletedDiffs && change.inserted.length > 0);
290
287
  if (isSmartNodeLevel) {
291
288
  var _iterator2 = _createForOfIteratorHelper(leafTextblockRanges(tr.doc, change.fromB, change.toB)),
292
289
  _step2;
@@ -350,7 +347,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
350
347
  }))));
351
348
  }
352
349
  if (change.deleted.length > 0) {
353
- var _shouldHideDeleted = isExtendedEnabled(diffType) ? !isInverted && (hideDeletedDiffs || showGranularWithBlock) && change.inserted.length > 0 : false;
350
+ var _shouldHideDeleted = isExtendedEnabled(diffType) ? !isInverted && hideDeletedDiffs && change.inserted.length > 0 : false;
354
351
  if (!_shouldHideDeleted) {
355
352
  decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget(_objectSpread(_objectSpread({
356
353
  change: change,
@@ -371,88 +368,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
371
368
  }
372
369
  }
373
370
  };
374
- if (diffType === 'step' && expValEquals('platform_editor_diff_granular_extended', 'isEnabled', true)) {
375
- // Uses getStepChanges instead of getChanges so that we have per-step granularity metadata.
376
- // Specifically, we need to know how many granular changes each step produced in order to
377
- // apply the shouldHideDeleted suppression threshold (> 3 granular changes per step).
378
- // getChanges returns a flat Change[] with no per-step grouping, making this count impossible
379
- // to derive after the fact without re-introducing per-change metadata.
380
- var stepChanges = getStepChanges(originalDoc, steps);
381
- stepChanges.forEach(function (_ref5) {
382
- var isGranular = _ref5.isGranular,
383
- stepChangeList = _ref5.changes;
384
- var granularCount = isGranular ? stepChangeList.length : 0;
385
-
386
- // Calculate the average ratio of changed content on both A (original) and B (new)
387
- // sides of the diff. If 30% or more of the block has changed on average, we show
388
- // the block reference widget even if the granular change count is below the threshold.
389
- // Block length is derived from the enclosing text block boundaries rather than the
390
- // first/last change positions, so unchanged words at the start/end are accounted for.
391
- var avgChangedRatio = 0;
392
- if (isGranular && stepChangeList.length > 0) {
393
- var first = stepChangeList[0];
394
- var last = stepChangeList[stepChangeList.length - 1];
395
- var resolvedA = originalDoc.resolve(first.fromA);
396
- var resolvedB = tr.doc.resolve(first.fromB);
397
- var blockStartA = first.fromA;
398
- var blockEndA = last.toA;
399
- var blockStartB = first.fromB;
400
- var blockEndB = last.toB;
401
- for (var depth = resolvedA.depth; depth >= 0; depth--) {
402
- var node = resolvedA.node(depth);
403
- if (node.isTextblock) {
404
- blockStartA = resolvedA.start(depth);
405
- blockEndA = blockStartA + node.nodeSize - 2; // exclude open/close tokens
406
- break;
407
- }
408
- }
409
- for (var _depth = resolvedB.depth; _depth >= 0; _depth--) {
410
- var _node = resolvedB.node(_depth);
411
- if (_node.isTextblock) {
412
- blockStartB = resolvedB.start(_depth);
413
- blockEndB = blockStartB + _node.nodeSize - 2; // exclude open/close tokens
414
- break;
415
- }
416
- }
417
- var blockLengthA = blockEndA - blockStartA;
418
- var blockLengthB = blockEndB - blockStartB;
419
- var totalChangedA = stepChangeList.reduce(function (sum, c) {
420
- return sum + (c.toA - c.fromA);
421
- }, 0);
422
- var totalChangedB = stepChangeList.reduce(function (sum, c) {
423
- return sum + (c.toB - c.fromB);
424
- }, 0);
425
- var ratioA = blockLengthA > 0 ? totalChangedA / blockLengthA : 0;
426
- var ratioB = blockLengthB > 0 ? totalChangedB / blockLengthB : 0;
427
- avgChangedRatio = (ratioA + ratioB) / 2;
428
- }
429
- var showGranularWithBlock = isGranular && granularCount !== 1 && (granularCount > 3 || avgChangedRatio >= 0.3);
430
- stepChangeList.forEach(function (change) {
431
- createDecorationsForChange(change, showGranularWithBlock);
432
- });
433
- if (showGranularWithBlock && stepChangeList.length > 0 && !hideDeletedDiffs) {
434
- var lastChange = stepChangeList[stepChangeList.length - 1];
435
- var granularBlockDiffId = crypto.randomUUID();
436
- var blockWidgets = createGranularBlockReferenceWidget({
437
- change: lastChange,
438
- originalDoc: originalDoc,
439
- newDoc: tr.doc,
440
- isInverted: isInverted,
441
- nodeViewSerializer: nodeViewSerializer,
442
- colorScheme: colorScheme,
443
- intl: intl,
444
- activeIndexPos: activeIndexPos,
445
- diffId: granularBlockDiffId,
446
- showIndicators: showIndicators
447
- });
448
- decorations.push.apply(decorations, _toConsumableArray(blockWidgets));
449
- }
450
- });
451
- } else {
452
- changes.forEach(function (change) {
453
- createDecorationsForChange(change, /* showGranularWithBlock */false);
454
- });
455
- }
371
+ changes.forEach(function (change) {
372
+ createDecorationsForChange(change);
373
+ });
456
374
  getMarkChangeRanges(steps).forEach(function (change) {
457
375
  var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
458
376
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration({
@@ -524,42 +442,42 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref4
524
442
  };
525
443
  export var calculateDiffDecorations = memoizeOne(calculateDiffDecorationsInner,
526
444
  // Cache results unless relevant inputs change
527
- function (_ref6, _ref7) {
528
- var _ref1;
445
+ function (_ref5, _ref6) {
446
+ var _ref0;
447
+ var _ref7 = _slicedToArray(_ref5, 1),
448
+ _ref7$ = _ref7[0],
449
+ pluginState = _ref7$.pluginState,
450
+ state = _ref7$.state,
451
+ colorScheme = _ref7$.colorScheme,
452
+ intl = _ref7$.intl,
453
+ activeIndexPos = _ref7$.activeIndexPos,
454
+ isInverted = _ref7$.isInverted,
455
+ diffType = _ref7$.diffType,
456
+ hideDeletedDiffs = _ref7$.hideDeletedDiffs,
457
+ hideAddedDiffsUnderline = _ref7$.hideAddedDiffsUnderline,
458
+ showIndicators = _ref7$.showIndicators,
459
+ smartThresholds = _ref7$.smartThresholds,
460
+ deletedDiffPlacement = _ref7$.deletedDiffPlacement,
461
+ inlineDeletedDiffPlacement = _ref7$.inlineDeletedDiffPlacement;
529
462
  var _ref8 = _slicedToArray(_ref6, 1),
530
463
  _ref8$ = _ref8[0],
531
- pluginState = _ref8$.pluginState,
532
- state = _ref8$.state,
533
- colorScheme = _ref8$.colorScheme,
534
- intl = _ref8$.intl,
535
- activeIndexPos = _ref8$.activeIndexPos,
536
- isInverted = _ref8$.isInverted,
537
- diffType = _ref8$.diffType,
538
- hideDeletedDiffs = _ref8$.hideDeletedDiffs,
539
- hideAddedDiffsUnderline = _ref8$.hideAddedDiffsUnderline,
540
- showIndicators = _ref8$.showIndicators,
541
- smartThresholds = _ref8$.smartThresholds,
542
- deletedDiffPlacement = _ref8$.deletedDiffPlacement,
543
- inlineDeletedDiffPlacement = _ref8$.inlineDeletedDiffPlacement;
544
- var _ref9 = _slicedToArray(_ref7, 1),
545
- _ref9$ = _ref9[0],
546
- lastPluginState = _ref9$.pluginState,
547
- lastState = _ref9$.state,
548
- lastColorScheme = _ref9$.colorScheme,
549
- lastIntl = _ref9$.intl,
550
- lastActiveIndexPos = _ref9$.activeIndexPos,
551
- lastIsInverted = _ref9$.isInverted,
552
- lastDiffType = _ref9$.diffType,
553
- lastHideDeletedDiffs = _ref9$.hideDeletedDiffs,
554
- lastHideAddedDiffsUnderline = _ref9$.hideAddedDiffsUnderline,
555
- lastShowIndicators = _ref9$.showIndicators,
556
- lastSmartThresholds = _ref9$.smartThresholds,
557
- lastDeletedDiffPlacement = _ref9$.deletedDiffPlacement,
558
- lastInlineDeletedDiffPlacement = _ref9$.inlineDeletedDiffPlacement;
464
+ lastPluginState = _ref8$.pluginState,
465
+ lastState = _ref8$.state,
466
+ lastColorScheme = _ref8$.colorScheme,
467
+ lastIntl = _ref8$.intl,
468
+ lastActiveIndexPos = _ref8$.activeIndexPos,
469
+ lastIsInverted = _ref8$.isInverted,
470
+ lastDiffType = _ref8$.diffType,
471
+ lastHideDeletedDiffs = _ref8$.hideDeletedDiffs,
472
+ lastHideAddedDiffsUnderline = _ref8$.hideAddedDiffsUnderline,
473
+ lastShowIndicators = _ref8$.showIndicators,
474
+ lastSmartThresholds = _ref8$.smartThresholds,
475
+ lastDeletedDiffPlacement = _ref8$.deletedDiffPlacement,
476
+ lastInlineDeletedDiffPlacement = _ref8$.inlineDeletedDiffPlacement;
559
477
  var originalDocIsSame = lastPluginState.originalDoc && pluginState.originalDoc && pluginState.originalDoc.eq(lastPluginState.originalDoc);
560
478
  if (isExtendedEnabled(diffType)) {
561
- var _ref0;
562
- return (_ref0 = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement) !== null && _ref0 !== void 0 ? _ref0 : false;
479
+ var _ref9;
480
+ return (_ref9 = colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isInverted === lastIsInverted && diffType === lastDiffType && isEqual(activeIndexPos, lastActiveIndexPos) && originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && hideDeletedDiffs === lastHideDeletedDiffs && hideAddedDiffsUnderline === lastHideAddedDiffsUnderline && showIndicators === lastShowIndicators && isEqual(smartThresholds, lastSmartThresholds) && deletedDiffPlacement === lastDeletedDiffPlacement && inlineDeletedDiffPlacement === lastInlineDeletedDiffPlacement) !== null && _ref9 !== void 0 ? _ref9 : false;
563
481
  }
564
- return (_ref1 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref1 !== void 0 ? _ref1 : false;
482
+ return (_ref0 = originalDocIsSame && isEqual(pluginState.steps, lastPluginState.steps) && state.doc.eq(lastState.doc) && colorScheme === lastColorScheme && intl.locale === lastIntl.locale && isEqual(activeIndexPos, lastActiveIndexPos) && hideDeletedDiffs === lastHideDeletedDiffs) !== null && _ref0 !== void 0 ? _ref0 : false;
565
483
  });
@@ -401,127 +401,4 @@ export var diffBySteps = function diffBySteps(originalDoc, steps) {
401
401
  });
402
402
  }
403
403
  return mergeOverlappingByNewDocRange(changes);
404
- };
405
-
406
- /**
407
- * A fork of `diffBySteps` that returns changes grouped per step, rather than as a flat list.
408
- *
409
- * Why forked rather than refactoring `diffBySteps`:
410
- * - `diffBySteps` returns a flat `Change[]` and is consumed by the existing decoration path.
411
- * Changing its return shape would require threading per-step metadata through all callers,
412
- * adding complexity to a stable code path.
413
- * - The per-step grouping is only needed for the `platform_editor_diff_granular_extended` gate,
414
- * where we need to know how many granular changes a single step produced in order to decide
415
- * whether to suppress deleted decorations (threshold: > 3 granular changes per step).
416
- * - Keeping the two functions separate means each has a clear, focused contract and neither
417
- * accumulates the other's concerns. Shared logic (mapping helpers, `mergeOverlappingByNewDocRange`,
418
- * `shouldCheckGranularDiff`, etc.) is already extracted and reused by both.
419
- */
420
- export var getStepChanges = function getStepChanges(originalDoc, steps) {
421
- var result = [];
422
- var currentDoc = originalDoc;
423
- var successfulStepMaps = [];
424
- var rangedSteps = [];
425
- var _iterator4 = _createForOfIteratorHelper(steps),
426
- _step4;
427
- try {
428
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
429
- var step = _step4.value;
430
- var before = currentDoc;
431
- var stepResult = step.apply(currentDoc);
432
- if (stepResult.failed !== null || !stepResult.doc) {
433
- continue;
434
- }
435
- var stepMap = step.getMap();
436
- var rangeStep = step;
437
- if (typeof rangeStep.from === 'number' && typeof rangeStep.to === 'number') {
438
- rangedSteps.push({
439
- before: before,
440
- doc: stepResult.doc,
441
- from: rangeStep.from,
442
- to: rangeStep.to,
443
- mapIndex: successfulStepMaps.length,
444
- step: step,
445
- stepMap: stepMap
446
- });
447
- }
448
- successfulStepMaps.push(stepMap);
449
- currentDoc = stepResult.doc;
450
- }
451
- } catch (err) {
452
- _iterator4.e(err);
453
- } finally {
454
- _iterator4.f();
455
- }
456
- for (var _i2 = 0, _rangedSteps2 = rangedSteps; _i2 < _rangedSteps2.length; _i2++) {
457
- var rangedStep = _rangedSteps2[_i2];
458
- var originalToBeforeStep = createMapping(successfulStepMaps.slice(0, rangedStep.mapIndex));
459
- var beforeStepToOriginal = originalToBeforeStep.invert();
460
- var fromA = mapPosition(beforeStepToOriginal, rangedStep.from);
461
- var toA = mapPosition(beforeStepToOriginal, rangedStep.to);
462
- var fromAfterStep = rangedStep.stepMap.map(rangedStep.from, -1);
463
- var toAfterStep = rangedStep.stepMap.map(rangedStep.to, 1);
464
- var afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1));
465
- var fromB = mapPosition(afterStepToFinal, fromAfterStep);
466
- var toB = mapPosition(afterStepToFinal, toAfterStep);
467
- if (shouldCheckGranularDiff(rangedStep.step, rangedStep.before, rangedStep.from, rangedStep.to)) {
468
- var granularStepChanges = ChangeSet.create(rangedStep.before).addSteps(rangedStep.doc, [rangedStep.stepMap], null);
469
- var optimizedGranularStepChanges = optimizeChanges(simplifyChanges(granularStepChanges.changes, rangedStep.doc));
470
- var stepChanges = [];
471
- var _iterator5 = _createForOfIteratorHelper(optimizedGranularStepChanges),
472
- _step5;
473
- try {
474
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
475
- var granularChange = _step5.value;
476
- var expandedA = expandToWordBoundaries(rangedStep.before, granularChange.fromA, granularChange.toA);
477
- var expandedB = expandToWordBoundaries(rangedStep.doc, granularChange.fromB, granularChange.toB);
478
- var aLeftDelta = granularChange.fromA - expandedA.from;
479
- var aRightDelta = expandedA.to - granularChange.toA;
480
- var bLeftDelta = granularChange.fromB - expandedB.from;
481
- var bRightDelta = expandedB.to - granularChange.toB;
482
- var finalA = expandedA;
483
- var finalB = expandedB;
484
- if (aLeftDelta > bLeftDelta || aRightDelta > bRightDelta) {
485
- var extraLeft = Math.max(0, aLeftDelta - bLeftDelta);
486
- var extraRight = Math.max(0, aRightDelta - bRightDelta);
487
- finalB = expandToWordBoundaries(rangedStep.doc, Math.max(expandedB.from - extraLeft, 0), expandedB.to + extraRight);
488
- }
489
- if (bLeftDelta > aLeftDelta || bRightDelta > aRightDelta) {
490
- var _extraLeft2 = Math.max(0, bLeftDelta - aLeftDelta);
491
- var _extraRight2 = Math.max(0, bRightDelta - aRightDelta);
492
- finalA = expandToWordBoundaries(rangedStep.before, Math.max(expandedA.from - _extraLeft2, 0), expandedA.to + _extraRight2);
493
- }
494
- stepChanges.push({
495
- fromA: mapPosition(beforeStepToOriginal, finalA.from),
496
- toA: mapPosition(beforeStepToOriginal, finalA.to),
497
- fromB: mapPosition(afterStepToFinal, finalB.from),
498
- toB: mapPosition(afterStepToFinal, finalB.to),
499
- deleted: createSpans(Math.max(0, finalA.to - finalA.from)),
500
- inserted: createSpans(Math.max(0, finalB.to - finalB.from))
501
- });
502
- }
503
- } catch (err) {
504
- _iterator5.e(err);
505
- } finally {
506
- _iterator5.f();
507
- }
508
- result.push({
509
- isGranular: true,
510
- changes: mergeOverlappingByNewDocRange(stepChanges)
511
- });
512
- continue;
513
- }
514
- result.push({
515
- isGranular: false,
516
- changes: [{
517
- fromA: fromA,
518
- toA: toA,
519
- fromB: fromB,
520
- toB: toB,
521
- deleted: createSpans(Math.max(0, toA - fromA)),
522
- inserted: createSpans(Math.max(0, toB - fromB))
523
- }]
524
- });
525
- }
526
- return result;
527
404
  };
@@ -14,7 +14,7 @@ import { wrapBlockNodeView, injectInnerWrapper, createContentWrapper } from './u
14
14
  * that is not in the current document.
15
15
  */
16
16
  export var createNodeChangedDecorationWidget = function createNodeChangedDecorationWidget(_ref) {
17
- var _slice$content, _slice$content2, _slice$content3, _slice$content$firstC;
17
+ var _slice$content, _slice$content2, _slice$content3, _$safeInsertPos$nodeB, _slice$content$firstC;
18
18
  var change = _ref.change,
19
19
  doc = _ref.doc,
20
20
  nodeViewSerializer = _ref.nodeViewSerializer,
@@ -71,6 +71,14 @@ export var createNodeChangedDecorationWidget = function createNodeChangedDecorat
71
71
 
72
72
  // For non-table content, use the existing span wrapper approach
73
73
  var dom = document.createElement('span');
74
+ var $safeInsertPos = newDoc.resolve(safeInsertPos);
75
+ var isTopLevelInsert = $safeInsertPos.depth === 0;
76
+ var hasPreviousBlock = ((_$safeInsertPos$nodeB = $safeInsertPos.nodeBefore) === null || _$safeInsertPos$nodeB === void 0 ? void 0 : _$safeInsertPos$nodeB.isBlock) === true;
77
+ if (isExtendedEnabled(diffType) && isTopLevelInsert && hasPreviousBlock) {
78
+ // Editor CSS removes the top margin from a block when it is its parent's first child.
79
+ // Keep the serialized block as a subsequent child so each block type retains its own margin.
80
+ dom.append(dom.ownerDocument.createElement('div'));
81
+ }
74
82
  // When mediaSingle nodes are rendered inside a widget decoration (e.g. as part of
75
83
  // a replaced panel), the centering CSS (margin-left: 50%; transform: translateX(-50%))
76
84
  // incorrectly applies because isNestedNode resolves to false (getPos returns 0).
@@ -1,24 +1,4 @@
1
1
  import { type Change } from 'prosemirror-changeset';
2
2
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  import type { Step } from '@atlaskit/editor-prosemirror/transform';
4
- type StepChanges = {
5
- changes: Change[];
6
- isGranular: boolean;
7
- };
8
4
  export declare const diffBySteps: (originalDoc: PMNode, steps: Step[]) => Change[];
9
- /**
10
- * A fork of `diffBySteps` that returns changes grouped per step, rather than as a flat list.
11
- *
12
- * Why forked rather than refactoring `diffBySteps`:
13
- * - `diffBySteps` returns a flat `Change[]` and is consumed by the existing decoration path.
14
- * Changing its return shape would require threading per-step metadata through all callers,
15
- * adding complexity to a stable code path.
16
- * - The per-step grouping is only needed for the `platform_editor_diff_granular_extended` gate,
17
- * where we need to know how many granular changes a single step produced in order to decide
18
- * whether to suppress deleted decorations (threshold: > 3 granular changes per step).
19
- * - Keeping the two functions separate means each has a clear, focused contract and neither
20
- * accumulates the other's concerns. Shared logic (mapping helpers, `mergeOverlappingByNewDocRange`,
21
- * `shouldCheckGranularDiff`, etc.) is already extracted and reused by both.
22
- */
23
- export declare const getStepChanges: (originalDoc: PMNode, steps: Step[]) => StepChanges[];
24
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "12.1.1",
3
+ "version": "12.1.3",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,10 +27,10 @@
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": "^146.0.0",
31
- "@atlaskit/tokens": "^16.6.0",
30
+ "@atlaskit/tmp-editor-statsig": "^147.0.0",
31
+ "@atlaskit/tokens": "^16.7.0",
32
32
  "@babel/runtime": "^7.0.0",
33
- "@compiled/react": "^1.0.0",
33
+ "@compiled/react": "^1.0.2",
34
34
  "lodash": "^4.17.21",
35
35
  "memoize-one": "^6.0.0",
36
36
  "prosemirror-changeset": "^2.3.1"
@@ -40,7 +40,7 @@
40
40
  "@atlaskit/button": "^25.0.0",
41
41
  "@atlaskit/css": "^1.0.0",
42
42
  "@atlaskit/dropdown-menu": "^18.0.0",
43
- "@atlaskit/editor-core": "^223.0.0",
43
+ "@atlaskit/editor-core": "^223.1.0",
44
44
  "@atlaskit/editor-json-transformer": "^9.2.0",
45
45
  "@atlaskit/form": "^17.0.0",
46
46
  "@atlaskit/primitives": "^22.2.0",
@@ -53,7 +53,7 @@
53
53
  "react-intl": "^7.0.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@atlaskit/editor-common": "^118.5.0",
56
+ "@atlaskit/editor-common": "^118.10.0",
57
57
  "react": "^18.2.0 || ^19.2.0"
58
58
  },
59
59
  "techstack": {
@@ -1,125 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.createGranularBlockReferenceWidget = void 0;
7
- var _view = require("@atlaskit/editor-prosemirror/view");
8
- var _createAnchorDecorationWidgets = require("./createAnchorDecorationWidgets");
9
- var _decorationKeys = require("./decorationKeys");
10
- var _wrapBlockNodeView = require("./utils/wrapBlockNodeView");
11
- /**
12
- * Creates a single block widget that renders a reference text block content
13
- * beneath a granular diff set, for reference when deleted content is hidden.
14
- *
15
- * Since granular diffing only applies to singular isTextBlock=true nodes, this
16
- * widget always renders exactly one block node and does not need the slice/fragment
17
- * complexity of createNodeChangedDecorationWidget.
18
- *
19
- * Resolves which doc and positions to render based on isInverted:
20
- * - !isInverted: renders originalDoc at A-side positions (what was there before)
21
- * - isInverted: renders newDoc at B-side positions (the current/new content)
22
- *
23
- * The widget is always inserted at the B-side block boundary in newDoc since
24
- * ProseMirror decorations are always anchored against the live (new) document.
25
- */
26
- var createGranularBlockReferenceWidget = exports.createGranularBlockReferenceWidget = function createGranularBlockReferenceWidget(_ref) {
27
- var change = _ref.change,
28
- originalDoc = _ref.originalDoc,
29
- newDoc = _ref.newDoc,
30
- isInverted = _ref.isInverted,
31
- nodeViewSerializer = _ref.nodeViewSerializer,
32
- colorScheme = _ref.colorScheme,
33
- intl = _ref.intl,
34
- activeIndexPos = _ref.activeIndexPos,
35
- diffId = _ref.diffId,
36
- _ref$showIndicators = _ref.showIndicators,
37
- showIndicators = _ref$showIndicators === void 0 ? false : _ref$showIndicators;
38
- // Determine which doc and positions to use for rendering the reference block.
39
- var renderDoc = isInverted ? newDoc : originalDoc;
40
- var renderTo = isInverted ? change.toB : change.toA;
41
-
42
- // The insertion point is always in newDoc — ProseMirror decorates against the live document.
43
- // Walk up from toB to find the enclosing text block boundary for widget placement.
44
- var insertResolvedPos = newDoc.resolve(change.toB);
45
- var blockEnd = change.toB;
46
- for (var depth = insertResolvedPos.depth; depth >= 0; depth--) {
47
- var node = insertResolvedPos.node(depth);
48
- if (node.isTextblock) {
49
- blockEnd = insertResolvedPos.start(depth) + node.nodeSize - 1;
50
- break;
51
- }
52
- }
53
-
54
- // Find the block node to render from the render doc at the render positions.
55
- var renderResolvedPos = renderDoc.resolve(renderTo);
56
- var renderBlockNode = null;
57
- for (var _depth = renderResolvedPos.depth; _depth >= 0; _depth--) {
58
- var _node = renderResolvedPos.node(_depth);
59
- if (_node.isTextblock) {
60
- renderBlockNode = _node;
61
- break;
62
- }
63
- }
64
- if (!renderBlockNode) {
65
- return [];
66
- }
67
- var isActive = activeIndexPos && change.fromB === activeIndexPos.from && change.toB === activeIndexPos.to;
68
-
69
- /**
70
- * This will always be a block node as it's a textBlock-like node.
71
- * We use div instead of span so we can add margins.
72
- */
73
- var dom = document.createElement('div');
74
- dom.setAttribute('data-testid', 'show-diff-granular-block-reference');
75
- dom.style.setProperty('margin-top', "var(--ds-space-200, 16px)");
76
- var nodeView = nodeViewSerializer.tryCreateNodeView(renderBlockNode);
77
- if (nodeView) {
78
- (0, _wrapBlockNodeView.wrapBlockNodeView)({
79
- dom: dom,
80
- nodeView: nodeView,
81
- targetNode: renderBlockNode,
82
- colorScheme: colorScheme,
83
- intl: intl,
84
- isActive: !!isActive,
85
- isInserted: false
86
- });
87
- } else {
88
- var serialized = nodeViewSerializer.serializeNode(renderBlockNode);
89
- if (serialized && serialized instanceof HTMLElement) {
90
- (0, _wrapBlockNodeView.injectInnerWrapper)({
91
- node: serialized,
92
- colorScheme: colorScheme,
93
- isActive: !!isActive,
94
- isInserted: false
95
- });
96
- dom.append(serialized);
97
- } else if (serialized) {
98
- dom.append(serialized);
99
- }
100
- }
101
- if (dom.childNodes.length === 0) {
102
- return [];
103
- }
104
- var decorations = [];
105
- if (showIndicators) {
106
- var leftAnchor = (0, _createAnchorDecorationWidgets.createLeftAnchorWidget)({
107
- doc: newDoc,
108
- from: blockEnd,
109
- diffId: diffId
110
- });
111
- dom.style.setProperty('anchor-name', "--".concat((0, _decorationKeys.buildAnchorDecorationKey)({
112
- diffId: diffId
113
- })));
114
- if (leftAnchor) {
115
- decorations.push(leftAnchor);
116
- }
117
- }
118
- decorations.push(_view.Decoration.widget(blockEnd, dom, (0, _decorationKeys.buildDiffDecorationSpec)({
119
- decorationType: 'widget',
120
- diffId: diffId,
121
- // We want it to be as close to the granular diff as possible
122
- side: -999
123
- })));
124
- return decorations;
125
- };