User:Abyssal/vector.user.js

// ==UserScript== // @name			Wikipedia Animate // @namespace		http://phiffer.org/greasemonkey/ // @description		Animates page modifications between two specific edit points // @include			http://*.wikipedia.tld/*action=history* // ==/UserScript==

// based on code by Dan Phiffer // and included here with his gracious permission

function Animation {

if (!document.getElementById('bodyContent')) { return; }

var url = window.location.href; this.base_url = url.substr(0, url.indexOf('&')); this.hostname = url.substr(7, url.indexOf('/', 8) - 7);

this.add_buttons; this.add_options; this.add_css;

}	Animation.prototype.add_buttons = function { // Create the animate buttons var button1 = document.createElement('input'); button1.className = 'historysubmit'; button1.style.marginLeft = '5px'; button1.setAttribute('type', 'button'); button1.value = 'Animate changes'; button1.addEventListener('click', function { animate.start; },	true); button1.setAttribute('id', 'animate_button1');

var button2 = button1.cloneNode(true); button2.addEventListener('click', function { animate.start; },	true); button2.setAttribute('id', 'animate_button2');

// Add the buttons to the page var history = document.getElementById('pagehistory'); history.parentNode.insertBefore(button1, history); history.parentNode.appendChild(document.createTextNode(' ')); history.parentNode.appendChild(button2);

}	Animation.prototype.add_options = function { // Create the options box var toolbox = document.getElementById('p-tb'); var options = document.createElement('div'); options.className = 'portlet';

options.innerHTML = ' animate options ' +

// Range selection 'Animate over:' + '  Selected ' + '  All versions ' + '  Skip minor edits ' +

// Diffs 'Highlight diffs:' + '  Yes ' + '  No ' +

// Speed 'Animate speed:' + ' Pause  sec ' + // Info 'Include info:' + '  Date/time ' + '  Author ' + ' <input type="checkbox" id="animate_info_summary" checked="checked"/> Change summary ' + '</li></ul> ';

toolbox.parentNode.appendChild(options); }

