Jump to content

User:Nihiltres/nothingthree.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Nihiltres (talk | contribs) at 23:34, 15 May 2014 (added $.collapsibleTabs.handleResize() to end of nothingthree.tabMove.core). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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, importStylesheet, jQuery, mw */
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 newDate, dateParts;
			dateParts = /^\s*(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z\s*$/.exec(dateString);
			if (dateParts) {
				newDate = new Date(Date.UTC(+dateParts[1], +dateParts[2] - 1, +dateParts[3], +dateParts[4], +dateParts[5], +dateParts[6]));
			} else {
				newDate = new Date(NaN); //In case dateString isn't what we're expecting…
			} //end if
			return newDate;
		}, //end parseDate

		stringifyDate: function (theDate) { //Turns a date object into a pretty UTC output string.
			var dateParts, timeParts, outputDate;
			timeParts = "0".concat(theDate.getUTCHours()).slice(-2).concat(":", "0".concat(theDate.getUTCMinutes()).slice(-2));
			if (nothingthree.settings.dateOrder === "mdy") {
				dateParts = nothingthree.settings.monthlist[theDate.getUTCMonth()].concat(" ", theDate.getUTCDate(), ", ", theDate.getUTCFullYear());
			} else {
				dateParts = theDate.getUTCDate().toString().concat(" ", nothingthree.settings.monthlist[theDate.getUTCMonth()], " ", theDate.getUTCFullYear());
			}
			outputDate = timeParts.concat(", ", dateParts, " (UTC)");
			return outputDate;
		}, //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
	}, //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;
			}
			mw.util.addPortletLink('p-personal', mw.util.getUrl("Special:Log", {user: mw.config.get("wgUserName")}), 'Log', 'pt-log', 'Log of your non-edit actions', '', '#pt-mycontris');
			//jQuery("#pt-log").addClass("collapsible"); not necessary on p-personal links
			if (mw.config.get("wgPageName") === "Special:Log" && jQuery("#mw-log-user").val() === mw.config.get("wgUserName")) {
				jQuery("#pt-log").addClass("active");
			}
		}, //end log

		sandbox: function () { //Adds a link to the user's very own sandbox page, where the sandbox is equivalent to [[Special:MyPage/Sandbox]].
			if (jQuery("#pt-sandbox").length > 0) {
				return;
			}
			mw.util.addPortletLink('p-personal', mw.util.getUrl("User:".concat(mw.config.get("wgUserName"), "/Sandbox")), 'Sandbox', 'pt-sandbox', 'Your sandbox', '', '#pt-preferences');
		}, //end sandbox

		purge: function () { //Adds a link to purge the current page.
			var placeBefore = null;
			if (jQuery("#ca-purge").length > 0 || mw.config.get("wgCanonicalNamespace") === "Special") {
				return;
			}
			if (jQuery("#ca-unwatch, #ca-watch").length > 0) {
				placeBefore = "#".concat(jQuery("#ca-watch, #ca-unwatch").first().attr("id"));
			}
			mw.util.addPortletLink('p-cactions', mw.util.getUrl(mw.config.get("wgPageName"), {"action": "purge"}), 'Purge', 'ca-purge', 'Purge the server cache of this page', null, placeBefore);
		} //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.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"); //convenience
			if (jQuery(".ambox-speedy, .ambox-delete, .ombox-speedy, .ombox-delete, .imbox-speedy, .imbox-delete, .cmbox-speedy, .cmbox-delete, .tmbox-speedy, .tmbox-delete").length >= 1) { //if there's a deletion template present…
				nothingthree.tabMove.core({"id": "ca-delete", "followingElement": "ca-history", "targetParent": "p-views"});
			}
			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({"id": "ca-delete", "followingElement": "ca-history", "targetParent": "p-views"});
				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({"id": "ca-delete", "followingElement": "ca-history", "targetParent": "p-views"});
				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
	}, //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;
			}
			mw.util.addPortletLink('p-cactions', '#', 'Toggle sidebar', 'ca-sidebar', 'Hide or show the sidebar', null, '#ca-purge');
			jQuery("#ca-sidebar").click(function () {
				nothingthree.sidebar.toggle();
			});
			jQuery("#p-views").removeClass("emptyPortlet");
			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 list, title, topElsewhere;
			list = jQuery("#bodyContent ul").first().find("li"); //List of contribs items
			topElsewhere = [];
			list.each(function () {
				title = jQuery(this).find(".mw-contributions-title").first().text();
				if (jQuery(this).find(".mw-uctop").length > 0) { //if it's a top edit…
					topElsewhere.push(title); //…push it to the top-titles list.
				} //end if
				if (topElsewhere.indexOf(title) !== -1) { //If it's on the top-titles list…
					jQuery(this).addClass("n3-tops-faded"); //…fade it!
				} //end if
			}); //end list.each
			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) {
				mw.util.addPortletLink('p-views', '#', 'Toggle (top) entries', 'ca-tops', 'Hide or show the (top) entries', null, 'ca-sidebar');
				jQuery("#ca-tops").click(function () {
					nothingthree.tops.toggle();
				});
				jQuery("#ca-tops").addClass("collapsible");
				jQuery("#p-views").removeClass("emptyPortlet");
			} //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) {
				//Group-adding code ought to be implemented right here.
				jQuery("#wpTextbox1").wikiEditor("addToToolbar", { //add the button itself
					"section": "main",
					"group": "insert", //should be "views" or something like that, but doesn't seem to be working, compromised for now to get the script functional
					"tools": {
						"monospace": {
							"label": "Toggle monospace",
							"type": "button",
							"icon": "/media/wikipedia/commons/c/c2/Toolbaricon_regular_M.png",  //Compromise, I wish I could include an image directly in base64
							"action": {
								"type": "callback",
								"execute": function () {
									nothingthree.monoedit.toggle();
								} //end execute
							} //end action
						} //end monospace
					} //end tools
				}); //end add button
			} //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("#bodyContent ul").first()); //SECOND ITEM IS AN UGLY PLACEHOLDER FOR NOW
					}); //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 user",
					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 revision",
					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", "").html("");
				nothingthree.customRevs.renderHistoryList(50);
			}
		} //end testRun

	} //end customRevs
}; //end nothingthree

importStylesheet("User:Nihiltres/nothingthree.css");
importScript("User:" + mw.config.get("wgUserName") + "/nothingthree-config.js"); //call the activator :)