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.0.7';

/**	 * 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' );					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 {					// Get tank info					var max = $( this ).data( 'tank-max' ) || 10000;					// Tile liquid image					$( this ).find( '.tankLiquidImageContainer.minetip .tankLiquidImage' ).each( function { var usage = $( this ).data( 'tank-usage' ) || 5000, imglink = $( this ).find( 'img' ).hide.attr( 'src' ); $( this ).css( {							backgroundImage: 'url(' + imglink + ')',							backgroundRepeat: 'repeat',							backgroundPosition: 'bottom',							height: usage / 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 Unknown */		pauseGrid: { conditional: $( '.grid' ).length, exec: function { var bc = document.getElementById( 'bodyContent' ), tags = ['span', 'div', 'table', 'td', 'th'], pops = function( elems ) { for ( var i = 0; i < elems.length; i++ ) { if ( !( ' ' + elems[i].className + ' ').match( / pops / ) ) { continue; }							var anchs = elems[i].getElementsByTagName( 'a' ); for ( var j = 0; j < anchs.length; j++ ) { anchs[j].target = '_blank'; }						}					};

function pauseGrid( grid ) { $( grid ).hover( function {						$( this ).find( '.grid .animated' ).removeClass( 'animated' ).addClass( 'paused' );					}, function {						$( this ).find( '.grid .paused' ).removeClass( 'paused' ).addClass( 'animated' );					} ); }

pauseGrid( '.grid-Crafting_Table' ); pauseGrid( '.grid-Furnace' ); pauseGrid( '.grid-Brewing_Stand' );

for ( var i = 0; i < tags.length; i++ ) { pops( bc.getElementsByTagName( tags[i] ) ); }			}		},

/**		 * 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 ).find( '.active' ).removeClass( 'active' ), next = current.next; if ( !current.next.length ) { next = $( this ).children.eq( 0 ); }							next.addClass( 'active' ); } );					}, 2000 );				}			}		},

/**		 * Gallery flipping, intended to be used with Crafting grid *		 * @author Unknown */		gallery: { conditional: $( '.gallery' ).length, exec: function { $( '.gallery > div, .gallery > span' ).addClass( 'page' ); $( '.gallery' ).append( '<span class=\'controls\'> \											<input type=\'button\' value=\'<\' class=\'prevPage\'> \											<span class=\'pagenum\'> /<span class=\'pagecount\'> \											<input type=\'button\' value=\'>\' class=\'nextPage\'> \										 '); $( '.gallery > .page:first-child' ).addClass( 'active' ); $( '.gallery span.controls span.pagenum' ).html( 1 ); $( '.gallery' ).each( function {					$( this ).find( 'span.pagecount' ).html( $( this ).children( '.page' ).length );				}); $( '.gallery span.controls input.prevPage' ).click( function {					var cur = $( this ).parents( '.gallery' ).children( '.page.active' ),						next = cur.prev( '.page' ),						pageNum = parseInt( $( this ).siblings( 'span.pagenum' ).html, 10 ) - 1;					if ( next.length === 0 ) {						next = cur.siblings( '.page' ).last;					}					cur.removeClass( 'active' );					next.addClass( 'active' );					if ( pageNum === 0 ) {						pageNum = parseInt( $( this ).siblings( 'span.pagecount' ).html, 10 );					}					$( this ).siblings( 'span.pagenum' ).html( pageNum );				}); $( '.gallery span.controls input.nextPage' ).click( function {					var cur = $( this ).parents( '.gallery' ).children( '.page.active' ),						next = cur.next( '.page' ),						pageNum = parseInt( $( this ).siblings( 'span.pagenum' ).html, 10 ) + 1;					if ( next.length === 0 ) {						next = cur.siblings( '.page' ).first;					}					cur.removeClass( 'active' );					next.addClass( 'active' );					if (pageNum > parseInt( $( this ).siblings( 'span.pagecount' ).html, 10 ) ) {						pageNum = 1;					}					$( this ).siblings( 'span.pagenum' ).html( pageNum );				}); }		},

