Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// ==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 "<small>" + s.substr(0,10)+'...'+ s.substr(len-10,10)+ "</small>";
} else {
return "<small>" + s + "</small>";
}
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 += "<DEL STYLE='background:#FFE6E6;' TITLE='i="+i+"'>"+t+"</DEL>";
else if (m == 1)
html += "<INS STYLE='background:#E6FFE6;' TITLE='i="+i+"'>"+t+"</INS>";
else
html += "<SPAN TITLE='i="+i+"'>" +getPlainText(t) + "</SPAN>";
}
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) + " " + "<b style='background:#E6FFE6;' class='diff'> " + out.n[i] +"</b>";
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 += " <s style='background:#FFE6E6;' class='diff'>" + out.o[n] +" </s>";
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
) ();
You must be logged in to post a comment.