MediaWiki:Common.js

/** * Please test any changes made to this file. * Jshint  can catch syntax errors to help testing. * * To see which scripts this has loaded, see `ftb.loaded` (from your js console) */ /*jshint bitwise:true, browser:true, camelcase:true, curly:true, devel:false, eqeqeq:true, es3:false, forin:true, immed:true, jquery:true, latedef:true, newcap:true, noarg:true, noempty:true, nonew:true, onevar:false, plusplus:false, quotmark:single, undef:true, unused:true, strict:true, trailing:true, multistr:true


 * ( function ( $, mw, ftb ) {

'use strict';

/**	 * Versioning handler */	ftb.version = '1.1';

/**	 * Cache mw.config values *	 * These are used in conditionals for checking various mediawiki settings * For a full list of available variables see  */	var conf = mw.config.get( [		'wgAction',		'wgCanonicalSpecialPageName',		'wgNamespaceNumber',		'wgTitle',		'wgUserName'	] );

/**	 * Settings of each script run/imported *	 * This is where each script on the wiki is imported * To import a new script see the example just below *	 * When adding new scripts, please keep them in alphabetical order */	var includes = { /*		example: { // {function|boolean} Conditional to pass for exec to run // Can be something that evaluates to a boolean if required // If it should always load, set to true conditional: true,

// {function} Function to run exec: function { console.log( 'loaded' ); }		}		*/

/**		 * Lazy loading *		 * @author Unknown */		lazy: { conditional: $( '.load-page' ).length, exec: function { $( '.load-page-button > a' ).click( function( e ) {					e.preventDefault;					var button = $( this ).parent,						body = button.closest( '.load-page' );					$(this).html('Loading');					new mw.Api.get( { action: 'parse', prop: 'text', page: body.data( 'page' ) } ).done( function ( data ) { console.log( 'Loaded data!' ); body.html( data.parse.text['*'] ); mw.hook( 'wikipage.content' ).fire( body ); } ).fail( function { console.log( 'Failed to load data!' ); } );					console.log( 'Firing request to load data!' );				} ); }		},

/**		 * desc *		 * @author Unknown */		tooltips: { conditional: $( '.minetip' ).length || $( '.grid' ).length, exec: function { var tooltip, escapeChars = { '\\&': '&#38;',						'<': '&#60;',						'>': '&#62;'					},					escape = function( text ) { // '\' must be escaped first return text.replace( /\\\\/g, '&#92;' ) .replace( /\\&|[<>]/g, function( char ) {								return escapeChars[char];							} ); },					win = $( window ), winWidth, winHeight, width, height; $( '#mw-content-text' ).on( {					'mouseenter.minetip': function( e ) {						var elem = $( this ),							title = elem.attr( 'data-minetip-title' );						// No title or title only contains formatting codes						if ( title === undefined || title && title.replace( /&([0-9a-fl-or])|\s+/g,  ) ===  ) {							// Find deepest child title							var childElem = elem[0], childTitle;							do {								if ( childElem.hasAttribute( 'title' ) ) {									childTitle = childElem.title;								}								childElem = childElem.firstChild;							} while( childElem && childElem.nodeType === 1 );							if ( childTitle === undefined ) {								return;							}							// Append child title as title may contain formatting codes							if ( !title ) {								title = '';							}							title += childTitle;							// Set the retrieved title as data for future use							elem.attr( 'data-minetip-title', title );						} if ( !elem.data( 'minetip-ready' ) ) { // Remove title attributes so the native tooltip doesn't get in the way elem.find( '[title]' ).addBack.removeAttr( 'title' ); elem.data( 'minetip-ready', true ); }						if ( title === '' ) { return; }						// Apply normal escaping title = escape( title ); var text = ' ' + title + '&r '; var description = elem.attr( 'data-minetip-text' ); if ( description ) { // Apply normal escaping plus '/' description = escape( description ).replace( /\\\//g, '&#47;' ); text += ' ' + description.replace( /\//g, ' ' ) + '&r '; }						// Add classes for minecraft formatting codes while ( /&[0-9a-fl-o]/.test( text ) ) { text = text.replace( /&([0-9a-fl-o])(.*?)(&r|$)/g, '$2 &r' ); }						// Remove reset formatting text = text.replace( /&r/g, '' ); // Unescape '&' so HTML entities work text = text.replace( /&#38;/g, '&' ); tooltip = $( '#minetip-tooltip' ); if ( !tooltip.length ) { tooltip = $( ' ' ).appendTo( 'body' ); }						tooltip.html( text ); // Cache current window and tooltip size winWidth = win.width; winHeight = win.height; width = tooltip.outerWidth( true ); height = tooltip.outerHeight( true ); // Trigger a mouse movement to position the tooltip elem.trigger( 'mousemove', e ); },					'mousemove.minetip': function( e, trigger ) { if ( !$( '#minetip-tooltip' ).length ) { $( this ).trigger( 'mouseenter' ); return; }						// Get event data from remote trigger e = trigger || e;						// Get mouse position and add default offsets var top = e.clientY - 34, left = e.clientX + 14; // If going off the right of the screen, go to the left of the cursor if ( left + width > winWidth ) { left -= width + 36; }						// If now going off to the left of the screen, resort to going above the cursor if ( left < 0 ) { left = 0; top -= height - 22; // Go below the cursor if too high if ( top < 0 ) { top += height + 47; }						// Don't go off the top of the screen } else if ( top < 0 ) { top = 0; // Don't go off the bottom of the screen } else if ( top + height > winHeight ) { top = winHeight - height; }						// Apply the positions tooltip.css( {							top: top,							left: left						} ); },					'mouseleave.minetip': function { if ( !tooltip ) { return; }

tooltip.remove; }				}, '.minetip, .grid' );			}		},

