MediaWiki:Common.js

/** * This file loads for every user visiting the wiki. * For skin specific variants see MediaWiki:Monobook.js and MediaWiki:Wikia.js * for monobook and oasis respectively * * Please test any changes made to this file. * Jshint  can catch syntax errors to help testing. * Alternatively, Wikia's code editor has jshint embedded to make life extra simple. * * To see which scripts this has loaded, see `rswiki.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

(function ($, mw, rs) {

'use strict';

/**    * Global variables set for imported scripts */   // AjaxRC window.AjaxRCRefreshText = 'Auto-refresh'; window.ajaxPages = [ 'Special:RecentChanges', 'Special:Contributions', 'Special:Log', 'Special:Log/move', 'Special:AbuseLog', 'Special:NewFiles', 'Special:NewPages', 'Special:Watchlist', 'Special:Statistics', 'Special:ListFiles', 'Category:Speedy_deletion_candidates', 'Category:Speedy_move_candidates' ];

/**    * 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([        'debug',        'skin',        'wgAction',        'wgArticlePath',        'wgCanonicalSpecialPageName',        'wgContentLanguage',        'wgContentReviewExtEnabled',        'wgContentReviewTestModeEnabled',        'wgIsMainPage',        'wgNamespaceNumber',        'wgPageName',        'wgRedirectedFrom',        'wgReviewedScriptsTimestamp',        'wgScriptsTimestamp',        'wgServer',        'wgTitle',        'wgUserName',        'wgUserGroups'    ]);

/**    * Storage for scripts and styles loaded through `util.loadAssets` to prevent double loading. */   var loadedAssets = {};

/**    * Reusable functions *    * These are available under the `rswiki` global variable. * @example `rswiki.addCommas` * The alias `rs` is also available in place of `rswiki`. */   var util = { /**        * Formats a number string with commas. *        * @todo fully replace this with Number.protoype.toLocaleString *      > 123456.78.toLocaleString('en') *        * @example 123456.78 -> 123,456.78 *        * @param num {Number|String} The number to format. * @return {String} The formated number. */       addCommas: function (num) { if (typeof num === 'number') { return num.toLocaleString('en'); }

// @todo chuck this into parseFloat first and then to toLocaleString? num += '';

var x = num.split('.'), x1 = x[0], x2 = x.length > 1 ? '.' + x[1] : '',               rgx = /(\d+)(\d{3})/;

while (rgx.test(x1)) { x1 = x1.replace(rgx, '$1,$2'); }

return x1 + x2; },

/**        * Extracts parameter-argument pairs from templates. *        * @todo Fix for multiple templates *        * @param tpl {String} Template to extract data from. * @param text {String} Text to look for template in. * @return {Object} Object containing parameter-argument pairs */       parseTemplate: function (tpl, text) { var rgx = new RegExp(                   '\\{\\{(template:)?' + tpl.replace(/[ _]/g, '[ _]') + '\\s*(\\||\\}\\})',                    'i'                ), exec = rgx.exec(text), // splits template into |arg=param or |param paramRgx = /\|(.*?(\{\{.+?\}\})?)(?=\s*\||$)/g, args = {}, params, i,               j;

// happens if the template is not found in the text if (exec === null) { return false; }

text = text.substring(exec.index + 2);

// used to account for nested templates j = 0;

// this purposefully doesn't use regex // as it became very difficult to make it work properly for (i = 0; i < text.length; i += 1) { if (text[i] === '{') { j += 1; } else if (text[i] === '}') { if (j > 0) { j -= 1; } else { break; }               }            }

// cut off where the template ends text = text.substring(0, i); // remove template name as we're not interested in it past this point text = text.substring(text.indexOf('|')).trim; // separate params and args into an array params = text.match(paramRgx);

// handle no params/args if (params !== null) { // used as an index for unnamed params i = 1;

params.forEach(function (el) {                   var str = el.trim.substring(1),                        eq = str.indexOf('='),                        tpl = str.indexOf('{{'),                        param,                        val;

// checks if the equals is after opening a template // to catch unnamed args that have templates with named args as params if (eq > -1 && (tpl === -1 || eq < tpl)) { param = str.substring(0, eq).trim.toLowerCase; val = str.substring(eq + 1).trim; } else { param = i;                       val = str.trim; i += 1; }

args[param] = val; });           }

return args; },

