User:Cacycle/diff.js: Difference between revisions

Content deleted Content added
1.0.8 (September 02, 2014) fix sep class
1.0.9 (September 02, 2014) fix old IE compatibility of handler, newline sign to blank, newline sign for moved blocks, moved block gray a bit darker, trim moved group borders to first word or newline
Line 3:
// ==UserScript==
// @name wDiff
// @version 1.0.89
// @date September 0203, 2014
// @description improved word-based diff library with block move detection
// @homepage https://en.wikipedia.org/wiki/User:Cacycle/diff
Line 65:
.oldBlock: number of block in old text order
.newBlock: number of block in new text order
.oldNumber: old text token number of first token in block
.newNumber: new text token number of first token in block
.oldStart: old text token index of first token in block
.count number of token in blocktokens
.charswords: charword length of blockcount
.chars: char length
.type: 'same', 'del', 'ins'
.section: section number of block (for testing)
.group: group number of block
.fixed: block belongs to a fixed (not moved) group (for testing)
.string: string of block tokens
 
groups[]: section blocks that are consecutive in old text
.oldNumber: first block's oldNumber
.blockStart: first block index of group
.blockEnd: last block index of group
.maxWords: word count of longest block
.words: word count of group
.chars: char count of group
.fixed: groupnot ismoved setfrom tooriginal fixed (not moved)position
.moved[]: list of groups that have been moved from this position
.movedFrom: position this group has been moved from
.color: color number of moved group
.diff: group diff
 
*/
 
// JSHint options: W004: is already defined, W097: Use the function form of "use strict", W100: This character may get silently deleted by one or more browsers
/* jshint -W004, -W097, -W100, newcap: false, browser: true, jquery: true, sub: true, bitwise: true, curly: falsetrue, evil: true, forin: true, freeze: true, immed: true, latedef: true, loopfunc: true, quotmark: single, undef: true */
/* global console */
 
