2 * This file is currently loaded as part of the 'mediawiki' module and therefore
3 * concatenated to mediawiki.js and executed at the same time. This file exists
4 * to help prepare for splitting up the 'mediawiki' module.
5 * This effort is tracked at https://phabricator.wikimedia.org/T192623
9 * - mediawiki.js will be reduced to the minimum needed to define mw.loader and
10 * mw.config, and then moved to its own private "mediawiki.loader" module that
11 * can be embedded within the StartupModule response.
13 * - mediawiki.base.js and other files in this directory will remain part of the
14 * "mediawiki" module, and will remain a default/implicit dependency for all
15 * regular modules, just like jquery and wikibits already are.
21 var slice
= Array
.prototype.slice
,
22 mwLoaderTrack
= mw
.track
,
23 trackCallbacks
= $.Callbacks( 'memory' ),
27 * Object constructor for messages.
29 * Similar to the Message class in MediaWiki PHP.
31 * Format defaults to 'text'.
37 * 'hello': 'Hello world',
38 * 'hello-user': 'Hello, $1!',
39 * 'welcome-user': 'Welcome back to $2, $1! Last visit by $1: $3'
42 * obj = new mw.Message( mw.messages, 'hello' );
43 * mw.log( obj.text() );
46 * obj = new mw.Message( mw.messages, 'hello-user', [ 'John Doe' ] );
47 * mw.log( obj.text() );
50 * obj = new mw.Message( mw.messages, 'welcome-user', [ 'John Doe', 'Wikipedia', '2 hours ago' ] );
51 * mw.log( obj.text() );
52 * // Welcome back to Wikipedia, John Doe! Last visit by John Doe: 2 hours ago
54 * // Using mw.message shortcut
55 * obj = mw.message( 'hello-user', 'John Doe' );
56 * mw.log( obj.text() );
59 * // Using mw.msg shortcut
60 * str = mw.msg( 'hello-user', 'John Doe' );
64 * // Different formats
65 * obj = new mw.Message( mw.messages, 'hello-user', [ 'John "Wiki" <3 Doe' ] );
67 * obj.format = 'text';
68 * str = obj.toString();
73 * // Hello, John "Wiki" <3 Doe!
75 * mw.log( obj.escaped() );
76 * // Hello, John "Wiki" <3 Doe!
81 * @param {mw.Map} map Message store
83 * @param {Array} [parameters]
85 function Message( map
, key
, parameters
) {
89 this.parameters
= parameters
=== undefined ? [] : slice
.call( parameters
);
95 * Get parsed contents of the message.
97 * The default parser does simple $N replacements and nothing else.
98 * This may be overridden to provide a more complex message parser.
99 * The primary override is in the mediawiki.jqueryMsg module.
101 * This function will not be called for nonexistent messages.
103 * @return {string} Parsed message
105 parser: function () {
106 return mw
.format
.apply( null, [ this.map
.get( this.key
) ].concat( this.parameters
) );
110 * Add (does not replace) parameters for `$N` placeholder values.
112 * @param {Array} parameters
113 * @return {mw.Message}
116 params: function ( parameters
) {
118 for ( i
= 0; i
< parameters
.length
; i
++ ) {
119 this.parameters
.push( parameters
[ i
] );
125 * Convert message object to its string form based on current format.
127 * @return {string} Message as a string in the current form, or `<key>` if key
130 toString: function () {
133 if ( !this.exists() ) {
134 // Use ⧼key⧽ as text if key does not exist
135 // Err on the side of safety, ensure that the output
136 // is always html safe in the event the message key is
137 // missing, since in that case its highly likely the
138 // message key is user-controlled.
139 // '⧼' is used instead of '<' to side-step any
140 // double-escaping issues.
141 // (Keep synchronised with Message::toString() in PHP.)
142 return '⧼' + mw
.html
.escape( this.key
) + '⧽';
145 if ( this.format
=== 'plain' || this.format
=== 'text' || this.format
=== 'parse' ) {
146 text
= this.parser();
149 if ( this.format
=== 'escaped' ) {
150 text
= this.parser();
151 text
= mw
.html
.escape( text
);
158 * Change format to 'parse' and convert message to string
160 * If jqueryMsg is loaded, this parses the message text from wikitext
161 * (where supported) to HTML
163 * Otherwise, it is equivalent to plain.
165 * @return {string} String form of parsed message
168 this.format
= 'parse';
169 return this.toString();
173 * Change format to 'plain' and convert message to string
175 * This substitutes parameters, but otherwise does not change the
178 * @return {string} String form of plain message
181 this.format
= 'plain';
182 return this.toString();
186 * Change format to 'text' and convert message to string
188 * If jqueryMsg is loaded, {{-transformation is done where supported
189 * (such as {{plural:}}, {{gender:}}, {{int:}}).
191 * Otherwise, it is equivalent to plain
193 * @return {string} String form of text message
196 this.format
= 'text';
197 return this.toString();
201 * Change the format to 'escaped' and convert message to string
203 * This is equivalent to using the 'text' format (see #text), then
204 * HTML-escaping the output.
206 * @return {string} String form of html escaped message
208 escaped: function () {
209 this.format
= 'escaped';
210 return this.toString();
214 * Check if a message exists
219 exists: function () {
220 return this.map
.exists( this.key
);
230 * @inheritdoc mw.inspect#runReports
233 mw
.inspect = function () {
234 var args
= arguments
;
235 mw
.loader
.using( 'mediawiki.inspect', function () {
236 mw
.inspect
.runReports
.apply( mw
.inspect
, args
);
241 * Format a string. Replace $1, $2 ... $N with positional arguments.
243 * Used by Message#parser().
246 * @param {string} formatString Format string
247 * @param {...Mixed} parameters Values for $N replacements
248 * @return {string} Formatted string
250 mw
.format = function ( formatString
) {
251 var parameters
= slice
.call( arguments
, 1 );
252 return formatString
.replace( /\$(\d+)/g, function ( str
, match
) {
253 var index
= parseInt( match
, 10 ) - 1;
254 return parameters
[ index
] !== undefined ? parameters
[ index
] : '$' + match
;
258 // Expose Message constructor
259 mw
.Message
= Message
;
262 * Get a message object.
264 * Shortcut for `new mw.Message( mw.messages, key, parameters )`.
267 * @param {string} key Key of message to get
268 * @param {...Mixed} parameters Values for $N replacements
269 * @return {mw.Message}
271 mw
.message = function ( key
) {
272 var parameters
= slice
.call( arguments
, 1 );
273 return new Message( mw
.messages
, key
, parameters
);
277 * Get a message string using the (default) 'text' format.
279 * Shortcut for `mw.message( key, parameters... ).text()`.
282 * @param {string} key Key of message to get
283 * @param {...Mixed} parameters Values for $N replacements
286 mw
.msg = function () {
287 return mw
.message
.apply( mw
.message
, arguments
).toString();
291 * Track an analytic event.
293 * This method provides a generic means for MediaWiki JavaScript code to capture state
294 * information for analysis. Each logged event specifies a string topic name that describes
295 * the kind of event that it is. Topic names consist of dot-separated path components,
296 * arranged from most general to most specific. Each path component should have a clear and
297 * well-defined purpose.
299 * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
300 * events that match their subcription, including those that fired before the handler was
303 * @param {string} topic Topic name
304 * @param {Object} [data] Data describing the event, encoded as an object
306 mw
.track = function ( topic
, data
) {
307 mwLoaderTrack( topic
, data
);
308 trackCallbacks
.fire( mw
.trackQueue
);
312 * Register a handler for subset of analytic events, specified by topic.
314 * Handlers will be called once for each tracked event, including any events that fired before the
315 * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
316 * the exact time at which the event fired, a string 'topic' property naming the event, and a
317 * 'data' property which is an object of event-specific data. The event topic and event data are
318 * also passed to the callback as the first and second arguments, respectively.
320 * @param {string} topic Handle events whose name starts with this string prefix
321 * @param {Function} callback Handler to call for each matching tracked event
322 * @param {string} callback.topic
323 * @param {Object} [callback.data]
325 mw
.trackSubscribe = function ( topic
, callback
) {
327 function handler( trackQueue
) {
329 for ( ; seen
< trackQueue
.length
; seen
++ ) {
330 event
= trackQueue
[ seen
];
331 if ( event
.topic
.indexOf( topic
) === 0 ) {
332 callback
.call( event
, event
.topic
, event
.data
);
337 trackHandlers
.push( [ handler
, callback
] );
339 trackCallbacks
.add( handler
);
343 * Stop handling events for a particular handler
345 * @param {Function} callback
347 mw
.trackUnsubscribe = function ( callback
) {
348 trackHandlers
= trackHandlers
.filter( function ( fns
) {
349 if ( fns
[ 1 ] === callback
) {
350 trackCallbacks
.remove( fns
[ 0 ] );
351 // Ensure the tuple is removed to avoid holding on to closures
358 // Fire events from before track() triggred fire()
359 trackCallbacks
.fire( mw
.trackQueue
);