User:Nihiltres/nothingthree.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.
/*
This nothingthree library is a collection of functions that are probably useful. They're created 
or collected (as noted) by Nihiltres for his use on Wikipedia, and you can use them as you see fit.
There is no guarantee that they work; Nihiltres only tests them under Safari on Mac OS, though will 
occasionally correct or minimize bugs that occur on other browsers. Enjoy! Note the importScript 
activator at the bottom. You'll need a user nothingthree-config.js file as well as importing this.
*/
/*global nothingthree: true, importScript, importStylesheetURI, jQuery, mw */
mw.loader.using(["mediawiki.util", "oojs-ui.styles.icons-editing-list"], function () {
	window.nothingthree = {

		settings: { //Holds nothingthree globals and defaults
			topsHidden: false, //Tops start out shown, so this has to be false to start.
			pageRCloaded: false, //Are revisions loaded? Has to be false to start.
			specificAutoWatchNamespaces: [1, 2, 3, 8, 9], //Default auto-watch namespaces
			monthlist: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], //default names for months, allows for basic i18n
			dateOrder: mw.user.options.get("date") //Default date order. Iff "mdy", nothingthree.util.stringifyDate's output changes slightly.
		}, //end settings

		util: {
			linkFix: function (targetId, replacementText) { // Tab name fixer; drills down to bottom first-child of targeted ID, then replaces its text.
				var drilldown = jQuery("#".concat(targetId));
				while (drilldown.children().length > 0) {
					drilldown = drilldown.children().first();
				}
				drilldown.text(replacementText); //jQuery.text() natively escapes HTML, AFAICT
			}, //end linkfix
	
			isMobile: function () { //really rather basic mobile-device detection here, but can be improved as needed. Mostly useful for tidy code.
				return !!(navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i) || (navigator.userAgent.match(/Android/i)));
			}, //end isMobile
	
			parseDate: function (dateString) { //Turns the date strings that the MW API likes to use (e.g. "2011-04-06T22:24:52Z") into JS dates. It assumes everything's in UTC, and expects a string for input.
				var dateParts = /^\s*(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z\s*$/.exec(dateString);
				if (dateParts) {
					return new Date(Date.UTC(+dateParts[1], +dateParts[2] - 1, +dateParts[3], +dateParts[4], +dateParts[5], +dateParts[6]));
				}
				return new Date(NaN); //In case dateString isn't what we're expecting…
			}, //end parseDate
	
			stringifyDate: function (theDate) { //Turns a date object into a pretty UTC output string.
				var dateParts, timeParts;
				timeParts = ("0" + theDate.getUTCHours()).slice(-2) + ":" + ("0" + theDate.getUTCMinutes()).slice(-2);
				if (nothingthree.settings.dateOrder === "mdy") {
					dateParts = nothingthree.settings.monthlist[theDate.getUTCMonth()] + " " + theDate.getUTCDate() + ", " + theDate.getUTCFullYear();
				} else {
					dateParts = theDate.getUTCDate() + " " + nothingthree.settings.monthlist[theDate.getUTCMonth()] + " " + theDate.getUTCFullYear();
				}
				return (timeParts + ", " + dateParts + " (UTC)");
			}, //end stringifyDate
	
			formatInt: function (num) { //Formats integers with commas
				var numString = num.toString();
				while (/(\d+)(\d{3})/.test(numString)) {
					numString = numString.replace(/(\d+)(\d{3})/, "$1,$2");
				}
				return numString;
			}, //end formatInt
	
			addPortletLink: function (obj) {
				var $item, $portlet, $list;
				if (!obj || !obj.portlet || !obj.portlet.jquery || !obj.portlet.length || !obj.text || (!obj.href && !obj.function)) {
					return null;
				}
				$item = jQuery("<li><a></li>").attr("id", (obj.id || null));
				$item.addClass("mw-list-item");
				$item.children().text(obj.text).attr({
					href: (obj.href || "#"),
					title: (obj.tooltip || null),
					accesskey: (obj.accesskey || null)
				}).updateTooltipAccessKeys();
				$portlet = obj.portlet.first();
				//if ($portlet.hasClass("vectorTabs")) {
				$item.find("a").wrapInner("<span>");
				//}
				if ($portlet.hasClass("vector-menu-tabs")) {
					$item.addClass("vector-tab-noicon");
				}
				$list = $portlet.find("ul").first();
				if ($list.length === 0) {
					$list = jQuery("<ul>");
					if ($portlet.find('div:first').length === 0) {
						$portlet.append($list);
					} else {
						$portlet.find('div').last().append($list);
					}
					if ($portlet.find("ul") === 0) {
						return null;
					}
				}
				$portlet.removeClass("emptyPortlet");
				if (obj.next && obj.next.jquery && $list.find(obj.next.first()).length !== 0) {
					$item.insertBefore(obj.next.first());
				} else if (obj.prev && obj.prev.jquery && $list.find(obj.prev.first()).length !== 0) {
					$item.insertAfter(obj.prev.first());
				} else {
					$item.appendTo($list);
				}
				return $item.click((typeof obj.function === "function") ? obj.function : null);
			} //end addPortletLink
		}, //end util
	
		tabAdd: { //Collection of independent tab-addition routines.
			log: function () { //Adds a link to the log of actions performed by the user. Mainly useful for administrators.
				if (jQuery("#pt-log").length > 0) {
					return;
				}
				nothingthree.util.addPortletLink({
					portlet: jQuery("#p-personal"),
					href: mw.util.getUrl("Special:Log", {user: mw.config.get("wgUserName")}),
					text: "Log",
					id: "pt-log",
					tooltip: "Log of your non-edit actions",
					next: jQuery("#pt-mycontris")
				});
				if (jQuery("#p-personal .mw-ui-icon").length > 0) {
					jQuery("#pt-log a").prepend($("<span class=\"mw-ui-icon mw-ui-icon-listBullet\">"))
					//userContributions is closer to what we want, but to differentiate
					//the icons a bit, we'll use listBullet
				}
				//jQuery("#pt-log").addClass("collapsible"); not necessary on p-personal links
				if (mw.config.get("wgPageName") === "Special:Log" && jQuery("#mw-input-user input").val() === mw.config.get("wgUserName")) {
					jQuery("#pt-log").addClass("active");
				}
			}, //end log
	
			sandbox: function () { //Used to add a sandbox link; now that that's present by default, this function normalizes the link to reflect the function's old target.
				jQuery("#pt-sandbox a").first().attr("href", mw.util.getUrl("User:".concat(mw.config.get("wgUserName"), "/Sandbox")));
			}, //end sandbox
	
			purge: function () { //Adds a link to purge the current page.
				if (jQuery("#ca-purge").length > 0 || mw.config.get("wgCanonicalNamespace") === "Special") {
					return;
				}
				nothingthree.util.addPortletLink({
					portlet: jQuery("#p-cactions"),
					href: mw.util.getUrl(mw.config.get("wgPageName"), {"action": "purge"}),
					text: "Purge",
					id: "ca-purge",
					tooltip: "Purge the server cache of this page",
					next: jQuery("#p-cactions #ca-unwatch, #p-cactions #ca-watch")
				});
			} //end purge
		}, //end tabAdd
	
		specificAutoWatch: function () { //Automatically checks the "Watch this page" button when editing MediaWiki-namespace pages.
			if (mw.config.get("wgAction") === "edit" && nothingthree.settings.specificAutoWatchNamespaces.indexOf(mw.config.get("wgNamespaceNumber")) !== -1) {
				jQuery("#wpWatchthis").prop("checked", true);
			}
		}, //end watchMediawikiSpaceEdits
	
		tabMove: { //Tab-moving routines, and custom bits that make use of them.
			core: function (obj) { //Now slightly less ugly! Does the actual moving part. Takes an object loaded like so: {id: "foo", followingElement: "bar", targetParent: "baz"}. The latter two arguments within that object are optional.
				var $id, $followingElement, $targetParent;
				$id = (obj.id ? jQuery("#".concat(obj.id)) : []);
				$followingElement = (obj.followingElement ? jQuery("#".concat(obj.followingElement)) : []);
				$targetParent = (obj.targetParent ? jQuery("#".concat(obj.targetParent)) : jQuery((obj.followingElement ? $followingElement.parent() : [])));
				if ($id.length !== 1 || $targetParent.length !== 1) {
					return;
				}
				if ($id.closest("ul").children().length === 1) {
					$id.closest("div:not(.menu)").addClass("emptyPortlet");
				}
				$targetParent.removeClass("emptyPortlet");
				//if (!$id.children().first().is("span")) {
				//	$id.wrapInner("<span></span>");
				//}
				if ($targetParent.hasClass("vector-menu-tabs")) {
					$id.addClass("vector-tab-noicon");
				}
				if ($targetParent.attr("id") === "p-views") { //Necessary until better collapsibleTabs support is implemented
					$id.addClass("collapsible");
				}
				if ($followingElement.length > 0) {
					$id.detach().insertBefore($followingElement.first());
				} else {
					$id.detach().appendTo($targetParent.find("ul").first());
				}
				//It'd be nice to properly update the collapsibleTabs data RIGHT HERE, but for now, that's not implemented.
				//jQuery.collapsibleTabs.handleResize(); //Force an update regardless
			}, //end core
	
			protection: function () { //if the page is protected, show the unprotect button; serves also as an indicator for protection, indicating the value in the title.
				var restrictionStringArray = [];
				if (jQuery("#ca-unprotect").length === 0) { //There are more thorough checks, but this seems stable enough…
					return;
				}
				nothingthree.tabMove.core({"id": "ca-unprotect", "followingElement": "ca-history", "targetParent": "p-views"});
				if (mw.config.get("wgRestrictionEdit")) {
					restrictionStringArray.push("edit: [".concat(mw.config.get("wgRestrictionEdit").join(", "), "]"));
				}
				if (mw.config.get("wgRestrictionMove")) {
					restrictionStringArray.push("move: [".concat(mw.config.get("wgRestrictionMove").join(", "), "]"));
				}
				if (mw.config.get("wgRestrictionCreate")) {
					restrictionStringArray.push("create: [".concat(mw.config.get("wgRestrictionCreate").join(", "), "]"));
				}
				jQuery("#ca-unprotect a").attr("title", "Change protection of this page (".concat(restrictionStringArray.join(", "), ")"));
			}, //end protection
	
			deletion: function () { //If the page contains a deletion template or a redlinked redirect, or is an empty proposed deletion category, move out the delete tab. In the redirect or category cases, give an automatic, useful deletion summary.
				var cada = jQuery("#ca-delete a"),
					moveObj = {id: "ca-delete", followingElement: "ca-history", targetParent: "p-views"};
				if (jQuery("[class*=mbox-speedy], [class*=mbox-delete]").length >= 1) { //if there's a deletion template present… 
					nothingthree.tabMove.core(moveObj);
				}
				if ((mw.config.get("wgPageName").indexOf("Category:Proposed_deletion_as_of") !== -1) && (jQuery("#mw-category-empty").length !== 0)) { //if it's an empty proposed deletion category…
					nothingthree.tabMove.core(moveObj);
					if (cada.length !== 0) {
						cada.first().attr("href", cada.first().attr("href").concat("&wpReason=%5B%5BWP%3ACSD%23G6%7CG6%5D%5D%3A%20Empty%20proposed%20deletion%20category"));
					}
				}
				if (jQuery(".redirectText a[href*='redlink=1']").length > 0) { //if it's a redlink redirect…
					nothingthree.tabMove.core(moveObj);
					if (cada.length !== 0) {
						cada.first().attr("href", cada.first().attr("href").concat("&wpReason=%5B%5BWP%3ACSD%23G8%7CG8%5D%5D%3A%20Redirect%20to%20a%20deleted%20page"));
					}
				}
			}, //end deletion
	
			watch: function () { //This bit moves the watch tab back into the menu after removing its icon status.
				jQuery("#ca-watch, #ca-unwatch").removeClass("icon");
				nothingthree.tabMove.core({"id": jQuery("#ca-watch, #ca-unwatch").first().attr("id"), "targetParent": "p-cactions"});
			}, //end watch
	
			contribs: function () {
				var user;
				if (mw.config.get("wgCanonicalSpecialPageName") == "Contributions") {
					user = mw.config.get("wgPageName").split("/")[1] || mw.util.getParamValue("target");
					nothingthree.util.addPortletLink({
						portlet: jQuery("#p-associated-pages"),
						href: mw.util.getUrl("User:" + user),
						text: "User page",
						id: "ca-nstab-user",
						tooltip: "View the user page",
						next: jQuery("#ca-nstab-special")
					});
					nothingthree.util.addPortletLink({
						portlet: jQuery("#p-associated-pages"),
						href: mw.util.getUrl("User_talk:" + user),
						text: "Talk",
						id: "ca-talk",
						tooltip: "Discussion about the content page",
						next: jQuery("#ca-nstab-special")
					});
					nothingthree.util.linkFix("ca-nstab-special", "Contributions");
					jQuery("#t-contributions").addClass("selected");
				}
				if (jQuery("#t-contributions").length === 1) {
					nothingthree.tabMove.core({id: "t-contributions", targetParent: "p-associated-pages"});
					jQuery("#t-contributions").attr("id", "ca-contributions");
					nothingthree.util.linkFix("ca-contributions", "Contributions");
				}
			}
		}, //end tabMove
	
		sidebar: { //A collection of functions for playing with the sidebar, mostly to collapse or expand it.
			toggle: function () { //Toggles between collapsed and expanded
				jQuery("#mw-panel, #content, #head-base, #footer, #mw-head-base, #left-navigation").toggleClass("n3-sidebar-collapsed");
				if (jQuery.cookie("sideBarCollapsed") === "collapsed") {
					jQuery.cookie("sideBarCollapsed", "expanded", {path: "/"});
				} else {
					jQuery.cookie("sideBarCollapsed", "collapsed", {path: "/"}); //This isn't error-tolerant enough for my taste, but the likelihood that something else will change this value is low, so I haven't bothered to write more.
				} //end if
			}, //end toggle
	
			toggleTab: function () { //Adds a tab to the menu to allow the collapsed status to be easily toggled.
				if (jQuery("#ca-sidebar").length > 0) {
					return;
				}
				nothingthree.util.addPortletLink({
					portlet: jQuery("#p-cactions"),
					function: nothingthree.sidebar.toggle,
					text: "Toggle sidebar",
					id: "ca-sidebar",
					tooltip: "Hide or show the sidebar",
					next: jQuery("#p-cactions #ca-purge, #p-cactions #ca-unwatch, #p-cactions #ca-watch")
				});
				if (mw.config.get("wgCanonicalNamespace") === "Special") {
					nothingthree.tabMove.core({"id": "ca-sidebar", "targetParent": "p-views"});
				} //end if
			}, //end toggleTab
	
			remember: function () { //Collapses the sidebar if it was previously collapsed.
				if (jQuery.cookie("sideBarCollapsed") === "collapsed") {
					nothingthree.sidebar.toggle();
				} //end if
			} //end remember
		}, //end sidebar
	
		tops: { //A collection of functions designed to allow the visibility of (top) edits on contribution pages to be toggled. Also included are edits where a later edit to the same page is (top).
			hide: function () { //Hides (top) edits.
				var title, topElsewhere;
				topElsewhere = [];
				jQuery(".mw-contributions-list li").each(function () {
					title = jQuery(this).find(".mw-contributions-title").first().text();
					if (jQuery(this).find(".mw-uctop").length > 0) {
						topElsewhere.push(title);
					}
					if (topElsewhere.indexOf(title) !== -1) {
						jQuery(this).addClass("n3-tops-faded");
					}
				});
				nothingthree.settings.topsHidden = true;
			}, //end hide
	
			show: function () { //Shows (top) edits.
				jQuery(".n3-tops-faded").removeClass("n3-tops-faded");
				nothingthree.settings.topsHidden = false;
			}, //end show
	
			toggle: function () { //Toggles between hidden and shown based on the current value.
				if (nothingthree.settings.topsHidden === true) {
					nothingthree.tops.show();
				} else {
					nothingthree.tops.hide();
				}
			}, //end toggle
	
			toggleTab: function () { //Adds a tab to the menu allowing the visibility of (top) edits to be toggled.
				if (jQuery("body").hasClass("mw-special-Contributions") && jQuery("#ca-tops").length === 0) {
					nothingthree.util.addPortletLink({
						portlet: jQuery("#p-views"),
						function: nothingthree.tops.toggle,
						text: "Toggle (top) entries",
						id: "ca-tops",
						tooltip: "Hide or show the (top) entries",
						next: jQuery("#ca-sidebar")
					});
					jQuery("#ca-tops").addClass("collapsible");
				} //end if
			} //end toggleTab
		}, //end tops
	
		monoedit: {
			toggle: function () {
				if ((mw.config.get("wgAction") === "edit" || mw.config.get("wgAction") === "submit") && jQuery("#wpTextbox1").length === 1) {
					jQuery("#wpTextbox1").toggleClass("n3-monoedit");
				} //end if
			}, //end toggle
	
			toggleButton: function () {
				if ((mw.config.get("wgAction") === "edit" || mw.config.get("wgAction") === "submit") && jQuery("#wpTextbox1").length === 1) {
					mw.loader.using("ext.wikiEditor.toolbar", function () {
						jQuery("#wpTextbox1").wikiEditor("addToToolbar", { //Add a custom group for the button
							"section": "main",
							"groups": {
								"views": {}
							}
						});
						jQuery("#wpTextbox1").wikiEditor("addToToolbar", { //add the button itself, to the group
							"section": "main",
							"group": "views",
							"tools": {
								"monospace": {
									"label": "Toggle monospace",
									"type": "button",
									"icon": "//upload.wikimedia.org/wikipedia/commons/c/c2/Toolbaricon_regular_M.png",  //Compromise, no base64 image support AFAICT
									"action": {
										"type": "callback",
										"execute": function () {
											nothingthree.monoedit.toggle();
										} //end execute
									} //end action
								} //end monospace
							} //end tools
						}); //end add button
						jQuery(".group-views").insertBefore(".section-main .group-format"); //Move the group where I want it.
					});
				} //end if
			} //end toggleButton
		}, //end monoedit
	
		customRevs: {
			getHistory: function (obj) { //Makes the main AJAX call, retrieving the core data and passing along the callback
				var historyCall;
				historyCall = new mw.Api();
				if (nothingthree.customRevs.gettingHistory) {
					return;
				}
				nothingthree.customRevs.gettingHistory = true;
				historyCall.get({ //Eventually this item should have something dangling from it for attaching filters
					action: "query",
					prop: "revisions",
					pageids: mw.config.get("wgArticleId"),
					continue: (obj ? obj.continueString || "" : ""),
					rvprop: "ids|timestamp|flags|user|parsedcomment|size|tags",
					rvdir: "older",
					rvlimit: (obj ? obj.limit || 50 : 50),
					rvtoken: "rollback"
				})
					.done(function (data) {
						if (data.continue && data.continue.rvcontinue) { //This should probably have "else" code if continuation is impossible
							nothingthree.customRevs.historyContinue = data.continue.rvcontinue;
						}
						nothingthree.customRevs.revisionCleanQueue = data.query.pages[mw.config.get("wgArticleId").toString()].revisions;
						nothingthree.customRevs.revisionCleanQueue[0].canRollback = true; //This is ugly; I need a better way to resolve rollback-ability
						nothingthree.customRevs.resolveRevisions();
						nothingthree.customRevs.gettingHistory = false;
					});
			}, //end getHistory
	
			resolveRevisions: function () { //Makes any further calls to preprocess the data, then renders the revisions to the page
				var revsToGet, historyCall, queryMax, cleaningList;
				revsToGet = [];
				if (mw.config.get("wgUserGroups").indexOf("sysop") !== -1) { //This isn't good, but fair enough for now…
					queryMax = 500;
				} else {
					queryMax = 50;
				}
				historyCall = new mw.Api();
				cleaningList = nothingthree.customRevs.revisionCleanQueue.splice(0, Math.max(nothingthree.customRevs.revisionCleanQueue.length, queryMax));
				jQuery.each(cleaningList, function () {
					revsToGet.push(this.parentid);
				}); //end jQuery.each
				historyCall.get({
					action: "query",
					prop: "revisions",
					revids: revsToGet.join("|"),
					rvprop: "size"
				})
					.done(function (data) {
						var retrievedRevisions = data.query.pages[mw.config.get("wgArticleId").toString()].revisions.reverse();
						jQuery.each(cleaningList, function (index) {
							this.sizediff = (this.size - retrievedRevisions[index].size);
							this.timestamp = nothingthree.util.parseDate(this.timestamp);
							if (!this.canRollback) { //Second part of the ugly bit
								delete this.rollbacktoken;
							}
							nothingthree.customRevs.revisionData.push(this);
							nothingthree.customRevs.renderHistoryItem(this, jQuery("#n3-pagehistory").first());
						}); //end jQuery.each
						if (nothingthree.customRevs.revisionCleanQueue.length > 0) {
							nothingthree.customRevs.resolveRevisions();
						} else {
							jQuery("input[type=checkbox]:not(.noshiftselect)").checkboxShiftClick(); //Reinitialize shift-clicking now that new elements have been created
						} //end if/else
					}); //end historyCall & done;
			}, //end getCleanHistory
	
			gettingHistory: false,
			revisionData: [], //Stores cleaned list, starts empty
			revisionCleanQueue: [],
			historyContinue: "", //Stores latest rvcontinue; this code needs better structure
	
			renderHistoryItem: function (revObject, $historylist) {
				var renderedItem, byteLabels;
				//Permanent structures
				//---- init THE BIG BLOB
				renderedItem = jQuery("<li class=\"n3-revision\"></li>");
				renderedItem.append("<div class=\"n3-rev-main\"><div class=\"n3-rev-diff\"><a class=\"n3-rev-timestamp\"></a></div></div><div class=\"n3-rev-summary\"><span><span class=\"n3-rev-comment\"></span></span></div><div class=\"n3-rev-meta\"><span class=\"n3-rev-compares\"><span>Compare:</span><ul></ul></span><span class=\"n3-rev-actions\"><span>Actions:</span><ul></ul></span><div class=\"n3-rev-bytediff\"></div></div>");
				//---- n3-rev-timestamp
				renderedItem.find(".n3-rev-timestamp")
					.attr("href", mw.util.getUrl(mw.config.get("wgPageName"), {oldid: revObject.revid}))
					.text(nothingthree.util.stringifyDate(revObject.timestamp));
				//---- n3-rev-compares
				jQuery.each(nothingthree.customRevs.compares, function () {
					if (typeof this === "function") {
						renderedItem.find(".n3-rev-compares ul").append(nothingthree.customRevs.newLia(this(revObject)));
					}
				});
				//----n3-rev-comment
				if (revObject.parsedcomment !== "") {
					renderedItem.find(".n3-rev-comment").html(revObject.parsedcomment);
				} else {
					renderedItem.find(".n3-rev-comment").addClass("n3-rev-nocomment").text("(no edit summary)");
				}
				//----n3-rev-actions
				jQuery.each(nothingthree.customRevs.actions, function () {
					if (typeof this === "function") {
						renderedItem.find(".n3-rev-actions ul").append(nothingthree.customRevs.newLia(this(revObject)));
					}
				});
				//----n3-rev-bytes & n3-rev-bytebar
				if (revObject.sizediff < 0) {
					byteLabels = ["n3-rev-bytesremoved", "&minus;"];
				} else if (revObject.sizediff > 0) {
					byteLabels = ["n3-rev-bytesadded", "&plus;"];
				} else {
					byteLabels = ["n3-rev-bytesnull", "&plusmn;"];
				}
				renderedItem.find(".n3-rev-bytediff").html("<span class=\"".concat(byteLabels[0], "\">", byteLabels[1], nothingthree.util.formatInt(Math.abs(revObject.sizediff)), "</span> &rarr; ", nothingthree.util.formatInt(revObject.size), " bytes"));
	
				//Structures which ought to eventually be optional:
				//---- n3-rev-form in n3-rev-diff, would be conditional on revision list type; revdel name attr is placeholder
				renderedItem.find(".n3-rev-diff").prepend("<div class=\"n3-rev-form\"><input name=\"oldid\" type=\"radio\"><input name=\"diff\" type=\"radio\"><input name=\"revdel\" type=\"checkbox\"></div>");
				renderedItem.find("input[name=\"oldid\"], input[name=\"diff\"]").attr("value", revObject.revid);
				renderedItem.find("input[name=\"revdel\"]").attr("name", "ids[" + revObject.revid.toString() + "]");
				//---- n3-rev-user in n3-rev-main, would be conditional on revision list type
				renderedItem.find(".n3-rev-main").append("<div class=\"n3-rev-user vectorMenu\"><h3 class=\"n3-rev-username\"></h3><div class=\"n3-rev-userlinks menu\"><ul></ul></div></div>");
				renderedItem.find(".n3-rev-username").text(revObject.user);
				jQuery.each(nothingthree.customRevs.userlinks, function () {
					if (typeof this === "function") {
						renderedItem.find(".n3-rev-userlinks ul").append(nothingthree.customRevs.newLia(this(revObject)));
					}
				});
				//---- n3-rev-flagtags in n3-rev-summary, would be conditional on presence
				renderedItem.find(".n3-rev-summary > span").append("<span class=\"n3-rev-flagtags\"><ul></ul></span>");
				if (revObject.minor !== undefined) {
					renderedItem.find(".n3-rev-flagtags ul").append("<li>Minor edit</li>");
				}
				jQuery.each(revObject.tags, function () {
					renderedItem.find(".n3-rev-flagtags ul")
						.append(nothingthree.customRevs.newLia({text: "Tag", href: mw.util.getUrl("Special:Tags"), title: "Special:Tags"}).append(": ", jQuery("<span></span>").text(this)));
				});
	
				//Initialize rendered revision item
				$historylist.append(renderedItem);
				$historylist.children().last().find(".n3-rev-user").click(function () {
					jQuery(this).toggleClass("menuForceShow");
				});
			}, //end renderHistoryItem
	
			newLia: function (obj) { //Simplifies making all of the individual listy items
				var newItem = jQuery("<li><a></a></li>");
				if (!obj) {
					return null;
				}
				if (obj.text) {
					newItem.children().text(obj.text);
				}
				if (obj.href) {
					newItem.children().attr("href", obj.href);
				}
				if (obj.title) {
					newItem.children().attr("title", obj.title);
				}
				return newItem;
			}, //end newLia
	
			compares: {
				diff: function (revObject) {
					return {
						text: "Previous",
						href: mw.util.getUrl(mw.config.get("wgPageName"), {diff: revObject.revid, oldid: revObject.parentid})
					};
				}, //end diff
				cur: function (revObject) {
					return {
						text: "Current",
						href: mw.util.getUrl(mw.config.get("wgPageName"), {oldid: revObject.revid, diff: "cur"})
					};
				} //end cur
			}, //end compares
	
			userlinks: {
				page: function (revObject) {
					return {
						text: "User page",
						href: mw.util.getUrl("User:" + revObject.user)
					};
				}, //end page
				talk: function (revObject) {
					return {
						text: "Talk",
						href: mw.util.getUrl("User talk:" + revObject.user)
					};
				}, //end talk
				contribs: function (revObject) {
					return {
						text: "Contributions",
						href: mw.util.getUrl("Special:Contributions/" + revObject.user)
					};
				}, //end contribs
				block: function (revObject) {
					if (mw.config.get("wgUserGroups").indexOf("sysop") === -1) {
						return null;
					}
					return {
						text: "Block",
						href: mw.util.getUrl("Special:Block/" + revObject.user)
					};
				} //end block
			}, //end userlinks
	
			actions: {
				rollback: function (revObject) {
					if (revObject.rollbacktoken === undefined) {
						return null;
					}
					return {
						text: "Rollback",
						href: mw.util.getUrl(mw.config.get("wgPageName"), {action: "rollback", from: revObject.user, token: revObject.rollbacktoken})
					};
				}, //end rollback
				undo: function (revObject) {
					return {
						text: "Undo",
						href: mw.util.getUrl(mw.config.get("wgPageName"), {action: "edit", undo: revObject.revid, undoafter: revObject.parentid})
					};
				}, //end undo
				thank: function (revObject) {
					return {
						text: "Thank",
						href: mw.util.getUrl("Special:Thanks/" + revObject.revid)
					};
				}, //end thank
				revdel: function (revObject) {
					if (mw.config.get("wgUserGroups").indexOf("sysop") === -1) {
						return null;
					}
					return {
						text: "Delete",
						href: mw.util.getUrl(mw.config.get("wgPageName"), {action: "revisiondelete", from: revObject.user}) + "&" + mw.util.rawurlencode("ids[" + revObject.revid + "]")
					};
				} //end revdel
			}, //end actions
	
			renderHistoryList: function (limit) {
				var maxlimit = 500; //default API max; this is a bit of an unhealthy assumption…
				if (mw.config.get("wgUserGroups").indexOf("sysop") !== -1) { //Same bad assumption, and hackish to boot
					maxlimit = 5000;
				}
				nothingthree.customRevs.getHistory({
					limit: (Math.min(parseInt(limit, 10), maxlimit) || Math.min(parseInt(mw.user.options.get("rclimit"), 10), maxlimit) || 50) //parseInt necessary to force some values to NaN for Math.min
				}); //end gethistory & its input object
			}, //end renderHistoryList
	
			testRun: function () {
				if (mw.config.get("wgAction") === "history") {
					jQuery("#pagehistory").attr("id", "n3-pagehistory").html("");
					nothingthree.customRevs.renderHistoryList(50);
				}
			} //end testRun
	
		} //end customRevs
	}; //end nothingthree
	
	mw.loader.load("//en.wikipedia.org/w/index.php?title=User:Nihiltres/nothingthree.css&action=raw&ctype=text/css", "text/css");
	mw.loader.load("//en.wikipedia.org/w/index.php?title=User:" + mw.util.wikiUrlencode(mw.config.get("wgUserName")) + "/nothingthree-config.js&action=raw&ctype=text/javascript"); //call the activator :)
});