User:Stevage/EnhanceHistory.user
// ==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() {
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("<getPlainText"); } function diffString(text1, text2) { var d = diff(text1, text2); var html = ; for (var x=0; x<d.length; 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, "<").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.length; 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<text.length; x+=2) { 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("<diffstring");
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('<textarea'); start += text.substr(start, 1000).indexOf('>') + 1; var end = text.indexOf('</textarea>'); GM_log("<mw_content"); text = text.substr(start, end - start); s = text.replace(/</g, "<"); 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("<loading!" + url); } 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("<loadedDiff"); }
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
) ();