User:Gracenotes/amelvand.js

From Wikipedia, the free encyclopedia

Note: After saving, you have to bypass your browser's cache to see the changes. In Internet Explorer and Firefox, hold down the Ctrl key and click the Refresh or Reload button. Opera users have to clear their caches through Tools→Preferences, see the instructions for Opera. Konqueror and Safari users can just click the Reload button.

//Ameliorating vandalism. Naught to do with the Belgian municipality.
//TODO: combine addRollback and addRestore into one function
//      use WikiTitle throughout the script
//      add history visualization to get around IE prompt "security" issue
 
var AmelPrefs = {
	data: {},
	set: function(name, value) {
		return this.data[name] = value;
	},
	get: function(name, alternate) {
		return this.contains(name) ? this.data[name] : alternate;
	},
	contains: function(name) {
		return (typeof this.data[name] != "undefined");
	}
};
 
AmelPrefs.set("summarySuffix", "");
AmelPrefs.set("watchReversion", false)
 
var isDiff = /[?&]diff=/.test(document.location.href);
 
if (isDiff) {
  addOnloadHook(addRollback);
  addOnloadHook(addRestore);
}
 
//combine these into one function.
function addRollback() {
	if (getElementsByClassName(document, "td", "diff-ntitle")[0].getElementsByTagName("a")[0].firstChild.nodeValue != "Current revision") return;
	var roll = document.createElement("input");
	roll.type = "button";
	roll.onclick = AmelUtil.diffRollback(document);
	roll.value = "Rollback";
	getElementsByClassName(document, "td", "diff-otitle")[0].appendChild(roll);
}
 
function addRestore() {
	var rest = document.createElement("input")
	rest.type = "button";
	rest.onclick = AmelUtil.diffRestore(document);
	rest.value = "Restore revision";
	getElementsByClassName(document, "td", "diff-otitle")[0].appendChild(rest);
}
 
function AdminRollback(url) {
	if (!url || this == window)
		return; //no title given, or "new" keyword not used
	this.url = url;
	try {
		this.userFrom = decodeURIComponent(url.match(/[&?]from=([^&]*)/)[1]).replace(/_/g, " ");
		this.title = url.match(/[&?]title=([^&]*)/)[1];
	} catch (e) {
		return; //error in parsing link; not correct
	}
	this.init();
}
 
AdminRollback.prototype.init = function() {
	var me = this;
	AmelUtil.colorStatus("#EFE");
	var req = new XMLHttpRequest();
	req.open("HEAD", this.url, true);
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			AmelUtil.colorStatus("#EEF");
			new WikiEdit(me.title).getHistory(function(obj){showHistory(me.title, obj)});
		}
	};
	req.send(null);
	new Warning(this.title, this.userFrom);
}
 
function Rollback(title, revFrom, userFrom) {
	if (!title || this == window)
		return; //no title given, or "new" keyword not used
	this.title = title; //in encoded form
	this.from = { "revid" : revFrom || undefined, "user" : userFrom || undefined };
	this.revTo = undefined;
	this.revnum = 0;
	this.editor = new WikiEdit(title, this);
	this.init();
}
 
Rollback.prototype.init = function () {
    AmelUtil.colorStatus("#FEE");
	this.editor.downloadEditFormData();
    this.editor.getHistory(this.parseHistory);
}
 
