%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/resources/src/mediawiki.base/ |
Current File : /www/varak.net/wiki.varak.net/resources/src/mediawiki.base/mediawiki.base.js |
/*! * This file is currently loaded as part of the 'mediawiki' module and therefore * concatenated to mediawiki.js and executed at the same time. This file exists * to help prepare for splitting up the 'mediawiki' module. * This effort is tracked at https://phabricator.wikimedia.org/T192623 * * In short: * * - mediawiki.js will be reduced to the minimum needed to define mw.loader and * mw.config, and then moved to its own private "mediawiki.loader" module that * can be embedded within the StartupModule response. * * - mediawiki.base.js and other files in this directory will remain part of the * "mediawiki" module, and will remain a default/implicit dependency for all * regular modules, just like jquery and wikibits already are. */ ( function () { 'use strict'; var slice = Array.prototype.slice, mwLoaderTrack = mw.track, trackCallbacks = $.Callbacks( 'memory' ), trackHandlers = [], queue; /** * Object constructor for messages. * * Similar to the Message class in MediaWiki PHP. * * Format defaults to 'text'. * * @example * * var obj, str; * mw.messages.set( { * 'hello': 'Hello world', * 'hello-user': 'Hello, $1!', * 'welcome-user': 'Welcome back to $2, $1! Last visit by $1: $3' * } ); * * obj = new mw.Message( mw.messages, 'hello' ); * mw.log( obj.text() ); * // Hello world * * obj = new mw.Message( mw.messages, 'hello-user', [ 'John Doe' ] ); * mw.log( obj.text() ); * // Hello, John Doe! * * obj = new mw.Message( mw.messages, 'welcome-user', [ 'John Doe', 'Wikipedia', '2 hours ago' ] ); * mw.log( obj.text() ); * // Welcome back to Wikipedia, John Doe! Last visit by John Doe: 2 hours ago * * // Using mw.message shortcut * obj = mw.message( 'hello-user', 'John Doe' ); * mw.log( obj.text() ); * // Hello, John Doe! * * // Using mw.msg shortcut * str = mw.msg( 'hello-user', 'John Doe' ); * mw.log( str ); * // Hello, John Doe! * * // Different formats * obj = new mw.Message( mw.messages, 'hello-user', [ 'John "Wiki" <3 Doe' ] ); * * obj.format = 'text'; * str = obj.toString(); * // Same as: * str = obj.text(); * * mw.log( str ); * // Hello, John "Wiki" <3 Doe! * * mw.log( obj.escaped() ); * // Hello, John "Wiki" <3 Doe! * * @class mw.Message * * @constructor * @param {mw.Map} map Message store * @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 = { /** * Get parsed contents of the message. * * The default parser does simple $N replacements and nothing else. * This may be overridden to provide a more complex message parser. * The primary override is in the mediawiki.jqueryMsg module. * * This function will not be called for nonexistent messages. * * @return {string} Parsed message */ parser: function () { return mw.format.apply( null, [ this.map.get( this.key ) ].concat( this.parameters ) ); }, /** * Add (does not replace) parameters for `$N` placeholder values. * * @param {Array} parameters * @return {mw.Message} * @chainable */ params: function ( parameters ) { var i; for ( i = 0; i < parameters.length; i++ ) { this.parameters.push( parameters[ i ] ); } return this; }, /** * Convert message object to its string form based on current format. * * @return {string} Message as a string in the current form, or `<key>` if key * does not exist. */ toString: function () { var text; if ( !this.exists() ) { // Use ⧼key⧽ as text if key does not exist // Err on the side of safety, ensure that the output // is always html safe in the event the message key is // missing, since in that case its highly likely the // message key is user-controlled. // '⧼' is used instead of '<' to side-step any // double-escaping issues. // (Keep synchronised with Message::toString() in PHP.) return '⧼' + mw.html.escape( 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; }, /** * Change format to 'parse' and convert 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(); }, /** * Change format to 'plain' and convert 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(); }, /** * Change format to 'text' and convert message to string * * If jqueryMsg is loaded, {{-transformation is done where supported * (such as {{plural:}}, {{gender:}}, {{int:}}). * * Otherwise, it is equivalent to plain * * @return {string} String form of text message */ text: function () { this.format = 'text'; return this.toString(); }, /** * Change the format to 'escaped' and convert message to string * * This is equivalent to using the 'text' format (see #text), then * HTML-escaping the output. * * @return {string} String form of html escaped message */ escaped: function () { this.format = 'escaped'; return this.toString(); }, /** * Check if a message exists * * @see mw.Map#exists * @return {boolean} */ exists: function () { return this.map.exists( this.key ); } }; /** * @class mw * @singleton */ /** * @inheritdoc mw.inspect#runReports * @method */ mw.inspect = function () { var args = arguments; // Lazy-load mw.loader.using( 'mediawiki.inspect', function () { mw.inspect.runReports.apply( mw.inspect, args ); } ); }; /** * Format a string. Replace $1, $2 ... $N with positional arguments. * * Used by Message#parser(). * * @since 1.25 * @param {string} formatString Format string * @param {...Mixed} parameters Values for $N replacements * @return {string} Formatted string */ mw.format = function ( 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; } ); }; // Expose Message constructor mw.Message = Message; /** * Get a message object. * * Shortcut for `new mw.Message( mw.messages, key, parameters )`. * * @see mw.Message * @param {string} key Key of message to get * @param {...Mixed} parameters Values for $N replacements * @return {mw.Message} */ mw.message = function ( key ) { var parameters = slice.call( arguments, 1 ); return new Message( mw.messages, key, parameters ); }; /** * Get a message string using the (default) 'text' format. * * Shortcut for `mw.message( key, parameters... ).text()`. * * @see mw.Message * @param {string} key Key of message to get * @param {...Mixed} parameters Values for $N replacements * @return {string} */ mw.msg = function () { return mw.message.apply( mw.message, arguments ).toString(); }; /** * Track an analytic event. * * This method provides a generic means for MediaWiki JavaScript code to capture state * information for analysis. Each logged event specifies a string topic name that describes * the kind of event that it is. Topic names consist of dot-separated path components, * arranged from most general to most specific. Each path component should have a clear and * well-defined purpose. * * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of * events that match their subcription, including those that fired before the handler was * bound. * * @param {string} topic Topic name * @param {Object} [data] Data describing the event, encoded as an object */ mw.track = function ( topic, data ) { mwLoaderTrack( topic, data ); trackCallbacks.fire( mw.trackQueue ); }; /** * Register a handler for subset of analytic events, specified by topic. * * Handlers will be called once for each tracked event, including any events that fired before the * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating * the exact time at which the event fired, a string 'topic' property naming the event, and a * 'data' property which is an object of event-specific data. The event topic and event data are * also passed to the callback as the first and second arguments, respectively. * * @param {string} topic Handle events whose name starts with this string prefix * @param {Function} callback Handler to call for each matching tracked event * @param {string} callback.topic * @param {Object} [callback.data] */ mw.trackSubscribe = function ( topic, callback ) { var seen = 0; function handler( trackQueue ) { var event; for ( ; seen < trackQueue.length; seen++ ) { event = trackQueue[ seen ]; if ( event.topic.indexOf( topic ) === 0 ) { callback.call( event, event.topic, event.data ); } } } trackHandlers.push( [ handler, callback ] ); trackCallbacks.add( handler ); }; /** * Stop handling events for a particular handler * * @param {Function} callback */ mw.trackUnsubscribe = function ( callback ) { trackHandlers = trackHandlers.filter( function ( fns ) { if ( fns[ 1 ] === callback ) { trackCallbacks.remove( fns[ 0 ] ); // Ensure the tuple is removed to avoid holding on to closures return false; } return true; } ); }; // Fire events from before track() triggered fire() trackCallbacks.fire( mw.trackQueue ); /** * Registry and firing of events. * * MediaWiki has various interface components that are extended, enhanced * or manipulated in some other way by extensions, gadgets and even * in core itself. * * This framework helps streamlining the timing of when these other * code paths fire their plugins (instead of using document-ready, * which can and should be limited to firing only once). * * Features like navigating to other wiki pages, previewing an edit * and editing itself – without a refresh – can then retrigger these * hooks accordingly to ensure everything still works as expected. * * Example usage: * * mw.hook( 'wikipage.content' ).add( fn ).remove( fn ); * mw.hook( 'wikipage.content' ).fire( $content ); * * Handlers can be added and fired for arbitrary event names at any time. The same * event can be fired multiple times. The last run of an event is memorized * (similar to `$(document).ready` and `$.Deferred().done`). * This means if an event is fired, and a handler added afterwards, the added * function will be fired right away with the last given event data. * * Like Deferreds and Promises, the mw.hook object is both detachable and chainable. * Thus allowing flexible use and optimal maintainability and authority control. * You can pass around the `add` and/or `fire` method to another piece of code * without it having to know the event name (or `mw.hook` for that matter). * * var h = mw.hook( 'bar.ready' ); * new mw.Foo( .. ).fetch( { callback: h.fire } ); * * Note: Events are documented with an underscore instead of a dot in the event * name due to jsduck not supporting dots in that position. * * @class mw.hook */ mw.hook = ( function () { var lists = Object.create( null ); /** * Create an instance of mw.hook. * * @method hook * @member mw * @param {string} name Name of hook. * @return {mw.hook} */ return function ( name ) { var list = lists[ name ] || ( lists[ name ] = $.Callbacks( 'memory' ) ); return { /** * Register a hook handler * * @param {...Function} handler Function to bind. * @chainable */ add: list.add, /** * Unregister a hook handler * * @param {...Function} handler Function to unbind. * @chainable */ remove: list.remove, /** * Run a hook. * * @param {...Mixed} data * @return {mw.hook} * @chainable */ fire: function () { return list.fireWith.call( this, null, slice.call( arguments ) ); } }; }; }() ); /** * HTML construction helper functions * * @example * * var Html, output; * * Html = mw.html; * output = Html.element( 'div', {}, new Html.Raw( * Html.element( 'img', { src: '<' } ) * ) ); * mw.log( output ); // <div><img src="<"/></div> * * @class mw.html * @singleton */ mw.html = ( function () { function escapeCallback( s ) { switch ( s ) { case '\'': return '''; case '"': return '"'; case '<': return '<'; case '>': return '>'; case '&': return '&'; } } return { /** * Escape a string for HTML. * * Converts special characters to HTML entities. * * mw.html.escape( '< > \' & "' ); * // Returns < > ' & " * * @param {string} s The string to escape * @return {string} HTML */ escape: function ( s ) { return s.replace( /['"<>&]/g, escapeCallback ); }, /** * Create an HTML element string, with safe escaping. * * @param {string} name The tag name. * @param {Object} [attrs] An object with members mapping element names to values * @param {string|mw.html.Raw|mw.html.Cdata|null} [contents=null] The contents of the element. * * - string: Text to be escaped. * - null: The element is treated as void with short closing form, e.g. `<br/>`. * - this.Raw: The raw value is directly included. * - this.Cdata: The raw value is directly included. An exception is * thrown if it contains any illegal ETAGO delimiter. * See <https://www.w3.org/TR/html401/appendix/notes.html#h-B.3.2>. * @return {string} HTML */ element: function ( name, attrs, contents ) { var v, attrName, s = '<' + name; if ( attrs ) { for ( attrName in attrs ) { v = attrs[ attrName ]; // Convert name=true, to name=name if ( v === true ) { v = attrName; // Skip name=false } else if ( v === false ) { continue; } s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"'; } } if ( contents === undefined || contents === null ) { // Self close tag s += '/>'; return s; } // Regular open tag s += '>'; switch ( typeof contents ) { case 'string': // Escaped s += this.escape( contents ); break; case 'number': case 'boolean': // Convert to string s += String( contents ); break; default: if ( contents instanceof this.Raw ) { // Raw HTML inclusion s += contents.value; } else if ( contents instanceof this.Cdata ) { // CDATA if ( /<\/[a-zA-z]/.test( contents.value ) ) { throw new Error( 'Illegal end tag found in CDATA' ); } s += contents.value; } else { throw new Error( 'Invalid type of contents' ); } } s += '</' + name + '>'; return s; }, /** * Wrapper object for raw HTML passed to mw.html.element(). * * @class mw.html.Raw * @constructor * @param {string} value */ Raw: function ( value ) { this.value = value; }, /** * Wrapper object for CDATA element contents passed to mw.html.element() * * @class mw.html.Cdata * @constructor * @param {string} value */ Cdata: function ( value ) { this.value = value; } }; }() ); /** * Execute a function as soon as one or more required modules are ready. * * Example of inline dependency on OOjs: * * mw.loader.using( 'oojs', function () { * OO.compare( [ 1 ], [ 1 ] ); * } ); * * Example of inline dependency obtained via `require()`: * * mw.loader.using( [ 'mediawiki.util' ], function ( require ) { * var util = require( 'mediawiki.util' ); * } ); * * Since MediaWiki 1.23 this also returns a promise. * * Since MediaWiki 1.28 the promise is resolved with a `require` function. * * @member mw.loader * @param {string|Array} dependencies Module name or array of modules names the * callback depends on to be ready before executing * @param {Function} [ready] Callback to execute when all dependencies are ready * @param {Function} [error] Callback to execute if one or more dependencies failed * @return {jQuery.Promise} With a `require` function */ mw.loader.using = function ( dependencies, ready, error ) { var deferred = $.Deferred(); // Allow calling with a single dependency as a string if ( typeof dependencies === 'string' ) { dependencies = [ dependencies ]; } if ( ready ) { deferred.done( ready ); } if ( error ) { deferred.fail( error ); } try { // Resolve entire dependency map dependencies = mw.loader.resolve( dependencies ); } catch ( e ) { return deferred.reject( e ).promise(); } mw.loader.enqueue( dependencies, function () { deferred.resolve( mw.loader.require ); }, deferred.reject ); return deferred.promise(); }; // Alias $j to jQuery for backwards compatibility // @deprecated since 1.23 Use $ or jQuery instead mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' ); // Process callbacks for Grade A that require modules. queue = window.RLQ; // Replace temporary RLQ implementation from startup.js with the // final implementation that also processes callbacks that can // require modules. It must also support late arrivals of // plain callbacks. (T208093) window.RLQ = { push: function ( entry ) { if ( typeof entry === 'function' ) { entry(); } else { mw.loader.using( entry[ 0 ], entry[ 1 ] ); } } }; while ( queue[ 0 ] ) { window.RLQ.push( queue.shift() ); } }() );