Merge "Add autocomplete for WhatLinksHere subpages"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.js
index 9235d69..1763c8e 100644 (file)
                }
        }
 
+       // String format helper. Replaces $1, $2 .. $N placeholders with positional
+       // args. Used by Message.prototype.parser() and exported as mw.format().
+       function format( formatString ) {
+               var parameters = slice.call( arguments, 1 );
+               return formatString.replace( /\$(\d+)/g, function ( str, match ) {
+                       var index = parseInt( match, 10 ) - 1;
+                       return parameters[index] !== undefined ? parameters[index] : '$' + match;
+               } );
+       }
+
        /* Object constructors */
 
        /**
         * @class mw.Map
         *
         * @constructor
-        * @param {Object|boolean} [values] Value-bearing object to map, or boolean
-        *  true to map over the global object. Defaults to an empty object.
+        * @param {Object|boolean} [values] Value-bearing object to map, defaults to an empty object.
+        *  For backwards-compatibility with mw.config, this can also be `true` in which case values
+        *  will be copied to the Window object as global variables (T72470). Values are copied in one
+        *  direction only. Changes to globals are not reflected in the map.
         */
        function Map( values ) {
-               this.values = values === true ? window : ( values || {} );
-               return this;
+               if ( values === true ) {
+                       this.values = {};
+
+                       // Override #set to also set the global variable
+                       this.set = function ( selection, value ) {
+                               var s;
+
+                               if ( $.isPlainObject( selection ) ) {
+                                       for ( s in selection ) {
+                                               setGlobalMapValue( this, s, selection[s] );
+                                       }
+                                       return true;
+                               }
+                               if ( typeof selection === 'string' && arguments.length ) {
+                                       setGlobalMapValue( this, selection, value );
+                                       return true;
+                               }
+                               return false;
+                       };
+
+                       return;
+               }
+
+               this.values = values || {};
+       }
+
+       /**
+        * Alias property to the global object.
+        *
+        * @private
+        * @static
+        * @param {mw.Map} map
+        * @param {string} key
+        * @param {Mixed} value
+        */
+       function setGlobalMapValue( map, key, value ) {
+               map.values[key] = value;
+               mw.log.deprecate(
+                       window,
+                       key,
+                       value,
+                       // Deprecation notice for mw.config globals (T58550, T72470)
+                       map === mw.config && 'Use mw.config instead.'
+               );
        }
 
        Map.prototype = {
                 *
                 * @param {string|Object} selection String key to set value for, or object mapping keys to values.
                 * @param {Mixed} [value] Value to set (optional, only in use when key is a string)
-                * @return {Boolean} This returns true on success, false on failure.
+                * @return {boolean} This returns true on success, false on failure.
                 */
                set: function ( selection, value ) {
                        var s;
                                }
                                return true;
                        }
-                       if ( typeof selection === 'string' && arguments.length > 1 ) {
+                       if ( typeof selection === 'string' && arguments.length ) {
                                this.values[selection] = value;
                                return true;
                        }
                 * 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;
-                       } );
+                       return format.apply( null, [ this.map.get( this.key ) ].concat( this.parameters ) );
                },
 
                /**
                                function () { return +new Date(); };
                }() ),
 
+               /**
+                * Format a string. Replace $1, $2 ... $N with positional arguments.
+                *
+                * @method
+                * @since 1.25
+                * @param {string} fmt Format string
+                * @param {Mixed...} parameters Substitutions for $N placeholders.
+                * @return {string} Formatted string
+                */
+               format: format,
+
                /**
                 * Track an analytic event.
                 *
                 *
                 * @property {mw.Map} config
                 */
-               // Dummy placeholder. Re-assigned in ResourceLoaderStartupModule to an instance of `mw.Map`.
+               // Dummy placeholder. Re-assigned in ResourceLoaderStartUpModule to an instance of `mw.Map`.
                config: null,
 
                /**
                                        } );
                                } catch ( err ) {
                                        // IE8 can throw on Object.defineProperty
+                                       // Create a copy of the value to the object.
                                        obj[key] = val;
                                }
                        };
                                if ( 'documentMode' in document && document.documentMode <= 9 ) {
 
                                        $style = getMarker().prev();
-                                       // Verify that the the element before Marker actually is a
+                                       // Verify that the element before Marker actually is a
                                        // <style> tag and one that came from ResourceLoader
                                        // (not some other style tag or even a `<meta>` or `<script>`).
                                        if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
                                 * @param {string} module Name of module
                                 * @param {Function|Array} script Function with module code or Array of URLs to
                                 *  be used as the src attribute of a new `<script>` tag.
-                                * @param {Object} style Should follow one of the following patterns:
+                                * @param {Object} [style] Should follow one of the following patterns:
                                 *
                                 *     { "css": [css, ..] }
                                 *     { "url": { <media>: [url, ..] } }
                                 * The reason css strings are not concatenated anymore is bug 31676. We now check
                                 * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
                                 *
-                                * @param {Object} msgs List of key/value pairs to be added to mw#messages.
+                                * @param {Object} [msgs] List of key/value pairs to be added to mw#messages.
                                 * @param {Object} [templates] List of key/value pairs to be added to mw#templates.
                                 */
                                implement: function ( module, script, style, msgs, templates ) {
                                        // Validate input
                                        if ( typeof module !== 'string' ) {
-                                               throw new Error( 'module must be a string, not a ' + typeof module );
+                                               throw new Error( 'module must be of type string, not ' + typeof module );
                                        }
                                        if ( !$.isFunction( script ) && !$.isArray( script ) ) {
-                                               throw new Error( 'script must be a function or an array, not a ' + typeof script );
+                                               throw new Error( 'script must be of type function or array, not ' + typeof script );
                                        }
-                                       if ( !$.isPlainObject( style ) ) {
-                                               throw new Error( 'style must be an object, not a ' + typeof style );
+                                       if ( style && !$.isPlainObject( style ) ) {
+                                               throw new Error( 'style must be of type object, not ' + typeof style );
                                        }
-                                       if ( !$.isPlainObject( msgs ) ) {
-                                               throw new Error( 'msgs must be an object, not a ' + typeof msgs );
+                                       if ( msgs && !$.isPlainObject( msgs ) ) {
+                                               throw new Error( 'msgs must be of type object, not a ' + typeof msgs );
                                        }
-                                       if ( templates !== undefined && !$.isPlainObject( templates ) ) {
-                                               throw new Error( 'templates must be an object, not a ' + typeof templates );
+                                       if ( templates && !$.isPlainObject( templates ) ) {
+                                               throw new Error( 'templates must be of type object, not a ' + typeof templates );
                                        }
                                        // Automatically register module
                                        if ( !hasOwn.call( registry, module ) ) {
                                        }
                                        // Attach components
                                        registry[module].script = script;
-                                       registry[module].style = style;
-                                       registry[module].messages = msgs;
+                                       registry[module].style = style || {};
+                                       registry[module].messages = msgs || {};
                                        // Templates are optional (for back-compat)
                                        registry[module].templates = templates || {};
                                        // The module may already have been marked as erroneous
                                 * Get the state of a module.
                                 *
                                 * @param {string} module Name of module
-                                * @return {string|null} The state, or null if the module (or its version) is not
+                                * @return {string|null} The state, or null if the module (or its state) is not
                                 *  in the registry.
                                 */
                                getState: function ( module ) {