Rollback.prototype.parseHistory = function(history) {
    AmelUtil.colorStatus("#FFE");
 
	if (!history || !history[0]) {
      alert("Error retriving page history"); //go modal boxes!
      return;
    }
    this.editor.setTimes(AmelUtil.isoToNumerical(history[0]["timestamp"]));
 
	//some situations the reverter should check out
    var cautionMode = undefined;
	if (this.from.revid) {
		if (history[0]["revid"] == this.from.revid)
			this.from.user = history[0]["user"]; //just in case it's not defined
		else
			cautionMode = "The revision amelvand was told to revert from is not the most recent one.";
    }
    else if (this.from.user && this.from.user != history[0]["user"]) 
		cautionMode = "The user amelvand was told to revert from is not the most recent editor of the article.";
	else
		cautionMode = "Neither a revision nor a user was received, so just making sure that we really want to revert.";
 
	i = 0;
	if (cautionMode) {
		j = [];
		var min = Math.min(history.length, 8);
		for (i = 0; i < min; i++) //add summary?
			j[i] = i + ". " + history[i]["user"] + (history[i]["revid"] == this.from.revid ? " (original)" : "");
		i = prompt(j.join("\n") + "\n\nCaution mode enabled. Reason: " + cautionMode + "\n\nFrom which revision do you wish to revert?", "0"); //moar options!
		if (isNaN(parseInt(i))) return;
		this.revid = history[i][0];
		this.user = history[i][1];
   }
   //cautionModeBox(cautionMode, history); } else {
 
   this.revnum = i;
   while (this.revnum < history.length && history[this.revnum]["user"] == this.from.user) {
      this.revnum++;
    }
 
    if (this.revnum - i != 1) {
      if(!confirm("Rollback " + (this.revnum - i) + " edits by " + this.from.user + "?")) {
        j = prompt("Then rollback how many edits?", "1")
        if (isNaN(parseInt(j)))
          return;
        else
          this.revnum = j;
      }
    }
 
    if (this.revnum < history.length) {
		this.revTo = history[this.revnum]["revid"];
        this.commit();
	}
	else
		alert("Last revision not by user not found.");
}
 
Rollback.prototype.commit = function() {
	new Warning(this.title, this.from.user);
	var editSum = "Reverted " + AmelUtil.plural(this.revnum, "edit", "edits") + " by [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]])".replace(/\$2/g, this.from.user) + " to last revision by $1"; //$1 is assigned by RestoreRevision
	new RestoreRevision(this.title, this.revTo, this.editor, editSum)
};
 
function RestoreRevision(title, revTo, editor, editSum) {
	if (!title || !revTo || this == window)
		return;
	AmelUtil.colorStatus("#EFE");
	this.title = title;
	this.revTo = revTo;
	this.editSum = editSum || "Restored revision " + this.revTo + " by [[Special:Contributions/$1|$1]]"; //$1 is assigned by commit(obj)
	if (!editor) {
		editor = new WikiEdit(title);
		editor.downloadEditFormData();
		editor.getHistory(function(obj) {
			editor.setTimes(AmelUtil.isoToNumerical(obj["timestamp"]));
		}, 1);
	}
	editor.setParent(this);
	this.editor = editor;
	this.init();
}
RestoreRevision.prototype.init = function() {
	this.editor.getText(this.commit, this.revTo);
}
 
RestoreRevision.prototype.commit = function(obj) {
	if (!obj) return;
	var text = obj["*"];
	if (!text && text != "") return;
	this.editSum = this.editSum.replace(/\$1/g, obj["user"]) + AmelPrefs.get("summarySuffix", "");
 
	var editor = this.editor; //short-hand reference
	var title = this.title;
	editor.setMinor(false);
	editor.setWatch(AmelPrefs.get("watchReversion", false));
	editor.setCallback(function() {
		AmelUtil.colorStatus("#EEF");
		editor.getHistory(function(obj){showHistory(title, obj)});
	});
	editor.setText(text);
	editor.setSummary(this.editSum);
	editor.submit();
}
 
//---------------------------------
//@Private
function WikiTitle(displayTitle, urlTitle) {
	this.displayTitle = displayTitle;
	this.urlTitle = urlTitle;
}
 
WikiTitle.fromURL = function(url, param) {
	var urlmatch = (!param && /\/wiki\/(.*)/.exec(url)) ||
	               new RegExp("[?&]" + (param || "title")  + "=([^&]*)").exec(url);
	if (urlmatch)
		return WikiEdit.fromEncoded(urlmatch[1]);
}
WikiTitle.fromEncoded = function(encoded) {
	return new WikiTitle(decodeURIComponent(encoded).replace(/_/g, " "),
                         encoded);
 
}
WikiTitle.fromDisplay = function(display) {
	return new WikiTitle(display,
	                     encodeURIComponent(display.replace(/ /g, "_")));
}
 
