1- export interface MatchedBlock {
1+ interface MatchedBlock {
22 oldStart : number
33 oldEnd : number
44 newStart : number
55 newEnd : number
66 size : number
77}
88
9- export interface Operation {
9+ interface Operation {
1010 oldStart : number
1111 oldEnd : number
1212 newStart : number
1313 newEnd : number
1414 type : 'equal' | 'delete' | 'create' | 'replace'
1515}
1616
17- import {
18- dressUpDiffContent ,
19- htmlImgTagReg ,
20- htmlTagReg ,
21- htmlVideoTagReg ,
22- } from './dress-up'
17+ type BaseOpType = 'delete' | 'create'
18+
19+ interface HtmlDiffConfig {
20+ minMatchedSize : number
21+ classNames : {
22+ createText : string
23+ deleteText : string
24+ createInline : string
25+ deleteInline : string
26+ createBlock : string
27+ deleteBlock : string
28+ }
29+ }
30+
31+ export interface HtmlDiffOptions {
32+ minMatchedSize ?: number
33+ classNames ?: Partial < {
34+ createText ?: string
35+ deleteText ?: string
36+ createInline ?: string
37+ deleteInline ?: string
38+ createBlock ?: string
39+ deleteBlock ?: string
40+ } >
41+ }
2342
2443const htmlStartTagReg = / ^ < (?< name > [ ^ \s / > ] + ) [ ^ > ] * > $ /
2544const htmlTagWithNameReg = / ^ < (?< isEnd > \/ ) ? (?< name > [ ^ \s > ] + ) [ ^ > ] * > $ /
2645
46+ const htmlTagReg = / ^ < [ ^ > ] + > /
47+ const htmlImgTagReg = / ^ < i m g [ ^ > ] * > $ /
48+ const htmlVideoTagReg = / ^ < v i d e o [ ^ > ] * > .* ?< \/ v i d e o > $ / ms
49+
2750export default class HtmlDiff {
28- minMatchedSize : number
51+ readonly config : HtmlDiffConfig
2952 readonly oldWords : string [ ] = [ ]
3053 readonly newWords : string [ ] = [ ]
3154 readonly matchedBlockList : MatchedBlock [ ] = [ ]
3255 readonly operationList : Operation [ ] = [ ]
33-
3456 unifiedContent ?: string
35- sideBySideContents ?: string [ ]
57+ sideBySideContents ?: [ string , string ]
3658
37- constructor ( oldHtml : string , newHtml : string , minMatchedSize = 2 ) {
38- this . minMatchedSize = minMatchedSize
59+ constructor (
60+ oldHtml : string ,
61+ newHtml : string ,
62+ {
63+ minMatchedSize = 2 ,
64+ classNames = {
65+ createText : 'html-diff-create-text-wrapper' ,
66+ deleteText : 'html-diff-delete-text-wrapper' ,
67+ createInline : 'html-diff-create-inline-wrapper' ,
68+ deleteInline : 'html-diff-delete-inline-wrapper' ,
69+ createBlock : 'html-diff-create-block-wrapper' ,
70+ deleteBlock : 'html-diff-delete-block-wrapper' ,
71+ } ,
72+ } : HtmlDiffOptions = { } ,
73+ ) {
74+ // init config
75+ this . config = {
76+ minMatchedSize,
77+ classNames : {
78+ createText : 'html-diff-create-text-wrapper' ,
79+ deleteText : 'html-diff-delete-text-wrapper' ,
80+ createInline : 'html-diff-create-inline-wrapper' ,
81+ deleteInline : 'html-diff-delete-inline-wrapper' ,
82+ createBlock : 'html-diff-create-block-wrapper' ,
83+ deleteBlock : 'html-diff-delete-block-wrapper' ,
84+ ...classNames ,
85+ } ,
86+ }
3987
88+ // no need to diff
4089 if ( oldHtml === newHtml ) {
4190 this . unifiedContent = oldHtml
4291 this . sideBySideContents = [ oldHtml , newHtml ]
@@ -66,13 +115,13 @@ export default class HtmlDiff {
66115 }
67116 break
68117 case 'delete' :
69- result += dressUpDiffContent (
118+ result += this . dressUpDiffContent (
70119 'delete' ,
71120 this . oldWords . slice ( operation . oldStart , operation . oldEnd ) ,
72121 )
73122 break
74123 case 'create' :
75- result += dressUpDiffContent (
124+ result += this . dressUpDiffContent (
76125 'create' ,
77126 this . newWords . slice ( operation . newStart , operation . newEnd ) ,
78127 )
@@ -119,7 +168,7 @@ export default class HtmlDiff {
119168 }
120169
121170 // deal normal tag
122- result += dressUpDiffContent ( 'delete' , deleteOfWords )
171+ result += this . dressUpDiffContent ( 'delete' , deleteOfWords )
123172 deleteOfWords . splice ( 0 )
124173 let isTagInNewFind = false
125174 for (
@@ -136,7 +185,7 @@ export default class HtmlDiff {
136185 ) {
137186 // find first matched tag, but not maybe the expected tag(to optimize)
138187 isTagInNewFind = true
139- result += dressUpDiffContent ( 'create' , createOfWords )
188+ result += this . dressUpDiffContent ( 'create' , createOfWords )
140189 result += createWord
141190 createOfWords . splice ( 0 )
142191 createIndex = tempCreateIndex + 1
@@ -157,8 +206,8 @@ export default class HtmlDiff {
157206 if ( createIndex < operation . newEnd ) {
158207 createOfWords . push ( ...this . newWords . slice ( createIndex , operation . newEnd ) )
159208 }
160- result += dressUpDiffContent ( 'delete' , deleteOfWords )
161- result += dressUpDiffContent ( 'create' , createOfWords )
209+ result += this . dressUpDiffContent ( 'delete' , deleteOfWords )
210+ result += this . dressUpDiffContent ( 'create' , createOfWords )
162211 break
163212 default :
164213 const exhaustiveCheck : never = operation . type
@@ -198,31 +247,31 @@ export default class HtmlDiff {
198247 break
199248 case 'delete' :
200249 const deleteWords = this . oldWords . slice ( operation . oldStart , operation . oldEnd )
201- oldHtml += dressUpDiffContent ( 'delete' , deleteWords )
250+ oldHtml += this . dressUpDiffContent ( 'delete' , deleteWords )
202251 break
203252 case 'create' :
204253 const createWords = this . newWords . slice ( operation . newStart , operation . newEnd )
205- newHtml += dressUpDiffContent ( 'create' , createWords )
254+ newHtml += this . dressUpDiffContent ( 'create' , createWords )
206255 break
207256 case 'replace' :
208257 const deleteOfReplaceWords = this . oldWords . slice (
209258 operation . oldStart ,
210259 operation . oldEnd ,
211260 )
212- oldHtml += dressUpDiffContent ( 'delete' , deleteOfReplaceWords )
261+ oldHtml += this . dressUpDiffContent ( 'delete' , deleteOfReplaceWords )
213262 const createOfReplaceWords = this . newWords . slice (
214263 operation . newStart ,
215264 operation . newEnd ,
216265 )
217- newHtml += dressUpDiffContent ( 'create' , createOfReplaceWords )
266+ newHtml += this . dressUpDiffContent ( 'create' , createOfReplaceWords )
218267 break
219268 default :
220269 const exhaustiveCheck : never = operation . type
221270 console . error ( 'Error operation type: ' + exhaustiveCheck )
222271 }
223272 } )
224273
225- const result = [ oldHtml , newHtml ]
274+ const result : [ string , string ] = [ oldHtml , newHtml ]
226275 this . sideBySideContents = result
227276 return result
228277 }
@@ -325,7 +374,7 @@ export default class HtmlDiff {
325374 }
326375 }
327376
328- return maxSize >= this . minMatchedSize ? bestMatchedBlock : null
377+ return maxSize >= this . config . minMatchedSize ? bestMatchedBlock : null
329378 }
330379
331380 // use matchedBlockList walk the words to find change description
@@ -380,4 +429,63 @@ export default class HtmlDiff {
380429 }
381430 return operationList
382431 }
432+
433+ private dressUpDiffContent ( type : BaseOpType , words : string [ ] ) : string {
434+ const wordsLength = words . length
435+ if ( ! wordsLength ) {
436+ return ''
437+ }
438+
439+ let result = ''
440+ let textStartIndex = 0
441+ for ( let i = 0 ; i < wordsLength ; i ++ ) {
442+ const word = words [ i ]
443+ // this word is html tag
444+ if ( word . match ( htmlTagReg ) ) {
445+ // deal text words before
446+ if ( i > textStartIndex ) {
447+ result += this . dressUpText ( type , words . slice ( textStartIndex , i ) )
448+ }
449+ // deal this tag
450+ textStartIndex = i + 1
451+ if ( word . match ( htmlVideoTagReg ) ) {
452+ result += this . dressUpBlockTag ( type , word )
453+ } else if ( [ htmlImgTagReg ] . some ( item => word . match ( item ) ) ) {
454+ result += this . dressUpInlineTag ( type , word )
455+ } else {
456+ result += word
457+ }
458+ }
459+ }
460+ if ( textStartIndex < wordsLength ) {
461+ result += this . dressUpText ( type , words . slice ( textStartIndex ) )
462+ }
463+ return result
464+ }
465+
466+ private dressUpText ( type : BaseOpType , words : string [ ] ) : string {
467+ const text = words . join ( '' )
468+ if ( ! text . trim ( ) ) return ''
469+ if ( type === 'create' )
470+ return `<span class="${ this . config . classNames . createText } ">${ text } </span>`
471+ if ( type === 'delete' )
472+ return `<span class="${ this . config . classNames . deleteText } ">${ text } </span>`
473+ return ''
474+ }
475+
476+ private dressUpInlineTag ( type : BaseOpType , word : string ) : string {
477+ if ( type === 'create' )
478+ return `<span class="${ this . config . classNames . createInline } ">${ word } </span>`
479+ if ( type === 'delete' )
480+ return `<span class="${ this . config . classNames . deleteInline } ">${ word } </span>`
481+ return ''
482+ }
483+
484+ private dressUpBlockTag ( type : BaseOpType , word : string ) : string {
485+ if ( type === 'create' )
486+ return `<div class="${ this . config . classNames . createBlock } ">${ word } </div>`
487+ if ( type === 'delete' )
488+ return `<div class="${ this . config . classNames . deleteBlock } ">${ word } </div>`
489+ return ''
490+ }
383491}
0 commit comments