User:Lunarity/global.js

/*jshint smarttabs:true laxbreak:true laxcomma:true curly:false bitwise:false browser:true devel:true jquery:true es5:true */ /*global importScriptPage mediaWiki */

// polyfill for 'matches' selector function // http://www.w3.org/TR/2012/WD-selectors-api2-20120628/#interface-definitions (function {	"use strict";	var e = document.createElement('div'); // document.body isn't available yet	if (!e.matches) {		var polyfill = e.mozMatchesSelector   		    || e.webkitMatchesSelector		    || e.msMatchesSelector //IE9		    || e.oMatchesSelector		    || (e.querySelectorAll && function(aSel) { //IE8 has querySelector[All] but not 'matches' var p = this.parentNode, result = 0, nodes, i;			if (!p) { // Detached node with no parent // There's a minor bug that a selector with 'div' as the last ancestor // will match because of this trick when it shouldn't.				p = e;				p.appendChild(this); result = 8; }			nodes = p.querySelectorAll(aSel); i = nodes.length; while (i--) { if (nodes[i] === this) { result |= 1; break; }			}			if (result & 8) p.removeChild(this); return !!(result & 1); });		if (polyfill) {			// http://msdn.microsoft.com/en-us/library/dd282900(v=vs.85).aspx			window.Element.prototype.matches = polyfill;			if (window.HTMLDocument && !window.HTMLDocument.prototype.matches) {				window.HTMLDocument.prototype.matches = polyfill;			}		}	} });

// Block enter key from submitting any and all forms. Forms must be submitted via clicking // Submit button or it will be blocked. This prevents accidental typoing Enter when moving // pages/edit summaries/etc. // TODO: This is on probation, it may need tweaking to avoid glitchery jQuery(function($) {	"use strict";	// Only visible submit buttons will be processed so we don't break anything.	$('input[type="submit"]').filter(':visible').closest('form').not('.WikiaSearch').each(function { // Textareas won't submit on enter so that's fine. $(this).on('keypress', 'input', function(event) {			// This is lighter than :not			if (({submit:1,reset:1,button:1,image:1})[this.type] === 1) return;			if (event.which === 13) event.preventDefault;		}); }); });

// Remove Wikia's keypress handler on the Edit Summary. It's a script that // does form.submit when enter is pressed which defeats the above. if (({edit:1,editredlink:1,submit:1})[mediaWiki.config.get('wgAction')] === 1) { window.GlobalTriggers.bind('WikiaEditorReady', function disableEnterKey {		"use strict";		console.log('HACK: Unbind edit summary keyboard handlers is running');		$('#wpSummary').off('keypress').on('keypress', function(e) { // Cancel event and stop propagation of enter key. return e.which !== 13; });		window.GlobalTriggers.unbind('WikiaEditorReady', disableEnterKey);	}); }

mediaWiki.user.options.set({	HideRailDebug: true,	HideRailSyncAcrossTabs: false }); importScriptPage('User:Lunarity/test.js', 'community');

importScriptPage('AntiUnicruft/code.js', 'dev');

importScriptPage('DisplayClock/code.js', 'dev');

// Reveal Anonymous Editors importScriptPage('RevealAnonIP/usercode.js', 'dev');