//accessors
WikiTitle.prototype = {
	toEncoded : function() {
		return this.urlTitle;
	},
	toIndexUrl : function() {
		return wgServer + wgScript + "?title=" + this.urlTitle;
	},
	toApiUrl : function() {
		return wgServer + wgScriptPath + "/api.php?titles=" + this.urlTitle;
	},
	toString : function() {
		return this.displayTitle;
	}
};
 
function Warning(title, user) {
	if (!title || !user || this == window)
		return;
	var me = this;
	this.user = user;
	title = WikiTitle.fromEncoded(title);
	this.title = title;
	var warnTitle = WikiTitle.fromDisplay("User talk:" + user),
		aivTitle = WikiTitle.fromDisplay("Wikipedia:Administrator intervention against vandalism");
	this.data = new Object();
	this.data[Warning.WARN] = {
			"title": warnTitle,
			"editor": new WikiEdit(warnTitle, me),
			"summary": "Note: edits on [[" + title + "]]",
			"savedtext": undefined,
			"addtext": undefined,
			"infotext": "",
			"button": "Report to AIV",
			"init": Warning.warnInit,
			"editpage": title
	};
	this.data[Warning.AIV] = {
			"title": aivTitle,
			"editor": new WikiEdit(aivTitle, me),
			"summary": "Reporting [[Special:Contributions/$1|$1]]".replace(/\$1/g, user),
			"savedtext": undefined,
			"addtext": "*{{" + (AmelUtil.isIP(user) ? "IPvandal" : "Userlinks") + "|" + user + "}} vandalism past last warning ~~" + "~~",
			"infotext": "Reporting " + user + ". ",
			"button": "Warn User",
			"init": Warning.aivInit,
			"regex":  new RegExp(user + "\\}\\}") //bathic thenthing mechanithm
 
	};
	this.mode = Warning.NONE;
 
	//initialize GUI. no "live" elements have innerHTML changed.
	var warnDiv = document.createElement("div");
	warnDiv.innerHTML = '<strong id="warnPage"></strong> <span id="warnInfo"></span><br /><textarea rows="10" name="warnBox" id="warnBox"> </textarea><br /><input type="text" value="" id="warnSum" size="30" onkeypress="if (event.which == 13) warning.submit();" /> <input type="button" value="Submit" id="warnSubmit" /> <input type="button" value="" id="warnSwitch" />';
	var jumpToNav = document.getElementById("jump-to-nav");
	if (!jumpToNav) return false;
	jumpToNav.parentNode.insertBefore(warnDiv, jumpToNav);
	this.gui = {
		"warnBox": document.getElementById("warnBox"),
		"warnPage": document.getElementById("warnPage"),
		"warnInfo": document.getElementById("warnInfo"),
		"warnSwitch": document.getElementById("warnSwitch"),
		"warnSubmit": document.getElementById("warnSubmit"),
		"warnSum": document.getElementById("warnSum")
	}
	this.gui["warnBox"].value = "";
	this.gui["warnPage"].appendChild(document.createTextNode(""));
	this.gui["warnInfo"].appendChild(document.createTextNode(""));
	this.gui["warnSwitch"].onclick = function() {me.setNextMode();};
	this.gui["warnSubmit"].onclick = function() {me.submit();};
	this.setNextMode();
}
 
Warning.NONE = ["NONE"];
Warning.WARN = ["WARN"];
Warning.AIV = ["AIV"];
 
Warning.warnInit = function(text) {
	this.savedtext = text;
	var parser = new WarningsParser(text);
	if (parser.report())
		this.infotext += "NOTE! User is past last warning. ";
	var now = new Date();
	var warnDate = parser.previousDate();
	var secElapsed = Math.ceil((now.getTime() - warnDate.getTime()) / 1000);
	if (secElapsed < 0) secElapsed += 3600; //an hour. voodoo programming!
	if (secElapsed < 600) { //ten minuteth
		var minElapsed = Math.floor(secElapsed / 60);
		this.infotext += "Latest warning was left " + (minElapsed == 0 ? "" : AmelUtil.plural(minElapsed, "minute", "minutes") + " and ") + AmelUtil.plural(secElapsed % 60, "second", "seconds") + " ago. ";
	}//12
	var headers = parser.getHeaders(), prefixHeader = "";
	if (!headers.warning || (headers.warning && headers.page) || (headers.warning && (now.getMonth() != warnDate.getMonth() || now.getFullYear() != warnDate.getFullYear())))
		prefixHeader = "== " + AmelUtil.monthNames[now.getUTCMonth()] + " " + now.getUTCFullYear() + " ==\n";
	this.savedtext += (/^\s*$/.test(this.savedtext) ? "" : "\n\n") + 
		(this.addtext = prefixHeader + "{{subst:uw-v" + parser.nextWarning() + "|" + this.editpage + "|Thank you.|subst=subst:}} ~~" + "~~");
}
 