/**        * Alternate version of `parseTemplate` for parsing exchange module data. *        * @notes Only works for key-value pairs *        * @param text {String} Text to parse. * @return {Object} Object containing parameter-argument pairs. */       parseExchangeModule: function (text) {

// strip down to just key-value pairs var str = text .replace(/return\s*\{/, '') .replace(/\}\s*$/, '') .trim, rgx = /\s*(.*?\s*=\s*(?:\{[\s\S]*?\}|.*?))(?=,?\n|$)/g, args = {}, params = str.match(rgx);

if (params !== null) { params.forEach(function (elem) {                   var str = elem.trim,                        eq = str.indexOf('='),                        param = str.substring(0, eq).trim.toLowerCase,                        val = str.substring(eq + 1).trim;

args[param] = val; });           }

return args; },

/**        * Alternative to mw.load.implement and importArticles *        * Pros compared to alternatives: * - Doesn't throw error for missing args unlike mw.loader.implement * - Returns a promise so you can use .done and .fail as a callback *        * Cons: * - More http requests, but they're async * - No message/i18n support, but no one ever uses them on Wikia anyway *        * @param js {object|array|string} Either an object with the keys 'js' and/or 'css', an         *                                 array JS pages as strings or a string representing a         *                                 single JS page. Values for object keys are either an        *                                 array or string with the same contents. * @param css {array|string} Not used if `js` is an object. Otherwise it can be an array *                          containing CSS pages as strings or a string representing a         *                           single CSS page. If it's not required it can be omitted.' *        * @return {jQuery.Promise} *        * @example rs.loadAssets('foo.js', 'bar.css').done(function  { ... }); * @example rs.loadAssets(['foo.js', 'bar.js'], ['baz.css', 'quux.css']).done(function { ... }); * @example rs.loadAssets({ js: 'foo.js', css: 'bar.css' }).done(function { ... }); * @example rs.loadAssets({ js: ['foo.js', 'bar.js'], css: ['baz.css', 'quux.css'] }).done(function { ... }); */       loadAssets: function (js, css) { var assets = normaliseArgs(js, css), loaded = [], $head = $('head'), $meta = $head.find('meta[name="ResourceLoaderDynamicStyles"]'), $style, style = $meta.prev('style').get(0); // fix cases where the RL style tag is missing // notably when ?debug=true // which causes RL to add link tags instead // @todo mimic that behaviour? if (!$meta.length) { mw.log('getMarker> No  found, inserting dynamically.'); $meta = $(' ').attr('name', 'ResourceLoaderDynamicStyles'); $head.append($meta); }           $style = $meta.prev('style'); if (!$style.length) { $style = $(' ').attr({type: 'text/css', media: 'all'}); $meta.before($style); }           style = $style.get(0);

if ($.isEmptyObject(assets)) { // silently fail return $.when; }

// append css to head (same as RL) // do it before js to prevent flashes of unstyled stuff as much as possible assets.css.forEach(function (elem) {               if (loadedAssets.hasOwnProperty(elem)) {                    loaded.push(loadedAssets[elem]);                    return;                }

var opts = { async: true, data: { debug: conf.debug, lang: conf.wgContentLanguage, mode: 'articles', articles: elem, only: 'styles', // not convinced skin does anything, but it's here just in case skin: conf.skin },                       dataType: 'text', error: function (_, textStatus, errorThrown) { mw.log(textStatus, errorThrown); },                       type: 'GET', url: mw.util.wikiScript('load') },                   $jqXhr = $.ajax(opts);

loaded.push($jqXhr); loadedAssets[elem] = $jqXhr;

$jqXhr.done(function (data) {                   style.innerHTML = style.innerHTML + '\n' + data;                }); });

// load js and execute straight away assets.js.forEach(function (elem) {               if (loadedAssets.hasOwnProperty(elem)) {                    loaded.push(loadedAssets[elem]);                    return;                }

var opts = { async: true, data: { debug: conf.debug, lang: conf.wgContentLanguage, mode: 'articles', articles: elem, only: 'scripts', // not convinced skin does anything, but it's here just in case skin: conf.skin },                       dataType: 'script', error: function (_, textStatus, errorThrown) { mw.log(textStatus, errorThrown); },                       type: 'GET', url: mw.util.wikiScript('load') },                   $jqXhr;

// extra params for js review/js test mode // lifted directly from the js for importArticles // see  // correct line as of 2015-11-22 (may change in the future as stuff is changed) if (conf.wgContentReviewExtEnabled) { if (opts.data.articles.search(/mediawiki:/i) > -1) { if (conf.wgContentReviewTestModeEnabled) { opts.data.current = conf.wgScriptsTimestamp; } else { opts.data.reviewed = conf.wgReviewedScriptsTimestamp; }                   }                }                $jqXhr = $.ajax(opts);

loaded.push($jqXhr); loadedAssets[elem] = $jqXhr; });

mw.log(assets, loaded); return $.when.apply(loaded); },

/**        * Helper for making cross domain requests to RuneScape's APIs. * If the APIs ever enable CORS, we can ditch this and do the lookup directly. *        * @param url {string} The URL to look up         * @param via {string} One of 'anyorigin', 'whateverorigin' or 'crossorigin'. Defaults to 'anyorigin'. *        * @return {string} The URLto use to make the API request. */       crossDomain: function (url, via) { switch (via) { case 'crossorigin': url = 'http://crossorigin.me/' + url; break;

case 'whateverorigin': url = 'http://whateverorigin.org/get?url=' + encodeURIComponent( url ) + '&callback=?'; break;

case 'anyorigin': url = 'http://anyorigin.com/go/?url=' + encodeURIComponent( url ) + '&callback=?'; break; case 'allorigins': default: url = 'https://allorigins.me/get?url=' + encodeURIComponent( url ) + '&callback=?'; break; }

return url; }   };