/**		 * Grid tank container handler *		 * @author Unknown */		gridTank: { conditional: $( '.gridTankContainer' ).length, exec: function { $( '.gridTankContainer' ).each( function {					var max = $( this ).data( 'tank-max' ) || 10000;					$( this ).children( '.tankLiquidImageContainer.minetip' ).children( '.tankLiquidImage' ).each( function { $( this ).css( {							backgroundImage: 'url(' + $( this ).children( 'img' ).hide.attr( 'src' ) + ')',							backgroundRepeat: 'repeat',							backgroundPosition: 'bottom',							height: ($( this ).data( 'tank-usage' ) || 5000) / max * 100 + '%'						} ); } );				} );			}		},

/**		 * Special page reporting *		 * @author Cblair91 * @version 1.0 */		specialPage: { conditional: $( 'specialMaintenance' ).length || conf.wgCanonicalSpecialPageName === 'Specialpages', exec: function { var pages = [ 'BrokenRedirects', 'DoubleRedirects', 'Unusedcategories', 'Unusedimages', 'Wantedcategories', 'Wantedfiles', 'Wantedpages', 'Wantedtemplates' ];

function getPages( page ) { $.getJSON( '/api.php?action=query&list=querypage&qppage=' + page + '&qplimit=100&format=json', function ( data ) {						$( '#' + page ).text( data.query.querypage.results.length );					}); }

function apiQuery { for ( var i = 0; i < pages.length; i++ ) { getPages( pages[i] ); }				}

if ( $( 'specialMaintenance').length ) { apiQuery; }

if ( conf.wgCanonicalSpecialPageName === 'Specialpages' ) { $( '#mw-content-text' ).before(' \						 Broken redirects ( ) &bull; Double redirects ( ) &bull; Unused categories ( ) &bull; Unused images ( ) \						 Wanted categories (<span id=\'Wantedcategories\'> )</a> &bull; Wanted files (<span id=\'Wantedfiles\'> )</a> &bull; Wanted pages (<span id=\'Wantedpages\'> )</a> &bull; Wanted templates (<span id=\'Wantedtemplates\'> )</a> \ ');					apiQuery;				}			}		},

/**		 * Pausing crafting grids *		 * @author Cblair91 */		pauseGrid: { conditional: $( '.grid' ).length, exec: function { $( '.grid-Crafting_Table, .grid-Furnace, .grid-Brewing_Stand' ).hover( function {					$( this ).children( '.grid .animated' ).removeClass( 'animated' ).addClass( 'paused' );				}, function {					$( this ).children( '.grid .paused' ).removeClass( 'paused' ).addClass( 'animated' );				} ); $( '#bodyContent' ).children( 'span.pops, div.pops, table.pops, td.pops, th.pops' ).each( function {					$( this ).children( 'a' ).each( function { $( this ).attr( 'target', '_blank' ); } );				} );			}		},

