User:Stevage/EnhanceHistory.user.js

// ==UserScript==

// @name Enhanced history display

// @namespace stevage

// @description Collapses consecutive edits from the same person into one, shows diffs on history page

// @include *.wikipedia.org/*action=history

// ==/UserScript==

// This page should be found at http://en.wikipedia.org/wiki/User:Stevage/EnhanceHistory.user.js

// Install it from http://en.wikipedia.org/w/index.php?action=raw&ctype=text/javascript&dontcountme=s&title=User:Stevage/EnhanceHistory.user.js

(

function() {

if(typeof GM_log === 'undefined') return;

GM_log('in blank function');

function compress() {

GM_log('in compress function');

if (!document.getElementById('bodyContent')) {

return;

}

this.add_buttons();

}

compress.prototype.add_buttons = function() {

GM_log('in add_buttons');

// Create the compress buttion

var button1 = document.createElement('input');

button1.setAttribute('id', 'compress_button1');

button1.className = 'historysubmit';

button1.style.marginLeft = '5px';

button1.setAttribute('type', 'button');

button1.value = 'Compress history';

button1.onclick = function() { compress.start(); }

// Create the ShowDiffs buttion

var button1 = document.createElement('input');

button1.setAttribute('id', 'showdiffs1');

button1.className = 'historysubmit';

button1.style.marginLeft = '5px';

button1.setAttribute('type', 'button');

button1.value = 'Show diffs';

button1.onclick = function() { compress.showDiffs(); }

// Add the button to the page

var history = document.getElementById('pagehistory');

history.parentNode.insertBefore(button1, history);

}

/////////////////////////////////////////////////////////

function getPlainText(s) {

GM_log(">getPlainText");

if (s==null)

return "";

var len = s.length;

if (len > 20) {

return "" + s.substr(0,10)+'...'+ s.substr(len-10,10)+ "";

} else {

return "" + s + "";

}

GM_log("

}

function diffString(text1, text2) {

var d = diff(text1, text2);

var html = '';

for (var x=0; x

var m = d[x][0]; // Mode (-1=delete, 0=copy, 1=add)

var i = d[x][1]; // Index of change.

var t = d[x][2]; // Text of change.

t = t.replace(/&/g, "&").replace(//g, ">");

if (m == -1)

html += ""+t+"";

else if (m == 1)

html += ""+t+"";

else

html += "" +getPlainText(t) + "";

}

return html;

}

// Find the differences between two texts. Return an array of changes.

function diff(text1, text2) {

// Check for equality (speedup)

if (text1 == text2)

return 0, 0, text1;

var a;

// Trim off common prefix (speedup)

a = diff_prefix(text1, text2);

text1 = a[0];

text2 = a[1];

var commonprefix = a[2];

// Trim off common suffix (speedup)

a = diff_suffix(text1, text2);

text1 = a[0];

text2 = a[1];

var commonsuffix = a[2];

if (!text1) { // Just add some text (speedup)

a = 1, commonprefix.length, text2;

} else if (!text2) { // Just delete some text (speedup)

a = -1, commonprefix.length, text1;

} else {

// Check to see if the problem can be split in two.

var longtext = text1.length > text2.length ? text1 : text2;

var shorttext = text1.length > text2.length ? text2 : text1;

var hm = diff_halfmatch(longtext, shorttext, Math.ceil(longtext.length/4));

if (!hm)

hm = diff_halfmatch(longtext, shorttext, Math.ceil(longtext.length/2));

if (hm) {

if (text1.length > text2.length) {

var text1_a = hm[0];

var text1_b = hm[1];

var text2_a = hm[2];

var text2_b = hm[3];

} else {

var text2_a = hm[0];

var text2_b = hm[1];

var text1_a = hm[2];

var text1_b = hm[3];

}

var mid_common = hm[4];

var result_a = diff(text1_a, text2_a);

var result_b = diff(text1_b, text2_b);

if (commonprefix) // Shift the indicies forwards due to the commonprefix.

for (var x=0; x

result_a[x][1] += commonprefix.length;

result_a.push([0, commonprefix.length+text2_a.length, mid_common]);

while (result_b.length) {

result_b[0][1] += commonprefix.length+text2_a.length+mid_common.length;

result_a.push(result_b.shift());

}

a = result_a;

} else {

var result = diff_map(text1, text2);

if (result)

a = diffchar2diffarray(result, commonprefix.length);

else // No acceptable result.

a = -1, commonprefix.length, text1], [1, commonprefix.length, text2;

}

}

if (commonprefix)

a.unshift([0, 0, commonprefix]);

if (commonsuffix)

a.push([0, commonprefix.length + text2.length, commonsuffix]);

return a;

}

function diff_map(text1, text2) {

// Explore the intersection points between the two texts.

var now = new Date();

var ms_end = now.getTime() + 1000; // Don't run for more than one second.

var max = text1.length + text2.length;

var v_map = new Array();

var v = new Array();

v[1] = 0;

var x, y;

for (var d=0; d<=max; d++) {

now = new Date();

if (now.getTime() > ms_end) // JavaScript timeout reached

return null;

v_map[d] = new Object;

for (var k=-d; k<=d; k+=2) {

if (k == -d || k != d && v[k-1] < v[k+1])

x = v[k+1];

else

x = v[k-1]+1;

y = x - k;

while (x < text1.length && y < text2.length && text1.charAt(x) == text2.charAt(y)) {

x++; y++;

}

v[k] = x;

v_map[d][k] = x;

if (x >= text1.length && y >= text2.length) {

var str = diff_path(v_map, text1, text2);

return str;

}

}

}

alert("No result. Can't happen. (diff_map)");

return null;

}

function diff_path(v_map, text1, text2) {

// Work from the end back to the start to determine the path.

var path = '';

var x = text1.length;

var y = text2.length;

for (var d=v_map.length-2; d>=0; d--) {

while(1) {

if (diff_match(v_map[d], x-1, y)) {

x--;

path = "-"+text1.substring(x, x+1) + path;

break;

} else if (diff_match(v_map[d], x, y-1)) {

y--;

path = "+"+text2.substring(y, y+1) + path;

break;

} else {

x--;

y--;

//if (text1.substring(x, x+1) != text2.substring(y, y+1))

// return alert("No diagonal. Can't happen. (diff_path)");

path = "="+text1.substring(x, x+1) + path;

}

}

}

return path;

}

function diff_match(v, x, y) {

// Does the vector list contain an x/y coordinate?

for (var k in v)

if (v[k] == x && x-k == y)

return true;

return false;

}

function diff_prefix(text1, text2) {

// Trim off common prefix

var pointermin = 0;

var pointermax = Math.min(text1.length, text2.length);

var pointermid = pointermax;

while(pointermin < pointermid) {

if (text1.substring(0, pointermid) == text2.substring(0, pointermid))

pointermin = pointermid;

else

pointermax = pointermid;

pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);

}

var commonprefix = text1.substring(0, pointermid);

text1 = text1.substring(pointermid);

text2 = text2.substring(pointermid);

return [text1, text2, commonprefix];

}

function diff_suffix(text1, text2) {

// Trim off common suffix

var pointermin = 0;

var pointermax = Math.min(text1.length, text2.length);

var pointermid = pointermax;

while(pointermin < pointermid) {

if (text1.substring(text1.length-pointermid) == text2.substring(text2.length-pointermid))

pointermin = pointermid;

else

pointermax = pointermid;

pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);

}

var commonsuffix = text1.substring(text1.length-pointermid);

text1 = text1.substring(0, text1.length-pointermid);

text2 = text2.substring(0, text2.length-pointermid);

return [text1, text2, commonsuffix];

}

function diff_halfmatch(longtext, shorttext, i) {

// Do the two texts share a substring which is at least half the length of the longer text?

// Start with a 1/4 length substring at position i as a seed.

if (longtext.length < 10 || shorttext.length < 1)

return null; // Pointless.

var seed = longtext.substring(i, i+Math.floor(longtext.length/4));

var j=0;

var j_index;

var best_common = '';

while ((j_index = shorttext.substring(j).indexOf(seed)) != -1) {

j += j_index;

var my_prefix = diff_prefix(longtext.substring(i), shorttext.substring(j));

var my_suffix = diff_suffix(longtext.substring(0, i), shorttext.substring(0, j));

if (best_common.length < (my_suffix[2] + my_prefix[2]).length) {

best_common = my_suffix[2] + my_prefix[2];

best_longtext_a = my_suffix[0];

best_longtext_b = my_prefix[0];

best_shorttext_a = my_suffix[1];

best_shorttext_b = my_prefix[1];

}

j++;

}

if (best_common.length >= longtext.length/2)

return [best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b, best_common];

else

return null;

}

function diffchar2diffarray(text, offset) {

// Convert '-h+c=a=t' into -1, 0, 'h'], [1, 0, 'c'], [0, 1, 'at'

// Old format: - remove char, = keep char, + add char

// New format: array of [m, i, t]

// Where m: -1 remove char, 0 keep char, 1 add char

// Where i: index of change in first text

// Where t: text to be added/kept/removed

var i = 0;

if (offset) i += offset;

var a = new Array();

var m;

var last_m = null;

for (var x=0; x

m = "-=+".indexOf(text.substring(x, x+1)) - 1;

if (m == -2) return alert("Error: '"+text.substring(x, x+1)+"' is not one of '+=-'");

if (last_m === m) {

a[a.length-1][2] += text.substring(x+1, x+2);

} else {

a[a.length] = new Array(m, i, text.substring(x+1, x+2));

}

last_m = m;

if (m != -1) i++;

}

return a;

}

/*

// JavaScript diff code thanks to John Resig (http://ejohn.org)

// http://ejohn.org/files/jsdiff.js

function diffString( o, n ) {

GM_log(">diffstring " + o.length + "/" + n.length);

var out = diff( o.split(/\s+/), n.split(/\s+/) );

GM_log("1diffstring");

var str = "";

GM_log("2diffstring");

var plaintext = "";

GM_log("3diffstring");

for ( var i = 0; i < out.n.length - 1; i++ ) {

if ( out.n[i].text == null ) {

if ( out.n[i].indexOf('"') == -1 && out.n[i].indexOf('<') == -1 && out.n[i].indexOf('=') == -1 ) {

str += getPlainText(plaintext) + " " + " " + out.n[i] +"";

plaintext = "";

} else

plaintext += " " + out.n[i];

} else {

var pre = "";

if ( out.n[i].text.indexOf('"') == -1 && out.n[i].text.indexOf('<') == -1 && out.n[i].text.indexOf('=') == -1 ) {

var n = out.n[i].row + 1;

while ( n < out.o.length && out.o[n].text == null ) {

if ( out.o[n].indexOf('"') == -1 && out.o[n].indexOf('<') == -1 && out.o[n].indexOf(':') == -1 && out.o[n].indexOf(';') == -1 && out.o[n].indexOf('=') == -1 )

pre += " " + out.o[n] +" ";

n++;

}

}

plaintext = plaintext + " " + out.n[i].text;

if (pre!="") {

str += getPlainText(plaintext) + " " + pre;

plaintext = "";

}

} // if

} // for

GM_log("

return str +" " +getPlainText(plaintext);

}

function diff( o, n ) {

var ns = new Array();

var os = new Array();

for ( var i = 0; i < n.length; i++ ) {

if ( ns[ n[i] ] == null )

ns[ n[i] ] = { rows: new Array(), o: null };

ns[ n[i] ].rows.push( i );

}

for ( var i = 0; i < o.length; i++ ) {

if ( os[ o[i] ] == null )

os[ o[i] ] = { rows: new Array(), n: null };

os[ o[i] ].rows.push( i );

}

for ( var i in ns ) {

if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {

n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };

o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };

}

}

for ( var i = 0; i < n.length - 1; i++ ) {

if ( n[i].text != null && n[i+1].text == null && o[ n[i].row + 1 ].text == null &&

n[i+1] == o[ n[i].row + 1 ] ) {

n[i+1] = { text: n[i+1], row: n[i].row + 1 };

o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };

}

}

for ( var i = n.length - 1; i > 0; i-- ) {

if ( n[i].text != null && n[i-1].text == null && o[ n[i].row - 1 ].text == null &&

n[i-1] == o[ n[i].row - 1 ] ) {

n[i-1] = { text: n[i-1], row: n[i].row - 1 };

o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };

}

}

return { o: o, n: n };

}

  • /

function stripHTML(oldString) {

var newString = "";

var inTag = false;

for(var i = 0; i < oldString.length; i++) {

if(oldString.charAt(i) == '<')

inTag = true;

if(oldString.charAt(i) == '>') {

inTag = false;

i++;

}

if(!inTag)

newString += oldString.charAt(i);

}

return newString;

}

compress.prototype.mediawiki_content = function(text) {

GM_log(">mw_content:");

if (text == "") {

return text;

} else {

text = '' + text;

var start = text.indexOf('

start += text.substr(start, 1000).indexOf('>') + 1;

var end = text.indexOf('');

GM_log("

text = text.substr(start, end - start);

s = text.replace(/

s = s.replace(/>/g, ">");

GM_log ("Stripped: " + s.substr(0,50));

return s;

}

}

compress.prototype.start = function() {

var hist = document.getElementById('pagehistory');

if (hist) {

var diffs;

diffs = document.evaluate(

"LI",

hist,

null,

XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

null

);

var last='*x!', prevdiffcomment;

for (var i = 0; i < diffs.snapshotLength; i++) {

var diff = diffs.snapshotItem(i);

var comment = document.evaluate(

'SPAN[@class="comment"]',

diff,

null,

XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

null

).snapshotItem(0);

//GM_log(comment.innerHTML);

var a = document.evaluate(

"SPAN/A",

diff,

null,

XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

null

);

eacha = a.snapshotItem(0);

if (eacha.title==last) {

if (comment) {

prevdiffcomment.innerHTML = prevdiffcomment.innerHTML + '//' + comment.innerHTML;

} else {

prevdiffcomment.innerHTML = prevdiffcomment.innerHTML + '//---';

}

diff.parentNode.removeChild(diff);

} else {

last = eacha.title;

if (!comment) {

comment = document.createElement('SPAN');

comment.className='comment';

comment.innerHTML=' ---';

diff.insertBefore(comment, null);

}

prevdiffcomment = comment;

} //if

}//for

} //if hist

} // function 'start'

compress.prototype.loadDiff = function(urlno) {

GM_log("in loadDiff");

this.urlno = urlno;

this.hostname = "en.wikipedia.org";

var url = this.urls[urlno] + '&action=edit';

if (this.urls[urlno] == null) {

var details = new String("");

details.responseText = ""; // force comparison with blank text;

compress.loadedDiff(details);

return;

}

GM_log(">loading!" + url);

GM_xmlhttpRequest({

method:'GET',

url:url,

headers:{

'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',

'Accept': 'application/xml',

},

onload:function(details) {

//alert("hello " + details.status + '/' + details.statusText + '/' + details.responseHeaders);

compress.loadedDiff(details);

}

});

GM_log("

}

compress.prototype.loadedDiff = function(details) {

GM_log(">loadedDiff "+this.urlno);

this.pages[this.urlno] = this.mediawiki_content(details.responseText);

GM_log("-loadedDiff "+this.urlno);

if (this.urlno > 0) {

s = diffString(this.pages[this.urlno], this.pages[this.urlno-1]);

GM_log("done diff");

wh = document.getElementById(this.info[this.urlno -1]);

span = document.createElement('span');

span.innerHTML = s;

wh.insertBefore(span, null);

}

if (details.responseText != "") {

compress.loadDiff(this.urlno+1); // if blank text, stop.

}

GM_log("

}

compress.prototype.showDiffs = function() {

var hist = document.getElementById('pagehistory');

if (hist) {

var diffs;

diffs = document.evaluate(

'LI/A[text() != "cur" and text() != "last"][1]',

hist,

null,

XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,

null

);

this.urls = new Array(diffs.snapshotLength);

this.info = new Array(diffs.snapshotLength);

this.pages = new Array(diffs.snapshotLength);

GM_log("Number of A's: " + diffs.snapshotLength);

for (var i = 0; i < diffs.snapshotLength; i++) {

var diff = diffs.snapshotItem(i);

diff.id = "difflink" + i;

diff.parentNode.id = "diffli" + i;

this.urls[i] = diff.href;

this.info[i] = "diffli" + i;

if (i==0) {

this.loadDiff(0);

}

}//for

} //if hist

} // function 'start'

var compress = new compress();

document.compress = compress;

} // unnamed function

) ();