%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() );
}
}() );