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("/g, "&gt;"); 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; xdiffstring " + 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('') + 1; var end = text.indexOf(' '); GM_log("/g, "&gt;"); 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("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

) ;