Warning.aivInit = function(text) {
	if (this.regex.test(text))
		this.infotext += "NOTE! \"" + this.user + "\" found on page. ";
	this.savedtext = text + "\n" + this.addtext;
}
 
Warning.nextMode = function(mode) {
	switch (mode) {
		case Warning.NONE: return Warning.WARN;
		case Warning.WARN: return Warning.AIV;
		case Warning.AIV: return Warning.WARN;
		default: return Warning.NONE;
	}
}
 
Warning.prototype.getDownloadCallback = function(mode) {
	var me = this;
	return function(obj) {
		me.set(obj, mode);
	}
}
Warning.prototype.setNextMode = function() {
	this.set(null, Warning.nextMode(this.mode));
}
 
Warning.prototype.set = function(obj, newMode) {
	var mData = this.data[newMode];
	var text = obj && obj["*"];
	if (!mData) return;
	if (!mData.savedtext || mData.savedtext == "") {
		if (text || text == "") {
			mData.init(text);
			mData.editor.setTimes(AmelUtil.isoToNumerical(obj["timestamp"]));
			this.gui["warnSwitch"].value += "...";
 
			this.showMode(newMode);
		}
		else {
			var func = this.getDownloadCallback(newMode);
			mData.editor.getText(func);
			mData.editor.downloadEditFormData();
		}
	}
	else {
		if (text) {
			if (confirm("The page \"" + mData.title + "\" is already initialized. Overwrite displayed text?"))
				mData.init(text);
			else
				return;
		}
		this.showMode(newMode);
	}
}
 
Warning.prototype.showMode = function(newMode) {
	var thisData = this.data[this.mode];
	var newData = this.data[newMode];
	var gui = this.gui;
	if (thisData) {
		thisData.savedtext = gui["warnBox"].value;
		thisData.summary = gui["warnSum"].value;
	}
	gui["warnBox"].value = newData.savedtext;
	gui["warnSum"].value = newData.summary;
	gui["warnPage"].firstChild.nodeValue = newData.title;
	gui["warnInfo"].firstChild.nodeValue = newData.infotext;
	gui["warnSwitch"].value = newData.button;
	this.mode = newMode;
}
 
