@createiq/htmldiff 1.2.0-beta.2 → 1.2.0-beta.4
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 +33 -19
- package/dist/HtmlDiff.cjs.map +1 -1
- package/dist/HtmlDiff.d.cts +11 -16
- package/dist/HtmlDiff.d.mts +11 -16
- package/dist/HtmlDiff.mjs +33 -19
- package/dist/HtmlDiff.mjs.map +1 -1
- package/package.json +1 -1
- package/src/HtmlDiff.ts +23 -25
- package/src/ThreeWayTable.ts +49 -4
- package/test/HtmlDiff.threeWay.spec.ts +47 -2
- package/test/HtmlDiff.threeWay.tables.spec.ts +74 -0
- package/test/Utils.spec.ts +3 -3
package/dist/HtmlDiff.d.cts
CHANGED
|
@@ -202,22 +202,6 @@ declare class HtmlDiff {
|
|
|
202
202
|
* why symmetry matters.
|
|
203
203
|
*/
|
|
204
204
|
static evaluateProjectionApplicability(oldText: string, newText: string): boolean;
|
|
205
|
-
/**
|
|
206
|
-
* Three-way HTML diff. Given V1 (the version Me last sent), V2 (the
|
|
207
|
-
* version CP sent back), and V3 (Me's current draft), produces a
|
|
208
|
-
* single attributed HTML output where CP's and Me's changes are
|
|
209
|
-
* distinguished by `data-author` ('cp' or 'me') and matching
|
|
210
|
-
* `class='diffins cp'` / `class='diffdel me'` etc. The "Me rejected
|
|
211
|
-
* CP's proposal" case (Me deleted text CP had inserted) gets a
|
|
212
|
-
* dedicated marker: `data-rejects='cp'` plus `class='... rejects-cp'`.
|
|
213
|
-
*
|
|
214
|
-
* Coordinates the symmetric-projection decision (D1) across both
|
|
215
|
-
* internal `analyze` calls so V2 tokenises identically on each side
|
|
216
|
-
* of the spine. When `useProjections` is left undefined, the decision
|
|
217
|
-
* is the conjunction of both pair-wise heuristics — project iff both
|
|
218
|
-
* pairs would project on their own. Pass an explicit boolean to
|
|
219
|
-
* override.
|
|
220
|
-
*/
|
|
221
205
|
/**
|
|
222
206
|
* Three-way HTML diff against a shared genesis. Produces attributed
|
|
223
207
|
* HTML that distinguishes CP's accumulated changes (genesis → cpLatest)
|
|
@@ -245,6 +229,17 @@ declare class HtmlDiff {
|
|
|
245
229
|
* buffer. Reusing the instance keeps the formatting-tag stack
|
|
246
230
|
* (`specialTagDiffStack`) coherent across segments — a `<strong>`
|
|
247
231
|
* opened in one segment and closed in another stays balanced.
|
|
232
|
+
*
|
|
233
|
+
* Edge case: an ins/del segment can open a formatting wrap whose
|
|
234
|
+
* matching closer ends up in an equal segment (`<strong>` deleted
|
|
235
|
+
* by CP but `</strong>` kept by both — buildSegments emits the open
|
|
236
|
+
* as del-cp and the close as equal). Equal segments bypass
|
|
237
|
+
* `insertTag` and push raw, so the stack entry for the open is
|
|
238
|
+
* never popped. Rather than throw — which forces the caller's UI
|
|
239
|
+
* into an error boundary — close every leftover wrap with `</ins>`
|
|
240
|
+
* at the end of emission. The resulting HTML has an extra
|
|
241
|
+
* `</ins>` next to the formatting closer; DOMParser-normalisation
|
|
242
|
+
* downstream produces sensible nesting.
|
|
248
243
|
*/
|
|
249
244
|
private static emitSegments;
|
|
250
245
|
/**
|
package/dist/HtmlDiff.d.mts
CHANGED
|
@@ -202,22 +202,6 @@ declare class HtmlDiff {
|
|
|
202
202
|
* why symmetry matters.
|
|
203
203
|
*/
|
|
204
204
|
static evaluateProjectionApplicability(oldText: string, newText: string): boolean;
|
|
205
|
-
/**
|
|
206
|
-
* Three-way HTML diff. Given V1 (the version Me last sent), V2 (the
|
|
207
|
-
* version CP sent back), and V3 (Me's current draft), produces a
|
|
208
|
-
* single attributed HTML output where CP's and Me's changes are
|
|
209
|
-
* distinguished by `data-author` ('cp' or 'me') and matching
|
|
210
|
-
* `class='diffins cp'` / `class='diffdel me'` etc. The "Me rejected
|
|
211
|
-
* CP's proposal" case (Me deleted text CP had inserted) gets a
|
|
212
|
-
* dedicated marker: `data-rejects='cp'` plus `class='... rejects-cp'`.
|
|
213
|
-
*
|
|
214
|
-
* Coordinates the symmetric-projection decision (D1) across both
|
|
215
|
-
* internal `analyze` calls so V2 tokenises identically on each side
|
|
216
|
-
* of the spine. When `useProjections` is left undefined, the decision
|
|
217
|
-
* is the conjunction of both pair-wise heuristics — project iff both
|
|
218
|
-
* pairs would project on their own. Pass an explicit boolean to
|
|
219
|
-
* override.
|
|
220
|
-
*/
|
|
221
205
|
/**
|
|
222
206
|
* Three-way HTML diff against a shared genesis. Produces attributed
|
|
223
207
|
* HTML that distinguishes CP's accumulated changes (genesis → cpLatest)
|
|
@@ -245,6 +229,17 @@ declare class HtmlDiff {
|
|
|
245
229
|
* buffer. Reusing the instance keeps the formatting-tag stack
|
|
246
230
|
* (`specialTagDiffStack`) coherent across segments — a `<strong>`
|
|
247
231
|
* opened in one segment and closed in another stays balanced.
|
|
232
|
+
*
|
|
233
|
+
* Edge case: an ins/del segment can open a formatting wrap whose
|
|
234
|
+
* matching closer ends up in an equal segment (`<strong>` deleted
|
|
235
|
+
* by CP but `</strong>` kept by both — buildSegments emits the open
|
|
236
|
+
* as del-cp and the close as equal). Equal segments bypass
|
|
237
|
+
* `insertTag` and push raw, so the stack entry for the open is
|
|
238
|
+
* never popped. Rather than throw — which forces the caller's UI
|
|
239
|
+
* into an error boundary — close every leftover wrap with `</ins>`
|
|
240
|
+
* at the end of emission. The resulting HTML has an extra
|
|
241
|
+
* `</ins>` next to the formatting closer; DOMParser-normalisation
|
|
242
|
+
* downstream produces sensible nesting.
|
|
248
243
|
*/
|
|
249
244
|
private static emitSegments;
|
|
250
245
|
/**
|
package/dist/HtmlDiff.mjs
CHANGED
|
@@ -1580,7 +1580,7 @@ function preprocessByContent(genesis, cpLatest, meCurrent, gTables, cTables, mTa
|
|
|
1580
1580
|
placeholderToDiff
|
|
1581
1581
|
};
|
|
1582
1582
|
}
|
|
1583
|
-
const POSITIONAL_PAIR_SIMILARITY_THRESHOLD = .
|
|
1583
|
+
const POSITIONAL_PAIR_SIMILARITY_THRESHOLD = .15;
|
|
1584
1584
|
function positionallyAligned(genesis, cpLatest, meCurrent, gTables, cTables, mTables) {
|
|
1585
1585
|
if (gTables.length !== cTables.length || cTables.length !== mTables.length) return false;
|
|
1586
1586
|
for (let i = 0; i < gTables.length; i++) {
|
|
@@ -1695,7 +1695,20 @@ function emitPreservedRow(genesis, cpLatest, meCurrent, rG, rC, rM, cellDiff) {
|
|
|
1695
1695
|
out.push(genesis.slice(cursor, rG.rowEnd));
|
|
1696
1696
|
return out.join("");
|
|
1697
1697
|
}
|
|
1698
|
-
|
|
1698
|
+
const cpRestructured = rC.cells.length !== rG.cells.length;
|
|
1699
|
+
const meRestructured = rM.cells.length !== rG.cells.length;
|
|
1700
|
+
const blocks = [];
|
|
1701
|
+
if (cpRestructured && meRestructured) {
|
|
1702
|
+
blocks.push(emitFullRowAttributed(cpLatest, rC, "ins", "cp"));
|
|
1703
|
+
blocks.push(emitFullRowAttributed(meCurrent, rM, "ins", "me"));
|
|
1704
|
+
} else if (cpRestructured) {
|
|
1705
|
+
blocks.push(emitFullRowAttributed(genesis, rG, "del", "cp"));
|
|
1706
|
+
blocks.push(emitFullRowAttributed(cpLatest, rC, "ins", "cp"));
|
|
1707
|
+
} else {
|
|
1708
|
+
blocks.push(emitFullRowAttributed(genesis, rG, "del", "me"));
|
|
1709
|
+
blocks.push(emitFullRowAttributed(meCurrent, rM, "ins", "me"));
|
|
1710
|
+
}
|
|
1711
|
+
return blocks.join("");
|
|
1699
1712
|
}
|
|
1700
1713
|
/**
|
|
1701
1714
|
* Returns map "genesis-row-boundary → list of new-side row indices
|
|
@@ -2191,22 +2204,6 @@ var HtmlDiff = class HtmlDiff {
|
|
|
2191
2204
|
return HtmlDiff.shouldUseContentProjections(oldWords, newWords, oldProj, newProj);
|
|
2192
2205
|
}
|
|
2193
2206
|
/**
|
|
2194
|
-
* Three-way HTML diff. Given V1 (the version Me last sent), V2 (the
|
|
2195
|
-
* version CP sent back), and V3 (Me's current draft), produces a
|
|
2196
|
-
* single attributed HTML output where CP's and Me's changes are
|
|
2197
|
-
* distinguished by `data-author` ('cp' or 'me') and matching
|
|
2198
|
-
* `class='diffins cp'` / `class='diffdel me'` etc. The "Me rejected
|
|
2199
|
-
* CP's proposal" case (Me deleted text CP had inserted) gets a
|
|
2200
|
-
* dedicated marker: `data-rejects='cp'` plus `class='... rejects-cp'`.
|
|
2201
|
-
*
|
|
2202
|
-
* Coordinates the symmetric-projection decision (D1) across both
|
|
2203
|
-
* internal `analyze` calls so V2 tokenises identically on each side
|
|
2204
|
-
* of the spine. When `useProjections` is left undefined, the decision
|
|
2205
|
-
* is the conjunction of both pair-wise heuristics — project iff both
|
|
2206
|
-
* pairs would project on their own. Pass an explicit boolean to
|
|
2207
|
-
* override.
|
|
2208
|
-
*/
|
|
2209
|
-
/**
|
|
2210
2207
|
* Three-way HTML diff against a shared genesis. Produces attributed
|
|
2211
2208
|
* HTML that distinguishes CP's accumulated changes (genesis → cpLatest)
|
|
2212
2209
|
* from Me's accumulated changes (genesis → meCurrent). Use this for
|
|
@@ -2253,6 +2250,17 @@ var HtmlDiff = class HtmlDiff {
|
|
|
2253
2250
|
* buffer. Reusing the instance keeps the formatting-tag stack
|
|
2254
2251
|
* (`specialTagDiffStack`) coherent across segments — a `<strong>`
|
|
2255
2252
|
* opened in one segment and closed in another stays balanced.
|
|
2253
|
+
*
|
|
2254
|
+
* Edge case: an ins/del segment can open a formatting wrap whose
|
|
2255
|
+
* matching closer ends up in an equal segment (`<strong>` deleted
|
|
2256
|
+
* by CP but `</strong>` kept by both — buildSegments emits the open
|
|
2257
|
+
* as del-cp and the close as equal). Equal segments bypass
|
|
2258
|
+
* `insertTag` and push raw, so the stack entry for the open is
|
|
2259
|
+
* never popped. Rather than throw — which forces the caller's UI
|
|
2260
|
+
* into an error boundary — close every leftover wrap with `</ins>`
|
|
2261
|
+
* at the end of emission. The resulting HTML has an extra
|
|
2262
|
+
* `</ins>` next to the formatting closer; DOMParser-normalisation
|
|
2263
|
+
* downstream produces sensible nesting.
|
|
2256
2264
|
*/
|
|
2257
2265
|
static emitSegments(segments) {
|
|
2258
2266
|
const emitter = new HtmlDiff("", "");
|
|
@@ -2264,7 +2272,13 @@ var HtmlDiff = class HtmlDiff {
|
|
|
2264
2272
|
const { tag, baseClass, metadata } = segmentEmissionShape(seg.attr);
|
|
2265
2273
|
emitter.insertTag(tag, baseClass, [...seg.words], metadata);
|
|
2266
2274
|
}
|
|
2267
|
-
if (emitter.specialTagDiffStack.length > 0)
|
|
2275
|
+
if (emitter.specialTagDiffStack.length > 0) {
|
|
2276
|
+
console.warn(`HtmlDiff.executeThreeWay: emission left ${emitter.specialTagDiffStack.length} unclosed formatting wrap(s) on the stack. Closing defensively. This usually means a formatting tag opens in a del/ins segment and its matching closer is in an equal segment.`);
|
|
2277
|
+
while (emitter.specialTagDiffStack.length > 0) {
|
|
2278
|
+
emitter.content.push("</ins>");
|
|
2279
|
+
emitter.specialTagDiffStack.pop();
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2268
2282
|
return emitter.content.join("");
|
|
2269
2283
|
}
|
|
2270
2284
|
/**
|