/**		 * Crafting grid handler *		 * @author Unknown */		craftingGrid: { conditional: $( '.CraftingGrid' ).length, exec: function { $( '.CraftingGrid' ).each( function {					var maxFrames = 0;					$( this ).find( '.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\'>' + String(maxFrames) + ' \ <input type=\'button\' value=\'>\' class=\'nextPage\'> \ ' );					$( this ).height( $( this ).height + $( this ).children( '.CraftingGridControls' ).height );					// Implement controls					$( this ).find( '.nextPage' ).click( function { var container = $( this ).parents( '.CraftingGrid' ); container.find( '.CraftingGridCell' ).each( function {							if ( $( this ).children( ':not(.ignore)' ).length === 1 ) {								$( this ).removeClass( '.CraftingGridCell' );								return 0;							}							var cur = $( this ).find( '.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 { var container = $( this ).parents( '.CraftingGrid' ); container.find( '.CraftingGridCell' ).each( function {							if ( $( this ).children( ':not(.ignore)' ).length === 1 ) {								$( this ).removeClass( '.CraftingGridCell' );								return 0;							}							var cur = $( this ).find( '.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 ); });				});			}		},

/**		 * Handling infobox block images *		 * @author Unknown */		infoboxImage: { conditional: $( '.infoboxBlockImage' ).length, exec: function { $( '.infoboxBlockImage img' ).each( function {					if ( parseInt( $( this ).attr( 'width' ), 10) > 240 ) {						$( this ).attr( 'height', 0 );						$( this ).attr( 'width', 240 );					}				}); }		},

/**		 * 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 flag = true,						next = $( this ).next;					while ( next && next !== undefined && next.html !== undefined && !next.hasClass( 'infoboxSubsectionBreak' ) && !next.hasClass( 'infoboxSectionHeader' ) ) {						if ( next.css( 'display' ) !== 'none' ) {							flag = false;						}						next = next.next;					}					if ( flag ) {						$( 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 $this = $( this ), matched = /(?:^| )autosort=(\d+)(?:,|-)(a|d)(?: |$)/.exec( $this.attr( 'class' ) ), $sortCol = $( $this.find( '> thead th:nth-child(' + matched[1] + ')' )[i] );

if ( matched[2] === 'd' ) { // descending $sortCol.click.click; } else { // ascending $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 Unknown */		navbox: { conditional: ( conf.wgNamespaceNumber === 0 && $( '.navbox' ).length ), exec: function { var $navbox = $( '.navbox' ); function collapseNavbox( navbox ) { var $navbox = $( navbox ).find( '> tbody > tr > td > table' ), $rows, $toggle; // add the collapsed class $navbox.addClass( 'mw-collapsed' ); // make sure we aren't selecting any nested navboxes $rows = $navbox.find( '> tbody > tr' ); $rows.each( function ( i ) {						// first row is the header						if ( i === 0 ) {							return;						}

$( this ).hide; } );					// toggle is always in header					$toggle = $rows.first.find( '.mw-collapsible-toggle' );					// this class is required to make expand work properly					$toggle.addClass( 'mw-collapsible-toggle-collapsed' );					$toggle.children( 'a' ).text( 'show' );

}				// collapse if more than 2 if ( $navbox.length > 1 ) { $navbox.each( function {						collapseNavbox( this );					} ); }				// collapse if taller than 300px $navbox.each( function {					if ( $( this ).height > 300 ) {						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 ) {					var text = $( '#wpTextbox1' ).val,						reminder = '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?';					if ( // don't trigger on minor edits $( '#wpMinoredit' ).prop( 'checked' ) ||

// check for signature text.replace( /( .*?<\/nowiki>)/g,  ).match(  ) ) {						return;					}

mw.log( 'sigreminder activated' ); if ( !confirm( reminder ) ) { mw.log( 'prevent no sig' ); 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 ) {

// used for tracking which includes are loading 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 || {} ) );