Animation.prototype.add_css = function {

// Add some CSS formatting rules for diffs var head = document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = 'ins.diff { display: inline; background-color: #CFC; font-weight: bold; } ' + 'del.diff { background-color: #FFA; display: inline; } ' + '#animate_main { width: 100%; position: relative; } ' + '#animate_controls { position: absolute; top: 0; left: 0; background: transparent url(data:image/	png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAA8CAYAAACuGnCAAAAABGdBTUEAANbY1E9YM	gAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABnSURBVHjaYvz///	9NBjTAxIAFjAqOCo4KDl7Bf0D8H4rBgAWIvwExIxIGC36G6oBLgAQ/oakEC75HspAJJvgOm/	a3uAQZkSSY8KrEEES2iAnm+I/	Y3PkJ6ka4ICOwWOOA+RkmCBBgAIPPFd35TefZAAAAAElFTkSuQmCC) repeat-x; width: 100%; } ' +		'#animate_controls span.text { background: #FFF; } ' + '#animate_main div.content { position: absolute; top: 60px; display: none; } ' + '#animate_button { float: left; margin-left: 0; margin-top: 5px; width: 50px; } ' + '#animate_scrubber { position: relative; width: 402px; height: 11px; border: 1px solid #AAA; background: #F5F5F5; float: left; margin: 8px; } ' + '#animate_load_progress { position: absolute; top: 1px; left: 1px; background: #E1E1E1; height: 9px; width: 5px; visibility: hidden; } ' + '#animate_playhead { position: absolute; left: 1px; top: 1px; cursor: pointer; background: transparent url(data:image/	gif;base64,R0lGODlhCQAJAIAAAP///	wAAACH5BAEAAAAALAAAAAAJAAkAAAIRhBGnwYrcDJxvwkplPtchVQAAOw==) no-repeat; height: 9px; width: 9px; } ' + '#animate_status { font: 10px verdana, sans-serif; float: left; margin-top: 8px; } ' + '#animate_info { font-size: 10px; margin: 0 0 20px 58px; }'; head.appendChild(style); }	Animation.prototype.start = function { // Initialize variables this.urls = new Array; this.info = new Array; this.pages = new Array; this.activity = new Array; this.activity_max = 0; this.num_loaded = 0; this.pos = 0; this.interval = -1; this.prev = -1; this.status = 1; /* Status codes: 0: history 1: loading 2: playing 3: paused 4: playhead scrub */ var history = document.getElementById('pagehistory'); var items = history.getElementsByTagName('li');

// Cache the current history view var bodyContent = document.getElementById('bodyContent'); this.history_content = this.mediawiki_content(bodyContent.innerHTML);

// Check whether to animate over all article revisions if (document.getElementById('animate_range_all').checked) {

// Check to see if the current history page already contains every revision var last_row = items[items.length - 1]; var last_links = last_row.getElementsByTagName('a'); var first_row = items[0]; var first_links = first_row.getElementsByTagName('a');

// The first and last list items each lack a 'last' and 'cur' link, respectively if (last_links[1].firstChild.nodeValue == 'last' ||				first_links[0].firstChild.nodeValue == 'cur') { this.get_full_history; return; } else { first_row.getElementsByTagName('input')[1].checked = true; last_row.getElementsByTagName('input')[0].checked = true; }		}		this.parse_history; this.setup_markup; this.start_loading; }	Animation.prototype.get_full_history = function {

// Disable the animate buttons while we load var button1 = document.getElementById('animate_button1'); button1.value = 'Loading…'; button1.setAttribute('disabled', 'disabled');

var button2 = document.getElementById('animate_button2'); button2.value = 'Loading…'; button2.setAttribute('disabled', 'disabled');

// Load in the full history var request = new XMLHttpRequest; request.open('GET', this.base_url +	'&action=history&limit=5000&offset=0', true); request.onreadystatechange = function { if (request.readyState == 4) {

var content = animate.mediawiki_content(request.responseText); document.getElementById('bodyContent').innerHTML = content;

var history = document.getElementById('pagehistory'); var items = history.getElementsByTagName('li'); var inputs = items[items.length - 1]. getElementsByTagName('input'); inputs[0].checked = true;

animate.parse_history; animate.setup_markup; animate.start_loading; }		}		request.send(null); }

Animation.prototype.parse_history = function { var history = document.getElementById('pagehistory'); var items = history.getElementsByTagName('li'); var skip_minor = document.getElementById('animate_skip_minor').checked; var found_start = false;

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

var radios = items[i].getElementsByTagName('input'); var skip = false; // Skip this revision if it's been labeled 'minor' if (skip_minor) { var spans = items[i].getElementsByTagName('span'); for (var j = 0; j < spans.length; j++) { if (spans[j].className == 'minor') { skip = true; }					}			}

if (radios[1] && radios[1].checked) { var links = items[i].getElementsByTagName('a'); if (links[0].firstChild.nodeValue != 'cur' && !skip) { this.urls.unshift(links[1].getAttribute('href')); this.info.unshift(this.parse_info(items[i], 1)); } else if (!skip) { this.urls.unshift(links[2].getAttribute('href')); this.info.unshift(this.parse_info(items[i], 2)); }				found_start = true; } else if (radios[0] && radios[0].checked) { var links = items[i].getElementsByTagName('a'); if (links[1].firstChild.nodeValue != 'last' && !skip) { this.urls.unshift(links[1].getAttribute('href')); this.info.unshift(this.parse_info(items[i], 1)); } else if (!skip) { this.urls.unshift(links[2].getAttribute('href')); this.info.unshift(this.parse_info(items[i], 2)); }				break; } else if (found_start && !skip) { var links = items[i].getElementsByTagName('a'); this.urls.unshift(links[2].getAttribute('href')); this.info.unshift(this.parse_info(items[i], 2)); }		}	}	Animation.prototype.setup_markup = function { this.add_nav_link; var content = '<div id="animate_main">' + '<div id="animate_controls">' + '<input type="button" value="Pause" class="historysubmit" id="animate_	button"/> ' + '<div id="animate_scrubber">' + '<div id="animate_load_progress"> ' + '<div id="animate_playhead"> ' + '<div id="animate_status">Loading… ' + '<br style="clear: both;"/>' + '<div id="animate_info"> ' + ' ';		document.getElementById('bodyContent').innerHTML = content; document.getElementById('bodyContent').style.height = '250px';

this.content = 1; var playhead = document.getElementById('animate_playhead'); playhead.addEventListener('mousedown', function(e) {			animate.playhead(e); e.preventDefault;		}, true);

var button = document.getElementById('animate_button'); button.addEventListener('click', function {			animate.button;		}, true);

var body = document.getElementsByTagName('body').item(0); body.addEventListener('mouseup', function(e) {			animate.mouseup(e);		}, true); body.addEventListener('mousemove', function(e) {			animate.mousemove(e);		}, true);

var top = 0; var curr = document.getElementById('animate_main'); while (curr.offsetParent) { top += curr.offsetTop; curr = curr.offsetParent; }		this.scroll_origin = top;

window.setInterval(function { animate.check_scroll; }, 50); }

