Jump to content

User:Benison/amelvand.js

From Wikipedia, the free encyclopedia
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.
//<nowiki>
//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
 
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");
	}
};
 
if(typeof(AmelPrefs.data["summarySuffix"]) == "undefined") AmelPrefs.set("summarySuffix", "");
if(typeof(AmelPrefs.data["watchReversion"]) == "undefined") AmelPrefs.set("watchReversion", false);
 
//combine these into one function.
function addRollback() {
	if(document.getElementById("mw-diff-ntitle1").getElementsByTagName("a")[0].firstChild.nodeValue.indexOf("Current revision") == -1) 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 mw.config.get('wgServer') + mw.config.get('wgScript') + "?title=" + this.urlTitle;
	},
	toApiUrl : function() {
		return mw.config.get('wgServer') + mw.config.get('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 + "\\}\\}") //basic sensing mechanism
	};
	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 minutes
		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 };
	}
}
 
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 > 1) {
			var rbUrl = rb[1].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
AmelKeyFuncs = {};
AmelKeyFuncs.enabled = document.cookie.indexOf("amelKeys=true") != -1;
AmelKeyFuncs.formatOnLoad = function() {
	mw.util.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";
}
//-------------------------------------
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", mw.config.get('wgServer') + mw.config.get('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", mw.config.get('wgServer') + mw.config.get('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();
}
//-------------------------------------
var isDiff = /[?&]diff=/.test(document.location.href);
if (isDiff) {
    addOnloadHook(addRollback);
    addOnloadHook(addRestore);
    if (AmelKeyFuncs.enabled)
        addOnloadHook(AmelKeyFuncs.formatOnLoad);
}
//</nowiki>