Warning.prototype.submit = function() {
	var summary = this.gui["warnSum"].value;
	var text = this.gui["warnBox"].value;
	if (this.mode == Warning.WARN) {
		var re = /\{\{subst:uw-(.*?)(?:\|.*?)?\}\}/gi;
		if (summary.charAt(summary.length - 1) == "*") //shorthand for "no template plz"
			summary = summary.substring(0, summary.length - 1);
		else {
			var templateMatch, templateName;
			while (templateMatch = re.exec(text))
				templateName = templateMatch[1];
			if (templateName)
				summary += " (" + templateName + ")";
		}
	} else if (this.mode != Warning.AIV) {
		return; //only Warning.WARN and Warning.AIV allowed at the moment
	}
 
	var button = this.gui["warnSubmit"];
	button.disabled = true;
 
	var editor = this.data[this.mode].editor;
	editor.setText(text);
	editor.setSummary(summary);
	editor.setMinor(false);
	editor.setWatch(false);
	editor.setCallback(function() { button.disabled = false; });
	editor.submit();
}
//---------------------------------
//construct object: get in, get out. No need for state.
function showHistory(title, historyObj, showButtons, limit) {
	limit = Math.min(historyObj.length, limit || 5);
	if (!limit) return;
 
	var pageURL = WikiTitle.fromEncoded(title).toIndexUrl();
	var currentId = historyObj[0]["revid"];
	var newLink = function(url, title) {
		var link = document.createElement("a");
		link.href = url;
		link.title = title;
		link.appendChild(document.createTextNode(title));
		return link;
	}
	var historyItem = function(item, nextItem) {
		//more pretty list under construction
		var liItem = document.createElement("li");
		if (nextItem) {
			liItem.appendChild(document.createTextNode("("));
			liItem.appendChild(newLink(pageURL + "&diff=" + currentId + "&oldid=" + item["revid"], "cur"));
			liItem.appendChild(document.createTextNode(") ("));
		} else {
			liItem.appendChild(document.createTextNode("(cur) ("));
		}
		liItem.appendChild(newLink(pageURL + "&diff=" + item["revid"] + "&dir=prev", "last"));
		liItem.appendChild(document.createTextNode(") "));
		liItem.appendChild(newLink(pageURL + "&oldid=" + item["revid"], AmelUtil.isoToStandard(item.timestamp)));
		liItem.appendChild(document.createTextNode(" "));
		liItem.appendChild(newLink(WikiTitle.fromDisplay("Special:Contributions/" + item.user).toIndexUrl(), item.user));
		if (item["comment"]) {
			var summary = item["comment"].replace(/\[\[([^\|\]]+)\|?(.*?)\]\]/g, function (ignore, link, display) { return "<a href=\"" + WikiTitle.fromDisplay(link).toIndexUrl() + "\">" + (display || link) + "</a>"});
			summary = summary.replace(/\/\*\s+(.+?)\s+\*\//g, function(ignore, anchor) { return "<a href=\"#" + WikiTitle.fromDisplay(anchor).toEncoded() + "\">→</a><span class=\"autocomment\">" + anchor + "</span> - "});
			var summaryElem = document.createElement("span");
summaryElem.innerHTML = " | " + summary;
			liItem.appendChild(summaryElem);
		}
		return liItem;
	}
	var historyDiv = document.getElementById("historyDiv");
	if (historyDiv && historyDiv.firstChild) {
		historyDiv.removeChild(historyDiv.firstChild);
	} else {
		historyDiv = document.createElement("div");
		historyDiv.id = "historyDiv";
		var jumpToNav = document.getElementById("jump-to-nav");
		if (!jumpToNav) return false;
		jumpToNav.parentNode.insertBefore(historyDiv, jumpToNav);
		historyDiv = document.getElementById("historyDiv");
	}
	var historyList = document.createElement("ol");
	var currentItem, nextItem;
	for (var i = 0; i < limit; i++) {
		currentItem = historyObj[i];
		historyList.appendChild(historyItem(currentItem, nextItem));
		nextItem = currentItem;
	}
	historyDiv.appendChild(historyList);
}
//---------------------------------
function WarningsParser(text) {
	var state = { header: false, hasWarnings: false };
	//if the latest header contains the most recent warning, if warnings can be found
	var recent = { header: undefined, text: undefined, date: new Date(0), level: 0 };
	//the header under which the latest warning appears, the warning's text,
	//the date of the warning, its level.
	var warningMatch = 0, warning;
	var dayAgo = new Date();
	dayAgo.setTime(dayAgo.getTime() - (24*60*60*1000));
	var re = /<!-- Template:(.*?) -->.*?(\d{2}:\d{2}, \d+ \w+ \d{4}) \(UTC\)/gi;
	var warning;
	while (warning = re.exec(text)) {
		if (!warning)
			break;
		recent.date = new Date(warning[2] + " UTC"); //UTC can't be in parentheses :|
		if (recent.date > dayAgo)
			recent.text = warning[1];
		state.hasWarnings = true;
		warningMatch = re.lastIndex;
	}
	//find the header the warning is under
	var headerRe = /^(={2,6}).+?\1\s*?$/mg;
	var warningH = text.substring(0, warningMatch).match(headerRe);
	var pageH = text.substring(warningMatch).match(headerRe);
	recent.header = warningH && warningH.pop();
	state.header = !!pageH;
 
	if (recent.text) {
		var levels = recent.text.match(/^.*?(\d)(im)?$/);
		if (levels && levels[1])
			recent.level = parseInt(levels[1]);
		else if (/first/i.test(recent.text)) recent.level = 1;
		else if (/second/i.test(recent.text)) recent.level = 2;
		else if (/third/i.test(recent.text)) recent.level = 3;
		else if (/fourth/i.test(recent.text)) recent.level = 4;
	} else if (state.hasWarnings) {
		recent.level = 1; //if there are warnings there, use a level 2. put this in prefs?
	}
	this.pageData = state;
	this.warningData = recent;
}
//encapsulate state
WarningsParser.prototype = {
	report : function() {
		return this.warningData.level == 4;
	},
	hasWarnings: function() {
		return this.pageData.hasWarnings;
	},
	nextWarning: function() {
		var recent = this.warningData;
		if (recent.level < 4)
			return recent.level + 1;
		else if (recent.level == 4)
			return 4;
		else //not sure what we have; stick with level 1.
			return 1; 
	},
	previousDate: function() {
		return this.warningData.date;
	},
	getHeaders: function() {
		return { page: this.pageData.header, warning: this.warningData.header };
	}
}
 