/** * Fix the MediaWiki diffs (including AJAX ones). * Display the diffs in monospace font with white-space preservation. * We can't just use CSS for this because MediaWiki generates bad diffs; there are * unnecessary new-lines added at the start and end of each modified line in the * diff which obviously end up being shown unless we fix it. * This script strips the unnecessary new-line characters as well as setting * the inline styles to monospace/pre-wrap (Easier to manage all in one place). * * The Edit page fixing only works in browsers with MutationObserver support. * Those are Firefox14+ or Chrome18+. */ jQuery(function {	"use strict";	var w = window,	   document = w.document,	    mwconf = w.mediaWiki.config;

var _selectors = 'td.diff-addedline, td.diff-deletedline, td.diff-context'; _selectors = _selectors.replace(/((?:^|,)\s*)([^,]+)/g, '$1table.diff $2 > div');

// jQuery hard constrains the given selectors to the context. If the selector // references any nodes which are above the context node in the tree then the // selector will always fail. It makes more sense for the CSS selector to	// start from the node and walk up the tree, following the selector right to // left (which is what the standard says) but whatever. // querySelectorAll works that way so we can just use that. //	function fixSingleNode(aNode/*, aIndex, aArray*/) // Array.forEach compatible {		aNode.normalize; // Merge adjacent text nodes to simplify this var n = aNode.firstChild; if (n !== aNode.lastChild) { if (n.nodeType === 3) // n.TEXT_NODE === 3 n.data = n.data.replace(/^\s*\n/, ""); n = aNode.lastChild; if (n.nodeType === 3) n.data = n.data.replace(/\n\s*$/, ""); } else if (!n) { // Null rows collapse which we DON'T want aNode.appendChild(document.createTextNode('\xA0')); // nbsp }	}	function observeDialog(mutations) {		// mutations is an Array of MutationRecord /*interface MutationRecord { readonly attribute DOMString type; readonly attribute Node target; readonly attribute NodeList? addedNodes; //? means optional readonly attribute NodeList? removedNodes; readonly attribute Node? previousSibling; readonly attribute Node? nextSibling; readonly attribute DOMString? attributeName; readonly attribute DOMString? attributeNamespace; readonly attribute DOMString? oldValue; };*/		// The mutation list only generates mutations for direct changes. Adding // a whole sub-tree only generates one mutation for the root node that // was placed into the DOM (i.e. a node already in the DOM had a new child		// added). This is understandable but it means the mutations aren't very // useful directly, we need to do a full sub-tree search with selectors. var m, n, sel = _selectors, forEach = Array.prototype.forEach; for (var i = mutations.length ; i-- ; ) { m = mutations[i].addedNodes; for (var j = m.length ; j-- ; ) { n = m[j]; // We get non-element nodes like Text and Comment nodes as well, filter if (n.nodeType !== 1) continue; // 1 = ELEMENT_NODE forEach.call(n.querySelectorAll(sel), fixSingleNode); // Both jQuery and querySelectorAll will only match children, fix. if (n.matches(sel)) fixSingleNode(n); }		}	}	var _observer, _dialog; function observeBody(/*mutations*/) {		// This is simpler than walking the mutation list, may be faster as well var node = document.getElementById('EditPageDialog'); if (node !== _dialog) { _dialog = node; _observer.disconnect; // Prevent leaking previous DOM sub-tree if (node) { Array.prototype.forEach.call(node.querySelectorAll(_selectors), fixSingleNode); _observer.observe(node, {					childList: true,					subtree: true // All children of all sub-nodes				}); }		}	}

// Build custom stylesheet. // I could stick this in global.css but then I'd have to keep the selectors in sync // IE doesn't process CSS nodes correctly, you need to set the non-standard 'styleSheet' prop // but I don't use IE so I don't care. var s = document.createElement('style'); s.type = 'text/css'; s.appendChild(document.createTextNode( _selectors + "{" + "font-family: monospace;" + "white-space: pre-wrap;" // Break anywhere, ignore language rules + ((/\.(?:js|css)(?:\/|$)/i).test(mwconf.get('wgPageName')) ? "word-break: break-all;" : '') + "word-wrap: break-word; overflow-wrap: break-word" + "}"	));	document.head.appendChild(s); // document.head is HTML5 if (mwconf.get('skin') !== 'oasis') return; // Monobook only needs the CSS

// If we're on a page with diffs then run the fix. // NOTE: Switches are crazy bad in JS (It's just a sugar if/else-if chain) // http://www.mediawiki.org/wiki/Manual:Parameters_to_index.php s = {}; s.view = s.historysubmit = function { // Regex uses look-ahead (?= means "followed by"). The trick is that the line // is not *consumed* by the look-ahead matches so the 2nd (or more) // look-aheads get to walk over the same characters. if (/^(?=.*?[\?&]diff=)(?=.*?[\?&]oldid=)/.test(w.location.search)) { Array.prototype.forEach.call(document.querySelectorAll(_selectors), fixSingleNode); }	};	s.edit = s.editredlink = s.submit = function { // We use the DOM4 MutationObserver to detect when the pop-up is shown. var Observer = w.MutationObserver || w.WebKitMutationObserver || w.MozMutationObserver; if (!Observer) return;

var bodyObs = new Observer(observeBody); _observer = new Observer(observeDialog); // We observe nodes added to the body *directly*, not any of its // children. This is more efficient since the overlay is always added // to and we won't get events caused by the WYSIWYG editor. Array.prototype.forEach.call(document.querySelectorAll(_selectors), fixSingleNode); bodyObs.observe(document.body, {childList: true}); };	var f = mwconf.get('wgAction'); if (s.hasOwnProperty(f)) try { s[f](f); } catch(e) {} f = s = null; });

/** * Magic Scroller Script * * Makes long PRE tags more pleasant to deal with by creating a floating scrollbar * that sticks to the top of the window and travels down the page with you so * that you don't have to scroll to the bottom just to scroll horizontally, or * awkwardly drag scroll the text. */ (function($, Date) {	"use strict";

// Helper functions for converting document pos to viewport pos var $window = $(window); function getDocumentTopOf(node) { var sum = 0; do { sum += node.offsetTop; } while((node = node.offsetParent)); return sum; }	function getViewportTopOf(node, top) { var absTop = top || getDocumentTopOf(node); absTop -= $window.scrollTop; return absTop; }	function isInView($node, top) { var absBottom = top || getDocumentTopOf($node[0]); absBottom += $node.outerHeight; return absBottom > $window.scrollTop; }

// One instance of this class per horizontally scrollable object var MagicScroller = function($elem) { this._$elem = $elem; this._$scroller = $(' ') .prependTo($elem) .on('scroll', $.proxy(this._onSelfScroll, this)) ;		this._$scrollhack = $(' ') .appendTo(this._$scroller) ;		$elem.on('scroll.MagicScroller', $.proxy(this._onElemScroll, this)); // Establish scroll width this.onResize; // Establish initial scroll position this._onElemScroll;

return this; };	$.extend(MagicScroller.prototype, {		onScroll: function {			// Move the top scroll bar			var top = getDocumentTopOf(this._$elem[0]);			if (getViewportTopOf(this._$elem[0], top) <= 0 && isInView(this._$elem, top)) {				if (this._isfixed === true) return;				this._isfixed = true;				this._$scroller.css({ position: 'fixed', top: '-1px', left: '' // Clear _repositionRelative value });			} else {				if (this._isfixed === false) return;				this._isfixed = false;				this._$scroller.css({ position: 'relative', top: '' });				this._repositionRelative;			}		},		onResize: function {			// Fix configuration			this._$scroller.css('width', this._$elem.width + 'px');			// This algo is to take padding into consideration to avoid a deadzone in			// the scrollbar			this._$scrollhack.css('width', (this._$elem[0].scrollWidth - this._$elem.innerWidth + this._$elem.width) + 'px' );			// If the PRE fits in one screen then don't bother adding the 2nd bar.			this._$scroller[this._$elem.height < $window.height ? 'hide' : 'show'];			this.onScroll;		},		_onSelfScroll: function {			// Mirror the scroll position			this._$elem.scrollLeft(this._$scroller.scrollLeft);		},		_onElemScroll: function {			// Mirror the scroll position			var pos = this._$elem.scrollLeft;			if (pos !== this._$scroller.scrollLeft) {				this._$scroller.scrollLeft(pos);			}			this._repositionRelative(pos);		},		_repositionRelative: function(scrollLeft) {			// If the bar is docked then keep it in view.			if (!this._isfixed) {				this._$scroller.css('left', (scrollLeft || this._$elem.scrollLeft) + 'px');			}		},		detach: function {			this._$scroller.remove;			this._$elem.off('scroll.MagicScroller');			cleanShared(this._$elem);		}	});

// Find all PREs that are horizontally scrollable var windowId = '.MagicScroller' + Math.random.toFixed(15).substr(2), $scrollables; $(function($) {		$scrollables = $('pre').filter(function { return !$.data(this, 'MagicScroller'); }).each(function { var $this = $(this); $this.data('MagicScroller', new MagicScroller($this)); });		if (!$scrollables.length) return;		$window.on('scroll' + windowId, function { for (var i = 0, len = $scrollables.length ; i < len ; ++i) { $.data($scrollables[i], 'MagicScroller').onScroll; }		}).on('resize' + windowId, function { for (var i = 0, len = $scrollables.length ; i < len ; ++i) { $.data($scrollables[i], 'MagicScroller').onResize; }		});		// Width transitions (animations) screw with us since they can cause		// the PRE to get wider/narrower after we already sampled its width,		// We sniff all transition ending events to detect this and treat them		// like resizes. [This is not very efficient since we're capturing		// every transition ever, and is a big problem when multiple elements		// transition simultaneously so we need to filter and rate-limit]		var rateLimit = Date.now;		$(document).on('transitionend' + windowId, function(evt) { if (evt.originalEvent.propertyName !== 'width') return; if (Date.now - rateLimit < 100) return; rateLimit = Date.now; for (var i = 0, len = $scrollables.length ; i < len ; ++i) { $.data($scrollables[i], 'MagicScroller').onResize; }		});	});	function cleanShared($node) { $scrollables = $scrollables.not($node); $.data(($node.jquery && $node[0]) || $node, 'MagicScroller', null); if ($scrollables.length) return; $window.off('scroll' + windowId + ' resize' + windowId); $(document).off('transitionend' + windowId); } })(jQuery, Date);