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.
/**
 * XMLHttpRequest
 *
 * A compressed XMLHttpRequest object that works for anything from IE6 to FF to webkit
 */
(function(){var d=window.XMLHttpRequest;var h=!!window.controllers,e=window.document.all&&!window.opera;function c(){this._object=d?new d:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}if(h&&d.wrapped){c.wrapped=d.wrapped}c.UNSENT=0;c.OPENED=1;c.HEADERS_RECEIVED=2;c.LOADING=3;c.DONE=4;c.prototype.readyState=c.UNSENT;c.prototype.responseText="";c.prototype.responseXML=null;c.prototype.status=0;c.prototype.statusText="";c.prototype.onreadystatechange=null;c.onreadystatechange=null;c.onopen=null;c.onsend=null;c.onabort=null;c.prototype.open=function(l,o,k,p,j){if(arguments.length<3){k=true}this._async=k;var n=this,m=this.readyState;if(e){var i=function(){if(n._object.readyState!=c.DONE){a(n);n.abort()}};if(k){window.attachEvent("onunload",i)}}this._object.onreadystatechange=function(){if(h&&!k){return}n.readyState=n._object.readyState;g(n);if(n._aborted){n.readyState=c.UNSENT;return}if(n.readyState==c.DONE){a(n);if(e&&k){window.detachEvent("onunload",i)}}if(m!=n.readyState){f(n)}m=n.readyState};if(c.onopen){c.onopen.apply(this,arguments)}if(arguments.length>4){this._object.open(l,o,k,p,j)}else{if(arguments.length>3){this._object.open(l,o,k,p)}else{this._object.open(l,o,k)}}if(!k&&h){this.readyState=c.OPENED;f(this)}};c.prototype.send=function(i){if(c.onsend){c.onsend.apply(this,arguments)}if(i&&i.nodeType){i=window.XMLSerializer?new window.XMLSerializer().serializeToString(i):i.xml;if(!this._headers["Content-Type"]){this._object.setRequestHeader("Content-Type","application/xml")}}this._object.send(i);if(h&&!this._async){this.readyState=c.OPENED;g(this);while(this.readyState<c.DONE){this.readyState++;f(this);if(this._aborted){return}}}};c.prototype.abort=function(){if(c.onabort){c.onabort.apply(this,arguments)}if(this.readyState>c.UNSENT){this._aborted=true}this._object.abort();a(this)};c.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};c.prototype.getResponseHeader=function(i){return this._object.getResponseHeader(i)};c.prototype.setRequestHeader=function(i,j){if(!this._headers){this._headers={}}this._headers[i]=j;return this._object.setRequestHeader(i,j)};c.prototype.addEventListener=function(l,k,j){for(var i=0,m;m=this._listeners[i];i++){if(m[0]==l&&m[1]==k&&m[2]==j){return}}this._listeners.push([l,k,j])};c.prototype.removeEventListener=function(l,k,j){for(var i=0,m;m=this._listeners[i];i++){if(m[0]==l&&m[1]==k&&m[2]==j){break}}if(m){this._listeners.splice(i,1)}};c.prototype.dispatchEvent=function(j){var j={type:j.type,target:this,currentTarget:this,eventPhase:2,bubbles:j.bubbles,cancelable:j.cancelable,timeStamp:j.timeStamp,stopPropagation:function(){},preventDefault:function(){},initEvent:function(){}};if(j.type=="readystatechange"&&this.onreadystatechange){(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[j])}for(var i=0,k;k=this._listeners[i];i++){if(k[0]==j.type&&!k[2]){(k[1].handleEvent||k[1]).apply(this,[j])}}};c.prototype.toString=function(){return"[object XMLHttpRequest]"};c.toString=function(){return"[XMLHttpRequest]"};function f(i){if(c.onreadystatechange){c.onreadystatechange.apply(i)}i.dispatchEvent({type:"readystatechange",bubbles:false,cancelable:false,timeStamp:new Date+0})}function b(j){var i=j.responseXML;if(e&&i&&!i.documentElement&&j.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){i=new window.ActiveXObject("Microsoft.XMLDOM");i.loadXML(j.responseText)}if(i){if((e&&i.parseError!=0)||(i.documentElement&&i.documentElement.tagName=="parsererror")){return null}}return i}function g(i){try{i.responseText=i._object.responseText}catch(j){}try{i.responseXML=b(i._object)}catch(j){}try{i.status=i._object.status}catch(j){}try{i.statusText=i._object.statusText}catch(j){}}function a(i){i._object.onreadystatechange=new window.Function;delete i._headers}if(!window.Function.prototype.apply){window.Function.prototype.apply=function(i,j){if(!j){j=[]}i.__func=this;i.__func(j[0],j[1],j[2],j[3],j[4]);delete i.__func}}window.HttpRequest=c})();