var AmelUtil = new Object();
 
AmelUtil.plural = function(num, singular, plural) {
	return num + " " + (num == 1 ? singular : plural);
}
 
//previously named AmelUtil.yetAnotherJsMonthNameArray. too long, though :P
AmelUtil.monthNames = ["January", "February", "March", "April",
					   "May", "June", "July", "August",
					   "September", "October", "November", "December"];
 
AmelUtil.isIP = function(str) {
	var rangeCheck = function(num) {
		return (!/\D/.test(num) && parseInt(num) >> 8 == 0);
	}
	var nums = str.split("."), index = nums.length;
	if (index != 4 && index != 6) return false;
	while (--index >= 0)
		if (!rangeCheck(nums[index])) return false;
	return true;
}
 
AmelUtil.numericalTsNow = function() {
	var now = new Date();
	var padDD = function(num) { //double-digit pad
		return ("0" + num).slice(-2);
	}
	return now.getUTCFullYear() + padDD(now.getUTCMonth() + 1) + padDD(now.getUTCDate()) +
	       padDD(now.getUTCHours()) + padDD(now.getUTCMinutes()) + padDD(now.getUTCSeconds());
}
 
AmelUtil.isoToNumerical = function(timestamp) {
	return timestamp && timestamp.replace( /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/, "$1$2$3$4$5$6")
}
 
AmelUtil.isoToStandard = function(timestamp) {
	return timestamp && timestamp.replace( /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/, function(ignore, year, month, date, hour, minute) { return hour + ":" + minute + ", " + date + " " + AmelUtil.monthNames[month - 1] + " " + year});
}
 
AmelUtil.getPageObj = function(jsonObj) {
	try {
		jsonObj = jsonObj.query.pages;
		for (var pageObj in jsonObj)
			return jsonObj[pageObj];
	} catch (e) {
	}
}
 
AmelUtil.colorStatus = function(color) {
	document.getElementById("content").style.background = color;
}
 
AmelUtil.rollbackPerm = function() {
	if (wgUserGroups.indexOf)
		return wgUserGroups.indexOf("sysop") != -1 || wgUserGroups.indexOf("rollbacker") != -1;
	for (var i = 0, length = wgUserGroups.length; i < length; i++)
		if (wgUserGroups[i] == "sysop" || wgUserGroups[i] == "rollbacker") return true;
	return false;
}
 
AmelUtil.diffRollback = function() {
	var checkExist = document.getElementById("mw-diff-ntitle1");
	if (!checkExist) return null;
	if (AmelUtil.rollbackPerm()) {
		var rb = document.getElementById("mw-diff-ntitle2").getElementsByTagName("span");
		if (rb.length > 0) {
			var rbUrl = rb[0].getElementsByTagName("a")[0].href
			return function() {
				new AdminRollback(rbUrl);
			};
		} //else, continue along with normal revert
	}
	var link = checkExist.getElementsByTagName("a");
	link = link[link.length - 1].href;
	var revFrom = link.match(/[&?]undo=([^&]*)/);
	var pageName = link.match(/[&?]title=([^&]*)/);
	var userFrom = document.getElementById("mw-diff-ntitle2")
		.getElementsByTagName("a")[0].title.match(/(?:User:|Special:Contributions\/)(.*$)/);
	if (pageName)
		return function() {
			new Rollback(pageName[1], (revFrom ? revFrom[1] : undefined), (userFrom ? userFrom[1] : undefined));
		}
};
 
