/* * Core MediaWiki JavaScript Library */ var mw = ( function ( $, undefined ) { 'use strict'; /* Private Members */ var hasOwn = Object.prototype.hasOwnProperty, slice = Array.prototype.slice; /* Object constructors */ /** * Creates an object that can be read from or written to from prototype functions * that allow both single and multiple variables at once. * @class mw.Map * * @constructor * @param {boolean} global Whether to store the values in the global window * object or a exclusively in the object property 'values'. */ function Map( global ) { this.values = global === true ? window : {}; return this; } Map.prototype = { /** * Get the value of one or multiple a keys. * * If called with no arguments, all values will be returned. * * @param selection mixed String key or array of keys to get values for. * @param fallback mixed Value to use in case key(s) do not exist (optional). * @return mixed If selection was a string returns the value or null, * If selection was an array, returns an object of key/values (value is null if not found), * If selection was not passed or invalid, will return the 'values' object member (be careful as * objects are always passed by reference in JavaScript!). * @return {string|Object|null} Values as a string or object, null if invalid/inexistant. */ get: function ( selection, fallback ) { var results, i; // If we only do this in the `return` block, it'll fail for the // call to get() from the mutli-selection block. fallback = arguments.length > 1 ? fallback : null; if ( $.isArray( selection ) ) { selection = slice.call( selection ); results = {}; for ( i = 0; i < selection.length; i++ ) { results[selection[i]] = this.get( selection[i], fallback ); } return results; } if ( typeof selection === 'string' ) { if ( !hasOwn.call( this.values, selection ) ) { return fallback; } return this.values[selection]; } if ( selection === undefined ) { return this.values; } // invalid selection key return null; }, /** * Sets one or multiple key/value pairs. * * @param selection {mixed} String key or array of keys to set values for. * @param value {mixed} Value to set (optional, only in use when key is a string) * @return {Boolean} This returns true on success, false on failure. */ set: function ( selection, value ) { var s; if ( $.isPlainObject( selection ) ) { for ( s in selection ) { this.values[s] = selection[s]; } return true; } if ( typeof selection === 'string' && arguments.length > 1 ) { this.values[selection] = value; return true; } return false; }, /** * Checks if one or multiple keys exist. * * @param selection {mixed} String key or array of keys to check * @return {boolean} Existence of key(s) */ exists: function ( selection ) { var s; if ( $.isArray( selection ) ) { for ( s = 0; s < selection.length; s++ ) { if ( typeof selection[s] !== 'string' || !hasOwn.call( this.values, selection[s] ) ) { return false; } } return true; } return typeof selection === 'string' && hasOwn.call( this.values, selection ); } }; /** * Object constructor for messages, * similar to the Message class in MediaWiki PHP. * @class mw.Message * * @constructor * @param {mw.Map} map Message storage * @param {string} key * @param {Array} [parameters] */ function Message( map, key, parameters ) { this.format = 'text'; this.map = map; this.key = key; this.parameters = parameters === undefined ? [] : slice.call( parameters ); return this; } Message.prototype = { /** * Simple message parser, does $N replacement, HTML-escaping (only for * 'escaped' format), and nothing else. * * This may be overridden to provide a more complex message parser. * * The primary override is in mediawiki.jqueryMsg. * * This function will not be called for nonexistent messages. */ parser: function () { var parameters = this.parameters; return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) { var index = parseInt( match, 10 ) - 1; return parameters[index] !== undefined ? parameters[index] : '$' + match; } ); }, /** * Appends (does not replace) parameters for replacement to the .parameters property. * * @param {Array} parameters * @chainable */ params: function ( parameters ) { var i; for ( i = 0; i < parameters.length; i += 1 ) { this.parameters.push( parameters[i] ); } return this; }, /** * Converts message object to it's string form based on the state of format. * * @return {string} Message as a string in the current form or `` if key does not exist. */ toString: function () { var text; if ( !this.exists() ) { // Use as text if key does not exist if ( this.format === 'escaped' || this.format === 'parse' ) { // format 'escaped' and 'parse' need to have the brackets and key html escaped return mw.html.escape( '<' + this.key + '>' ); } return '<' + this.key + '>'; } if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) { text = this.parser(); } if ( this.format === 'escaped' ) { text = this.parser(); text = mw.html.escape( text ); } return text; }, /** * Changes format to 'parse' and converts message to string * * If jqueryMsg is loaded, this parses the message text from wikitext * (where supported) to HTML * * Otherwise, it is equivalent to plain. * * @return {string} String form of parsed message */ parse: function () { this.format = 'parse'; return this.toString(); }, /** * Changes format to 'plain' and converts message to string * * This substitutes parameters, but otherwise does not change the * message text. * * @return {string} String form of plain message */ plain: function () { this.format = 'plain'; return this.toString(); }, /** * Changes format to 'text' and converts message to string * * If jqueryMsg is loaded, {{-transformation is done where supported * (such as {{plural:}}, {{gender:}}, {{int:}}). * * Otherwise, it is equivalent to plain. */ text: function () { this.format = 'text'; return this.toString(); }, /** * Changes the format to 'escaped' and converts message to string * * This is equivalent to using the 'text' format (see text method), then * HTML-escaping the output. * * @return {string} String form of html escaped message */ escaped: function () { this.format = 'escaped'; return this.toString(); }, /** * Checks if message exists * * @see mw.Map#exists * @return {boolean} */ exists: function () { return this.map.exists( this.key ); } }; /** * @class mw * @alternateClassName mediaWiki * @singleton */ return { /* Public Members */ /** * Dummy function which in debug mode can be replaced with a function that * emulates console.log in console-less environments. */ log: function () { }, // Make the Map constructor publicly available. Map: Map, // Make the Message constructor publicly available. Message: Message, /** * List of configuration values * * Dummy placeholder. Initiated in startUp module as a new instance of mw.Map(). * If `$wgLegacyJavaScriptGlobals` is true, this Map will have its values * in the global window object. * @property */ config: null, /** * Empty object that plugins can be installed in. * @property */ libs: {}, /* Extension points */ /** * @property */ legacy: {}, /** * Localization system * @property {mw.Map} */ messages: new Map(), /* Public Methods */ /** * Gets a message object, similar to wfMessage(). * * @param {string} key Key of message to get * @param {Mixed...} parameters Parameters for the $N replacements in messages. * @return {mw.Message} */ message: function ( key ) { // Variadic arguments var parameters = slice.call( arguments, 1 ); return new Message( mw.messages, key, parameters ); }, /** * Gets a message string, similar to wfMessage() * * @see mw.Message#toString * @param {string} key Key of message to get * @param {Mixed...} parameters Parameters for the $N replacements in messages. * @return {string} */ msg: function ( /* key, parameters... */ ) { return mw.message.apply( mw.message, arguments ).toString(); }, /** * Client-side module loader which integrates with the MediaWiki ResourceLoader * @class mw.loader * @singleton */ loader: ( function () { /* Private Members */ /** * Mapping of registered modules * * The jquery module is pre-registered, because it must have already * been provided for this object to have been built, and in debug mode * jquery would have been provided through a unique loader request, * making it impossible to hold back registration of jquery until after * mediawiki. * * For exact details on support for script, style and messages, look at * mw.loader.implement. * * Format: * { * 'moduleName': { * 'version': ############## (unix timestamp), * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {} * 'group': 'somegroup', (or) null, * 'source': 'local', 'someforeignwiki', (or) null * 'state': 'registered', 'loaded', 'loading', 'ready', 'error' or 'missing' * 'script': ..., * 'style': ..., * 'messages': { 'key': 'value' }, * } * } * * @property * @private */ var registry = {}, // // Mapping of sources, keyed by source-id, values are objects. // Format: // { // 'sourceId': { // 'loadScript': 'http://foo.bar/w/load.php' // } // } // sources = {}, // List of modules which will be loaded as when ready batch = [], // List of modules to be loaded queue = [], // List of callback functions waiting for modules to be ready to be called jobs = [], // Selector cache for the marker element. Use getMarker() to get/use the marker! $marker = null, // Buffer for addEmbeddedCSS. cssBuffer = '', // Callbacks for addEmbeddedCSS. cssCallbacks = $.Callbacks(); /* Private methods */ function getMarker() { // Cached ? if ( $marker ) { return $marker; } $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' ); if ( $marker.length ) { return $marker; } mw.log( 'getMarker> No found, inserting dynamically.' ); $marker = $( '' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' ); return $marker; } /** * Create a new style tag and add it to the DOM. * * @private * @param {string} text CSS text * @param {Mixed} [nextnode] An Element or jQuery object for an element where * the style tag should be inserted before. Otherwise appended to the ``. * @return {HTMLElement} Node reference to the created `