@createiq/htmldiff 1.2.0-beta.7 → 1.2.0-beta.9

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/dist/HtmlDiff.mjs CHANGED
@@ -1333,13 +1333,31 @@ function collectInsertionsKeyedByEnd(d) {
1333
1333
  return out;
1334
1334
  }
1335
1335
  /**
1336
- * Emit any insertions at boundary `b`. When both authors inserted at
1337
- * the same boundary AND the inserted token sequences are textually
1338
- * identical, the insertion is treated as agreed and emitted unmarked.
1339
- * Otherwise each side's insertion is emitted with author attribution.
1336
+ * Emit any insertions at boundary `b`. Three cases:
1340
1337
  *
1341
- * The CP-then-Me ordering for disagreement is arbitrary but consistent;
1342
- * callers don't depend on it.
1338
+ * 1. One side inserted, the other didn't emit that side's tokens
1339
+ * with author attribution.
1340
+ * 2. Both sides inserted the EXACT same sequence → settled, emit
1341
+ * unmarked.
1342
+ * 3. Both sides inserted overlapping but different sequences (the
1343
+ * common case: one author accepted the other's insertion and
1344
+ * edited it, so e.g. cp's "X Y Z" overlaps me's "X Y a Z" with
1345
+ * "a" being a one-author-only addition). Run an LCS sub-diff
1346
+ * between the two insertion sequences and emit:
1347
+ * - tokens in BOTH → settled (equal segment)
1348
+ * - tokens only in cp → ins-cp
1349
+ * - tokens only in me → ins-me
1350
+ * The order of emission preserves the natural reading flow of
1351
+ * the merged insertion — common tokens read where they appear,
1352
+ * with author-only deltas inserted in their LCS-determined
1353
+ * positions.
1354
+ *
1355
+ * Without this sub-alignment, real-world flows like "Me added 'add
1356
+ * more things here', CP accepted minus 'things'" would render as two
1357
+ * full redundant insertions (`<ins cp>add more here</ins><ins me>add
1358
+ * more things here</ins>`) rather than the obvious single shared
1359
+ * insertion with a me-only "things" word — confusing to read and a
1360
+ * regression vs Word's track-changes UX.
1343
1361
  */
1344
1362
  function emitBoundary(b, cpInsAt, meInsAt, _cpDiffWords, _meDiffWords, segments) {
1345
1363
  const cpIns = cpInsAt.get(b);
@@ -1347,18 +1365,34 @@ function emitBoundary(b, cpInsAt, meInsAt, _cpDiffWords, _meDiffWords, segments)
1347
1365
  const hasCp = !!cpIns && cpIns.length > 0;
1348
1366
  const hasMe = !!meIns && meIns.length > 0;
1349
1367
  if (!hasCp && !hasMe) return;
1350
- if (hasCp && hasMe && tokenArraysEqual(cpIns, meIns)) {
1368
+ if (!hasCp) {
1369
+ appendSegment(segments, {
1370
+ kind: "ins",
1371
+ author: "me"
1372
+ }, meIns);
1373
+ return;
1374
+ }
1375
+ if (!hasMe) {
1376
+ appendSegment(segments, {
1377
+ kind: "ins",
1378
+ author: "cp"
1379
+ }, cpIns);
1380
+ return;
1381
+ }
1382
+ if (tokenArraysEqual(cpIns, meIns)) {
1351
1383
  appendSegment(segments, { kind: "equal" }, cpIns);
1352
1384
  return;
1353
1385
  }
1354
- if (hasCp) appendSegment(segments, {
1386
+ const alignment = lcsAlign(cpIns, meIns);
1387
+ for (const a of alignment) if (a.oldIdx !== null && a.newIdx !== null) appendSegment(segments, { kind: "equal" }, [cpIns[a.oldIdx]]);
1388
+ else if (a.oldIdx !== null) appendSegment(segments, {
1355
1389
  kind: "ins",
1356
1390
  author: "cp"
1357
- }, cpIns);
1358
- if (hasMe) appendSegment(segments, {
1391
+ }, [cpIns[a.oldIdx]]);
1392
+ else if (a.newIdx !== null) appendSegment(segments, {
1359
1393
  kind: "ins",
1360
1394
  author: "me"
1361
- }, meIns);
1395
+ }, [meIns[a.newIdx]]);
1362
1396
  }
1363
1397
  function tokenArraysEqual(a, b) {
1364
1398
  if (a.length !== b.length) return false;
@@ -2751,6 +2785,17 @@ var HtmlDiff = class HtmlDiff {
2751
2785
  curr = next;
2752
2786
  continue;
2753
2787
  }
2788
+ let allTags = true;
2789
+ for (let i = curr.startInNew; i < curr.endInNew; i++) if (!Utils_default.isTag(wordsForDiffNew[i])) {
2790
+ allTags = false;
2791
+ break;
2792
+ }
2793
+ if (allTags) {
2794
+ yield curr;
2795
+ prev = curr;
2796
+ curr = next;
2797
+ continue;
2798
+ }
2754
2799
  let oldDistanceInChars = 0;
2755
2800
  for (let i = prev.endInOld; i < next.startInOld; i++) oldDistanceInChars += wordsForDiffOld[i].length;
2756
2801
  let newDistanceInChars = 0;