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