revert r110340 after talking with krinle
[lhc/web/wiklou.git] / resources / mediawiki / mediawiki.js
index c9bcdec..585483b 100644 (file)
@@ -8,7 +8,6 @@ var mw = ( function ( $, undefined ) {
        /* Private Members */
 
        var hasOwn = Object.prototype.hasOwnProperty;
-
        /* Object constructors */
 
        /**
@@ -130,6 +129,20 @@ var mw = ( function ( $, undefined ) {
        }
 
        Message.prototype = {
+               /**
+                * Simple message parser, does $N replacement and nothing else.
+                * This may be overridden to provide a more complex message parser.
+                * 
+                * 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.
                 *
@@ -150,7 +163,9 @@ var mw = ( function ( $, undefined ) {
                 * @return string Message as a string in the current form or <key> if key does not exist.
                 */
                toString: function() {
-                       if ( !this.map.exists( this.key ) ) {
+                       var text;
+
+                       if ( !this.exists() ) {
                                // Use <key> as text if key does not exist
                                if ( this.format !== 'plain' ) {
                                        // format 'escape' and 'parse' need to have the brackets and key html escaped
@@ -158,28 +173,24 @@ var mw = ( function ( $, undefined ) {
                                }
                                return '<' + this.key + '>';
                        }
-                       var     text = this.map.get( this.key ),
-                               parameters = this.parameters;
-
-                       text = text.replace( /\$(\d+)/g, function ( str, match ) {
-                               var index = parseInt( match, 10 ) - 1;
-                               return parameters[index] !== undefined ? parameters[index] : '$' + match;
-                       } );
 
                        if ( this.format === 'plain' ) {
-                               return text;
+                               // @todo FIXME: Although not applicable to core Message,
+                               // Plugins like jQueryMsg should be able to distinguish
+                               // between 'plain' (only variable replacement and plural/gender)
+                               // and actually parsing wikitext to HTML.
+                               text = this.parser();
                        }
+
                        if ( this.format === 'escaped' ) {
-                               // According to Message.php this needs {{-transformation, which is
-                               // still todo
-                               return mw.html.escape( text );
+                               text = this.parser();
+                               text = mw.html.escape( text );
                        }
-
-                       /* This should be fixed up when we have a parser
-                       if ( this.format === 'parse' && 'language' in mw ) {
-                               text = mw.language.parse( text );
+                       
+                       if ( this.format === 'parse' ) {
+                               text = this.parser();
                        }
-                       */
+
                        return text;
                },
 
@@ -236,6 +247,11 @@ var mw = ( function ( $, undefined ) {
                 * @var constructor Make the Map constructor publicly available.
                 */
                Map: Map,
+
+               /**
+                * @var constructor Make the Message constructor publicly available.
+                */
+               Message: Message,
        
                /**
                 * List of configuration values
@@ -322,7 +338,7 @@ var mw = ( function ( $, undefined ) {
                         *                      'dependencies': ['required.foo', 'bar.also', ...], (or) function() {}
                         *                      'group': 'somegroup', (or) null,
                         *                      'source': 'local', 'someforeignwiki', (or) null
-                        *                      'state': 'registered', 'loading', 'loaded', 'ready', or 'error'
+                        *                      'state': 'registered', 'loading', 'loaded', 'ready', 'error' or 'missing'
                         *                      'script': ...,
                         *                      'style': ...,
                         *                      'messages': { 'key': 'value' },
@@ -346,8 +362,11 @@ var mw = ( function ( $, undefined ) {
                                queue = [],
                                // List of callback functions waiting for modules to be ready to be called
                                jobs = [],
-                               // Flag inidicating that document ready has occured
+                               // Flag indicating that document ready has occured
                                ready = false,
+                               // Whether we should try to load scripts in a blocking way 
+                               // Set with setBlocking()
+                               blocking = false,
                                // Selector cache for the marker element. Use getMarker() to get/use the marker!
                                $marker = null;
        
@@ -435,18 +454,22 @@ var mw = ( function ( $, undefined ) {
                                                                ' -> ' + deps[n]
                                                        );
                                                }
+
+                                               // Add to unresolved
+                                               unresolved[unresolved.length] = module;
                                                recurse( deps[n], resolved, unresolved );
+                                               // module is at the end of unresolved
+                                               unresolved.pop();
                                        }
                                }
                                resolved[resolved.length] = module;
-                               unresolved.splice( $.inArray( module, unresolved ), 1 );
                        }
        
                        /**
                         * Gets a list of module names that a module depends on in their proper dependency order
                         *
                         * @param module string module name or array of string module names
-                        * @return list of dependencies
+                        * @return list of dependencies, including 'module'.
                         * @throws Error if circular reference is detected
                         */
                        function resolve( module ) {
@@ -463,10 +486,6 @@ var mw = ( function ( $, undefined ) {
                                        }
                                        return modules;
                                } else if ( typeof module === 'string' ) {
-                                       // Undefined modules have no dependencies
-                                       if ( registry[module] === undefined ) {
-                                               return [];
-                                       }
                                        resolved = [];
                                        recurse( module, resolved, [] );
                                        return resolved;
@@ -476,12 +495,13 @@ var mw = ( function ( $, undefined ) {
        
                        /**
                         * Narrows a list of module names down to those matching a specific
-                        * state. Possible states are 'undefined', 'registered', 'loading',
-                        * 'loaded', or 'ready'
+                        * state (see comment on top of this scope for a list of valid states).
+                        * One can also filter for 'unregistered', which will return the
+                        * modules names that don't have a registry entry.
                         *
                         * @param states string or array of strings of module states to filter by
-                        * @param modules array list of module names to filter (optional, all modules
-                        *  will be used by default)
+                        * @param modules array list of module names to filter (optional, by default the entire
+                        * registry is used)
                         * @return array list of filtered module names
                         */
                        function filter( states, modules ) {
@@ -504,7 +524,7 @@ var mw = ( function ( $, undefined ) {
                                        for ( m = 0; m < modules.length; m += 1 ) {
                                                if ( registry[modules[m]] === undefined ) {
                                                        // Module does not exist
-                                                       if ( states[s] === 'undefined' ) {
+                                                       if ( states[s] === 'unregistered' ) {
                                                                // OK, undefined
                                                                list[list.length] = modules[m];
                                                        }
@@ -568,15 +588,15 @@ var mw = ( function ( $, undefined ) {
                        }
        
                        /**
-                        * Adds a script tag to the body, either using document.write or low-level DOM manipulation,
-                        * depending on whether document-ready has occured yet.
+                        * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
+                        * depending on whether document-ready has occured yet and whether we are in blocking mode.
                         *
                         * @param src String: URL to script, will be used as the src attribute in the script tag
                         * @param callback Function: Optional callback which will be run when the script is done
                         */
                        function addScript( src, callback ) {
-                               var done = false, script;
-                               if ( ready ) {
+                               var done = false, script, head;
+                               if ( ready || !blocking ) {
                                        // jQuery's getScript method is NOT better than doing this the old-fashioned way
                                        // because jQuery will eval the script's code, and errors will not have sane
                                        // line numbers.
@@ -611,13 +631,27 @@ var mw = ( function ( $, undefined ) {
                                                        }
                                                };
                                        }
-                                       document.body.appendChild( script );
+                                       
+                                       if ( window.opera ) {
+                                               // Appending to the <head> blocks rendering completely in Opera,
+                                               // so append to the <body> after document ready. This means the
+                                               // scripts only start loading after  the document has been rendered,
+                                               // but so be it. Opera users don't deserve faster web pages if their
+                                               // browser makes it impossible
+                                               $( function() { document.body.appendChild( script ); } );
+                                       } else {
+                                               // IE-safe way of getting the <head> . document.documentElement.head doesn't
+                                               // work in scripts that run in the <head>
+                                               head = document.getElementsByTagName( 'head' )[0];
+                                               ( document.body || head ).appendChild( script );
+                                       }
                                } else {
                                        document.write( mw.html.element(
                                                'script', { 'type': 'text/javascript', 'src': src }, ''
                                        ) );
                                        if ( $.isFunction( callback ) ) {
                                                // Document.write is synchronous, so this is called when it's done
+                                               // FIXME: that's a lie. doc.write isn't actually synchronous
                                                callback();
                                        }
                                }
@@ -736,15 +770,15 @@ var mw = ( function ( $, undefined ) {
                                if ( arguments.length > 1 ) {
                                        jobs[jobs.length] = {
                                                'dependencies': filter(
-                                                       ['undefined', 'registered', 'loading', 'loaded'],
+                                                       ['registered', 'loading', 'loaded'],
                                                        dependencies
                                                ),
                                                'ready': ready,
                                                'error': error
                                        };
                                }
-                               // Queue up any dependencies that are undefined or registered
-                               dependencies = filter( ['undefined', 'registered'], dependencies );
+                               // Queue up any dependencies that are registered
+                               dependencies = filter( ['registered'], dependencies );
                                for ( n = 0; n < dependencies.length; n += 1 ) {
                                        if ( $.inArray( dependencies[n], queue ) === -1 ) {
                                                queue[queue.length] = dependencies[n];
@@ -822,15 +856,13 @@ var mw = ( function ( $, undefined ) {
                
                                        // Appends a list of modules from the queue to the batch
                                        for ( q = 0; q < queue.length; q += 1 ) {
-                                               // Only request modules which are undefined or registered
-                                               if ( registry[queue[q]] === undefined || registry[queue[q]].state === 'registered' ) {
+                                               // Only request modules which are registered
+                                               if ( registry[queue[q]] !== undefined && registry[queue[q]].state === 'registered' ) {
                                                        // Prevent duplicate entries
                                                        if ( $.inArray( queue[q], batch ) === -1 ) {
                                                                batch[batch.length] = queue[q];
                                                                // Mark registered modules as loading
-                                                               if ( registry[queue[q]] !== undefined ) {
-                                                                       registry[queue[q]].state = 'loading';
-                                                               }
+                                                               registry[queue[q]].state = 'loading';
                                                        }
                                                }
                                        }
@@ -985,7 +1017,7 @@ var mw = ( function ( $, undefined ) {
                                                throw new Error( 'module must be a string, not a ' + typeof module );
                                        }
                                        if ( registry[module] !== undefined ) {
-                                               throw new Error( 'module already implemented: ' + module );
+                                               throw new Error( 'module already registered: ' + module );
                                        }
                                        // List the module as registered
                                        registry[module] = {
@@ -1053,8 +1085,6 @@ var mw = ( function ( $, undefined ) {
                                                registry[module].dependencies ) )
                                        {
                                                execute( module );
-                                       } else {
-                                               request( module );
                                        }
                                },
                
@@ -1107,45 +1137,59 @@ var mw = ( function ( $, undefined ) {
                                 *  "text/javascript"; if no type is provided, text/javascript is assumed.
                                 */
                                load: function ( modules, type ) {
+                                       var filtered, m;
+
                                        // Validate input
                                        if ( typeof modules !== 'object' && typeof modules !== 'string' ) {
                                                throw new Error( 'modules must be a string or an array, not a ' + typeof modules );
                                        }
-                                       // Allow calling with an external script or single dependency as a string
+                                       // Allow calling with an external url or single dependency as a string
                                        if ( typeof modules === 'string' ) {
                                                // Support adding arbitrary external scripts
                                                if ( /^(https?:)?\/\//.test( modules ) ) {
                                                        if ( type === 'text/css' ) {
-                                                               $( 'head' ).append( $( '<link/>', {
+                                                               $( 'head' ).append( $( '<link>', {
                                                                        rel: 'stylesheet',
                                                                        type: 'text/css',
                                                                        href: modules
                                                                } ) );
-                                                               return true;
+                                                               return;
                                                        } else if ( type === 'text/javascript' || type === undefined ) {
                                                                addScript( modules );
-                                                               return true;
+                                                               return;
                                                        }
                                                        // Unknown type
-                                                       return false;
+                                                       throw new Error( 'invalid type for external url, must be text/css or text/javascript. not ' + type );
                                                }
                                                // Called with single module
                                                modules = [modules];
                                        }
+
+                                       // Filter out undefined modules, otherwise resolve() will throw
+                                       // an exception for trying to load an undefined module.
+                                       // Undefined modules are acceptable here in load(), because load() takes
+                                       // an array of unrelated modules, whereas the modules passed to
+                                       // using() are related and must all be loaded.
+                                       for ( filtered = [], m = 0; m < modules.length; m += 1 ) {
+                                               if ( registry[modules[m]] !== undefined ) {
+                                                       filtered[filtered.length] = modules[m];
+                                               }
+                                       }
+
                                        // Resolve entire dependency map
-                                       modules = resolve( modules );
+                                       filtered = resolve( filtered );
                                        // If all modules are ready, nothing dependency be done
-                                       if ( compare( filter( ['ready'], modules ), modules ) ) {
-                                               return true;
+                                       if ( compare( filter( ['ready'], filtered ), filtered ) ) {
+                                               return;
                                        }
-                                       // If any modules have errors return false
-                                       else if ( filter( ['error'], modules ).length ) {
-                                               return false;
+                                       // If any modules have errors
+                                       else if ( filter( ['error'], filtered ).length ) {
+                                               return;
                                        }
                                        // Since some modules are not yet ready, queue up a request
                                        else {
-                                               request( modules );
-                                               return true;
+                                               request( filtered );
+                                               return;
                                        }
                                },
                
@@ -1210,6 +1254,18 @@ var mw = ( function ( $, undefined ) {
                                                return key;
                                        } );
                                },
+
+                               /**
+                                * Enable or disable blocking. If blocking is enabled and
+                                * document ready has not yet occurred, scripts will be loaded
+                                * in a blocking way (using document.write) rather than
+                                * asynchronously using DOM manipulation
+                                * 
+                                * @param b {Boolean} True to enable blocking, false to disable it
+                                */
+                               setBlocking: function( b ) {
+                                       blocking = b;
+                               },
                
                                /**
                                 * For backwards-compatibility with Squid-cached pages. Loads mw.user