Line 180 ⟶ 181:
wDiff.stylesheet =
'.wDiffTab:before { content: "→"; color: #bbb; font-size: smaller; }' +
'.wDiffNewline:before { content: " "; color: #ccc; padding: 0 0.2em 0 1px; }' +
'.wDiffMarkRight:before { content: "▶"; }' +
'.wDiffMarkLeft:before { content: "◀"; }' +
'.wDiffDelete { font-weight: normal; text-decoration: none; color: #fff; background-color: #c33; border-radius: 0.25em; padding: 0.2em 1px; }' +
'.wDiffInsert { font-weight: normal; text-decoration: none; color: #fff; background-color: #07e; border-radius: 0.25em; padding: 0.2em 1px; }' +
'.wDiffBlockLeft { background-color: #dddd8d8d8; border-radius: 0.25em; padding: 0.25em 1px; margin: 0 1px; }' +
'.wDiffBlockRight { background-color: #dddd8d8d8; border-radius: 0.25em; padding: 0.25em 1px; margin: 0 1px; }' +
'.wDiffMarkLeft { color: #dddd8d8d8; background-color: #c33; border-radius: 0.25em; padding: 0.2em 0.2em; margin: 0 1px; }' +
'.wDiffMarkRight { color: #dddd8d8d8; background-color: #c33; border-radius: 0.25em; padding: 0.2em 0.2em; margin: 0 1px; }' +
'.wDiffFragment { white-space: pre-wrap; background: #fcfcfc; border: #bbb solid; border-width: 1px 1px 1px 0.5em; border-radius: 0.5em; font-family: inherit; font-size: 88%; line-height: 1.6; box-shadow: 2px 2px 2px #ddd; padding: 1em; margin: 0; }' +
'.wDiffNoChange { white-space: pre-wrap; background: #f0f0f0; border: #bbb solid; border-width: 1px 1px 1px 0.5em; border-radius: 0.5em; font-family: inherit; font-size: 88%; line-height: 1.6; box-shadow: 2px 2px 2px #ddd; padding: 0.5em; margin: 1em 0; }' +
'.wDiffSeparator { margin-bottom: 1em; }' +
'.wDiffBlock { background-color: #ddd; }' +
'.wDiffBlock0 { background-color: #ffff60; }' +
'.wDiffBlock1 { background-color: #c0ff60; }' +
Line 202 ⟶ 203:
'.wDiffBlock7 { background-color: #ffbbbb; }' +
'.wDiffBlock8 { background-color: #a0e8a0; }' +
'.wDiffMark { color: #ddd; }' +
'.wDiffMark0 { color: #ffff60; }' +
'.wDiffMark1 { color: #c0ff60; }' +
Line 297 ⟶ 298:
var block = document.getElementById('wDiffBlock' + number);
var mark = document.getElementById('wDiffMark' + number);
switch (type) {
 
// highlight corresponding mark/block pairs
if ( (type === undefined) || (type == 'mouseover') ) {
case undefined:
if (element.addEventListener !== undefined) {
case 'mouseover':
if (element.addEventListener('mouseout', !==wDiff.BlockHandler, undefinedfalse) {;
element.addEventListener('mouseoutclick', wDiff.BlockHandler, false);
}
element.addEventListener('click', wDiff.BlockHandler, false);
else if (element.attachEvent !== undefined) {
}
else if (element.attachEvent('onmouseout', !== undefinedwDiff.BlockHandler) {;
element.attachEvent('onmouseoutonclick', wDiff.BlockHandler);
}
element.attachEvent('onclick', wDiff.BlockHandler);
}else {
else {return;
}
return;
block.className += ' wDiffBlockHighlight';
}
blockmark.className += ' wDiffBlockHighlightwDiffMarkHighlight';
}
mark.className += ' wDiffMarkHighlight';
break;
 
// remove mark/block highlighting
else if ( (type == 'mouseout') || (type == 'click') ) {
case 'mouseout':
 
var highlighted = document.getElementsByClassName('wDiffBlockHighlight');
// getElementsByClassName
for (var i = 0; i < highlighted.length; i ++) {
var container = element.parentNode;
highlighted[i].className = highlighted[i].className.replace(/ wDiffBlockHighlight/g, '');
var spans = container.getElementsByTagName('span');
}
for (var i = 0; i < spans.length; i ++) {
var highlighted = document.getElementsByClassName('wDiffMarkHighlight');
if ( ( (spans[i] != block) && (spans[i] != mark) ) || (type != 'click') ) {
for (var i = 0; i < highlighted.length; i ++) {
if (spans[i].className.indexOf(' wDiffBlockHighlight') != -1) {
highlighted[i].className = highlighted[i].className.replace(/ wDiffMarkHighlight/g, '');
spans[i].className = spans[i].className.replace(/ wDiffBlockHighlight/g, '');
}
else if (spans[i].className.indexOf(' wDiffMarkHighlight') != -1) {
spans[i].className = spans[i].className.replace(/ wDiffMarkHighlight/g, '');
}
}
break;}
}
 
// scroll to corresponding mark/block element
case if (type == 'click':) {
 
// remove element click handler
if (element.removeEventListener !== undefined) {
element.removeEventListener('mouseout', wDiff.BlockHandler, false);
}
else {
element.detachEvent('onmouseout', wDiff.BlockHandler);
}
 
// get corresponding element
var corrElement;
if (element == block) {
corrElement = mark;
}
else {
corrElement = block;
}
 
// get offsetTopgetOffsetTop
var corrElementPos = 0;
var node = corrElement;
do {
corrElementPos += node.offsetTop;
} while ( (node = node.offsetParent) !== null );
 
// scroll element under mouse cursor
var top = window.pageYOffset;
var cursor = event.pageY;
var line = parseInt(window.getComputedStyle(corrElement).getPropertyValue('line-height'));
window.scroll(0, corrElementPos + top - cursor + line / 2);
break;
}
return;
Line 1,296 ⟶ 1,300:
// find groups of continuous old text blocks
wDiff.GetGroups(blocks, groups);
 
// set longest sequence of increasing groups in sections as fixed (not moved)
wDiff.SetFixed(blocks, groups, sections);
 
// convert groups to insertions/deletions if maximal block length is too short
Line 1,304 ⟶ 1,311:
wDiff.GetSections(blocks, sections);
wDiff.GetGroups(blocks, groups);
wDiff.SetFixed(blocks, groups, sections);
}
 
// set longest sequence of increasing groups in sections as fixed (not moved)
wDiff.SetFixed(blocks, groups, sections);
 
// collect deletion ('del') blocks from old text
Line 1,383 ⟶ 1,388:
oldStart: jStart,
count: count,
words: wDiff.WordCount(string),
chars: chars,
type: 'same',
Line 1,491 ⟶ 1,497:
 
// get word and char count of block
varif blockWords = wDiff.WordCount(blocks[i].stringwords > maxWords); {
maxWords = blocks[i].words;
if (blockWords > maxWords) {
maxWords = blockWords;
}
words += blockWordsblocks[i].words;
chars += blocks[i].chars;
groupEnd = i;
Line 1,536 ⟶ 1,541:
 
 
// wDiff.UnlinkBlocks: removeconvert 'same' blocks in groups of continuous old text blocks into 'ins'/'del' pairs if too short
// called from: DetectBlocks()
// changes: text.newText/oldText[].link
Line 1,547 ⟶ 1,552:
// cycle through groups
for (var group = 0; group < groups.length; group ++) {
if ( (groups[group].maxWords < wDiff.blockMinLength) && (groups[group].fixed === false) ) {
var blockStart = groups[group].blockStart;
var blockEnd = groups[group].blockEnd;
 
// unlink whole group if no block is at least blockMinLength words long
// cycle through blocks
if (groups[group].maxWords < wDiff.blockMinLength) {
for (var block = blockStart; block <= blockEnd; block ++) {
for (var block = blockStart; block <= blockEnd; block ++) {
wDiff.UnlinkSingleBlock(blocks[block], text);
unlinked = true;
}
}
 
// unlink border blocks without words/blanks
// cycle through old text
else {
var j = blocks[block].oldStart;
for (var count = 0; count < blocks[block].count; count ++) {
 
// unlink tokensstart blocks of 0 words/newlines
for (var block = blockStart; block <= blockEnd; block ++) {
text.newText.tokens[ text.oldText.tokens[j].link ].link = null;
if ( (blocks[block].words !== 0) || (blocks[block].string == '\n') ) {
text.oldText.tokens[j].link = null;
break;
j = text.oldText.tokens[j].next;
}
wDiff.UnlinkSingleBlock(blocks[block], text);
unlinked = true;
blockStart = block;
}
 
// unlink end blocks of 0 words/newlines
for (var block = blockEnd; block > blockStart; block --) {
if ( (blocks[block].words !== 0) || (blocks[block].string == '\n') ) {
break;
}
wDiff.UnlinkSingleBlock(blocks[block], text);
unlinked = true;
}
unlinked = true;
}
}
}
return unlinked;
};
 
 
// wDiff.UnlinkBlock: un-link text tokens of block, converting them into 'ins'/'del' pairs
// called from: wDiff.UnlinkBlocks()
// changes: text.newText/oldText[].link
 
wDiff.UnlinkSingleBlock = function (block, text) {
 
// cycle through old text
var j = block.oldStart;
for (var count = 0; count < block.count; count ++) {
 
// unlink tokens
text.newText.tokens[ text.oldText.tokens[j].link ].link = null;
text.oldText.tokens[j].link = null;
j = text.oldText.tokens[j].next;
}
return;
};
 
Line 1,698 ⟶ 1,738:
oldStart: oldStart,
count: count,
words: null,
chars: null,
type: 'del',
Line 1,833 ⟶ 1,874:
oldStart: null,
count: count,
words: null,
chars: null,
type: 'ins',
Line 2,084 ⟶ 2,126:
// check for colored block and move direction
var blockFrom = null;
if ( (fixed === false) && (color !== null) ) {
if (groups[ groups[group].movedFrom ].blockStart < blockStart) {
blockFrom = 'left';
Line 2,111 ⟶ 2,153:
// add 'same' (unchanged) text
if (type == 'same') {
if (color !== null) {
string = string.replace(/\n/g, wDiff.htmlNewline);
}
diff += string;
}
Line 2,611 ⟶ 2,656:
script.textContent = code;
}
wikEddocument.getElementsByTagName('head')[0].appendChild(script);
return;
};
Line 2,666 ⟶ 2,711:
 
wDiff.DebugBlocks = function (blocks) {
var dump = '\ni \toldBl \tnewBl \toldNm \tnewNm \toldSt \tcount \twords \tchars \ttype \tsect \tgroup \tfixed \tstring\n';
for (var i = 0; i < blocks.length; i ++) {
dump += i + ' \t' + blocks[i].oldBlock + ' \t' + blocks[i].newBlock + ' \t' + blocks[i].oldNumber + ' \t' + blocks[i].newNumber + ' \t' + blocks[i].oldStart + ' \t' + blocks[i].count + ' \t' + blocks[i].words + ' \t' + blocks[i].chars + ' \t' + blocks[i].type + ' \t' + blocks[i].section + ' \t' + blocks[i].group + ' \t' + blocks[i].fixed + ' \t' + wDiff.DebugShortenString(blocks[i].string) + '\n';
}
return dump;