MediaWiki:UserTags/code.js

/*jshint smarttabs:true jquery:true browser:true bitwise:false laxbreak:true */ /*global mediaWiki */

// ES5 requires Date.parse to support this, ES3 doesn't (i.e. IE8 chokes on it) Date.parseISO8601 = function(text) { "use strict"; // Decode MediaWiki ISO8601 Strict date and convert it to milliseconds // This regex only supports basic dates that MediaWiki produces, it isn't comprehensive. // It also doesn't support non UTC Timezones, I'm pretty sure MW never curveballs us // like that though so it should be fine. var when = (/^(\d{4})-?(\d\d)-?(\d\d)[T\s](\d\d):?(\d\d):?(\d\d)(?:\.?(\d+))?(?:Z|\+00(?::?00)?)$/).exec(text); if (!when) { return NaN; }	return +new Date(when[1], when[2] - 1, when[3], when[4], when[5], when[6], ('.' + when[7]) * 1000 | 0); };

/** * UserTags Loader script * * UserTags is quite large and complex which means that the page weight is too much to bear * on every page load since most pages ARE NOT user pages. This mini-script decides whether * or not the current page is a user page then loads the full UserTags script only on those * pages. This drastically reduces the weight since this is far lighter so represents a * more acceptable every-page cost. */ (function(window, $, mw, config) { "use strict";

// Core logic, detect user page and determine the skin and user names var loaderData = (function($, mw) {		//		// Figure out what skin we're on.		//		var siteSkin = mw.config.get('skin');		if (({oasis:1, wikia:1, fandomdesktop:1})[siteSkin] === 1) {			siteSkin = 'oasis';		} else if (({wowwiki:1, uncyclopedia:1, monobook:1, vector:1})[siteSkin] === 1) {			siteSkin = 'monobook';		} else {			if (window.console) {				window.console.log('USERTAGS(Loader): Unsupported skin:', siteSkin);			}			return;		}		//		// Grab the username (complicated) and decide if we are going to run or not.		// NOTE: We could use mw.Title for this but it must be loaded using		//	mw.loader.using('mediaWiki.Title') first and doesn't really give us the right tools.		//		var username = mw.config.get('wgTitle'),			namespace = mw.config.get('wgNamespaceNumber') | 0;		if (siteSkin === 'oasis') {			// We need to figure out if we're on a user page or not without a DOM query			// since we want to launch the AJAX ASAP without waiting for the DOM. if (({'-1':1, 2:1, 3:1, 500:1, 1200:1})[namespace] !== 1) { return; }			// No masthead on edit pages // Message Walls are always in edit mode if (mw.config.get('wgAction') === 'edit' && namespace !== 1200) { return; }			// MediaWiki disallows names from containing forward slashes which is very // useful since we need to check for subpages and forward slashes are the // only real way to do it. // (Subpages are lacking the masthead) if (({1200:1, 500:1, 2:1, 3:1})[namespace] === 1 && username.indexOf('/') !== -1) { return; }			// Special pages need special handling... if (namespace === -1) { username = null; namespace = mw.config.get('wgCanonicalSpecialPageName'); if (namespace === 'Contributions') { // wgPageName/wgTitle does not include the username, we need to pull // it directly from the window location. username = window.location.pathname; // Special:Contributions is dumb, here are the URL possibilities: // Special:Contributions = You // Special:Contributions/ = You // Special:Contributions/Username = Username // index.php?title=Special:Contributions = You // index.php?title=Special:Contributions&target= = You // index.php?title=Special:Contributions&target=Username = Username // Special:Contributions?target=Username = Username (*sigh*) // Special:Contributions/Username?target=OtherUser = OtherUser (*facepalm*) // Find /Username namespace = window.decodeURIComponent(username.substr(username.lastIndexOf('/') + 1)); // No user name, it displays self namespace = (namespace !== mw.config.get('wgPageName') && namespace); // Find ?target=Username username = (/(?:^\?|&)target=([^&]*)/).exec(window.location.search); // If target is missing or has an empty string then it displays self username = (username && window.decodeURIComponent(username[1])); // Target param overrides the slash param username = username || namespace; // Canonicalise back to space instead of underscores username = (username && username.replace(/_/g, ' ')); } else if (namespace === 'UserProfileActivity') { username = mw.config.get('profileUserName'); } else if (namespace !== 'Following') { // Following is self only return; // Some other special page. }				// If the username is blank then they show self. username = username || mw.config.get('wgUserName'); }		} else { // User, User Talk, Message Wall if (({2:1, 3:1, 1200:1})[namespace] !== 1) { return; }			// If we're on a subpage, drop the subpage part username = username.match(/^[^\/]+/)[0]; }		// Lastly, check if this is an anon userpage if (mw.util.isIPv4Address(username) || mw.util.isIPv6Address(username)) { return; }		// NOTE: We only get here if this IS a compatible user page return { skin: siteSkin, user: username };	})($, mw);	// Make sure the config exists and is usable	config = ($.isPlainObject(config) && config) || {};	mw.loader.using('mediawiki.util').then(function { // You can use mw.Api and mw.util. // If it's a user page then we need to expose our data to the core if (loaderData) { window.UserTagsJS = config; config.loader = loaderData; // Debugging hook for debugging arbitrary Wikis without having to modify // MediaWiki:Common.js to enable debug mode. if ((/(?:^\?|&)debugusertags=1(?:&|$)/i).test(window.location.search)) { config.debug = true; }			// Default module configuration when one is not provided. // TODO: Maybe I should just force this always using $.extend? //	[i.e. optout by false to disable instead of optin] config.modules = ($.isPlainObject(config.modules) && config.modules) || {}; if ($.isEmptyObject(config.modules)) { config.modules = { inactive: true, // Internal defaults newuser: true, // Internal defaults autoconfirmed: true, mwGroups: true, // Internal defaults metafilter: { sysop: ['bureaucrat','founder'], bureaucrat: ['founder'] }				};			}			// Force load the blocking modules unless they are manually disabled // (Manual disable means manually set to false/0/etc instead of just being undefined) config.modules.stopblocked = config.modules.stopblocked || config.modules.stopblocked === void 0; // Only Monobook needs this, OasisTagsModule can scrape the blocked state from // the DOM instead. config.modules.isblocked = config.modules.isblocked || (config.modules.isblocked === void 0 && loaderData.skin === 'monobook'); // Force the i18n module to load config.modules.i18n = true; }		// Dependency computations for loading modules var importList = (function(config, alwaysOnly) {			// These are core modules that have irregular features so need special handling			// Most modules fit the "module.NAME.js" pattern			var moduleMap = {				explode: { s: 'u:dev:MediaWiki:UserTags/module.implode.js' }			};			// Look for external module requests			// NOTE: Disabled. Needs a design review for if this is the way I want to go or not.			/*if ($.isArray(config.externals)) {				data = config.externals;				for (var i = 0, len = data.length ; i < len ; ++i) {					module = data[i] + '';					if (alwaysOnly) {						if (module.substr(0, 5) !== 'meta.') {							continue;						}					}					module = 'u:dev:MediaWiki:UserTags/' + module + '.js';					if (hash[module] !== 1) {						hash[module] = 1;						list[list.length] = module;					}				}			}*/			// Check all the modules for matches in the above list.			// Hash ensures that each one will only be imported once. /*jshint forin:true */ var list = [], hash = {}, exts = config.extensions || {}, script, data, module; for (module in config.modules) { // Skip already loaded if (exts[module] !== void 0) { continue; } if (moduleMap.hasOwnProperty(module)) { data = moduleMap[module]; if (alwaysOnly && !data.always) { continue; }					script = data.s;				} else if (!alwaysOnly) { // Attempt to load the module by name from the Dev page script = 'u:dev:MediaWiki:UserTags/module.' + module + '.js'; } else { continue; }				if (hash[script] !== 1) { hash[script] = 1; list[list.length] = script; }			}			/*jshint forin:false */ // Improve caching by ensuring list always has the same order list.sort; // Core script (must always be last, obviously) if (!alwaysOnly) { list[list.length] = 'u:dev:MediaWiki:UserTags/core.js'; }			return list; })(config, !loaderData);		// If we aren't going to load anything then we're done.		if (!importList.length) {			// Tidy up to reduce memory usage			window.UserTagsJS = null; // IE8 won't let you delete stuff from the window			try { delete window.UserTagsJS; } catch(e) { /* IE8 throws just to increase it's crapness */ }			return;		}		// If we are going to load modules despite not being on a user page then we		// need to honor the module contract by ensuring the global exists and has		// the extensions member. Everything else is unnecessary.		if (!loaderData) {			window.UserTagsJS = config = {};		}		config.extensions = ($.isPlainObject(config.extensions) && config.extensions) || {};		// Do the actual load. We also need to load the core script's dependencies from		// ResourceLoader as well.		var coreDeps = ['mediawiki.util'];		mw.loader.load(coreDeps, null, true);		var interval = setInterval(function { if (!$('#userProfileApp').length) { return; }			clearInterval(interval); if (config.debug !== 'noload') { mw.loader.using(coreDeps, function {					window.importArticles({ type:'script', articles: importList });				}); } else { config.imports = importList; }		});	});	// Done. })(window, jQuery, mediaWiki, window.UserTagsJS);