@egainoss/htmldiff-ts
v1.0.9
Published
Revamped version of htmldiff-js. Display diff between two html strings.
Maintainers
Readme
htmldiff-ts
This is a fork of htmldiff-js, which is a JavaScript port of HtmlDiff.NET, which is itself a C# port of the Ruby implementation, HtmlDiff.
NPM package: @wesley-edwards/htmldiff-ts
Changes in 1.0.9
Version 1.0.9 fixes a regression from 1.0.8 where deleted content lost its bold and italic formatting. On the delete side, insertTag was replacing every <strong> / <em> with <del class="mod">, so diffing an article against an empty article stripped all 271 formatting tags from the output.
Fix
Delete-side tag runs are now emitted verbatim. Only the insert side uses renderTagRun to wrap formatting changes with <ins class="mod">. Deleted bold text now renders as <strong><del class="diffdel">text</del></strong> instead of <del class="mod"><del class="diffdel">text</del></del>.
Changes in 1.0.8
Version 1.0.8 fixes a leaking <strong> (or any inline formatting tag) that made the rest of the document render as bold. It showed up most clearly when diffing an article against an empty article, where every word becomes a delete.
Root cause
insertTag only inspected the first token of a run of consecutive tags. A formatting tag that appeared later in the run (for example the </strong> in </a></strong>, or the <strong> in <li><strong>) was written to the output verbatim and never pushed to or popped from specialTagDiffStack. The stack drifted out of sync, so a later </strong> matched a stale entry and was replaced by </del> instead of being written out, leaving its <strong> open for the rest of the document.
Fixes (insertTag)
| Change | Why |
|--------|-----|
| Walk every tag in a run (renderTagRun) instead of only words[0] | Keeps specialTagDiffStack in sync with what is actually written to the output |
| Derive formatting tags from FORMATTING_TAGS in Utils instead of a separate regex and closing-tag map | Removes the stateful /g regex and the missing </sup> entry |
| Emit the remaining mod wrappers at the end of insertTag | A formatting element can open in one operation and close in another; the output now stays balanced |
Formatting-only changes
coalesceWrapperOperations now also coalesces formatting tags, not just semantic wrappers such as eg-condition. Adding or removing only the formatting around unchanged text (for example <strong>First option:</strong> → First option:) is split into delete / equal / delete operations, which previously emitted an empty <del class="mod"> plus an orphan </strong>. Those three operations are now coalesced into a single replace, so they render through the format-change highlights added in 1.0.5.
Changes in 1.0.7
Version 1.0.7 fixes corrupted diffs when only block-level tag attributes change (for example <table> gaining class="confluenceTable wrapped" or <div> gaining panel wrapper classes). Block and structural elements now receive a data-diff="attrmod" marker on the changed opening tag instead of being wrapped in <ins class="diffmod"> / <del class="diffmod">, which previously produced invalid HTML and caused the browser to highlight most of the document.
Inline and semantic elements (such as eg-condition) still use the existing <ins class="diffmod"> wrap behaviour from 1.0.4.
Changes in 1.0.6
Version 1.0.6 fixes invisible diffs when plain text is wrapped or unwrapped in semantic tags such as eg-condition. Wrapper insert/delete operations are coalesced and rendered with structure-change highlights instead of emitting raw tags.
Changes in 1.0.5
Version 1.0.5 fixes noisy diffs for formatting-only inline changes (for example bolding one character in test → t<strong>e</strong>st). When plain text is unchanged, processReplaceOperation now renders format-change highlights instead of del.diffmod/ins.diffmod fragmentation.
Changes in 1.0.4
Version 1.0.4 fixes invisible diffs when only HTML tag attributes change (for example eg-condition tags="..." updates). Matching still strips attributes to find structural anchors, but processEqualOperation now detects attribute-only tag differences and wraps the full new element subtree in <ins class="diffmod"> without a <del> wrapper.
Changes in 1.0.3
Version 1.0.3 fixes incorrect handling of inline formatting tags (<strong>, <em>, <b>, <i>, etc.) inside insertTag in src/Diff.js. The bug showed up when a replace operation ran a delete pass followed by an insert pass on the same region (for example, collapsing a nested list step into flat list text). The diff could emit unbalanced markup such as </strong></li><strong>, which made the rest of the document render as bold.
Root cause
specialTagDiffStack tracked open formatting tags while wrapping changed text with <ins class="mod"> / <del class="mod">. The stack was shared across the delete and insert halves of a replace, and closing tags were popped even when they did not match the stack top. On delete, closing tags were sometimes removed from the token stream without being written to the output.
Fixes (insertTag)
| Change | Why |
|--------|-----|
| Reset specialTagDiffStack at the start of each insertTag call | Prevents state from the delete pass leaking into the insert pass on replace operations |
| Use <del class="mod"> / </del> for delete and <ins class="mod"> / </ins> for insert | Mod wrappers must match the operation type; previously both used <ins class="mod"> |
| Peek the stack top and only pop when the closing tag matches | Avoids dropping </strong> (and similar) on mismatch |
| On delete, strip closing formatting tags only after a successful stack match | Keeps needed closing tags in the output when the stack does not match |
Build
npm install
npm run buildThe package entry point is dist/htmldiff.min.js. Use Node 14 when building this project (see package.json scripts).