AmelUtil.diffRestore = function(container) {
	var checkExist = container.getElementById("mw-diff-otitle1");
	if (!checkExist) return null;
	var link = checkExist.getElementsByTagName("a")[0].href;
	var pageName = link.match(/[&?]title=([^&]*)/);
	var revTo = link.match(/[&?]oldid=([^&]*)/);
	var userTo = container.getElementById("mw-diff-otitle2")
		.getElementsByTagName('a')[0].title.match(/(?:User:|Special:Contributions\/)(.*$)/);
 
	if (pageName && revTo)
    return function() {
		latest = new RestoreRevision(pageName[1], revTo[1]);
    }
};
 
//-------------------------------------
//Mode: press spacebar to revert
var AmelKeyFuncs = {};
AmelKeyFuncs.enabled = document.cookie.indexOf("amelKeys=true") != -1;
AmelKeyFuncs.formatOnLoad = function() {
	addPortletLink("p-tb", "javascript:AmelKeyFuncs.toggleUse()", (AmelKeyFuncs.enabled ? "Don't use" : "Use") + " spacebar reversion", "t-amelkeys", "Enable reversion on diffs using the spacebar");
	window.onkeypress = AmelKeyFuncs.checkToRevert;
	window.focus();
 
}
AmelKeyFuncs.pageEnabled = true;
AmelKeyFuncs.checkToRevert = function(e) {
	if (!AmelKeyFuncs.enabled || !AmelKeyFuncs.pageEnabled)
		return true;
	e = e || window.event;
	if (e.charCode == 32) {
		var func = AmelUtil.diffRollback();
		AmelKeyFuncs.pageEnabled = false;
		if (func) {
			func.call(this);
			return false;
		}
	}
	return true;
}
AmelKeyFuncs.toggleUse = function() {
	var now = new Date();
	if (!AmelKeyFuncs.enabled)
		now.setTime(now.getTime() + 365*24*60*60*1000);
	AmelKeyFuncs.enabled = !AmelKeyFuncs.enabled;
	document.cookie = "amelKeys=" + AmelKeyFuncs.enabled + "; expires=" + now.toGMTString() + "; path=/"
	document.getElementById("t-amelkeys").firstChild.firstChild.nodeValue = (AmelKeyFuncs.enabled ? "Don't use" : "Use") + " spacebar reversion";
}
 
if (isDiff && AmelKeyFuncs.enabled)
	addOnloadHook(AmelKeyFuncs.formatOnLoad);
 
 
//-------------------------------------
 
var WikiEdit = function(title, parent, autosubmit) {
	if (!title)
		throw new Error("title needed for WikiEdit");
	this.title = title;
	this.autosubmit = autosubmit;
	this.submitCallback = function(){};
	this.closed = false;
	this.editMonitor = false;
	this.submitData = {}; //the form fields
	this.onSubmitHooks = []; //to be processed immediately before submitting
	this.submitXHR = null;
	this.parent = parent;
 
}
 
WikiEdit.needToSubmit = ["wpTextbox1", "wpSummary", "wpEditToken", "wpStarttime", "wpEdittime"];
WikiEdit.prototype.setCallback = function(callback) {
	this.submitCallback = callback;
}
 
WikiEdit.prototype.cancel = function() {
	this.closed = true;
	if (this.submitXHR)
		this.submitXHR.abort();
}
 
WikiEdit.prototype.addSubmitHook = function(func) {
	if (this.closed)
		return false;
	this.onSubmitHooks.push(func);
	return true;
}
 
WikiEdit.prototype.doPreSubmitHook = function(func) {
	if (this.closed)
		return false;
	this.editMonitor = true;
	func.call(this);
	this.editMonitor = false;
	this.addSubmitData({});
	return true;
}
 