/**    * Actions for which to load scripts that modify the edit UI     */ var editActions = ['edit', 'submit'];

/**    * Settings of each script run/imported * Based on  *    * 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 the scripts/styles // to be imported or exec to run // Can be something that evaluates to a boolean if required // if it should always load, set to true // try to use a `mw.config` value where possible conditional: true,

// {array|string} Scripts to import // Remove if unused scripts: [],

// {array|string} Styles to import // Remove if unused styles: [],

// {boolean} Whether to expose exec under the rswiki global // Defaults to false expose: true,

// {function} Function to run // Typically used for small scripts that aren't imported // or for minor things that need to run before importing another script // Will execute before any scripts are imported exec: function { console.log( 'loaded' ); }       }        */

/**        * Inserts  on new talk pages *        * @todo Get this approved as a bot task instead */       addTalkheader: { conditional: true, exec: function { var params = '&preload=Template:Talkheader/preload';

// for redlinks // make sure this is only selecting anchor tags // otherwise this selects li tags used for monobook discussion tab $('a.new').attr('href', function (_, attr) {                   if (attr.indexOf('Talk:') > -1 || attr.indexOf('_talk') > -1) {                        // User_talk doesn't get the template                        if (attr.indexOf('User_talk:') > -1) {                            return;                        }

return attr + params; }               });

// for talk pages if (                   conf.wgNamespaceNumber % 2 === 1 &&                    conf.wgNamespaceNumber !== 3 &&                    $('#noarticletext').length                ) {

// oasis support if (conf.skin === 'oasis') { $('#ca-addsection').attr('href', function (_, attr) {                           return attr + params;                        });

// monobook support } else { // create page and new section tabs $('#ca-edit a, #ca-addsection a').attr('href', function (_, attr) {                           return attr + params;                        }); }               }            }        },

/**        * Ajax refresh for various pages */       ajaxrc: { conditional: (window.ajaxPages.indexOf(conf.wgPageName) > -1), scripts: 'u:dev:AjaxRC/code.js' },

/**        * Embeds .ogg files */       audioEmbed: { conditional: $('.embedMe').length, scripts: 'MediaWiki:Common.js/embedding.js' },