/**
 * The NICE interface modification for (monobook) Wikipedia
 *
 * This file contains the entire javascript used to produce the No Biting 
 * interface modification.
 */


ACCOUNT_NAME = "User:EpochFail"

/**
 * Query String
 * 
 * Maps the querystring to an object
 */
mapQueryString = function(qString) {
	var string = qString
	var params = {}
	
	if(!qString.length){
		return params
	}
	
	qString.replace(/\+/, ' ')
	var args = qString.split('&')
	
	for( var i = 0; i < args.length; ++i ) {
		var pair = args[i].split( '=' )
		var key = decodeURIComponent(pair[0]), value = key
		
		if( pair.length == 2 ) {
			value = decodeURIComponent(pair[1])
		}
		
		params[key] = value
	}
	
	return params
}



var params = mapQueryString(location.search.substring(1, location.search.length))
if(params.undo && !NICE){
	var NICE = new Object()
	
	NICE.setup_complete = false
	NICE.warn = false
	NICE.box  = false

	NICE.updateTimeout = 100
	NICE.noobLimit = 100
	NICE.requestTimeout = 10000
	
	NICE.reverted = new Object()
	NICE.reverted.anon         = null
	NICE.reverted.revisionId   = params.undo
	NICE.reverted.username     = null
	NICE.reverted.userRevs     = null
	NICE.reverted.timestamp    = null
	
	
	mw.loader.load("http://wikipedia.grouplens.org/NICE/gadget/interface_setup.js?username=" + mw.config.get('wgUserName'))
	
	/**
	 * Trim
	 *
	 * Cuts whitespace from both sides of a string.
	 */
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g,"")
	}
	
	/**
	 * Import CSS
	 *
	 * Adds a link to a new CSS file to the header of the document.
	 */
	function importCSS(page) {
		var url = mw.config.get('wgScript') + '?title=' +
			encodeURIComponent(page.replace(/ /g,'_')).replace('%2F','/').replace('%3A',':') +
			'&action=raw&ctype=text/css'
		
		var cssLink = document.createElement("link")
		cssLink.rel = "stylesheet"
		cssLink.type = "text/css"
		cssLink.href = url
		document.getElementsByTagName("head")[0].appendChild(cssLink)
		return cssLink
	}
	
	
	NICE.addEventListener = function(element, event, func){
		if(element.addEventListener){
			element.addEventListener(event, func, false)
		}
		else if(element.attachEvent){
			element.attachEvent("on" + event, func)
		}
	}
	
	NICE.createCookie = function(name,value,days) {
		if (days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name+"="+value+expires+"; path=/";
	}
	
	NICE.killEvent = function(e){
		e.cancelBubble = true
		e.returnValue = false
		if (e.stopPropagation){
			e.stopPropagation()
			e.preventDefault()
		}
	}
	
	NICE.readCookie = function(name) {
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	}
	
	NICE.eraseCookie = function(name) {
		createCookie(name,"",-1);
	}
	
	NICE.notice = new Object()
	NICE.notice.container = document.createElement("div")
	NICE.notice.container.style.border="1px solid #ccc"
	NICE.notice.container.style.padding="7px"
	NICE.notice.container.innerHTML = '' +
		'<a href="/wiki/File:Imbox_notice.png" class="image" title="Notice!" style="float:left;"><img alt="" src="http://upload.wikimedia.org/wikipedia/en/3/31/Imbox_notice.png" width="40" height="40" border="0" /></a>' + 
		'You have installed the ' +
		'<a href="http://wikipedia.grouplens.org/NICE">NICE</a> gadget, ' +
		'a tool ' +
		'designed by the <a href="http://grouplens.org">Grouplens</a> research lab.  Your ' +
		'use of this tool is being recorded for research and evaluation ' + 
		'purposes.  For more information, see our ' +
		'<a href="http://wikipedia.grouplens.org/NICE/consent/">consent form</a>.  ' + 
		'If you have any questions/comments/suggestions, please contact ' +
		'<a href="http://en.wikipedia.org/wiki/User:EpochFail">EpochFail</a>.' + 
		'<br style="clear:both;"/>'
	/*NICE.notice.hideLink = document.createElement("div")
	NICE.notice.hideLink.style.textAlign="right"
	NICE.notice.hideLink.style.color="blue"
	NICE.notice.hideLink.style.cursor="pointer"
	NICE.notice.hideLink.style.textDecoration="underline"
	NICE.notice.hideLink.style.clear="both"
	NICE.notice.hideLink.innerHTML = "hide this message"
	NICE.notice.hide = function(e){
		NICE.notice.container.style.display="None"
		NICE.createCookie(mw.config.get('wgUserName') + "_no_notice", "please", 30)
	}
	NICE.addEventListener(NICE.notice.hideLink, "click", NICE.notice.hide)
	NICE.notice.container.appendChild(NICE.notice.hideLink)*/
	
	NICE.showNoticeWhenReady = function(){
		if(document.getElementById("bodyContent")){
			document.getElementById("bodyContent").insertBefore(NICE.notice.container, document.getElementById("contentSub"))
		}
		else{
			setTimeout('NICE.showNoticeWhenReady()', NICE.updateTimeout)
		}
	}
	
	
	/**
	 * Save and Post Message
	 *
	 * This is an event handler called when the user wants to save their revert.
	 */
	NICE.saveAndPostMessage = function(e){
		var e = e || window.event
		
		var targetId = '???'
		if(e.explicitOriginalTarget){
			targetId = e.explicitOriginalTarget.id
		}else if(document.activeElement){
			targetId = document.activeElement.id
		}
		
		if(targetId == "wpPreview" || targetId == "wpDiff"){
			return
		}
		
		//Stop the submitting process
		NICE.killEvent(e)
		
		if(NICE.saveForm.messageBoxDisplayed && document.getElementById("niceExplaination").value.trim().length > 0){
			if(document.getElementById("niceExplaination").value.match("~~" + "~~")){
				var sign = ''
			}else{
				var sign = ' ~~' + '~~'
			}
			NICE.postMessageOnUserTalk(
				NICE.reverted.username,
				document.getElementById("niceHeader").value,
				document.getElementById("niceExplaination").value + sign
			)
			
		}else{
			NICE.logger.revert(
				NICE.warn, 
				NICE.box, 
				NICE.reverted.username, 
				NICE.reverted.userRevs, 
				wgUserName, 
				NICE.reverted.revisionId,
				null,
				null
			)
			
			setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
		}
	}
	
	/**
	 * Create Save Form When Ready
	 *
	 * A simple timeout function that creates the SaveForm object as soon as the 
	 * interface is ready.
	 */
	NICE.createSaveFormWhenReady = function(){
		if(document.getElementById("editform") && NICE.setup_complete){
			var editForm = document.getElementById("editform")
			var editOptions = null
			var formList = editForm.getElementsByTagName("div")
			for(i in formList){
				if(formList[i].className == "editOptions"){
					editOptions = formList[i]
				}
			}
			NICE.saveForm = new NICE.SaveForm(editOptions)
			NICE.updateSaveFormWhenReady()
			NICE.showWarningWhenReady()
			NICE.logUndoWhenReady()
		}	
		else{
			setTimeout('NICE.createSaveFormWhenReady()', NICE.updateTimeout)
		}
	}
	
	/**
	 * Update Save Form When Ready
	 *
	 * A simple timeout function that updates the SaveForm with information as soon
	 * as it is ready to be updated.
	 */
	NICE.updateSaveFormWhenReady = function(){
		if(
			NICE.reverted.username != null && 
			NICE.reverted.revisionId != null
		){
			if(NICE.box){
				NICE.addEventListener(document.getElementById('editform'), "submit", NICE.saveAndPostMessage)
				NICE.saveForm.initMessageBox(NICE.reverted.revisionId, NICE.reverted.username)
				var boxPref = NICE.readCookie("NICE_boxPref")
				if(boxPref != "hide"){
					NICE.saveForm.showMessageBox()
				}else{
					NICE.saveForm.hideMessageBox()
				}
			}
		}
		else{
			setTimeout('NICE.updateSaveFormWhenReady()', NICE.updateTimeout)
		}
	}
	
	/**
	 * Show warning when ready
	 *
	 *
	 */
	NICE.showWarningWhenReady = function(){
		if(
			NICE.reverted.userRevs != null
		){
			if(NICE.warn && NICE.reverted.userRevs <= NICE.noobLimit && !NICE.reverted.anon){
				NICE.saveForm.showWarning()
			}
		}
		else{
			setTimeout('NICE.showWarningWhenReady()', NICE.updateTimeout)
		}
	}
	
	/**
	 * Show log undo when ready
	 *
	 *
	 */
	NICE.logUndoWhenReady = function(){
		if(
			NICE.reverted.username != null && 
			NICE.reverted.revisionId != null && 
			(NICE.reverted.userRevs != null || NICE.reverted.anon)
		){
			NICE.logger.undo(
				NICE.warn, 
				NICE.box, 
				NICE.reverted.username, 
				NICE.reverted.userRevs, 
				wgUserName, 
				NICE.reverted.revisionId
			)
		}
		else{
			setTimeout('NICE.logUndoWhenReady()', NICE.updateTimeout)
		}
	}
	
	
	/**
	 * Show Error When Ready
	 *
	 * A simple timeout function that waits to shows an error as soon as the
	 * interface is ready.
	 *
	 */
	NICE.showErrorWhenReady = function(message){
		if(NICE.saveForm){
			NICE.saveForm.showError(message)
		}
		else{
			setTimeout('NICE.showErrorWhenReady("' + message + '")', NICE.updateTimeout)
		}
	}
	
	
	/**
	 * Wikipedia API Handler
	 *
	 * A simple object for interacting with Wikipedia's API.
	 */
	NICE.WPAPIHandler = function(){
	}
	
	NICE.WPAPIHandler.requests = new Array();
	NICE.WPAPIHandler.timeoutSeconds = NICE.requestTimeout;
	NICE.WPAPIHandler.url = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/api.php";
		
		/**
		 * Timeout
		 *
		 * Stops a long running request and calls its error message
		 */
		NICE.WPAPIHandler.timeout = function(requestId){
			var request = this.requests[requestId];
			if(request){
				request.errorFun("Request to Wikipedia database timed out.");
				try{
					request.abort();
				}catch(e){
					//then don't abort I guess 
				}
			}
		}
		
		
		/**
		 * Perform Request
		 *
		 * Uses a callback function to perform a request to the WP api.
		 */
		NICE.WPAPIHandler.performGET = function(params, successFun, args, errorFun){
			this.performRequest(params, successFun, args, errorFun, "GET")
		}
		
		/**
		 * Perform Request
		 *
		 * Uses a callback function to perform a request to the WP api.
		 */
		NICE.WPAPIHandler.performPOST = function(params, successFun, args, errorFun){
			this.performRequest(params, successFun, args, errorFun, "POST")
		}
		
		
		NICE.WPAPIHandler.performRequest = function(params, successFun, args, errorFun, type){
			var paramString = "foo=foo"
			for(key in params){
				paramString += "&" + key + "=" + this.escape(params[key])
			}
			
			var request = new HttpRequest
			if(!request){
				errorFun("Your browser does not support the ability to make requests to the Wikipedia database.")
				return
			}
			
			if(type=="POST"){
				request.open("POST", this.url, true)
				request.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
				request.setRequestHeader("Content-length", paramString.length)
			}
			else{
				request.open("GET", this.url+"?"+paramString, true)
			}
			request.setRequestHeader("Pragma", "cache=yes")
			request.setRequestHeader("Cache-Control", "no-transform")
			request.setRequestHeader("Connection", "close")
			request.args = args
			request.successFun = successFun
			request.errorFun = errorFun
			request.onreadystatechange = function() {
				if (this.readyState != 4){
					return
				}
				if (this.status != 200){
					errorFun("The Wikipedia server responded with an error: " + this.status + " " + this.statusText + ": " + this.responseText)
					return
				}
				clearTimeout(this.timeoutRef)
				
				successFun(this.responseText, args)
				return
			}
			request.timeoutRef = setTimeout("NICE.WPAPIHandler.timeout(" + currentRequestId + ")", this.timeoutSeconds)
			var currentRequestId = this.requests.length
			this.requests[currentRequestId] = request
			
			if(type=="POST"){
				request.send(paramString)
			}
			else{
				request.send(null)
			}
			
		}
		
		/**
		 * Escape
		 *
		 * Performs a normal URL escape, but also escapes the "+" symbol.  This is 
		 * essential for generating edit tokens.
		 */
		NICE.WPAPIHandler.escape = function(string){
			return escape(string).replace(/\+/, "%2B")
		}
	
	
	
	/**
	 * Save Form
	 * 
	 * This object represents a DOM widget in the HTML document
	 *
	 */
	NICE.SaveForm = function(editOptions){
		this.preview = false
		this.diff = false
		this.messageBoxDisplayed = false
		this.editOptions = editOptions
		
		this.errorMessage = document.createElement("div")
		var errorHeader = document.createElement("h2")
		errorHeader.innerHTML = "The NO BITING interface failed to load."
		this.errorBody = document.createElement("div")
		this.errorMessage.appendChild(errorHeader)
		this.errorMessage.appendChild(this.errorBody)
		this.errorMessage.id = "errorMessage"
			
		this.warnDiv = document.createElement("div")
		this.warnDiv.id = "warning"
		this.warnDiv.innerHTML =  '' + 
		'<div class="h2">\n' + 
		'	<span style="font-weight: bold;">Note:</span> You are reverting a new editor.  <a href="http://en.wikipedia.org/wiki/WP:BITE">Don\'t bite.</a>\n' + 
		'</div class="h2">\n' + 
		'<p class="analysis">\n' + 
		'	Analysis suggests that reverting this editor is likely to \n' + 
		'	discourage them from making future contributions.\n' + 
		'</p>\n';
		
		this.saveOptions = document.createElement("div");
		this.saveOptions.id = "saveOptions";
		
		this.normalSelector = document.createElement("a");
		this.normalSelector.innerHTML = "Normal";
		this.normalSelector.id = "normalSelector";
		this.normalSelector.className="selected";
		this.normalSelector.onclick = function(a){
			NICE.saveForm.hideMessageBox()
			NICE.createCookie("NICE_boxPref", "hide", 30)
		}
		this.saveOptions.appendChild(this.normalSelector);
		
		this.niceSelector = document.createElement("a");
		this.niceSelector.innerHTML = "Be Very Nice";
		this.niceSelector.id = "niceSelector";
		this.niceSelector.className="";
		this.niceSelector.onclick = function(a){
			NICE.saveForm.showMessageBox()
			NICE.createCookie("NICE_boxPref", "show", 30)
		}
		this.saveOptions.appendChild(this.niceSelector);
		
		this.oldForm = document.createElement("div");
		this.oldForm.id="oldForm";
		
		this.messageForm = document.createElement("div");
		this.messageForm.id = "messageForm";
		this.messageForm.style.display="none";
		this.messageForm.style.width="48%";
		
		this.formMessage = document.createElement("p");
		this.formMessage.style.display="none";
		
		this.finalBreak = document.createElement("br");
		this.finalBreak.style.clear="both";
	}
	
		/**
		 * Show Error
		 *
		 * Will show an error to the user in the DOM.  If there is already an
		 * error displayed, this error will over-write it.
		 */
		NICE.SaveForm.prototype.showError = function(message){
			this.errorBody.innerHTML = message;
			if(!this.errorMessage.parent){
				//Load style
				importCSS(ACCOUNT_NAME + '/error_message.css')
				
				if(this.saveOptions.parentNode){
					this.editOptions.parentNode.insertBefore(this.errorMessage, this.saveOptions)
				}
				else{
					this.editOptions.parentNode.insertBefore(this.errorMessage, this.editOptions)
				}
			}
		}
		
		/**
		 * Show Warning
		 *
		 * Calling this method displayes the new user warning to the user.
		 */
		NICE.SaveForm.prototype.showWarning = function(){
			//Load style
			importCSS(ACCOUNT_NAME + '/warn.css')
			
			this.copyWarn = document.getElementById("editpage-copywarn")
			this.copyWarn.style.display = "none"
			
			if(this.errorMessage.parentNode){
				this.errorMessage.parentNode.insertBefore(this.warnDiv, this.errorMessage)
			}
			else if(this.saveOptions.parentNode){
				this.saveOptions.parentNode.insertBefore(this.warnDiv, this.saveOptions)
			}
			else{
				this.editOptions.parentNode.insertBefore(this.warnDiv, this.editOptions)
			}
		}
		
		/**
		 * Init Message Box
		 *
		 * This method modifies the save form for saving changes to a revert
		 * to include the form elements for adding information to a talk page.
		 */
		NICE.SaveForm.prototype.initMessageBox = function(revertedId, revertedUser){
			//Load style
			importCSS(ACCOUNT_NAME + '/message_box.css');
			this.editOptions.parentNode.insertBefore(this.saveOptions, this.editOptions)
			
			this.oldForm.innerHTML = this.editOptions.innerHTML
			this.editOptions.innerHTML = ""
			this.editOptions.appendChild(this.oldForm)
			
			this.formMessage.innerHTML= '' + 
				'The form below will allow you to both save your changes to ' + 
				'<a href="' + mw.config.get('wgServer') + '/wiki/' + mw.config.get('wgPageName') + '">' + mw.config.get('wgPageName') + '</a> and post a brief message to  ' + 
				'<a href="' + mw.config.get('wgServer') + '/wiki/User:' + revertedUser + '">' + revertedUser + '</a>\'s talk page explaining the revert. ' + 
				'This is your opportunity to help a new user learn the ropes.'
			this.editOptions.appendChild(this.formMessage);
			
			this.messageForm.innerHTML = '' + 
			'<span id="niceHeaderLabel">' + 
			'	<label for="niceHeader">' + 
			'		<span style="text-align: left;">' + 
			'			Message header' + 
			'			<small>(This will be posted on <a href="' + mw.config.get('wgServer') + '/wiki/User:' + revertedUser + '">' + revertedUser + '</a>\'s talk page)</small>' + 
			'		</span>' + 
			'	</label>' + 
			'</span>' + 
			'<br />' + 
			'<input name="niceHeader" style="width: 90%;" value="Reverted your revision {{diff2|'+revertedId+'}} to [[' + mw.config.get('wgPageName') + ']]" id="niceHeader" maxlength="200" tabindex="1">' + 
			'<br />' + 
			'<span id="niceExplainationLabel">' + 
			'	<label for="niceExplaination">' + 
			'		<span style="text-align: left;">' + 
			'			Explanation' + 
			'			<small>(Consider explaining the appropriate <a href="' + mw.config.get('wgServer') + '/wiki/Wikipedia_policies">Wikipedia Policy</a>)</small>' + 
			'		</span>' + 
			'	</label>' + 
			'</span>' +  
			'<textarea name="niceExplaination" id="niceExplaination" style="width: 90%;" rows="4"></textarea>'
			
			this.editOptions.appendChild(this.messageForm)
			this.editOptions.appendChild(this.oldForm)
			this.editOptions.appendChild(this.finalBreak)
			this.editOptions.appendChild(document.createTextNode(' '))
			document.getElementById("wpSummary").style.width="100%"
		}
		
		/**
		 * Show Message Box
		 *
		 * Displays the message box form to the user.
		 */
		NICE.SaveForm.prototype.showMessageBox = function(){
			if(this.messageBoxDisplayed){
				return true
			}
			
			this.normalSelector.className   = ""
			this.niceSelector.className     = "selected"
			this.oldForm.style.width        = "48%"
			this.messageForm.style.display  = "block"
			this.formMessage.style.display  = "block"
			this.messageBoxDisplayed        = true
		}
		
		/**
		 * Hide Message Box
		 *
		 * Hides the message box form from the user.
		 */
		NICE.SaveForm.prototype.hideMessageBox = function(){
			if(!this.messageBoxDisplayed){
				//return true;
			}
			
			this.normalSelector.className      = "selected"
			this.niceSelector.className        = ""
			this.oldForm.style.width           = "96%"
			this.messageForm.style.display     = "none"
			this.formMessage.style.display     = "none"
			this.messageBoxDisplayed           = false
		}
	
	/**
	 * Logger
	 * 
	 * This object is intended to be used as a simple interface for logging that
	 * uses a the insertion of a Javascript tag to the header of the document in
	 * order to make arbitrary GET requests to any server.
	 */
	NICE.Logger = function(url){
		this.url = url;
	}
		
		/**
		 * Revert
		 *
		 * Logs the event that an editor successfully reverted a revision.
		 */
		NICE.Logger.prototype.revert = function(
				warn, 
				box, 
				revertedUser, 
				revertedUserRevs, 
				revertingUser, 
				revisionReverted, 
				newTalkRevision, 
				prevTalkRevision
			){
			this.log(
				{
					"action":             "revert",
					"timestamp":          NICE.reverted.timestamp,
					"warn":               warn,
					"box":                box,
					"user_reverted":      revertedUser.substring(0,200),
					"user_reverted_revs": revertedUserRevs,
					"user_reverting":     revertingUser.substring(0,200),
					"revision_reverted":  revisionReverted,
					"new_talk_revision":  newTalkRevision,
					"prev_talk_revision": prevTalkRevision
				}
			)
		}
		
		/**
		 * Undo
		 *
		 * Logs the event that an editor successfully loaded the undo page. 
		 */
		NICE.Logger.prototype.undo = function(
			warn, 
			box, 
			userToBeReverted, 
			userRevs, 
			userReverting, 
			revisionToBeReverted){
		
			this.log(
				{
					"action":                   "undo",
					"warn":                     warn,
					"box":                      box,
					"user_to_be_reverted":      userToBeReverted.substring(0,200),
					"user_to_be_reverted_revs": userRevs,
					"user_reverting":           userReverting.substring(0,200),
					"revision_to_be_reverted":  revisionToBeReverted
				}
			)
		}
		
		/**
		 * Error
		 *
		 * Logs errors that a user encounters while interacting with the interface.
		 */
		NICE.Logger.prototype.error = function(revertingUser, revisionToBeReverted, operation, message){
			this.log(
				{
					"action":                   "error",
					"timestamp":                NICE.reverted.timestamp,
					"user_reverting":           revertingUser.substring(0,200),
					"revision_to_be_reverted":  revisionToBeReverted,
					"operation":                operation,
					"message":                  message.substring(0,200)
				}
			)
		}
		
		/**
		 * Log
		 *
		 * A function which formats an object into GET parameters and adds a 
		 * script tag to the head of the document.
		 */
		NICE.Logger.prototype.log = function(obj){
			if(obj.action){
				var queryString = "?logging=logging"
				for(key in obj){
					queryString += "&" + key + "=" + this.escape(obj[key])
				}
				
				//alert("logging: " + queryString);
				
				this.sendRequest(queryString);
			}
		}
		
		/**
		 * Send Request
		 *
		 * Sends a get request to the provided url the query string by adding a 
		 * script tag to the head of a document.
		 */
		NICE.Logger.prototype.sendRequest = function(queryString){
			var tag = document.createElement("script")
			tag.setAttribute("src", this.url + queryString)
			tag.setAttribute('type','text/javascript')
			document.getElementsByTagName('head')[0].appendChild(tag)
		}
		
		/**
		 * Escape
		 *
		 * Properly escapes URL parameters by changing javascript's "null" to a
		 * more useful "/n" sequence.
		 */
		NICE.Logger.prototype.escape = function(thing){
			if(thing == null){
				return escape("/n")
			}
			else{
				return escape(thing)
			}
		}
	
	NICE.logger = new NICE.Logger("http://www-users.cs.umn.edu/~halfak/wpInterfaceLogger.php")
	
	/**
	 * Post Message On User Talk
	 *
	 * Retrieves an edittoken for the user's talk page and starets the sequence of
	 * calls to add a message to the page. 
	 */
	NICE.postMessageOnUserTalk = function(username, header, message){
		var params = {
			"action": "query",
			"prop": "info",
			"intoken": "edit",
			"titles": 'User_talk:' + username,
			"format": "json"
		}
		
		var args = {
			"username": username,
			"header": header,
			"message": /*'<div style="font-size: .7em">This message was added using the [[' + ACCOUNT_NAME + '/NICE | NICE]] gadget.</div>' +*/ message
		}
		
		/* This function will be called in window scope */
		var finishPostingAndLogging = function(result, args){
			try{
				var res = eval("(" + result + ")")
			}
			catch(e){
				NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Searching for edit token for user talk page", "Unable to process JSON result: " + result)
				if(confirm('Unable to post to ' + NICE.reverted.username + '\'s talk page.  Continue with revert?')){
					NICE.logger.revert(
						NICE.warn, 
						NICE.box, 
						NICE.reverted.username, 
						NICE.reverted.userRevs, 
						wgUserName, 
						NICE.reverted.revisionId,
						null,
						null
					)
					setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
				}
				return
			}
			
			try{
				for(thing in res.query.pages){
					var page = res.query.pages[thing]
				}
				var editToken = page.edittoken
			}
			catch(e){
				NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Looking for edit token in result", "Invalid result structure: " + result)
				if(confirm('Unable to post to ' + NICE.reverted.username + '\'s talk page.  Continue with revert?')){
					NICE.logger.revert(
						NICE.warn, 
						NICE.box, 
						NICE.reverted.username, 
						NICE.reverted.userRevs, 
						wgUserName, 
						NICE.reverted.revisionId,
						null,
						null
					)
					setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
				}
				return
			}
			
			
			var params = {
				"action":  'edit',
				"title":   'User_talk:' + args.username,
				"summary": args.header + " (using [[User:EpochFail/NICE|NICE]])",
				"appendtext":  "\n\n==" + args.header + "==\n\n" + args.message,
				"token":   editToken,
				"format":  'json'
			}
			
			NICE.WPAPIHandler.performPOST(
				params, 
				function(result, args){
					try{
						var res = eval("(" + result + ")")
					}catch(e){
						NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Posting to user's talk page", "Unable to process JSON result: " + result)
						if(confirm('Unable to post to ' + NICE.reverted.username + '\'s talk page.  Continue with revert?')){
							NICE.logger.revert(
								NICE.warn, 
								NICE.box, 
								NICE.reverted.username, 
								NICE.reverted.userRevs, 
								wgUserName, 
								NICE.reverted.revisionId,
								null,
								null
							)
							setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
						}
						return
					}
					try{
						var newRevId = null
						var oldRevId = null
						newRevId = res.edit.newrevid,
						oldRevId = res.edit.oldrevid
					}catch(e){
						NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Logging a successful revert", "Invalid result structure: " + result)
						//do not return here.  If we don't have the newRevId and oldRevId, we can still log the revert.
					}
					
					NICE.logger.revert(
						NICE.warn, 
						NICE.box, 
						NICE.reverted.username, 
						NICE.reverted.userRevs, 
						wgUserName, 
						NICE.reverted.revisionId,
						newRevId,
						oldRevId
					)
					
					setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
				}, 
				{},
				function(message){
					NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Posting on user's talk page", message)
					if(confirm('Unable to post to ' + NICE.reverted.username + '\'s talk page.  Continue with revert?')){
						NICE.logger.revert(
							NICE.warn, 
							NICE.box, 
							NICE.reverted.username, 
							NICE.reverted.userRevs, 
							wgUserName, 
							NICE.reverted.revisionId,
							null,
							null
						)
						setTimeout('document.getElementById("wpSummary").value += " (using [[" + ACCOUNT_NAME + "/NICE|NICE]])";document.getElementById("editform").submit()', 1000)
					}
				}
			)
		}
		
		NICE.WPAPIHandler.performGET(
			params, 
			finishPostingAndLogging, 
			args, 
			function(message){
				NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Asking for edit token", message)
				if(confirm('Unable to post to ' + NICE.reverted.username + '\'s talk page.  Continue with revert?')){
					document.getElementById("editform").submit()
				}
			}
		)
	}
	
	
	NICE.loadRevertedUserInformation = function(revisionId){
		//Start AJAX call sequence
		var params = {
			"action": 'query',
			"prop": 'revisions',
			"revids": revisionId,
			"rvprop": 'user',
			"format": 'json'
		}
		
		NICE.WPAPIHandler.performGET(
			params, 
			function(result, args){
				try{
					var res = eval("(" + result + ")")
				}
				catch(e){
					NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Searching for username using revisionId", "Unable to process JSON result" + result)
					NICE.showErrorWhenReady("The Wikipedia server returned an invalid result while asking for user information.")
					return
				}
				
				try{
					for(thing in res.query.pages){
						var page = res.query.pages[thing]
					}
					NICE.reverted.username = page.revisions[0].user
					if(page.revisions[0].anon){
						NICE.reverted.anon = true
					}
					else{
						NICE.reverted.anon = false
					}
				}
				catch(e){
					NICE.reverted.username = ""
					NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Looking for username in result", "Invalid result structure: " + result)
					NICE.showErrorWhenReady("The Wikipedia server returned an invalid result while asking for user information.")
					return
				}
				
				if(NICE.reverted.anon){
					//do nothing
					NICE.reverted.userRevs = 0
					return
				}
				
				var params = {
					"action": 'query',
					"list": 'users',
					"ususers": NICE.reverted.username,
					"usprop": 'editcount',
					"format": 'json'
				}
				
				NICE.WPAPIHandler.performGET(
					params, 
					function(result, args){
						try{
							var res = eval("(" + result + ")")
						}
						catch(e){
							NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Searching for editcount using username", "Unable to process JSON result: " + result)
							NICE.showErrorWhenReady("The Wikipedia server returned an invalid result while asking for user information.")
							return
						}
						
						try{
							NICE.reverted.userRevs = res.query.users[0].editcount
						}
						catch(e){
							NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Looking for editcount in result", "Invalid result structure: " + result)
							NICE.showErrorWhenReady("The Wikipedia server returned an invalid result while asking for user information.")
							return
						}
					}, 
					{},
					function(message){
						NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Requesting edit count of user", message)
						NICE.showErrorWhenReady(message)
					}
				)
			}, 
			{}, 
			function(message){
				NICE.logger.error(mw.config.get('wgUserName'), NICE.reverted.revisionId, "Searching for username using revisionId", message)
				NICE.showErrorWhenReady(message)
			}
		)
	}
	
	/* Show notice when ready */
	NICE.showNoticeWhenReady()
	
	/* Start getting work done */
	NICE.loadRevertedUserInformation(NICE.reverted.revisionId)
	
	//Start ready catcher
	NICE.createSaveFormWhenReady()
}