]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - resources/src/mediawiki/mediawiki.util.js
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / resources / src / mediawiki / mediawiki.util.js
1 ( function ( mw, $ ) {
2         'use strict';
3
4         var util;
5
6         /**
7          * Encode the string like PHP's rawurlencode
8          * @ignore
9          *
10          * @param {string} str String to be encoded.
11          * @return {string} Encoded string
12          */
13         function rawurlencode( str ) {
14                 str = String( str );
15                 return encodeURIComponent( str )
16                         .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
17                         .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
18         }
19
20         /**
21          * Private helper function used by util.escapeId*()
22          * @ignore
23          *
24          * @param {string} str String to be encoded
25          * @param {string} mode Encoding mode, see documentation for $wgFragmentMode
26          *     in DefaultSettings.php
27          * @return {string} Encoded string
28          */
29         function escapeIdInternal( str, mode ) {
30                 str = String( str );
31
32                 switch ( mode ) {
33                         case 'html5':
34                                 return str.replace( / /g, '_' );
35                         case 'html5-legacy':
36                                 str = str.replace( /[ \t\n\r\f_'"&#%]+/g, '_' )
37                                         .replace( /^_+|_+$/, '' );
38                                 if ( str === '' ) {
39                                         str = '_';
40                                 }
41                                 return str;
42                         case 'legacy':
43                                 return rawurlencode( str.replace( / /g, '_' ) )
44                                         .replace( /%3A/g, ':' )
45                                         .replace( /%/g, '.' );
46                         default:
47                                 throw new Error( 'Unrecognized ID escaping mode ' + mode );
48                 }
49         }
50
51         /**
52          * Utility library
53          * @class mw.util
54          * @singleton
55          */
56         util = {
57
58                 /* Main body */
59
60                 /**
61                  * Encode the string like PHP's rawurlencode
62                  *
63                  * @param {string} str String to be encoded.
64                  * @return {string} Encoded string
65                  */
66                 rawurlencode: rawurlencode,
67
68                 /**
69                  * Encode string into HTML id compatible form suitable for use in HTML
70                  * Analog to PHP Sanitizer::escapeIdForAttribute()
71                  *
72                  * @since 1.30
73                  *
74                  * @param {string} str String to encode
75                  * @return {string} Encoded string
76                  */
77                 escapeIdForAttribute: function ( str ) {
78                         var mode = mw.config.get( 'wgFragmentMode' )[ 0 ];
79
80                         return escapeIdInternal( str, mode );
81                 },
82
83                 /**
84                  * Encode string into HTML id compatible form suitable for use in links
85                  * Analog to PHP Sanitizer::escapeIdForLink()
86                  *
87                  * @since 1.30
88                  *
89                  * @param {string} str String to encode
90                  * @return {string} Encoded string
91                  */
92                 escapeIdForLink: function ( str ) {
93                         var mode = mw.config.get( 'wgFragmentMode' )[ 0 ];
94
95                         return escapeIdInternal( str, mode );
96                 },
97
98                 /**
99                  * Encode page titles for use in a URL
100                  *
101                  * We want / and : to be included as literal characters in our title URLs
102                  * as they otherwise fatally break the title.
103                  *
104                  * The others are decoded because we can, it's prettier and matches behaviour
105                  * of `wfUrlencode` in PHP.
106                  *
107                  * @param {string} str String to be encoded.
108                  * @return {string} Encoded string
109                  */
110                 wikiUrlencode: function ( str ) {
111                         return util.rawurlencode( str )
112                                 .replace( /%20/g, '_' )
113                                 // wfUrlencode replacements
114                                 .replace( /%3B/g, ';' )
115                                 .replace( /%40/g, '@' )
116                                 .replace( /%24/g, '$' )
117                                 .replace( /%21/g, '!' )
118                                 .replace( /%2A/g, '*' )
119                                 .replace( /%28/g, '(' )
120                                 .replace( /%29/g, ')' )
121                                 .replace( /%2C/g, ',' )
122                                 .replace( /%2F/g, '/' )
123                                 .replace( /%7E/g, '~' )
124                                 .replace( /%3A/g, ':' );
125                 },
126
127                 /**
128                  * Get the link to a page name (relative to `wgServer`),
129                  *
130                  * @param {string|null} [pageName=wgPageName] Page name
131                  * @param {Object} [params] A mapping of query parameter names to values,
132                  *  e.g. `{ action: 'edit' }`
133                  * @return {string} Url of the page with name of `pageName`
134                  */
135                 getUrl: function ( pageName, params ) {
136                         var titleFragmentStart, url, query,
137                                 fragment = '',
138                                 title = typeof pageName === 'string' ? pageName : mw.config.get( 'wgPageName' );
139
140                         // Find any fragment
141                         titleFragmentStart = title.indexOf( '#' );
142                         if ( titleFragmentStart !== -1 ) {
143                                 fragment = title.slice( titleFragmentStart + 1 );
144                                 // Exclude the fragment from the page name
145                                 title = title.slice( 0, titleFragmentStart );
146                         }
147
148                         // Produce query string
149                         if ( params ) {
150                                 query = $.param( params );
151                         }
152                         if ( query ) {
153                                 url = title ?
154                                         util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query :
155                                         util.wikiScript() + '?' + query;
156                         } else {
157                                 url = mw.config.get( 'wgArticlePath' )
158                                         .replace( '$1', util.wikiUrlencode( title ).replace( /\$/g, '$$$$' ) );
159                         }
160
161                         // Append the encoded fragment
162                         if ( fragment.length ) {
163                                 url += '#' + util.escapeIdForLink( fragment );
164                         }
165
166                         return url;
167                 },
168
169                 /**
170                  * Get address to a script in the wiki root.
171                  * For index.php use `mw.config.get( 'wgScript' )`.
172                  *
173                  * @since 1.18
174                  * @param {string} str Name of script (e.g. 'api'), defaults to 'index'
175                  * @return {string} Address to script (e.g. '/w/api.php' )
176                  */
177                 wikiScript: function ( str ) {
178                         str = str || 'index';
179                         if ( str === 'index' ) {
180                                 return mw.config.get( 'wgScript' );
181                         } else if ( str === 'load' ) {
182                                 return mw.config.get( 'wgLoadScript' );
183                         } else {
184                                 return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php';
185                         }
186                 },
187
188                 /**
189                  * Append a new style block to the head and return the CSSStyleSheet object.
190                  * Use .ownerNode to access the `<style>` element, or use mw.loader#addStyleTag.
191                  * This function returns the styleSheet object for convience (due to cross-browsers
192                  * difference as to where it is located).
193                  *
194                  *     var sheet = util.addCSS( '.foobar { display: none; }' );
195                  *     $( foo ).click( function () {
196                  *         // Toggle the sheet on and off
197                  *         sheet.disabled = !sheet.disabled;
198                  *     } );
199                  *
200                  * @param {string} text CSS to be appended
201                  * @return {CSSStyleSheet} Use .ownerNode to get to the `<style>` element.
202                  */
203                 addCSS: function ( text ) {
204                         var s = mw.loader.addStyleTag( text );
205                         return s.sheet || s.styleSheet || s;
206                 },
207
208                 /**
209                  * Grab the URL parameter value for the given parameter.
210                  * Returns null if not found.
211                  *
212                  * @param {string} param The parameter name.
213                  * @param {string} [url=location.href] URL to search through, defaulting to the current browsing location.
214                  * @return {Mixed} Parameter value or null.
215                  */
216                 getParamValue: function ( param, url ) {
217                         // Get last match, stop at hash
218                         var     re = new RegExp( '^[^#]*[&?]' + mw.RegExp.escape( param ) + '=([^&#]*)' ),
219                                 m = re.exec( url !== undefined ? url : location.href );
220
221                         if ( m ) {
222                                 // Beware that decodeURIComponent is not required to understand '+'
223                                 // by spec, as encodeURIComponent does not produce it.
224                                 return decodeURIComponent( m[ 1 ].replace( /\+/g, '%20' ) );
225                         }
226                         return null;
227                 },
228
229                 /**
230                  * The content wrapper of the skin (e.g. `.mw-body`).
231                  *
232                  * Populated on document ready. To use this property,
233                  * wait for `$.ready` and be sure to have a module dependency on
234                  * `mediawiki.util` which will ensure
235                  * your document ready handler fires after initialization.
236                  *
237                  * Because of the lazy-initialised nature of this property,
238                  * you're discouraged from using it.
239                  *
240                  * If you need just the wikipage content (not any of the
241                  * extra elements output by the skin), use `$( '#mw-content-text' )`
242                  * instead. Or listen to mw.hook#wikipage_content which will
243                  * allow your code to re-run when the page changes (e.g. live preview
244                  * or re-render after ajax save).
245                  *
246                  * @property {jQuery}
247                  */
248                 $content: null,
249
250                 /**
251                  * Add a link to a portlet menu on the page, such as:
252                  *
253                  * p-cactions (Content actions), p-personal (Personal tools),
254                  * p-navigation (Navigation), p-tb (Toolbox)
255                  *
256                  * The first three parameters are required, the others are optional and
257                  * may be null. Though providing an id and tooltip is recommended.
258                  *
259                  * By default the new link will be added to the end of the list. To
260                  * add the link before a given existing item, pass the DOM node
261                  * (e.g. `document.getElementById( 'foobar' )`) or a jQuery-selector
262                  * (e.g. `'#foobar'`) for that item.
263                  *
264                  *     util.addPortletLink(
265                  *         'p-tb', 'https://www.mediawiki.org/',
266                  *         'mediawiki.org', 't-mworg', 'Go to mediawiki.org', 'm', '#t-print'
267                  *     );
268                  *
269                  *     var node = util.addPortletLink(
270                  *         'p-tb',
271                  *         new mw.Title( 'Special:Example' ).getUrl(),
272                  *         'Example'
273                  *     );
274                  *     $( node ).on( 'click', function ( e ) {
275                  *         console.log( 'Example' );
276                  *         e.preventDefault();
277                  *     } );
278                  *
279                  * @param {string} portlet ID of the target portlet ( 'p-cactions' or 'p-personal' etc.)
280                  * @param {string} href Link URL
281                  * @param {string} text Link text
282                  * @param {string} [id] ID of the new item, should be unique and preferably have
283                  *  the appropriate prefix ( 'ca-', 'pt-', 'n-' or 't-' )
284                  * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix
285                  * @param {string} [accesskey] Access key to activate this link (one character, try
286                  *  to avoid conflicts. Use `$( '[accesskey=x]' ).get()` in the console to
287                  *  see if 'x' is already used.
288                  * @param {HTMLElement|jQuery|string} [nextnode] Element or jQuery-selector string to the item that
289                  *  the new item should be added before, should be another item in the same
290                  *  list, it will be ignored otherwise
291                  *
292                  * @return {HTMLElement|null} The added element (a ListItem or Anchor element,
293                  * depending on the skin) or null if no element was added to the document.
294                  */
295                 addPortletLink: function ( portlet, href, text, id, tooltip, accesskey, nextnode ) {
296                         var $item, $link, $portlet, $ul;
297
298                         // Check if there's at least 3 arguments to prevent a TypeError
299                         if ( arguments.length < 3 ) {
300                                 return null;
301                         }
302                         // Setup the anchor tag
303                         $link = $( '<a>' ).attr( 'href', href ).text( text );
304                         if ( tooltip ) {
305                                 $link.attr( 'title', tooltip );
306                         }
307
308                         // Select the specified portlet
309                         $portlet = $( '#' + portlet );
310                         if ( $portlet.length === 0 ) {
311                                 return null;
312                         }
313                         // Select the first (most likely only) unordered list inside the portlet
314                         $ul = $portlet.find( 'ul' ).eq( 0 );
315
316                         // If it didn't have an unordered list yet, create it
317                         if ( $ul.length === 0 ) {
318
319                                 $ul = $( '<ul>' );
320
321                                 // If there's no <div> inside, append it to the portlet directly
322                                 if ( $portlet.find( 'div:first' ).length === 0 ) {
323                                         $portlet.append( $ul );
324                                 } else {
325                                         // otherwise if there's a div (such as div.body or div.pBody)
326                                         // append the <ul> to last (most likely only) div
327                                         $portlet.find( 'div' ).eq( -1 ).append( $ul );
328                                 }
329                         }
330                         // Just in case..
331                         if ( $ul.length === 0 ) {
332                                 return null;
333                         }
334
335                         // Unhide portlet if it was hidden before
336                         $portlet.removeClass( 'emptyPortlet' );
337
338                         // Wrap the anchor tag in a list item (and a span if $portlet is a Vector tab)
339                         // and back up the selector to the list item
340                         if ( $portlet.hasClass( 'vectorTabs' ) ) {
341                                 $item = $link.wrap( '<li><span></span></li>' ).parent().parent();
342                         } else {
343                                 $item = $link.wrap( '<li></li>' ).parent();
344                         }
345
346                         // Implement the properties passed to the function
347                         if ( id ) {
348                                 $item.attr( 'id', id );
349                         }
350
351                         if ( accesskey ) {
352                                 $link.attr( 'accesskey', accesskey );
353                         }
354
355                         if ( tooltip ) {
356                                 $link.attr( 'title', tooltip );
357                         }
358
359                         if ( nextnode ) {
360                                 // Case: nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js)
361                                 // Case: nextnode is a CSS selector for jQuery
362                                 if ( nextnode.nodeType || typeof nextnode === 'string' ) {
363                                         nextnode = $ul.find( nextnode );
364                                 } else if ( !nextnode.jquery ) {
365                                         // Error: Invalid nextnode
366                                         nextnode = undefined;
367                                 }
368                                 if ( nextnode && ( nextnode.length !== 1 || nextnode[ 0 ].parentNode !== $ul[ 0 ] ) ) {
369                                         // Error: nextnode must resolve to a single node
370                                         // Error: nextnode must have the associated <ul> as its parent
371                                         nextnode = undefined;
372                                 }
373                         }
374
375                         // Case: nextnode is a jQuery-wrapped DOM element
376                         if ( nextnode ) {
377                                 nextnode.before( $item );
378                         } else {
379                                 // Fallback (this is the default behavior)
380                                 $ul.append( $item );
381                         }
382
383                         // Update tooltip for the access key after inserting into DOM
384                         // to get a localized access key label (T69946).
385                         $link.updateTooltipAccessKeys();
386
387                         return $item[ 0 ];
388                 },
389
390                 /**
391                  * Validate a string as representing a valid e-mail address
392                  * according to HTML5 specification. Please note the specification
393                  * does not validate a domain with one character.
394                  *
395                  * FIXME: should be moved to or replaced by a validation module.
396                  *
397                  * @param {string} mailtxt E-mail address to be validated.
398                  * @return {boolean|null} Null if `mailtxt` was an empty string, otherwise true/false
399                  * as determined by validation.
400                  */
401                 validateEmail: function ( mailtxt ) {
402                         var rfc5322Atext, rfc1034LdhStr, html5EmailRegexp;
403
404                         if ( mailtxt === '' ) {
405                                 return null;
406                         }
407
408                         // HTML5 defines a string as valid e-mail address if it matches
409                         // the ABNF:
410                         //     1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
411                         // With:
412                         // - atext   : defined in RFC 5322 section 3.2.3
413                         // - ldh-str : defined in RFC 1034 section 3.5
414                         //
415                         // (see STD 68 / RFC 5234 https://tools.ietf.org/html/std68)
416                         // First, define the RFC 5322 'atext' which is pretty easy:
417                         // atext = ALPHA / DIGIT / ; Printable US-ASCII
418                         //     "!" / "#" /    ; characters not including
419                         //     "$" / "%" /    ; specials. Used for atoms.
420                         //     "&" / "'" /
421                         //     "*" / "+" /
422                         //     "-" / "/" /
423                         //     "=" / "?" /
424                         //     "^" / "_" /
425                         //     "`" / "{" /
426                         //     "|" / "}" /
427                         //     "~"
428                         rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~';
429
430                         // Next define the RFC 1034 'ldh-str'
431                         //     <domain> ::= <subdomain> | " "
432                         //     <subdomain> ::= <label> | <subdomain> "." <label>
433                         //     <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
434                         //     <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
435                         //     <let-dig-hyp> ::= <let-dig> | "-"
436                         //     <let-dig> ::= <letter> | <digit>
437                         rfc1034LdhStr = 'a-z0-9\\-';
438
439                         html5EmailRegexp = new RegExp(
440                                 // start of string
441                                 '^' +
442                                 // User part which is liberal :p
443                                 '[' + rfc5322Atext + '\\.]+' +
444                                 // 'at'
445                                 '@' +
446                                 // Domain first part
447                                 '[' + rfc1034LdhStr + ']+' +
448                                 // Optional second part and following are separated by a dot
449                                 '(?:\\.[' + rfc1034LdhStr + ']+)*' +
450                                 // End of string
451                                 '$',
452                                 // RegExp is case insensitive
453                                 'i'
454                         );
455                         return ( mailtxt.match( html5EmailRegexp ) !== null );
456                 },
457
458                 /**
459                  * Note: borrows from IP::isIPv4
460                  *
461                  * @param {string} address
462                  * @param {boolean} allowBlock
463                  * @return {boolean}
464                  */
465                 isIPv4Address: function ( address, allowBlock ) {
466                         var block, RE_IP_BYTE, RE_IP_ADD;
467
468                         if ( typeof address !== 'string' ) {
469                                 return false;
470                         }
471
472                         block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '';
473                         RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])';
474                         RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
475
476                         return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
477                 },
478
479                 /**
480                  * Note: borrows from IP::isIPv6
481                  *
482                  * @param {string} address
483                  * @param {boolean} allowBlock
484                  * @return {boolean}
485                  */
486                 isIPv6Address: function ( address, allowBlock ) {
487                         var block, RE_IPV6_ADD;
488
489                         if ( typeof address !== 'string' ) {
490                                 return false;
491                         }
492
493                         block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '';
494                         RE_IPV6_ADD =
495                                 '(?:' + // starts with "::" (including "::")
496                                         ':(?::|(?::' +
497                                                 '[0-9A-Fa-f]{1,4}' +
498                                         '){1,7})' +
499                                         '|' + // ends with "::" (except "::")
500                                         '[0-9A-Fa-f]{1,4}' +
501                                         '(?::' +
502                                                 '[0-9A-Fa-f]{1,4}' +
503                                         '){0,6}::' +
504                                         '|' + // contains no "::"
505                                         '[0-9A-Fa-f]{1,4}' +
506                                         '(?::' +
507                                                 '[0-9A-Fa-f]{1,4}' +
508                                         '){7}' +
509                                 ')';
510
511                         if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) {
512                                 return true;
513                         }
514
515                         // contains one "::" in the middle (single '::' check below)
516                         RE_IPV6_ADD =
517                                 '[0-9A-Fa-f]{1,4}' +
518                                 '(?:::?' +
519                                         '[0-9A-Fa-f]{1,4}' +
520                                 '){1,6}';
521
522                         return (
523                                 new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) &&
524                                 /::/.test( address ) &&
525                                 !/::.*::/.test( address )
526                         );
527                 },
528
529                 /**
530                  * Check whether a string is an IP address
531                  *
532                  * @since 1.25
533                  * @param {string} address String to check
534                  * @param {boolean} allowBlock True if a block of IPs should be allowed
535                  * @return {boolean}
536                  */
537                 isIPAddress: function ( address, allowBlock ) {
538                         return util.isIPv4Address( address, allowBlock ) ||
539                                 util.isIPv6Address( address, allowBlock );
540                 }
541         };
542
543         /**
544          * @method wikiGetlink
545          * @inheritdoc #getUrl
546          * @deprecated since 1.23 Use #getUrl instead.
547          */
548         mw.log.deprecate( util, 'wikiGetlink', util.getUrl, 'Use mw.util.getUrl instead.', 'mw.util.wikiGetlink' );
549
550         /**
551          * Add the appropriate prefix to the accesskey shown in the tooltip.
552          *
553          * If the `$nodes` parameter is given, only those nodes are updated;
554          * otherwise we update all elements with accesskeys on the page.
555          *
556          * @method updateTooltipAccessKeys
557          * @param {Array|jQuery} [$nodes] A jQuery object, or array of nodes to update.
558          * @deprecated since 1.24 Use the module jquery.accessKeyLabel instead.
559          */
560         mw.log.deprecate( util, 'updateTooltipAccessKeys', function ( $nodes ) {
561                 if ( !$nodes ) {
562                         $nodes = $( '[accesskey]' );
563                 } else if ( !( $nodes instanceof $ ) ) {
564                         $nodes = $( $nodes );
565                 }
566
567                 $nodes.updateTooltipAccessKeys();
568         }, 'Use jquery.accessKeyLabel instead.', 'mw.util.updateTooltipAccessKeys' );
569
570         /**
571          * Add a little box at the top of the screen to inform the user of
572          * something, replacing any previous message.
573          * Calling with no arguments, with an empty string or null will hide the message
574          *
575          * @method jsMessage
576          * @deprecated since 1.20 Use mw#notify
577          * @param {Mixed} message The DOM-element, jQuery object or HTML-string to be put inside the message box.
578          *  to allow CSS/JS to hide different boxes. null = no class used.
579          */
580         mw.log.deprecate( util, 'jsMessage', function ( message ) {
581                 if ( !arguments.length || message === '' || message === null ) {
582                         return true;
583                 }
584                 if ( typeof message !== 'object' ) {
585                         message = $.parseHTML( message );
586                 }
587                 mw.notify( message, { autoHide: true, tag: 'legacy' } );
588                 return true;
589         }, 'Use mw.notify instead.', 'mw.util.jsMessage' );
590
591         /**
592          * Encode the string like Sanitizer::escapeId() in PHP
593          *
594          * @method escapeId
595          * @deprecated since 1.30 use escapeIdForAttribute() or escapeIdForLink()
596          * @param {string} str String to be encoded.
597          * @return {string} Encoded string
598          */
599         mw.log.deprecate( util, 'escapeId', function ( str ) {
600                 return escapeIdInternal( str, 'legacy' );
601         }, 'Use mw.util.escapeIdForAttribute or mw.util.escapeIdForLink instead.', 'mw.util.escapeId' );
602
603         /**
604          * Initialisation of mw.util.$content
605          */
606         function init() {
607                 util.$content = ( function () {
608                         var i, l, $node, selectors;
609
610                         selectors = [
611                                 // The preferred standard is class "mw-body".
612                                 // You may also use class "mw-body mw-body-primary" if you use
613                                 // mw-body in multiple locations. Or class "mw-body-primary" if
614                                 // you use mw-body deeper in the DOM.
615                                 '.mw-body-primary',
616                                 '.mw-body',
617
618                                 // If the skin has no such class, fall back to the parser output
619                                 '#mw-content-text'
620                         ];
621
622                         for ( i = 0, l = selectors.length; i < l; i++ ) {
623                                 $node = $( selectors[ i ] );
624                                 if ( $node.length ) {
625                                         return $node.first();
626                                 }
627                         }
628
629                         // Should never happen... well, it could if someone is not finished writing a
630                         // skin and has not yet inserted bodytext yet.
631                         return $( 'body' );
632                 }() );
633         }
634
635         /**
636          * Former public initialisation. Now a no-op function.
637          *
638          * @method util_init
639          * @deprecated since 1.30
640          */
641         mw.log.deprecate( util, 'init', $.noop, 'Remove the call of mw.util.init().', 'mw.util.init' );
642
643         $( init );
644
645         mw.util = util;
646         module.exports = util;
647
648 }( mediaWiki, jQuery ) );