WikiEdit.prototype.downloadEditFormData = function() {
	if (this.closed)
		return;
	var me = this;
	var req = new XMLHttpRequest();
	req.open("GET", wgServer + wgScriptPath + "/api.php?action=query&prop=info&intoken=edit&format=json&titles=" + this.title, true);
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var pageObj = AmelUtil.getPageObj(eval("(" + req.responseText + ")"));
			if (pageObj) {
				me.addSubmitData({"wpEditToken" : pageObj["edittoken"]});
			}
			else
				alert("Could not retrieve edit form data");
		}
	}
	req.send(null);
}
 
WikiEdit.prototype.setParent = function(parent) {
	this.parent = parent;
}
WikiEdit.prototype.setText = function(wikitext) {
	this.addSubmitData({"wpTextbox1" : wikitext});
}
WikiEdit.prototype.setTimes = function(edittime) {
	var tsNow = AmelUtil.numericalTsNow();
	this.addSubmitData({"wpEdittime" : edittime || tsNow,
		       	    "wpStarttime" : tsNow});
}
 
WikiEdit.prototype.setSummary = function(text) {
	this.addSubmitData({"wpSummary" : text});
}
WikiEdit.prototype.setMinor = function(boole) {
	if (boole) this.addSubmitData({"wpMinoredit" : "1"});
}
WikiEdit.prototype.setWatch = function(boole) {
	if (boole) this.addSubmitData({"wpWatchthis" : "1"});
}
WikiEdit.prototype.getHistory = function(callback, limit) {
	this.getRevInformation(callback, ["user", "ids", "comment", "timestamp"], limit || 20, undefined);
}
WikiEdit.prototype.getText = function(callback, oldid) {
	this.getRevInformation(callback, ["content", "user", "timestamp"], 1, oldid);
}
 
//@Private. precondition: props are properly encoded
WikiEdit.prototype.getRevInformation = function(callback, props, limit, revid) {
	if (!callback)
		return;
	var me = this;
	var req = new XMLHttpRequest();
	req.open("GET", wgServer + wgScriptPath +  
	         "/api.php?action=query&prop=revisions&format=json&rvprop=" + props.join("|") +
			 (revid ? "&rvstartid=" + revid : "") + (limit ? "&rvlimit=" + limit : "") +
			 "&titles=" + this.title, true);
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var pageObj = AmelUtil.getPageObj(eval("(" + req.responseText + ")"));
			if (!pageObj) return;
			if (pageObj.revisions) {
				var returnObj = pageObj["revisions"];
				if (limit == 1) returnObj = returnObj[0];
				returnObj.title = pageObj.title;
				callback.call(me.parent, returnObj);
			} else if (pageObj.missing == "") {
				callback.call(me.parent, {"user":"", "*":""});	
			}			
		}
	}
	req.send(null);
}
 
WikiEdit.prototype.submit = function() {
	this.autosubmit = true;
	if (this.closed || this.editMonitor)
		return false;
 
	var submitData = this.submitData, onSubmitHooks = this.onSubmitHooks;
	for (var i = 0, nts = WikiEdit.needToSubmit; i < nts.length; i++)
		if (!(nts[i] in submitData)) return false;
 
	this.closed = true;
	while (onSubmitHooks.length > 0)
		onSubmitHooks.shift().call(this);
	var parameters = [];
	for (var property in submitData)
		parameters.push(encodeURIComponent(property) + "=" + encodeURIComponent(submitData[property]));
 
	var me = this;
	var req = this.submitXHR = new XMLHttpRequest();
	req.open("POST", wgServer + wgScript + "?title=" + this.title + "&action=submit", true);
	req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			me.submitCallback.call(me.parent);
		}
	};
	req.send(parameters.join("&"));
	return true;
}
 
//@Private. Adds data; submits to index.php if has all information
WikiEdit.prototype.addSubmitData = function(obj) {
	var submitData = this.submitData;
	if (this.closed)
		return;
	for (var property in obj) //merge into submitData
		submitData[property] = obj[property];
	if (this.autosubmit)
		this.submit();
}