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

) ();