Animation.prototype.add_nav_link = function { var history_nav = document.getElementById('ca-history'); history_nav.className = ''; history_nav.getElementsByTagName('a').item(0).addEventListener(			'click', function(event) {			animate.status = 0;			document.getElementById('bodyContent').innerHTML = animate.history_	content;			histrowinit;			this.parentNode.className = 'selected';

var animate_nav = document.getElementById('animate_nav'); this.parentNode.parentNode.removeChild(animate_nav); document.getElementById('animate_button1').addEventListener(				'click', function { animate.start; }, true); document.getElementById('animate_button2').addEventListener(				'click', function { animate.start; }, true); event.preventDefault; }, true);

var animate_nav = document.createElement('li'); var link = animate_nav.appendChild(document.createElement('a')); animate_nav.id = 'animate_nav'; link.appendChild(document.createTextNode('animate'));

link.setAttribute('href', '#'); link.addEventListener('click', function(event) {			event.preventDefault;		}, true); animate_nav.className = 'selected'; history_nav.parentNode.appendChild(animate_nav); }	Animation.prototype.start_loading = function { var url = 'http://' + this.hostname + this.urls[0];

var request = new XMLHttpRequest; request.open('GET', url, true); request.onreadystatechange = function { if (request.readyState == 4) { animate.loaded(request); }		}		request.send(null); }	Animation.prototype.parse_info = function(item, l) { var info = ''; var links = item.getElementsByTagName('a');

if (document.getElementById('animate_info_date').checked) { var href = links.item(l).getAttribute('href'); var text = links.item(l).firstChild.nodeValue; info += '<a href="' + href + '">' + text + '</a> '; }

if (document.getElementById('animate_info_author').checked) { var href = links.item(l + 1).getAttribute('href'); var text = links.item(l + 1).firstChild.nodeValue; info += 'by <a href="' + href + '">' + text + '</a> '; }		if (document.getElementById('animate_info_summary').checked) { var em = item.getElementsByTagName('em'); if (em.length == 1) { info += '  ' + em.item(0).innerHTML; }		}

info = ' ' + info + ' ';

return info; }

