Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
node_modules
htmldiff-cli.js
htmldiff-cli.js

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.DS_Store
dist-ssr
coverage
*.local
.env
.prettierignore
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
idea-types.json
/.idea/
45 changes: 29 additions & 16 deletions js/htmldiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
*/
var atomicTagsRegExp;
// Added head and style (for style tags inside the body)
var defaultAtomicTagsRegExp = new RegExp('^<(iframe|object|math|svg|script|video|head|style|a)\b');
var defaultAtomicTagsRegExp = /^<(iframe|object|math|svg|script|video|head|style|a)\b/;

/**
* Checks if the current word is the beginning of an atomic tag. An atomic tag is one whose
* child nodes should not be compared - the entire tag should be treated as one token. This
Expand Down Expand Up @@ -238,6 +238,19 @@
words.push(createToken(currentWord));
}
currentWord = char;
} else if (/[.,!?;:'"()\[\]{}\/\\…""''–—-]/.test(char)){
// Check if this is a semicolon ending an HTML entity (e.g., &nbsp;)
if (char === ';' && currentWord.charAt(0) === '&'){
currentWord += char;
words.push(createToken(currentWord));
currentWord = '';
} else {
if (currentWord){
words.push(createToken(currentWord));
}
words.push(createToken(char));
currentWord = '';
}
} else {
currentWord += char;
words.push(createToken(currentWord));
Expand Down Expand Up @@ -287,7 +300,7 @@
if (img) {
return '<img src="' + img[1] + '">';
}

// If the token is an a element, grab it's data attribute to include in the key.
var a = /^<a.*href=['"]([^"']*)['"]/.exec(token);
if (a) {
Expand All @@ -310,7 +323,7 @@
return start + end;
} else {
return token;
}
}
}

// If the token is an iframe element, grab it's src attribute to include in it's key.
Expand Down Expand Up @@ -505,7 +518,7 @@
// beforeTokens and afterTokens.
var bestMatchLength = bestMatch ? bestMatch.length : 0;
var match = getFullMatch(
segment, beforeIndex, afterIndex, bestMatchLength, lookBehind);
segment, beforeIndex, afterIndex, bestMatchLength, lookBehind);

// If we got a new best match, we'll save it aside.
if (match && match.length > bestMatchLength){
Expand Down Expand Up @@ -635,11 +648,11 @@
// from that area and throw it into the segments array to get processed.
if (match.segmentStartInBefore > 0 && match.segmentStartInAfter > 0){
var leftBeforeTokens = segment.beforeTokens.slice(
0, match.segmentStartInBefore);
0, match.segmentStartInBefore);
var leftAfterTokens = segment.afterTokens.slice(0, match.segmentStartInAfter);

segments.push(createSegment(leftBeforeTokens, leftAfterTokens,
segment.beforeIndex, segment.afterIndex));
segment.beforeIndex, segment.afterIndex));
}

// If there's an unmatched area at the end of the segment, create a new segment from that
Expand All @@ -651,7 +664,7 @@

if (rightBeforeTokens.length && rightAfterTokens.length){
segments.push(createSegment(rightBeforeTokens, rightAfterTokens,
rightBeforeIndex, rightAfterIndex));
rightBeforeIndex, rightAfterIndex));
}

matches.add(match);
Expand Down Expand Up @@ -707,10 +720,10 @@
action: actionUpToMatchPositions,
startInBefore: positionInBefore,
endInBefore: (actionUpToMatchPositions !== 'insert' ?
match.startInBefore - 1 : null),
match.startInBefore - 1 : null),
startInAfter: positionInAfter,
endInAfter: (actionUpToMatchPositions !== 'delete' ?
match.startInAfter - 1 : null)
match.startInAfter - 1 : null)
});
}
if (match.length !== 0){
Expand Down Expand Up @@ -743,7 +756,7 @@
var op = operations[i];

if ((isSingleWhitespace(op) && lastOp.action === 'replace') ||
(op.action === 'replace' && lastOp.action === 'replace')){
(op.action === 'replace' && lastOp.action === 'replace')){
lastOp.endInBefore = op.endInBefore;
lastOp.endInAfter = op.endInAfter;
} else {
Expand Down Expand Up @@ -936,7 +949,7 @@
function renderOperations(beforeTokens, afterTokens, operations, dataPrefix, className){
return operations.reduce(function(rendering, op, index){
return rendering + OPS[op.action](
op, beforeTokens, afterTokens, index, dataPrefix, className);
op, beforeTokens, afterTokens, index, dataPrefix, className);
}, '');
}

Expand All @@ -949,8 +962,8 @@
* @param {string} className (Optional) The class attribute to include in <ins> and <del> tags.
* @param {string} dataPrefix (Optional) The data prefix to use for data attributes. The
* operation index data attribute will be named `data-${dataPrefix-}operation-index`.
* @param {string} atomicTags (Optional) Comma separated list of atomic tag names. The
* list has to be in the form `tag1,tag2,...` e. g. `head,script,style`. If not used,
* @param {string} atomicTags (Optional) Comma separated list of atomic tag names. The
* list has to be in the form `tag1,tag2,...` e. g. `head,script,style`. If not used,
* the default list `iframe,object,math,svg,script,video,head,style` will be used.
*
* @return {string} The combined HTML content with differences wrapped in <ins> and <del> tags.
Expand All @@ -959,7 +972,7 @@
if (before === after) return before;

// Enable user provided atomic tag list.
atomicTags ?
atomicTags ?
(atomicTagsRegExp = new RegExp('^<(' + atomicTags.replace(/\s*/g, '').replace(/,/g, '|') + ')\b'))
: (atomicTagsRegExp = defaultAtomicTagsRegExp);

Expand All @@ -981,7 +994,7 @@

if (typeof define === 'function'){
define([], function(){
return diff;
return diff;
});
} else if (typeof module !== 'undefined' && module !== null){
module.exports = diff;
Expand Down
Loading