@createiq/htmldiff 1.0.1 → 1.0.2
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/.editorconfig +9 -0
- package/.gitlab-ci.yml +82 -9
- package/.husky/pre-commit +1 -0
- package/README.md +138 -3
- package/biome.json +14 -2
- package/dist/HtmlDiff.cjs +53 -19
- package/dist/HtmlDiff.cjs.map +1 -1
- package/dist/HtmlDiff.d.cts +1 -0
- package/dist/HtmlDiff.d.ts +1 -0
- package/dist/HtmlDiff.js +53 -19
- package/dist/HtmlDiff.js.map +1 -1
- package/package.json +18 -13
- package/src/HtmlDiff.ts +41 -20
- package/src/MatchFinder.ts +10 -5
- package/src/Utils.ts +1 -1
- package/src/WordSplitter.ts +1 -1
- package/test/HtmlDiff.spec.ts +14 -1
- package/test/expected.html +4207 -0
- package/test/input1.html +4205 -0
- package/test/input2.html +4206 -0
package/.editorconfig
ADDED
package/.gitlab-ci.yml
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
stages:
|
|
2
2
|
- test
|
|
3
3
|
- deploy
|
|
4
|
+
- generate_artifacts
|
|
4
5
|
|
|
5
6
|
default:
|
|
6
7
|
interruptible: true
|
|
7
8
|
|
|
8
9
|
"Run unit tests":
|
|
9
10
|
stage: test
|
|
10
|
-
image: ${
|
|
11
|
+
image: ${ACR_HOSTNAME}/infra/baseimages/node:22-alpine
|
|
12
|
+
before_script:
|
|
13
|
+
- npm ci --prefer-offline --no-audit
|
|
11
14
|
script:
|
|
12
|
-
- npm i
|
|
13
15
|
- VITEST_MAX_THREADS=4 npm run test:ci -- --coverage
|
|
14
16
|
allow_failure: false
|
|
15
17
|
needs: []
|
|
@@ -26,12 +28,14 @@ default:
|
|
|
26
28
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
27
29
|
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_TRIGGERED != "true"
|
|
28
30
|
- if: $CI_COMMIT_BRANCH == "prod" && $CI_PIPELINE_TRIGGERED != "true"
|
|
31
|
+
tags: ["kluster:aks-ciqi-eun"]
|
|
29
32
|
|
|
30
33
|
"Lint":
|
|
31
34
|
stage: test
|
|
32
|
-
image: ${
|
|
35
|
+
image: ${ACR_HOSTNAME}/infra/baseimages/node:22-alpine
|
|
36
|
+
before_script:
|
|
37
|
+
- npm ci --prefer-offline --no-audit
|
|
33
38
|
script:
|
|
34
|
-
- npm i
|
|
35
39
|
- npm run lint
|
|
36
40
|
- npm run format
|
|
37
41
|
allow_failure: false
|
|
@@ -40,32 +44,101 @@ default:
|
|
|
40
44
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
41
45
|
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_TRIGGERED != "true"
|
|
42
46
|
- if: $CI_COMMIT_BRANCH == "prod" && $CI_PIPELINE_TRIGGERED != "true"
|
|
47
|
+
tags: ["kluster:aks-ciqi-eun"]
|
|
43
48
|
|
|
49
|
+
# TODO this runs on gke01-london for firewall reasons to be able to talk to registry.npmjs.org
|
|
44
50
|
"Publish to npm - dry-run":
|
|
45
51
|
stage: deploy
|
|
46
52
|
image: ${CI_REGISTRY}/infra/baseimages/node:22-alpine
|
|
53
|
+
before_script:
|
|
54
|
+
- npm ci --prefer-offline --no-audit
|
|
47
55
|
script:
|
|
48
|
-
- npm i
|
|
49
56
|
- npm run build
|
|
50
57
|
# Use the default registry for publishing
|
|
51
58
|
- npm --verbose config --global delete registry
|
|
52
|
-
-
|
|
59
|
+
- npm publish --access public --dry-run
|
|
53
60
|
allow_failure: false
|
|
54
61
|
# No needs, only run if the test stage passes
|
|
55
62
|
rules:
|
|
56
63
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
57
64
|
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_TRIGGERED != "true"
|
|
65
|
+
tags: ["kluster:gke01-london"]
|
|
58
66
|
|
|
67
|
+
# TODO this runs on gke01-london for firewall reasons to be able to talk to registry.npmjs.org
|
|
59
68
|
"Publish to npm":
|
|
60
69
|
stage: deploy
|
|
61
70
|
image: ${CI_REGISTRY}/infra/baseimages/node:22-alpine
|
|
71
|
+
before_script:
|
|
72
|
+
- npm ci --prefer-offline --no-audit
|
|
62
73
|
script:
|
|
63
|
-
- npm i
|
|
64
74
|
- npm run build
|
|
65
75
|
# Use the default registry for publishing
|
|
66
76
|
- npm --verbose config --global delete registry
|
|
67
|
-
-
|
|
77
|
+
- npm publish --access public
|
|
68
78
|
allow_failure: false
|
|
69
79
|
# No needs, only run if the test stage passes
|
|
70
80
|
rules:
|
|
71
|
-
- if: $CI_COMMIT_BRANCH == "prod" && $CI_PIPELINE_TRIGGERED != "true"
|
|
81
|
+
- if: $CI_COMMIT_BRANCH == "prod" && $CI_PIPELINE_TRIGGERED != "true"
|
|
82
|
+
tags: ["kluster:gke01-london"]
|
|
83
|
+
|
|
84
|
+
.generate_sbom: &generate_sbom
|
|
85
|
+
stage: generate_artifacts
|
|
86
|
+
image: ${ACR_HOSTNAME}/infra/baseimages/node:22-alpine
|
|
87
|
+
before_script:
|
|
88
|
+
- npm ci --prefer-offline --no-audit
|
|
89
|
+
script:
|
|
90
|
+
- mkdir -p cyclonedx
|
|
91
|
+
- npm exec -- @cyclonedx/cyclonedx-npm --package-lock-only --omit dev --spec-version 1.6 --mc-type library --output-file cyclonedx/bom_${CI_COMMIT_SHORT_SHA}.json --validate
|
|
92
|
+
artifacts:
|
|
93
|
+
name: "sbom_htmldiff_$CI_COMMIT_SHORT_SHA"
|
|
94
|
+
paths:
|
|
95
|
+
- cyclonedx/*
|
|
96
|
+
expire_in: 1 month
|
|
97
|
+
tags: ["kluster:aks-ciqi-eun"]
|
|
98
|
+
|
|
99
|
+
.push_sbom_deptrack: &push_sbom_deptrack
|
|
100
|
+
stage: generate_artifacts
|
|
101
|
+
variables:
|
|
102
|
+
DTRACK_SERVER: https://dependencytrack.stag.a.nakhoda.ai
|
|
103
|
+
DTRACK_API_KEY: odt_DKxpr0Sx_TmAc11wSPPl1YphFxEuiwDPb1VmSRY7d
|
|
104
|
+
DTRACK_PROJECT: 2f456575-183e-4985-99f6-ad8767b86476
|
|
105
|
+
script:
|
|
106
|
+
- |-
|
|
107
|
+
cat <<EOF >payload.json
|
|
108
|
+
{
|
|
109
|
+
"project": "${DTRACK_PROJECT}",
|
|
110
|
+
"bom": "$(base64 --wrap=0 cyclonedx/bom_${CI_COMMIT_SHORT_SHA}.json)"
|
|
111
|
+
}
|
|
112
|
+
EOF
|
|
113
|
+
# Upload using a limited permission API key that can only upload SBOMs
|
|
114
|
+
- |
|
|
115
|
+
curl --fail-with-body -X "PUT" "${DTRACK_SERVER}/api/v1/bom" \
|
|
116
|
+
-H 'Content-Type: application/json' \
|
|
117
|
+
-H "X-API-Key: ${DTRACK_API_KEY}" \
|
|
118
|
+
-d @payload.json
|
|
119
|
+
tags: ["kluster:aks-ciqi-eun"]
|
|
120
|
+
|
|
121
|
+
"Generate SBOM":
|
|
122
|
+
<<: *generate_sbom
|
|
123
|
+
rules:
|
|
124
|
+
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_TRIGGERED != "true"
|
|
125
|
+
|
|
126
|
+
"Push SBOM to Dependency-Track":
|
|
127
|
+
<<: *push_sbom_deptrack
|
|
128
|
+
needs:
|
|
129
|
+
- "Generate SBOM"
|
|
130
|
+
rules:
|
|
131
|
+
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_TRIGGERED != "true"
|
|
132
|
+
|
|
133
|
+
# Don't need this on every build but useful for testing changes to the above
|
|
134
|
+
#"MR: Generate SBOM":
|
|
135
|
+
# <<: *generate_sbom
|
|
136
|
+
# rules:
|
|
137
|
+
# - if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
138
|
+
#
|
|
139
|
+
#"MR: Push SBOM to Dependency-Track":
|
|
140
|
+
# <<: *push_sbom_deptrack
|
|
141
|
+
# needs:
|
|
142
|
+
# - "MR: Generate SBOM"
|
|
143
|
+
# rules:
|
|
144
|
+
# - if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm exec -- lint-staged
|
package/README.md
CHANGED
|
@@ -1,5 +1,140 @@
|
|
|
1
|
-
##
|
|
1
|
+
## @createiq/htmldiff
|
|
2
2
|
|
|
3
|
-
A library for comparing two HTML
|
|
3
|
+
A library for comparing two HTML snippets and highlighting the differences using simple HTML.
|
|
4
4
|
|
|
5
|
-
This HTML Diff implementation is a TypeScript port of the C# port found [here](https://github.com/Rohland/htmldiff.net),
|
|
5
|
+
This HTML Diff implementation is a TypeScript port of the C# port found [here](https://github.com/Rohland/htmldiff.net),
|
|
6
|
+
which is a port of the ruby implementation found [here](https://github.com/myobie/htmldiff).
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
`npm install @createiq/htmldiff`
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
This is intended to be a drop-in replacement for the `htmldiff` npm library (https://github.com/dfoverdx/htmldiff-js),
|
|
15
|
+
and intentionally mirrors the structure and classes of the [HtmlDiff.NET](https://github.com/Rohland/htmldiff.net)
|
|
16
|
+
library that was initially ported, hence usage differing slightly from many JavaScript-ecosystem libraries.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import HtmlDiff from '@createiq/htmldiff'
|
|
20
|
+
|
|
21
|
+
const oldHtml = `<p>Some <em>old</em> html here</p>`
|
|
22
|
+
const newHtml = `<p>Some <b>new</b> html goes here</p>`
|
|
23
|
+
|
|
24
|
+
const diffHtml = HtmlDiff.execute(oldHtml, newHtml)
|
|
25
|
+
|
|
26
|
+
// Output: <p>Some <ins class='mod em'><del class='diffmod'>old</del></ins><b><ins class='mod b'><ins class='diffmod'>new</ins></ins></b> html <ins class='diffins'>goes </ins>here</p>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Configuring the diff output
|
|
30
|
+
|
|
31
|
+
There are a couple of options that can be set directly on the `HtmlDiff` instance, `HtmlDiff.execute` will always create
|
|
32
|
+
a new instance and use defaults, but for custom output you can use `new HtmlDiff(oldHtml, newHtml)` and set the options
|
|
33
|
+
directly:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import HtmlDiff from '@createiq/htmldiff'
|
|
37
|
+
|
|
38
|
+
const oldHtml = `<p>Some <em>old</em> html here</p>`
|
|
39
|
+
const newHtml = `<p>Some <b>new</b> html goes here</p>`
|
|
40
|
+
|
|
41
|
+
const diff = new HtmlDiff(oldHtml, newHtml)
|
|
42
|
+
diff.repeatingWordsAccuracy = 0.5
|
|
43
|
+
diff.ignoreWhitespaceDifferences = true
|
|
44
|
+
diff.orphanMatchThreshold = 0.2
|
|
45
|
+
|
|
46
|
+
const diffHtml = diff.build()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### HtmlDiff.execute(oldHtml, newHtml)
|
|
52
|
+
|
|
53
|
+
Returns a HTML diff from `oldHtml` to `newHtml` with the default options. This is exactly the same as calling
|
|
54
|
+
`new HtmlDiff(oldHtml, newHtml).build()`
|
|
55
|
+
|
|
56
|
+
### new HtmlDiff(oldHtml, newHtml)
|
|
57
|
+
|
|
58
|
+
Returns an HtmlDiff instance.
|
|
59
|
+
|
|
60
|
+
#### addBlockExpression(expression: RegExp)
|
|
61
|
+
|
|
62
|
+
Uses `expression` to group text together so that any change detected within the group is treated as a single block, for
|
|
63
|
+
example to keep dates together.
|
|
64
|
+
|
|
65
|
+
This **MUST** have the global flag (`/g`) enabled on the RegExp and must not overlap with other block expressions.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const oldHtml = 'This is a date 1 Jan 2016 that will change'
|
|
71
|
+
const newHtml = 'This is a date 22 Feb 2017 that did change'
|
|
72
|
+
|
|
73
|
+
const diff = new HtmlDiff(oldHtml, newHtml)
|
|
74
|
+
diff.addBlockExpression(/\d{1,2}\s*(Jan|Feb)\s*\d{4}/g)
|
|
75
|
+
|
|
76
|
+
const diffHtml = diff.build()
|
|
77
|
+
|
|
78
|
+
// Output: This is a date<del class='diffmod'> 1 Jan 2016</del><ins class='diffmod'> 22 Feb 2017</ins> that <del class='diffmod'>will</del><ins class='diffmod'>did</ins> change
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### .repeatingWordsAccuracy <sup>get/set</sup>
|
|
82
|
+
|
|
83
|
+
Type: `number`\
|
|
84
|
+
Default: `1.0`
|
|
85
|
+
|
|
86
|
+
Defines how to compare repeating words. Valid values are from 0 to 1. This value allows to exclude some words from
|
|
87
|
+
comparison that eventually reduces the total time of the diff algorithm.
|
|
88
|
+
|
|
89
|
+
- `0` means that all words are excluded so the diff will not find any matching words at all.
|
|
90
|
+
- `1` (default value) means that all words participate in comparison so this is the most accurate case.
|
|
91
|
+
- `0.5` means that any word that occurs more than 50% times may be excluded from comparison. This doesn't
|
|
92
|
+
mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.
|
|
93
|
+
|
|
94
|
+
#### .ignoreWhitespaceDifferences <sup>get/set</sup>
|
|
95
|
+
|
|
96
|
+
Type: `boolean`\
|
|
97
|
+
Default: `false`
|
|
98
|
+
|
|
99
|
+
If true all whitespaces are considered as equal
|
|
100
|
+
|
|
101
|
+
#### .orphanMatchThreshold <sup>get/set</sup>
|
|
102
|
+
|
|
103
|
+
Type: `number`\
|
|
104
|
+
Default: `0.0`
|
|
105
|
+
|
|
106
|
+
If some match is too small and located far from its neighbors then it is considered as orphan and removed. For example:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
aaaaa bb ccccccccc dddddd ee
|
|
110
|
+
11111 bb 222222222 dddddd ee
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
will find two matches `bb` and `dddddd ee` but the first will be considered as orphan and ignored, as result it will
|
|
114
|
+
consider texts `aaaaa bb ccccccccc` and `11111 bb 222222222` as single replacement:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<del>aaaaa bb ccccccccc</del><ins>11111 bb 222222222</ins> dddddd ee
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This property defines relative size of the match to be considered as orphan, from 0 to 1.
|
|
121
|
+
|
|
122
|
+
- `1` means that all matches will be considered as orphans.
|
|
123
|
+
- `0` (default) means that no match will be considered as orphan.
|
|
124
|
+
- `0.2` means that if match length is less than 20% of distance between its neighbors it is considered as orphan.
|
|
125
|
+
|
|
126
|
+
#### build()
|
|
127
|
+
|
|
128
|
+
Returns the diff from an HtmlDiff instance.
|
|
129
|
+
|
|
130
|
+
## Contributing
|
|
131
|
+
|
|
132
|
+
The library uses [Biome](https://biomejs.dev/) for linting and formatting, and [Vitest](https://vitest.dev/) for unit
|
|
133
|
+
tests and benchmarking. It's worth ensuring that you have appropriate plugins for your development environment,
|
|
134
|
+
particularly for Biome to avoid having to fix formatting issues late.
|
|
135
|
+
|
|
136
|
+
### Releasing
|
|
137
|
+
|
|
138
|
+
Merge requests to the `main` branch should be reviewed by the team as normal but will not release a new version of the
|
|
139
|
+
library to npm. This happens when merge requests are made to the `prod` branch, this should be an MR directly from
|
|
140
|
+
`main` to `prod` and **MUST** include a bump to the version in package.json satisfying semver.
|
package/biome.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "
|
|
2
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
|
3
3
|
"formatter": {
|
|
4
4
|
"enabled": true,
|
|
5
5
|
"formatWithErrors": false,
|
|
@@ -9,13 +9,25 @@
|
|
|
9
9
|
"lineWidth": 120,
|
|
10
10
|
"attributePosition": "auto"
|
|
11
11
|
},
|
|
12
|
-
"
|
|
12
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
13
13
|
"linter": {
|
|
14
14
|
"enabled": true,
|
|
15
15
|
"rules": {
|
|
16
16
|
"recommended": true,
|
|
17
17
|
"suspicious": {
|
|
18
18
|
"noConfusingVoidType": "off"
|
|
19
|
+
},
|
|
20
|
+
"style": {
|
|
21
|
+
"noParameterAssign": "error",
|
|
22
|
+
"useAsConstAssertion": "error",
|
|
23
|
+
"useDefaultParameterLast": "error",
|
|
24
|
+
"useEnumInitializers": "error",
|
|
25
|
+
"useSelfClosingElements": "error",
|
|
26
|
+
"useSingleVarDeclarator": "error",
|
|
27
|
+
"noUnusedTemplateLiteral": "error",
|
|
28
|
+
"useNumberNamespace": "error",
|
|
29
|
+
"noInferrableTypes": "error",
|
|
30
|
+
"noUselessElse": "error"
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
},
|
package/dist/HtmlDiff.cjs
CHANGED
|
@@ -68,7 +68,7 @@ var closingTagTexRegex = /^\s*<\/[^>]+>\s*$/;
|
|
|
68
68
|
var tagWordRegex = /<[^\s>]+/;
|
|
69
69
|
var whitespaceRegex = /^(\s| )+$/;
|
|
70
70
|
var wordRegex = /[\w#@]+/;
|
|
71
|
-
var tagRegex = /<\/?(?<name>[^\s
|
|
71
|
+
var tagRegex = /<\/?(?<name>[^\s/>]+)[^>]*>/;
|
|
72
72
|
var SpecialCaseWordTags = ["<img"];
|
|
73
73
|
function isTag(item) {
|
|
74
74
|
if (SpecialCaseWordTags.some((re) => item?.startsWith(re))) {
|
|
@@ -168,10 +168,10 @@ var MatchFinder = class _MatchFinder {
|
|
|
168
168
|
if (key === null) {
|
|
169
169
|
continue;
|
|
170
170
|
}
|
|
171
|
-
this.wordIndices
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
if (!this.wordIndices[key]) {
|
|
172
|
+
this.wordIndices[key] = [];
|
|
173
|
+
}
|
|
174
|
+
this.wordIndices[key].push(i);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
static putNewWord(block, word, blockSize) {
|
|
@@ -194,7 +194,12 @@ var MatchFinder = class _MatchFinder {
|
|
|
194
194
|
findMatch() {
|
|
195
195
|
this.indexNewWords();
|
|
196
196
|
this.removeRepeatingWords();
|
|
197
|
-
|
|
197
|
+
let hasIndices = false;
|
|
198
|
+
for (const _key in this.wordIndices) {
|
|
199
|
+
hasIndices = true;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
if (!hasIndices) {
|
|
198
203
|
return null;
|
|
199
204
|
}
|
|
200
205
|
let bestMatchInOld = this.startInOld;
|
|
@@ -209,7 +214,7 @@ var MatchFinder = class _MatchFinder {
|
|
|
209
214
|
continue;
|
|
210
215
|
}
|
|
211
216
|
const newMatchLengthAt = /* @__PURE__ */ new Map();
|
|
212
|
-
if (!
|
|
217
|
+
if (!this.wordIndices[index]) {
|
|
213
218
|
matchLengthAt = newMatchLengthAt;
|
|
214
219
|
continue;
|
|
215
220
|
}
|
|
@@ -471,7 +476,7 @@ var BlockFinder = class {
|
|
|
471
476
|
const from = match.index;
|
|
472
477
|
const to = match.index + match[0].length;
|
|
473
478
|
result.addBlock(from, to);
|
|
474
|
-
} catch
|
|
479
|
+
} catch {
|
|
475
480
|
throw new ArgumentError(
|
|
476
481
|
`One or more block expressions result in a text sequence that overlaps. Current expression: ${exp}`
|
|
477
482
|
);
|
|
@@ -503,6 +508,20 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
503
508
|
"</s>",
|
|
504
509
|
"</span>"
|
|
505
510
|
];
|
|
511
|
+
static SpecialCaseClosingTagsSet = /* @__PURE__ */ new Set([
|
|
512
|
+
"</strong>",
|
|
513
|
+
"</em>",
|
|
514
|
+
"</b>",
|
|
515
|
+
"</i>",
|
|
516
|
+
"</big>",
|
|
517
|
+
"</small>",
|
|
518
|
+
"</u>",
|
|
519
|
+
"</sub>",
|
|
520
|
+
"</sup>",
|
|
521
|
+
"</strike>",
|
|
522
|
+
"</s>",
|
|
523
|
+
"</span>"
|
|
524
|
+
]);
|
|
506
525
|
static SpecialCaseOpeningTagRegex = /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\s]+/i;
|
|
507
526
|
content = [];
|
|
508
527
|
newText;
|
|
@@ -612,15 +631,15 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
612
631
|
this.processInsertOperation(operation, "diffmod");
|
|
613
632
|
}
|
|
614
633
|
processInsertOperation(operation, cssClass) {
|
|
615
|
-
const text = this.newWords.
|
|
634
|
+
const text = this.newWords.slice(operation.startInNew, operation.endInNew);
|
|
616
635
|
this.insertTag(_HtmlDiff.InsTag, cssClass, text);
|
|
617
636
|
}
|
|
618
637
|
processDeleteOperation(operation, cssClass) {
|
|
619
|
-
const text = this.oldWords.
|
|
638
|
+
const text = this.oldWords.slice(operation.startInOld, operation.endInOld);
|
|
620
639
|
this.insertTag(_HtmlDiff.DelTag, cssClass, text);
|
|
621
640
|
}
|
|
622
641
|
processEqualOperation(operation) {
|
|
623
|
-
const result = this.newWords.
|
|
642
|
+
const result = this.newWords.slice(operation.startInNew, operation.endInNew);
|
|
624
643
|
this.content.push(result.join(""));
|
|
625
644
|
}
|
|
626
645
|
/**
|
|
@@ -663,7 +682,13 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
663
682
|
let specialCaseTagInjection = "";
|
|
664
683
|
let specialCaseTagInjectionIsBefore = false;
|
|
665
684
|
if (_HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
|
|
666
|
-
const
|
|
685
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
686
|
+
for (const word of words) {
|
|
687
|
+
if (Utils_default.isTag(word)) {
|
|
688
|
+
tagNames.add(Utils_default.getTagName(word));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const styledTagNames = Array.from(tagNames).join(" ");
|
|
667
692
|
this.specialTagDiffStack.push(words[0]);
|
|
668
693
|
specialCaseTagInjection = `<ins class='mod ${styledTagNames}'>`;
|
|
669
694
|
if (tag === _HtmlDiff.DelTag) {
|
|
@@ -672,7 +697,7 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
672
697
|
words.shift();
|
|
673
698
|
}
|
|
674
699
|
}
|
|
675
|
-
} else if (_HtmlDiff.
|
|
700
|
+
} else if (_HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {
|
|
676
701
|
const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop();
|
|
677
702
|
const openingAndClosingTagsMatch = !!openingTag && Utils_default.getTagName(openingTag) === Utils_default.getTagName(words[indexLastTagInFirstTagBlock]);
|
|
678
703
|
if (!!openingTag && openingAndClosingTagsMatch) {
|
|
@@ -683,7 +708,7 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
683
708
|
}
|
|
684
709
|
if (tag === _HtmlDiff.DelTag) {
|
|
685
710
|
words.shift();
|
|
686
|
-
while (words.length > 0 && _HtmlDiff.
|
|
711
|
+
while (words.length > 0 && _HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {
|
|
687
712
|
words.shift();
|
|
688
713
|
}
|
|
689
714
|
}
|
|
@@ -714,13 +739,13 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
714
739
|
}
|
|
715
740
|
}
|
|
716
741
|
if (indexOfFirstTag !== null) {
|
|
717
|
-
const items2 = words.
|
|
742
|
+
const items2 = words.slice(0, indexOfFirstTag);
|
|
718
743
|
if (indexOfFirstTag > 0) {
|
|
719
744
|
words.splice(0, indexOfFirstTag);
|
|
720
745
|
}
|
|
721
746
|
return items2;
|
|
722
747
|
}
|
|
723
|
-
const items = words.
|
|
748
|
+
const items = words.slice(0);
|
|
724
749
|
words.splice(0, words.length);
|
|
725
750
|
return items;
|
|
726
751
|
}
|
|
@@ -769,9 +794,18 @@ var HtmlDiff = class _HtmlDiff {
|
|
|
769
794
|
curr = next;
|
|
770
795
|
continue;
|
|
771
796
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
797
|
+
let oldDistanceInChars = 0;
|
|
798
|
+
for (let i = prev.endInOld; i < next.startInOld; i++) {
|
|
799
|
+
oldDistanceInChars += this.oldWords[i].length;
|
|
800
|
+
}
|
|
801
|
+
let newDistanceInChars = 0;
|
|
802
|
+
for (let i = prev.endInNew; i < next.startInNew; i++) {
|
|
803
|
+
newDistanceInChars += this.newWords[i].length;
|
|
804
|
+
}
|
|
805
|
+
let currMatchLengthInChars = 0;
|
|
806
|
+
for (let i = curr.startInNew; i < curr.endInNew; i++) {
|
|
807
|
+
currMatchLengthInChars += this.newWords[i].length;
|
|
808
|
+
}
|
|
775
809
|
if (currMatchLengthInChars > Math.max(oldDistanceInChars, newDistanceInChars) * this.orphanMatchThreshold) {
|
|
776
810
|
yield curr;
|
|
777
811
|
}
|