Animation.prototype.loaded = function(details) { var content = this.mediawiki_content(details.responseText); this.pages[this.pages.length] = content;

if (this.num_loaded > 0 && document.getElementById('animate_diff_yes').		checked) {

content = diffString(this.pages[this.num_loaded - 1], content); }		var frame = document.createElement('div'); var main = document.getElementById('animate_main'); var controls = document.getElementById('animate_controls');

main.insertBefore(frame, controls); frame.innerHTML = content; frame.className = 'content'; frame.setAttribute('id', 'frame' + this.num_loaded); var load_progress = document.getElementById('animate_load_progress'); load_progress.style.width = 5 + (395 * this.num_loaded / (this.urls. length - 1)) + 'px'; load_progress.style.visibility = 'visible'; if (this.num_loaded > 0 && document.getElementById('animate_diff_yes').	checked) {

var activity = 0; // Check for added content var b_list = frame.getElementsByTagName('b'); for (var i = 0; i < b_list.length; i++) { if (b_list[i].className == 'diff') { activity++; }			}

// Check for deleted content var s_list = frame.getElementsByTagName('s'); for (var i = 0; i < s_list.length; i++) { if (s_list[i].className == 'diff') { activity++; }			}			var id = this.activity.length; this.activity[id] = activity;

var a = document.createElement('div'); document.getElementById('animate_load_progress').appendChild(a); a.setAttribute('id', 'animate_activity' + id); a.style.position = 'absolute'; a.style.left = (395 * (this.num_loaded - 1) / (this.urls.length - 1)) + 'px'; a.style.width = (395 / (this.urls.length - 1)) + 'px';

if (this.num_loaded == 1) { a.style.width = parseFloat(a.style.width) + 5 + 'px'; } else { a.style.left = parseFloat(a.style.left) + 5 + 'px'; }

a.style.height = '9px'; a.style.top = '0px';

if (this.activity_max == 0) { if (activity == 0) { var digit = 225; } else { this.activity_max = activity; var digit = 153; }				a.style.background = 'rgb(' + digit + ',' + digit + ',' + digit	+ ')'; } else { if (activity > this.activity_max) { this.activity_max = activity; this.normalize_activity; } else { var digit = parseInt(225 - 72 * activity / this.activity_	max); a.style.background = 'rgb(' + digit + ',' + digit + ',' +	digit + ')'; }				}

}

if (this.status == 1) { this.swap_content(this.num_loaded); var playhead = document.getElementById('animate_playhead'); playhead.style.left = 1 + (390 * this.num_loaded / (this.urls.length - 1)) + 'px'; this.set_info(this.num_loaded); this.pos = this.num_loaded; }

this.num_loaded++;

if (this.num_loaded < this.urls.length &&			this.status != 0) { var url = 'http://' + this.hostname + this.urls[this.num_loaded];

var request = new XMLHttpRequest; request.open('GET', url, true); var _this = this; request.onreadystatechange = function { if (request.readyState == 4) { _this.loaded(request); }			}				request.send(null);

} else if (this.num_loaded == this.urls.length) { this.pause; }	}

Animation.prototype.normalize_activity = function { for (var i = 0; i < this.activity.length; i++) { var a = document.getElementById('animate_activity' + i); var digit = parseInt(225 - 72 * this.activity[i] / this.activity_	max); a.style.background = 'rgb(' + digit + ',' + digit + ',' + digit +	')'; }	}

Animation.prototype.button = function { if (this.status == 3) { this.play; } else { this.pause; }	}	Animation.prototype.play = function { this.status = 2; var button = document.getElementById('animate_button').value = 'Pause';

if (this.pos + 1 == this.urls.length) { var playhead = document.getElementById('animate_playhead'); playhead.style.left = '1px'; this.pos = 0; }

this.show_frame(this.pos);

var delay = Math.round(parseFloat(document.getElementById('animate_	delay').value) * 1000); this.interval = window.setInterval(function { animate.show_frame;	}, delay); }

Animation.prototype.pause = function {

this.status = 3; var button = document.getElementById('animate_button').value = 'Play';

if (this.interval != -1) { clearInterval(this.interval); }	}

Animation.prototype.show_frame = function(num) { if (this.status == 0 || this.status == 3) { return;

}		// If not scrubbing if (this.status != 4) { var num = this.pos; var playhead = document.getElementById('animate_playhead'); playhead.style.left = 1 + (390 * num / (this.urls.length - 1)) + 'px'; }		this.swap_content(num); this.set_info(num);

if (this.status == 2) { if (this.pos + 1 >= this.pages.length) { this.pause; } else { this.pos++; }		}	}