/**		 * Element animator *		 * @author Unknown */		animated: { conditional: $( '.animated' ).length, exec: function { // Remove from animated class if only one child $( '.animated' ).each( function {					if ( $( this ).children( 'span, div' ).length === 1 ) {						$( this ).removeClass( 'animated' );					}				}); // Add the active class to all of the first child of .animated $( '.animated > span:first-child, .animated > div:first-child' ).addClass( 'active' ); if ( $( '.animated' ).length ) { setInterval( function {						$( '.animated' ).each( function { var current = $( this ).children( '.active' ).removeClass( 'active' ), next = current.next; if ( !current.next.length ) { next = $( this ).children.eq( 0 ); }							next.addClass( 'active' ); } );					}, 2000 );				}			}		},

/**		 * Crafting grid handler *		 * @author Unknown */		craftingGrid: { conditional: $( '.CraftingGrid' ).length, exec: function { $( '.CraftingGrid' ).each( function {					var maxFrames = 0;					$( this ).children( '.CraftingGridCell' ).each( function { var frames = $( this ).children( 'span:not(.ignore), div.GridTank:not(.ignore)' ).length; if ( frames > maxFrames) { maxFrames = frames; }						// Initialize cell states $( this ).children( 'span:first-child:not(.ignore), div.GridTank:first-child:not(.ignore)' ).addClass( 'ActiveSlide' ); });					if ( maxFrames <= 1 ) {						return;					}					// Create crafting grid controls					$( this ).append( '<div class=\'CraftingGridControls\' style=\'position:absolute; bottom:0; width:100%; text-align:center;\'> \ <input type=\'button\' value=\'<\' class=\'prevPage\'> \ <span class=\'pageNum\'>1 /<span class=\'pageCount\'>' + maxFrames + ' \ <input type=\'button\' value=\'>\' class=\'nextPage\'> \ ' );					$( this ).height( $( this ).height + $( this ).children( '.CraftingGridControls' ).height );					// Implement controls					$( this ).find( '.nextPage' ).click( function { $( this ).parents( '.CraftingGrid' ).children( '.CraftingGridCell' ).each( function {							if ( $( this ).children( ':not(.ignore)' ).length === 1 ) {								$( this ).removeClass( '.CraftingGridCell' );								return;							}							var cur = $( this ).children( '.ActiveSlide' ),								next = cur.next( 'span:not(.ignore), div.GridTank:not(.ignore)' );							if ( next.length === 0 ) {								next = cur.siblings( 'span:not(.ignore), div.GridTank:not(.ignore)' ).first;							}							cur.removeClass( 'ActiveSlide' );							next.addClass( 'ActiveSlide' );						}); var pageNum = parseInt( $( this ).siblings( 'span.pageNum' ).html, 10 ) + 1; if ( pageNum > parseInt( $( this ).siblings( 'span.pageCount' ).html, 10 ) ) { pageNum = 1; }						$( this ).siblings( 'span.pageNum' ).html( pageNum ); });					$( this ).find( '.prevPage' ).click( function { $( this ).parents( '.CraftingGrid' ).children( '.CraftingGridCell' ).each( function {							if ( $( this ).children( ':not(.ignore)' ).length === 1 ) {								$( this ).removeClass( '.CraftingGridCell' );								return 0;							}							var cur = $( this ).children( '.ActiveSlide' ),								next = cur.prev( 'span:not(.ignore), div.GridTank:not(.ignore)' );							if ( next.length === 0 ) {								next = cur.siblings( 'span:not(.ignore), div.GridTank:not(.ignore)' ).last;							}							cur.removeClass( 'ActiveSlide' );							next.addClass( 'ActiveSlide' );						}); var pageNum = parseInt( $( this ).siblings( 'span.pageNum' ).html, 10 ) - 1; if (pageNum === 0) { pageNum = parseInt( $( this ).siblings( 'span.pageCount' ).html, 10 ); }						$( this ).siblings( 'span.pageNum' ).html( pageNum ); });				});			}		},

/**		 * Infobox collapsing *		 * @author Unknown */		infobox: { conditional: $( '.infobox' ).length, exec: function { $( '.infobox:not(.infoboxNoCollapse) td' ).each( function {					if ( $( this ).html.match( /}/ ) ) {						$( this ).parent( 'tr' ).hide;					}				}); $( '.infobox:not(.infoboxNoCollapse) .infoboxSubsectionBreak, .infobox:not(.infoboxNoCollapse) tr.infoboxSectionHeader' ).each( function {					var next = $( this ).next;					while ( next !== undefined && next.html !== undefined && !next.is( '.infoboxSubsectionBreak, .infoboxSectionHeader' ) ) {						if ( next.is( ':visible' ) ) {							return;						}						next = next.next;					}					$( this ).hide;				}); }		},

/**		 * Autosorting sortable tables *		 * @author Cblair91 */		autosort: { conditional: $( '.sortable' ).length, exec: function { mw.loader.using( 'jquery.tablesorter', function {					$( '.sortable[class*="autosort="]' ).each( function ( i ) { var matched = /(?:^| )autosort=(\d+)(?:,|-)(a|d)(?: |$)/.exec( $( this ).attr( 'class' ) ), $sortCol = $( $( this ).children( '> thead th:nth-child(' + matched[1] + ')' )[i] ); if ( matched[2] === 'd' ) { $sortCol.click.click; } else { $sortCol.click; }					} );				} );			}		},

/**		 * Script for *		 * @author Cblair91 */		insertUsername: { conditional: conf.wgUserName && conf.wgNamespaceNumber === 2 && conf.wgTitle.indexOf( '/' ) > -1, exec: function { $( '.insertusername' ).text( conf.wgUserName ); }		},		/**		 * Remove the fade animation from mw-collapsible *		 * @author Cblair91 */		instantCollapsible: { conditional: $( '.mw-collapsible' ).length, exec: function { // @TODO: Fix? MediaWiki:Common.js/collapsible.js			} },

/**		 * Collapses navboxes under certain conditions *		 * @author Cblair91 */		navbox: { conditional: conf.wgNamespaceNumber === 0 && $( '.navbox' ).length, exec: function { function collapseNavbox( navbox ) { $( navbox ).children( '> tbody > tr > td > table' ).addClass( 'mw-collapsed' ).children( '> tbody > tr' ).each( function ( i ) {						if ( i === 0 ) {							$( this ).children( '.mw-collapsible-toggle' ).addClass( 'mw-collapsible-toggle-collapsed' ).children( 'a' ).text( 'show' );							return;						}						$( this ).hide;					} ); }				var $navbox = $( '.navbox' ); $navbox.each( function {					if ( $( this ).height > 300 || $navbox.length > 1 ) {						collapseNavbox( this );					}				} ); }		},

/**		 * Signature reminder on talk pages *		 * @author Cblair91 */		sigReminder: { conditional: ['edit', 'submit'].indexOf( conf.wgAction ) > -1 && ( conf.wgNamespaceNumber % 2 === 1 ), exec: function { $( '#wpSave' ).on( 'click', function ( e ) {					if ( $( '#wpMinoredit' ).prop( 'checked' ) || $( '#wpTextbox1' ).val.replace( /( .*?<\/nowiki>)/g,  ).match(  ) ) {						return;					}					if ( !confirm( 'It looks like you forgot to sign your comment. You can sign by placing 4 tildes (~) to the end of your message.\nAre you sure you want to post it?' ) ) {						e.preventDefault;					}				} ); }		}	};

var loaded = [];

/**	 * Used to detect incorrectly spelt keys for each include *	 * @param obj {object} * @param key {string} */	function checkKeys( obj, key ) { var inclKeys = Object.keys( obj );

['conditional', 'exec'].forEach( function ( elem ) {			var index = inclKeys.indexOf( elem );

if ( index > -1 ) { inclKeys.splice( index, 1 ); }		} );

if ( inclKeys.length ) { console.warn( 'Error in MediaWiki:Common.js: `includes.' + key + '` contains unknown key(s): ' + inclKeys.toString ); }	}

/**	 * Loading method *	 * Iterates over each entry in `includes` to check if the script should be executed */	function init { $.each( includes, function ( k, v ) {

if ( $.isFunction( v.conditional ) ? v.conditional : v.conditional ) {

loaded.push( 'common.' + k ); v.exec;

}

checkKeys( v, k ); } );

ftb.loaded = ( ftb.loaded || [] ).concat( loaded );

// add `ftb` an an alias for `ftbwiki` window.ftb = ftb; }

$( init );

}( this.jQuery, this.mediaWiki, this.ftbwiki = this.ftbwiki || {} ) );