/**        * For autosorting sortable tables * @example  */       autosort: { conditional: $('.sortable').length, expose: true, exec: function { mw.loader.using('jquery.tablesorter', function {                    $('.sortable[class*="autosort="]').each(function  { var $this = $(this), matched = (' ' + $(this).attr( 'class') + ' ') .match(/autosort=(\d+)[,-]{1}(a|d)/), $sortCol = $this .find('> thead th:nth-child(' + matched[1] + ')');

if (matched[2] === 'd') { // descending $sortCol.click.click; } else { // ascending $sortCol.click; }                   });                });            }        },

/**        * Calculators */       calc: { conditional: $('.jcConfig').length, scripts: 'MediaWiki:Common.js/calc.js', styles: 'MediaWiki:Common.css/calc.css' },

/**        * For adding to charm logs */       charmadd: { conditional: $('.charmtable').length, scripts: 'MediaWiki:Common.js/charmadd.js' },

/**        * Countdown timer */       countdown: { conditional: $('.countdown').length, scripts: 'u:dev:Countdown/code.js' },

/**        * Adds custom edit buttons to the editor */       customEditButtons: { conditional: (editActions.indexOf(conf.wgAction) > -1), exec: function { var more;

// redirect mw.toolbar.addButton(                   'https://images.wikia.nocookie.net/central/images/c/c8/Button_redirect.png',                    'Redirect',                    '#REDIRECT ',                    '',                    'Insert text',                    'mw-editbutton-redirect'                );

// wikitable mw.toolbar.addButton(                   'https://images.wikia.nocookie.net/central/images/4/4a/Button_table.png',                    'Insert a table',                    '{| class="wikitable"\n|-\n',                    '\n|}',                    '! header 1\n! header 2\n! header 3\n|-\n| row 1, cell 1\n| row 1, cell 2\n| row 1, cell 3\n|-\n| row 2, cell 1\n| row 2, cell 2\n| row 2, cell 3',                    'mw-editbutton-wikitable'                );

// line break mw.toolbar.addButton(                   'https://images.wikia.nocookie.net/central/images/1/13/Button_enter.png',                    'Line break',                    ' ',                    ,                    ,                    'mw-editbutton-linebreak'                );

// gallery mw.toolbar.addButton(                   'https://images.wikia.nocookie.net/central/images/1/12/Button_gallery.png',                    'Insert a picture gallery',                    '\n  ',                    'File:Example.jpg|Caption1\nFile:Example.jpg|Caption2',                    'mw-editbutton-gallery'                );

// move edittools expand to end of buttons in oasis editor if (conf.skin === 'oasis') { // pass true to .clone to keep event listeners more = $('.cke_toolbar_expand').clone(true); $('.cke_toolbar_expand').remove; $('.cke_toolbar_source').append(more); }           }        },

/**        * Form for reporting vandals in RS:CVU */       cvu: { conditional: (conf.wgPageName === 'RuneScape:Counter-Vandalism_Unit'), scripts: 'MediaWiki:Cvu.js' },

/**        * Database script */       database: { conditional: $('.DBQuery').length, scripts: 'MediaWiki:Database.js' },

/**        * disassembly calculators */       discalc: { conditional: $('#dis-calc-table').length && $('#dis-calc-dropdown').length, scripts: 'MediaWiki:Common.js/discalc.js' },

/**         * Additional tier search functionality for Equipment tables * this hides switchfo entries and the no-JS warning message * actual search utilises stewCalc */       equipmentTables: { conditional: $('.dplequipmenttable').length, exec: function { $('.dplequipmenttable').each(function {                    var $eqt = $(this), tmin = parseInt($eqt.attr('data-tiermin'), 10), tmax = parseInt($eqt.attr('data-tiermax'), 10);                    if (isNaN(tmin)) {                        if (isNaN(tmax)) {                            //missing both tmin and tmax, skip the table entirely                            return;                        }                        tmin = 0;                    }                    if (isNaN(tmax)) {                        tmax = 120;                    }                    $eqt.find('tr[data-tier]').each(function  { var $tr = $(this), t = parseInt($tr.attr('data-tier'), 10); if (isNaN(t)) { return; }                       if (t < tmin || t > tmax) { $tr.hide; }                   });                });                //hide no-JS msg and unhide calc $('.jshide').hide; $('.jsunhide').show; }       },

/**        * Adds a form for updating exchange data */       exchangeCreate: { conditional: (               ( conf.wgUserGroups.indexOf('autoconfirmed') > -1 ) && (                   (conf.wgNamespaceNumber === 828 && conf.wgTitle.indexOf('Exchange/') === 0) || conf.wgNamespaceNumber === 112 )           ),            scripts: 'MediaWiki:Common.js/exchangeCreate.js', styles: 'MediaWiki:Common.css/exchangeCreate.css' },

/**        * Adds an editintro when editing exchange pages */       exchangeIntro: { conditional: (               conf.wgNamespaceNumber === 112 &&                conf.wgPageName.split('/')[1] === 'Data'            ), scripts: 'MediaWiki:Common.js/exchangeintro.js' },

/**        * Hides the "< BASEPAGE" link when the page isn't a true subpage * see also */       falseSubpage: { conditional: $('.false-subpage').length, exec: function { if (conf.skin === 'monobook') { $('#contentSub .subpages').hide; } else { var $el = $('.page-header__page-subtitle'), html = $el.html; if (!$el.length) { return; }

// test for undefined if (html.search(' \\| ') > -1) { $el.html(                           html.substring( html.search(' \\| ') + 3, html.length )                       );                    } else { $el.hide; }               }            }        },

/**        * Exchange data charts */       geCharts: { conditional: $('.GEdatachart').length, scripts: 'MediaWiki:Common.js/GECharts.js' },

/**        * Highlight tables */       highlightTable: { conditional: $('.lighttable').length, scripts: 'MediaWiki:Common.js/highlightTable.js' },

/**        * Remove the fade animation from mw-collapsible */       instantCollapsible: { conditional: $('.mw-collapsible').length, scripts: 'MediaWiki:Common.js/instantCollapsible.js' },

/**        * Script for */       insertUsername: { conditional: !!conf.wgUserName, exec: function { $('.insertusername').text(conf.wgUserName); }       },        /*         * Issue form - RuneScape:Issues */       /*        issues: { // main, project, file, template, category, update, exchange, charm, calculator, map, transcript conditional: [0, 4, 6, 10, 14, 100, 112, 114, 116, 118, 120].includes(conf.wgNamespaceNumber), scripts: 'MediaWiki:Common.js/issues.js', //styles: 'MediaWiki:Common.css/issues.css' },       */

/**        * Compares equipment stats */       itemCompare: { conditional: $('.cioCompareLink, .infobox-bonuses').length, scripts: 'MediaWiki:Common.js/compare.js', styles: 'MediaWiki:Common.css/compare.css' },

/**        * Monster Kill XP for Infobox Monster new */       killCalc: { conditional: $('.infobox-monster').length, scripts: 'MediaWiki:Common.js/killCalc.js' },

/**        * Fight Kiln map interactivity */       kiln: { conditional: $('#kilnmap').length, scripts: 'MediaWiki:Common.js/kiln.js' },       /**         * Konami code easter egg */       konami: { conditional: true, scripts: 'MediaWiki:Common.js/Konami.js' },       /**         * Lazy load base64 images via js         * * Give the key a generic name in case we decide to do this more often */       lazyImages: { conditional: true, scripts: 'MediaWiki:Common.js/thgems.js' },

/**        * Adds calcs to infoboxes */       monsterCalc: { conditional: $('#XPEach, #GEPrice, #killXP').length, scripts: 'MediaWiki:Monstercalc.js' },

/**        * Adds calcs to new infoboxes */       infoboxQtyCalc: { conditional: $('span.infobox-quantity').length, scripts: 'MediaWiki:Common.js/infoboxQty.js' },

/**        * Hides the mainpage poll results until after a user's vote has been cast */       mainpagePoll: { conditional: conf.wgIsMainPage, exec: function { $('#mp-poll .pollAnswerVotes').hide;

var cookieName = 'rs-mp-poll', pollId = $('#mp-poll .ajax-poll').attr('id').split('-')[2];

function showPoll { if ($.cookie(cookieName) === pollId) { $('#mp-poll .pollAnswerVotes').show; }               }

$('#mp-poll input[type="submit"]').click(function {                    $.cookie('rs-mp-poll', pollId, {expires: 365});                    showPoll;                });

showPoll; }       },

/**        * Collapses navboxes under certain conditions *         * @todo Rework this to implement autocollapse better *      assuming we still use it, check Module:Navbox */       navbox: { conditional: ($('.navbox').length), exec: function { // should be defined by MediaWiki:Collapsible-expand // currently hardcoded into template due to wikia bug var expand = 'show', $navbox = $('.navbox'), // maximum number of navboxes before they all get collapsed maxShow = 2, // maximum allowable height of navbox before it gets collapsed maxHeight = 300;

function collapseNavbox(navbox) { var $navbox = $(navbox), $rows, $toggle;

if ($navbox.hasClass('mw-collapsed') || $navbox.hasClass('navbox-uncollapsed')) { return; }

// 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(expand);

}

// collapse if more than `maxShow` if ($navbox.length > (maxShow - 1)) { $navbox.each(function {                        collapseNavbox(this);                    }); }

// collapse if taller than `maxHeight` $navbox.each(function {                    if ($(this).height > maxHeight) {                        collapseNavbox(this);                    }                }); }       },

/**        * Lists namespace numbers at MediaWiki:Namespace numbers */       nsNumbers: { conditional: (conf.wgPageName === 'MediaWiki:Namespace_numbers'), scripts: 'MediaWiki:Common.js/namespaceNumbersList.js' },

/**        * Peng hunting highlight table */       pengLocations: { conditional: (               [                    'Distractions_and_Diversions/Locations',                    'Distractions_and_Diversions/Locations/Penguin_Hide_and_Seek'                ].indexOf(conf.wgPageName) > -1            ), scripts: 'MediaWiki:Common.js/pengLocations.js' },

/**        * Ratings sidebar module (oasis) */       ratings: { conditional: (               conf.skin === 'oasis' &&                conf.wgAction === 'view' &&                conf.wgNamespaceNumber === 0            ), scripts: 'MediaWiki:Wikia.js/ratings.js' },

/**        * Script for RuneScape:RC patrol */       rcPatrol: { conditional: (conf.wgPageName === 'RuneScape:RC_Patrol'), scripts: 'MediaWiki:Rcpatrol.js', styles: 'MediaWiki:Rcp.css' },

/**        * Replaces URLs to the target page name when accessed through a redirect *        * @todo Retain hashes and params (except title params) */       replaceRedirect: { conditional: !!conf.wgRedirectedFrom, exec: function { var newUrl = conf.wgServer + conf.wgArticlePath.replace('$1', conf.wgPageName) + location.search + location.hash;

history.replaceState(                   history.state,                    document.title,                    newUrl                ); }       },

/**        * Revolution calculator */       revolution: { conditional: (conf.wgPageName === 'Calculator:Revolution'), scripts: 'MediaWiki:Common.js/revocalc.js', styles: 'MediaWiki:Common.css/revocalc.css' },

/**        * Scale up images for users with higher resolutions */       scaleImgs: { conditional: matchMedia('(-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx)').matches, exec: function { $('img').each(function {                    var $this = $(this);

function repl(_, p1) { var num = parseInt(p1, 10); return '/scale-to-width-down/' + (num * 2); }

if ($this.hasClass('lzy') && !$this.hasClass('lzyLoaded')) { $this.attr(                           'data-src',                            $this.attr('data-src').replace(/\/scale-to-width-down\/(\d+)/, repl)                        ); } else { $this.attr(                           'src',                            $this.attr('src').replace(/\/scale-to-width-down\/(\d+)/, repl)                        ); }               });            }        },

/**        * Custom oasis sidebar module(s) */       sidebar: { conditional: (conf.skin === 'oasis' && conf.wgAction === 'view'), scripts: 'MediaWiki:Wikia.js/sidebar.js' },

/**        * Signature reminder on forum and talk pages */       sigReminder: { conditional: (               editActions.indexOf(conf.wgAction) > -1 &&                (conf.wgNamespaceNumber % 2 === 1 || conf.wgNamespaceNumber === 110)            ), exec: function { $('#wpSave').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() ||

// check for &undo= or ?undo= in URL as summary can be altered mw.util.getParamValue('undo') ||

// check for user welcome notice in edit summary // since those often don't need signatures $('#wpSummary').val.match(/welcome/i) ) {                       return;                    }

mw.log('sigreminder activated');

if (!confirm(reminder)) { mw.log('prevent no sig'); e.preventDefault; }               });            }        },

/**        * Makes interactive skill training guides interactive */       skillGuide: { conditional: $('.skillguide').length, scripts: 'MediaWiki:Calc.js' },

/**        * Redirects skin.js/css to correct skin name */       skinRedirect: { conditional: (conf.wgUserName && conf.wgNamespaceNumber === 2), exec: function { var skinpage = '/' + conf.skin.replace('oasis', 'wikia') + '.', pagename = 'User:' + conf.wgUserName.replace(/ /g, '_') + '/skin.';

switch (conf.wgPageName) { case pagename + 'js': location.replace(                           location.href.replace(/\/skin\.js/i, skinpage + 'js')                        ); break;

case pagename + 'css': location.replace(                           location.href.replace(/\/skin\.css/i, skinpage + 'css')                        ); break; }           }        },

/**        * Adds a report of special maintenance pages to         * Special:SpecialPages and RS:MAINTENANCE */       spReport: { conditional: (               conf.wgCanonicalSpecialPageName === 'Specialpages' ||                $('.specialMaintenance').length            ), scripts: 'MediaWiki:Common.js/spreport.js' },

/**        * Script for  and Module:Infobox */       switchInfobox: { conditional: $('.switch-infobox').length || $('.infobox-buttons').length, scripts: 'MediaWiki:Common.js/switchInfobox.js' },

/**        * More calculators */       stewCalc: { conditional: $('.jcInput, [class*="jcPane"], .skiplinkcontainer').length, scripts: 'MediaWiki:Common.js/calc2.js' },

/**        * expanded tablesorter */       tablesorter2: { conditional: $('.sortable2').length, scripts: 'MediaWiki:Common.js/tablesorter2.js' },       /**         * Prevents uploading videos through youtube tags * by switching them to         */ tagSwitch: { conditional: (editActions.indexOf(conf.wgAction) > -1), exec: function { $('#wpSave').click(function {                    // Stop it changing the docs on here (monobook issue)                    if (conf.skin === 'monobook' && /\.js$/.test(conf.wgTitle)) {                        return;                    }

var wikitext = $('#wpTextbox1') .val // @todo can these be condensed down into a callback for .replace ? .replace(/ /g, '{{youtube|') .replace(                           //g,                            '{{youtube|height=$1|'                        ) .replace(                           //g,                            '{{youtube|width=$1|'                        ) .replace(                           //g,                            '{{youtube|height=$1|width=$2|'                        ) .replace(                           //g,                            '{{youtube|height=$2|width=$1|'                        ) .replace(/<\/youtube>/g, '}}');

$('#wpTextbox1').val(wikitext); });           }        },

/**        * Makes the disambiguation parenthesis grey * See also Template:Parentitle override */       titleParenthesis: { conditional: (               conf.wgNamespaceNumber === 0 &&                conf.wgTitle.lastIndexOf('(') > -1 && !$('.no-parenthesis-style').length ),           exec: function  {                    // use the title in the DOM so this respects DISPLAYTITLE                var title = $('.page-header__title, h1#firstHeading').text,                    start = title.lastIndexOf('('), end = title.substring(start, title.length).lastIndexOf(')');               // add offset here                end += start + 1;

// oasis & monobook $('.page-header__title, h1#firstHeading') .empty .append(                       title.substring(0, start),                        $(' ')                            .addClass('title-parenthesis')                            .text(title.substring(start, end)),                        title.substring(end, title.length)                    ); }       },        // javascript tooltips tooltips: { conditional: $('.js-tooltip-wrapper').length && $('.js-tooltip-click').length, scripts: 'MediaWiki:Common.js/tooltips.js' },       /**         * Moves icons from Template:External outside #mw-content-text to get around alignment issues * Modification to header allowed per http://archive.is/TW9fq and zd#334064. *        * @notes Requires additional CSS in MediaWiki:Custom-Common.less/external.less *        * @author The 888th Avatar (Avatar Wiki) * @author Cqm */       topIcon: { conditional: $('.topright-icon').length, exec: function { var $icons = $(' ').attr('id', 'rs-header-icons');

// there's no class on this div, but it should end up after the language // dropdown if it's present or where the dropdown would be if not $('.page-header__contribution > div').first.append($icons);

$('.topright-icon').each(function {                    $icons.append($(this).html);                }); }       },

/**        * Adds an editintro when editing update pages */       updateIntro: { conditional: (conf.wgNamespaceNumber === 100), scripts: 'MediaWiki:Common.js/updateintro.js' },

/**        * Adds a timer to Warbands */       warbandsTimer: { conditional: $('#wb-timer').length, scripts: 'MediaWiki:Common.js/warbandstimer.js' },

/**        * Adds a timer to Supply run */       supplyrunTimer: { conditional: $('#sr-timer').length, scripts: 'MediaWiki:Common.js/supplyruntimer.js' },

/**        * Add edit links to Special:WhatLinksHere *        * @todo Move to gadget */       wlhEdit: { conditional: (conf.wgCanonicalSpecialPageName === 'Whatlinkshere'), scripts: 'MediaWiki:Common.js/WLH_edit.js' },

/**        * Youtube embedding */       youtube: { conditional: $('.youtube').length, scripts: 'MediaWiki:Common.js/youtube.js' },

/**        * Fix group pages * Only need JS because CSS still works *        * Because Wikia broke it on purpose in August '16 and it's still broken 10 months later */       groupAutoconfirmed: { conditional: conf.wgUserGroups.indexOf('autoconfirmed') > -1, scripts: 'MediaWiki:Group-autoconfirmed.js' },

groupCustodian: { conditional: conf.wgUserGroups.indexOf('custodian') > -1, scripts: 'MediaWiki:Group-custodian.js' },

groupSysop: { conditional: conf.wgUserGroups.indexOf('sysop') > -1, scripts: 'MediaWiki:Group-sysop.js' }   };

// variables used by `init` // @todo move to there? var scripts = [], styles = [], loaded = [], expose = {};

/**    * Helper function to normalise arguments passed to `util.loadAssets` *    * @param js {object|array|string} * @param css {undefined|array|string} * @return {object} */   function normaliseArgs(js, css) { // set defaults so it silently fails later on       var ret = { js: [], css: [] };

// stop here and silently fail if neither are defined if (js === undefined && css === undefined) { return ret; }

// normalise args to an object if ($.isPlainObject(js)) { ret.js = js.js; ret.css = js.css; } else { ret.js = js; ret.css = css; }

// map strings to one element arrays // map undefined and all other unrecognised type keys to empty arrays if (typeof ret.js === 'string') { ret.js = [ret.js]; } else if (!Array.isArray(ret.js)) { ret.js = []; }

if (typeof ret.css === 'string') { ret.css = [ret.css]; } else if (!Array.isArray(ret.css)) { ret.css = []; }

return ret; }

/**    * Used to detect incorrectly spelt keys for each include *    * @param obj {object} * @param key {string} */   function checkKeys(obj, key) { var inclKeys = Object.keys(obj), allowedKeys = ['conditional', 'scripts', 'styles', 'expose', 'exec'];

allowedKeys.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 imported * And then imports each approved script in one HTTP request *    * This also maintains backwards compatibility with older versions of this page * @todo remove the backwards compatibility when each dependent script has been updated */   function init { $.each(includes, function (k, v) {

var check = $.isFunction(v.conditional) ? v.conditional : v.conditional;

if (check) {

// used for tracking which includes are loading loaded.push('common.' + k);

if (v.scripts) { scripts = scripts.concat(v.scripts); }

if (v.styles) { styles = styles.concat(v.styles); }

if (v.exec) { v.exec;

if (v.expose) { expose[k] = v.exec; }               }

}

checkKeys(v, k); });

$.extend(rs, util, expose); rs.loaded = (rs.loaded || []).concat(loaded); if (conf.debug) { // keep this hidden most of the time so it doesn't cause confusion with rs.loaded rs.loadedAssets = loadedAssets; }

// add rs as a global alias window.rs = rs;

// load our script and styles util.loadAssets({           js: scripts,            css: styles        });

}

$(init);

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