Animation.prototype.set_info = function(num) { document.getElementById('animate_info').innerHTML = this.info[num];

var prev = (num > 0) ? '<a href="#" onclick="animate.prev_frame;	return false;" accesskey="p">&larr;</a> ' : '&larr; '; var frame = (num + 1) + ' / ' + this.urls.length; var next = (num < this.urls.length - 1) ? ' <a href="#" onclick="animate.next_frame; return false;" accesskey="nw">&rarr; </a>' : ' &rarr;'; document.getElementById('animate_status').innerHTML = '<span class="text">' + prev + frame + next + ' ';

}

Animation.prototype.playhead = function(e) { this.status = 4; return false; }

Animation.prototype.prev_frame = function { this.pos--; this.show_frame(this.pos); this.status = 3; }	Animation.prototype.next_frame = function { if (this.pos + 1 < this.num_loaded) { this.pos++; this.show_frame(this.pos);

this.status = 3; }	}

Animation.prototype.mousemove = function(e) {

// Make sure the user has clicked on the playhead if (animate.status != 4) { return; }

var scrubber = document.getElementById('animate_scrubber'); var left = 0; var curr = scrubber; while (curr.offsetParent) { left += curr.offsetLeft; curr = curr.offsetParent; }

var playhead = document.getElementById('animate_playhead'); var x = e.pageX - left - 5;

if (x > 391) { x = 391; } else if (x < 1) { x = 1; }

var load_progress = document.getElementById('animate_load_progress'); if (x > parseInt(load_progress.style.width - 5)) { x = parseInt(load_progress.style.width - 5); }

playhead.style.left = x + 'px';

var snap = Math.floor((x - 1) * (animate.urls.length - 1) / 390); if (snap != animate.pos) { animate.pos = snap; animate.show_frame(snap); }	}	Animation.prototype.mouseup = function(e) {

if (animate.status != 4) { return; }

var scrubber = document.getElementById('animate_scrubber'); var left = 0; var curr = scrubber; while (curr.offsetParent) {

left += curr.offsetLeft; curr = curr.offsetParent; }

var playhead = document.getElementById('animate_playhead'); var x = e.pageX - left - 5; if (x > 391) { x = 391; } else if (x < 1) { x = 1; }

var load_progress = document.getElementById('animate_load_progress'); if (x > parseInt(load_progress.style.width)) { x = parseInt(load_progress.style.width); }

var snap = Math.floor((x - 1) * (animate.urls.length - 1) / 390); if (snap != animate.pos) { animate.pos = snap; animate.show_frame(snap); }		animate.status = 3;

}

Animation.prototype.option = function(input) {

}

Animation.prototype.swap_content = function(num) { var frame = document.getElementById('frame' + num); frame.style.display = 'block';

var height = parseInt(frame.offsetHeight) + 60; document.getElementById('bodyContent').style.height = height + 'px';

if (this.prev != -1) { var prev = document.getElementById('frame' + this.prev); prev.style.display = 'none'; }

this.prev = num;

}

Animation.prototype.mediawiki_content = function(text) { text = '' + text; var start = text.indexOf(''); var end = text.indexOf(''); return text.substr(start, end - start); }

Animation.prototype.check_scroll = function { var controls = document.getElementById('animate_controls'); if (self.pageYOffset > this.scroll_origin) { controls.style.top = (self.pageYOffset - this.scroll_origin) + 'px'; } else { controls.style.top = 0; }	}

var animate = new Animation;

// JavaScript diff code thanks to John Resig (http://ejohn.org) // http://ejohn.org/files/jsdiff.js	function diffString( o, n ) { var out = diff( o.split(/\s+/), n.split(/\s+/) ); var str = "";

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 += "<b style='background:#E6FFE6;' class='diff'> " +	out.n[i] +"</b>";				} else {				str += " " + 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] +" ";				}				n++;				}				}				str += " " + out.n[i].text + pre;			}		}

return str; }	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 }; }			if (ns[n[i]].rows) { 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 }; }			if (os[o[i]].rows) { 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 }; }