summaryrefslogtreecommitdiff
path: root/resources/mediawiki
diff options
context:
space:
mode:
Diffstat (limited to 'resources/mediawiki')
-rw-r--r--resources/mediawiki/images/arrow-collapsed-ltr.pngbin133 -> 0 bytes
-rw-r--r--resources/mediawiki/images/arrow-collapsed-rtl.pngbin136 -> 0 bytes
-rw-r--r--resources/mediawiki/images/arrow-expanded.pngbin134 -> 0 bytes
-rw-r--r--resources/mediawiki/mediawiki.Title.js587
-rw-r--r--resources/mediawiki/mediawiki.Uri.js334
-rw-r--r--resources/mediawiki/mediawiki.debug.css183
-rw-r--r--resources/mediawiki/mediawiki.debug.init.js3
-rw-r--r--resources/mediawiki/mediawiki.debug.js365
-rw-r--r--resources/mediawiki/mediawiki.feedback.css9
-rw-r--r--resources/mediawiki/mediawiki.feedback.js279
-rw-r--r--resources/mediawiki/mediawiki.feedback.spinner.gifbin1108 -> 0 bytes
-rw-r--r--resources/mediawiki/mediawiki.hidpi.js5
-rw-r--r--resources/mediawiki/mediawiki.htmlform.js128
-rw-r--r--resources/mediawiki/mediawiki.icon.css15
-rw-r--r--resources/mediawiki/mediawiki.inspect.js204
-rw-r--r--resources/mediawiki/mediawiki.jqueryMsg.js1148
-rw-r--r--resources/mediawiki/mediawiki.jqueryMsg.peg81
-rw-r--r--resources/mediawiki/mediawiki.js1938
-rw-r--r--resources/mediawiki/mediawiki.log.js126
-rw-r--r--resources/mediawiki/mediawiki.notification.css36
-rw-r--r--resources/mediawiki/mediawiki.notification.js504
-rw-r--r--resources/mediawiki/mediawiki.notify.js28
-rw-r--r--resources/mediawiki/mediawiki.searchSuggest.css16
-rw-r--r--resources/mediawiki/mediawiki.searchSuggest.js218
-rw-r--r--resources/mediawiki/mediawiki.user.js255
-rw-r--r--resources/mediawiki/mediawiki.util.js624
26 files changed, 0 insertions, 7086 deletions
diff --git a/resources/mediawiki/images/arrow-collapsed-ltr.png b/resources/mediawiki/images/arrow-collapsed-ltr.png
deleted file mode 100644
index b17e578b..00000000
--- a/resources/mediawiki/images/arrow-collapsed-ltr.png
+++ /dev/null
Binary files differ
diff --git a/resources/mediawiki/images/arrow-collapsed-rtl.png b/resources/mediawiki/images/arrow-collapsed-rtl.png
deleted file mode 100644
index a834548e..00000000
--- a/resources/mediawiki/images/arrow-collapsed-rtl.png
+++ /dev/null
Binary files differ
diff --git a/resources/mediawiki/images/arrow-expanded.png b/resources/mediawiki/images/arrow-expanded.png
deleted file mode 100644
index 2bec798e..00000000
--- a/resources/mediawiki/images/arrow-expanded.png
+++ /dev/null
Binary files differ
diff --git a/resources/mediawiki/mediawiki.Title.js b/resources/mediawiki/mediawiki.Title.js
deleted file mode 100644
index 5038c515..00000000
--- a/resources/mediawiki/mediawiki.Title.js
+++ /dev/null
@@ -1,587 +0,0 @@
-/*!
- * @author Neil Kandalgaonkar, 2010
- * @author Timo Tijhof, 2011-2013
- * @since 1.18
- */
-( function ( mw, $ ) {
-
- /**
- * @class mw.Title
- *
- * Parse titles into an object struture. Note that when using the constructor
- * directly, passing invalid titles will result in an exception. Use #newFromText to use the
- * logic directly and get null for invalid titles which is easier to work with.
- *
- * @constructor
- * @param {string} title Title of the page. If no second argument given,
- * this will be searched for a namespace
- * @param {number} [namespace=NS_MAIN] If given, will used as default namespace for the given title
- * @throws {Error} When the title is invalid
- */
- function Title( title, namespace ) {
- var parsed = parse( title, namespace );
- if ( !parsed ) {
- throw new Error( 'Unable to parse title' );
- }
-
- this.namespace = parsed.namespace;
- this.title = parsed.title;
- this.ext = parsed.ext;
- this.fragment = parsed.fragment;
-
- return this;
- }
-
- /* Private members */
-
- var
-
- /**
- * @private
- * @static
- * @property NS_MAIN
- */
- NS_MAIN = 0,
-
- /**
- * @private
- * @static
- * @property NS_TALK
- */
- NS_TALK = 1,
-
- /**
- * @private
- * @static
- * @property NS_SPECIAL
- */
- NS_SPECIAL = -1,
-
- /**
- * Get the namespace id from a namespace name (either from the localized, canonical or alias
- * name).
- *
- * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or
- * even 'Bild'.
- *
- * @private
- * @static
- * @method getNsIdByName
- * @param {string} ns Namespace name (case insensitive, leading/trailing space ignored)
- * @return {number|boolean} Namespace id or boolean false
- */
- getNsIdByName = function ( ns ) {
- var id;
-
- // Don't cast non-strings to strings, because null or undefined should not result in
- // returning the id of a potential namespace called "Null:" (e.g. on null.example.org/wiki)
- // Also, toLowerCase throws exception on null/undefined, because it is a String method.
- if ( typeof ns !== 'string' ) {
- return false;
- }
- ns = ns.toLowerCase();
- id = mw.config.get( 'wgNamespaceIds' )[ns];
- if ( id === undefined ) {
- return false;
- }
- return id;
- },
-
- rUnderscoreTrim = /^_+|_+$/g,
-
- rSplit = /^(.+?)_*:_*(.*)$/,
-
- // See Title.php#getTitleInvalidRegex
- rInvalid = new RegExp(
- '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
- // URL percent encoding sequences interfere with the ability
- // to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' +
- // XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
- '|&#[0-9]+;' +
- '|&#x[0-9A-Fa-f]+;'
- ),
-
- /**
- * Internal helper for #constructor and #newFromtext.
- *
- * Based on Title.php#secureAndSplit
- *
- * @private
- * @static
- * @method parse
- * @param {string} title
- * @param {number} [defaultNamespace=NS_MAIN]
- * @return {Object|boolean}
- */
- parse = function ( title, defaultNamespace ) {
- var namespace, m, id, i, fragment, ext;
-
- namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
-
- title = title
- // Normalise whitespace to underscores and remove duplicates
- .replace( /[ _\s]+/g, '_' )
- // Trim underscores
- .replace( rUnderscoreTrim, '' );
-
- if ( title === '' ) {
- return false;
- }
-
- // Process initial colon
- if ( title.charAt( 0 ) === ':' ) {
- // Initial colon means main namespace instead of specified default
- namespace = NS_MAIN;
- title = title
- // Strip colon
- .substr( 1 )
- // Trim underscores
- .replace( rUnderscoreTrim, '' );
- }
-
- // Process namespace prefix (if any)
- m = title.match( rSplit );
- if ( m ) {
- id = getNsIdByName( m[1] );
- if ( id !== false ) {
- // Ordinary namespace
- namespace = id;
- title = m[2];
-
- // For Talk:X pages, make sure X has no "namespace" prefix
- if ( namespace === NS_TALK && ( m = title.match( rSplit ) ) ) {
- // Disallow titles like Talk:File:x (subject should roundtrip: talk:file:x -> file:x -> file_talk:x)
- if ( getNsIdByName( m[1] ) !== false ) {
- return false;
- }
- }
- }
- }
-
- // Process fragment
- i = title.indexOf( '#' );
- if ( i === -1 ) {
- fragment = null;
- } else {
- fragment = title
- // Get segment starting after the hash
- .substr( i + 1 )
- // Convert to text
- // NB: Must not be trimmed ("Example#_foo" is not the same as "Example#foo")
- .replace( /_/g, ' ' );
-
- title = title
- // Strip hash
- .substr( 0, i )
- // Trim underscores, again (strips "_" from "bar" in "Foo_bar_#quux")
- .replace( rUnderscoreTrim, '' );
- }
-
-
- // Reject illegal characters
- if ( title.match( rInvalid ) ) {
- return false;
- }
-
- // Disallow titles that browsers or servers might resolve as directory navigation
- if (
- title.indexOf( '.' ) !== -1 && (
- title === '.' || title === '..' ||
- title.indexOf( './' ) === 0 ||
- title.indexOf( '../' ) === 0 ||
- title.indexOf( '/./' ) !== -1 ||
- title.indexOf( '/../' ) !== -1 ||
- title.substr( -2 ) === '/.' ||
- title.substr( -3 ) === '/..'
- )
- ) {
- return false;
- }
-
- // Disallow magic tilde sequence
- if ( title.indexOf( '~~~' ) !== -1 ) {
- return false;
- }
-
- // Disallow titles exceeding the 255 byte size limit (size of underlying database field)
- // Except for special pages, e.g. [[Special:Block/Long name]]
- // Note: The PHP implementation also asserts that even in NS_SPECIAL, the title should
- // be less than 512 bytes.
- if ( namespace !== NS_SPECIAL && $.byteLength( title ) > 255 ) {
- return false;
- }
-
- // Can't make a link to a namespace alone.
- if ( title === '' && namespace !== NS_MAIN ) {
- return false;
- }
-
- // Any remaining initial :s are illegal.
- if ( title.charAt( 0 ) === ':' ) {
- return false;
- }
-
- // For backwards-compatibility with old mw.Title, we separate the extension from the
- // rest of the title.
- i = title.lastIndexOf( '.' );
- if ( i === -1 || title.length <= i + 1 ) {
- // Extensions are the non-empty segment after the last dot
- ext = null;
- } else {
- ext = title.substr( i + 1 );
- title = title.substr( 0, i );
- }
-
- return {
- namespace: namespace,
- title: title,
- ext: ext,
- fragment: fragment
- };
- },
-
- /**
- * Convert db-key to readable text.
- *
- * @private
- * @static
- * @method text
- * @param {string} s
- * @return {string}
- */
- text = function ( s ) {
- if ( s !== null && s !== undefined ) {
- return s.replace( /_/g, ' ' );
- } else {
- return '';
- }
- },
-
- // Polyfill for ES5 Object.create
- createObject = Object.create || ( function () {
- return function ( o ) {
- function Title() {}
- if ( o !== Object( o ) ) {
- throw new Error( 'Cannot inherit from a non-object' );
- }
- Title.prototype = o;
- return new Title();
- };
- }() );
-
-
- /* Static members */
-
- /**
- * Constructor for Title objects with a null return instead of an exception for invalid titles.
- *
- * @static
- * @method
- * @param {string} title
- * @param {number} [namespace=NS_MAIN] Default namespace
- * @return {mw.Title|null} A valid Title object or null if the title is invalid
- */
- Title.newFromText = function ( title, namespace ) {
- var t, parsed = parse( title, namespace );
- if ( !parsed ) {
- return null;
- }
-
- t = createObject( Title.prototype );
- t.namespace = parsed.namespace;
- t.title = parsed.title;
- t.ext = parsed.ext;
- t.fragment = parsed.fragment;
-
- return t;
- };
-
- /**
- * Get the file title from an image element
- *
- * var title = mw.Title.newFromImg( $( 'img:first' ) );
- *
- * @static
- * @param {HTMLElement|jQuery} img The image to use as a base
- * @return {mw.Title|null} The file title or null if unsuccessful
- */
- Title.newFromImg = function ( img ) {
- var matches, i, regex, src, decodedSrc,
-
- // thumb.php-generated thumbnails
- thumbPhpRegex = /thumb\.php/,
-
- regexes = [
- // Thumbnails
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)\/[0-9]+px-\1[^\s\/]*$/,
-
- // Thumbnails in non-hashed upload directories
- /\/([^\s\/]+)\/[0-9]+px-\1[^\s\/]*$/,
-
- // Full size images
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)$/,
-
- // Full-size images in non-hashed upload directories
- /\/([^\s\/]+)$/
- ],
-
- recount = regexes.length;
-
- src = img.jquery ? img[0].src : img.src;
-
- matches = src.match( thumbPhpRegex );
-
- if ( matches ) {
- return mw.Title.newFromText( 'File:' + mw.util.getParamValue( 'f', src ) );
- }
-
- decodedSrc = decodeURIComponent( src );
-
- for ( i = 0; i < recount; i++ ) {
- regex = regexes[i];
- matches = decodedSrc.match( regex );
-
- if ( matches && matches[1] ) {
- return mw.Title.newFromText( 'File:' + matches[1] );
- }
- }
-
- return null;
- };
-
- /**
- * Whether this title exists on the wiki.
- *
- * @static
- * @param {string|mw.Title} title prefixed db-key name (string) or instance of Title
- * @return {boolean|null} Boolean if the information is available, otherwise null
- */
- Title.exists = function ( title ) {
- var match,
- type = $.type( title ),
- obj = Title.exist.pages;
-
- if ( type === 'string' ) {
- match = obj[title];
- } else if ( type === 'object' && title instanceof Title ) {
- match = obj[title.toString()];
- } else {
- throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' );
- }
-
- if ( typeof match === 'boolean' ) {
- return match;
- }
-
- return null;
- };
-
- Title.exist = {
- /**
- * Boolean true value indicates page does exist.
- *
- * @static
- * @property {Object} exist.pages Keyed by PrefixedDb title.
- */
- pages: {},
-
- /**
- * Example to declare existing titles:
- * Title.exist.set(['User:John_Doe', ...]);
- * Eample to declare titles nonexistent:
- * Title.exist.set(['File:Foo_bar.jpg', ...], false);
- *
- * @static
- * @property exist.set
- * @param {string|Array} titles Title(s) in strict prefixedDb title form
- * @param {boolean} [state=true] State of the given titles
- * @return {boolean}
- */
- set: function ( titles, state ) {
- titles = $.isArray( titles ) ? titles : [titles];
- state = state === undefined ? true : !!state;
- var pages = this.pages, i, len = titles.length;
- for ( i = 0; i < len; i++ ) {
- pages[ titles[i] ] = state;
- }
- return true;
- }
- };
-
- /* Public members */
-
- Title.prototype = {
- constructor: Title,
-
- /**
- * Get the namespace number
- *
- * Example: 6 for "File:Example_image.svg".
- *
- * @return {number}
- */
- getNamespaceId: function () {
- return this.namespace;
- },
-
- /**
- * Get the namespace prefix (in the content language)
- *
- * Example: "File:" for "File:Example_image.svg".
- * In #NS_MAIN this is '', otherwise namespace name plus ':'
- *
- * @return {string}
- */
- getNamespacePrefix: function () {
- return this.namespace === NS_MAIN ?
- '' :
- ( mw.config.get( 'wgFormattedNamespaces' )[ this.namespace ].replace( / /g, '_' ) + ':' );
- },
-
- /**
- * Get the page name without extension or namespace prefix
- *
- * Example: "Example_image" for "File:Example_image.svg".
- *
- * For the page title (full page name without namespace prefix), see #getMain.
- *
- * @return {string}
- */
- getName: function () {
- if ( $.inArray( this.namespace, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
- return this.title;
- } else {
- return $.ucFirst( this.title );
- }
- },
-
- /**
- * Get the page name (transformed by #text)
- *
- * Example: "Example image" for "File:Example_image.svg".
- *
- * For the page title (full page name without namespace prefix), see #getMainText.
- *
- * @return {string}
- */
- getNameText: function () {
- return text( this.getName() );
- },
-
- /**
- * Get the extension of the page name (if any)
- *
- * @return {string|null} Name extension or null if there is none
- */
- getExtension: function () {
- return this.ext;
- },
-
- /**
- * Shortcut for appendable string to form the main page name.
- *
- * Returns a string like ".json", or "" if no extension.
- *
- * @return {string}
- */
- getDotExtension: function () {
- return this.ext === null ? '' : '.' + this.ext;
- },
-
- /**
- * Get the main page name (transformed by #text)
- *
- * Example: "Example_image.svg" for "File:Example_image.svg".
- *
- * @return {string}
- */
- getMain: function () {
- return this.getName() + this.getDotExtension();
- },
-
- /**
- * Get the main page name (transformed by #text)
- *
- * Example: "Example image.svg" for "File:Example_image.svg".
- *
- * @return {string}
- */
- getMainText: function () {
- return text( this.getMain() );
- },
-
- /**
- * Get the full page name
- *
- * Eaxample: "File:Example_image.svg".
- * Most useful for API calls, anything that must identify the "title".
- *
- * @return {string}
- */
- getPrefixedDb: function () {
- return this.getNamespacePrefix() + this.getMain();
- },
-
- /**
- * Get the full page name (transformed by #text)
- *
- * Example: "File:Example image.svg" for "File:Example_image.svg".
- *
- * @return {string}
- */
- getPrefixedText: function () {
- return text( this.getPrefixedDb() );
- },
-
- /**
- * Get the fragment (if any).
- *
- * Note that this method (by design) does not include the hash character and
- * the value is not url encoded.
- *
- * @return {string|null}
- */
- getFragment: function () {
- return this.fragment;
- },
-
- /**
- * Get the URL to this title
- *
- * @see mw.util#getUrl
- * @return {string}
- */
- getUrl: function () {
- return mw.util.getUrl( this.toString() );
- },
-
- /**
- * Whether this title exists on the wiki.
- *
- * @see #static-method-exists
- * @return {boolean|null} Boolean if the information is available, otherwise null
- */
- exists: function () {
- return Title.exists( this );
- }
- };
-
- /**
- * @alias #getPrefixedDb
- * @method
- */
- Title.prototype.toString = Title.prototype.getPrefixedDb;
-
-
- /**
- * @alias #getPrefixedText
- * @method
- */
- Title.prototype.toText = Title.prototype.getPrefixedText;
-
- // Expose
- mw.Title = Title;
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.Uri.js b/resources/mediawiki/mediawiki.Uri.js
deleted file mode 100644
index a2d4d6cb..00000000
--- a/resources/mediawiki/mediawiki.Uri.js
+++ /dev/null
@@ -1,334 +0,0 @@
-/**
- * Library for simple URI parsing and manipulation. Requires jQuery.
- *
- * Do not expect full RFC 3986 compliance. Intended to be minimal, but featureful.
- * The use cases we have in mind are constructing 'next page' or 'previous page' URLs,
- * detecting whether we need to use cross-domain proxies for an API, constructing
- * simple URL-based API calls, etc.
- *
- * Intended to compress very well if you use a JS-parsing minifier.
- *
- * Dependencies: mw, jQuery
- *
- * Example:
- *
- * var uri = new mw.Uri( 'http://foo.com/mysite/mypage.php?quux=2' );
- *
- * if ( uri.host == 'foo.com' ) {
- * uri.host = 'www.foo.com';
- * uri.extend( { bar: 1 } );
- *
- * $( 'a#id1' ).attr( 'href', uri );
- * // anchor with id 'id1' now links to http://foo.com/mysite/mypage.php?bar=1&quux=2
- *
- * $( 'a#id2' ).attr( 'href', uri.clone().extend( { bar: 3, pif: 'paf' } ) );
- * // anchor with id 'id2' now links to http://foo.com/mysite/mypage.php?bar=3&quux=2&pif=paf
- * }
- *
- * Parsing here is regex based, so may not work on all URIs, but is good enough for most.
- *
- * Given a URI like
- * 'http://usr:pwd@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=&test3=value+%28escaped%29&r=1&r=2#top':
- * The returned object will have the following properties:
- *
- * protocol 'http'
- * user 'usr'
- * password 'pwd'
- * host 'www.test.com'
- * port '81'
- * path '/dir/dir.2/index.htm'
- * query {
- * q1: 0,
- * test1: null,
- * test2: '',
- * test3: 'value (escaped)'
- * r: [1, 2]
- * }
- * fragment 'top'
- *
- * n.b. 'password' is not technically allowed for HTTP URIs, but it is possible with other
- * sorts of URIs.
- * You can modify the properties directly. Then use the toString() method to extract the
- * full URI string again.
- *
- * Parsing based on parseUri 1.2.2 (c) Steven Levithan <stevenlevithan.com> MIT License
- * http://stevenlevithan.com/demo/parseuri/js/
- *
- */
-
-( function ( mw, $ ) {
-
- /**
- * Function that's useful when constructing the URI string -- we frequently encounter the pattern of
- * having to add something to the URI as we go, but only if it's present, and to include a character before or after if so.
- * @param {string|undefined} pre To prepend.
- * @param {string} val To include.
- * @param {string} post To append.
- * @param {boolean} raw If true, val will not be encoded.
- * @return {string} Result.
- */
- function cat( pre, val, post, raw ) {
- if ( val === undefined || val === null || val === '' ) {
- return '';
- }
- return pre + ( raw ? val : mw.Uri.encode( val ) ) + post;
- }
-
- // Regular expressions to parse many common URIs.
- var parser = {
- strict: /^(?:([^:\/?#]+):)?(?:\/\/(?:(?:([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?([^:\/?#]*)(?::(\d*))?)?((?:[^?#\/]*\/)*[^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
- loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(?:(?:([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/
- },
-
- // The order here matches the order of captured matches in the above parser regexes.
- properties = [
- 'protocol', // http
- 'user', // usr
- 'password', // pwd
- 'host', // www.test.com
- 'port', // 81
- 'path', // /dir/dir.2/index.htm
- 'query', // q1=0&&test1&test2=value (will become { q1: '0', test1: '', test2: 'value' } )
- 'fragment' // top
- ];
-
-
- /**
- * We use a factory to inject a document location, for relative URLs, including protocol-relative URLs.
- * so the library is still testable & purely functional.
- */
- mw.UriRelative = function ( documentLocation ) {
- var defaultUri;
-
- /**
- * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
- * @constructor
- * @param {Object|string} uri URI string, or an Object with appropriate properties (especially another URI object to clone).
- * Object must have non-blank 'protocol', 'host', and 'path' properties.
- * This parameter is optional. If omitted (or set to undefined, null or empty string), then an object will be created
- * for the default uri of this constructor (e.g. document.location for mw.Uri in MediaWiki core).
- * @param {Object|boolean} Object with options, or (backwards compatibility) a boolean for strictMode
- * - {boolean} strictMode Trigger strict mode parsing of the url. Default: false
- * - {boolean} overrideKeys Wether to let duplicate query parameters override eachother (true) or automagically
- * convert to an array (false, default).
- */
- function Uri( uri, options ) {
- options = typeof options === 'object' ? options : { strictMode: !!options };
- options = $.extend( {
- strictMode: false,
- overrideKeys: false
- }, options );
-
- if ( uri !== undefined && uri !== null && uri !== '' ) {
- if ( typeof uri === 'string' ) {
- this.parse( uri, options );
- } else if ( typeof uri === 'object' ) {
- // Copy data over from existing URI object
- for ( var prop in uri ) {
- // Only copy direct properties, not inherited ones
- if ( uri.hasOwnProperty( prop ) ) {
- // Deep copy object properties
- if ( $.isArray( uri[prop] ) || $.isPlainObject( uri[prop] ) ) {
- this[prop] = $.extend( true, {}, uri[prop] );
- } else {
- this[prop] = uri[prop];
- }
- }
- }
- if ( !this.query ) {
- this.query = {};
- }
- }
- } else {
- // If we didn't get a URI in the constructor, use the default one.
- return defaultUri.clone();
- }
-
- // protocol-relative URLs
- if ( !this.protocol ) {
- this.protocol = defaultUri.protocol;
- }
- // No host given:
- if ( !this.host ) {
- this.host = defaultUri.host;
- // port ?
- if ( !this.port ) {
- this.port = defaultUri.port;
- }
- }
- if ( this.path && this.path.charAt( 0 ) !== '/' ) {
- // A real relative URL, relative to defaultUri.path. We can't really handle that since we cannot
- // figure out whether the last path component of defaultUri.path is a directory or a file.
- throw new Error( 'Bad constructor arguments' );
- }
- if ( !( this.protocol && this.host && this.path ) ) {
- throw new Error( 'Bad constructor arguments' );
- }
- }
-
- /**
- * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986
- * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a +
- * @param {string} s String to encode.
- * @return {string} Encoded string for URI.
- */
- Uri.encode = function ( s ) {
- return encodeURIComponent( s )
- .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28')
- .replace( /\)/g, '%29').replace( /\*/g, '%2A')
- .replace( /%20/g, '+' );
- };
-
- /**
- * Standard decodeURIComponent, with '+' to space.
- * @param {string} s String encoded for URI.
- * @return {string} Decoded string.
- */
- Uri.decode = function ( s ) {
- return decodeURIComponent( s.replace( /\+/g, '%20' ) );
- };
-
- Uri.prototype = {
-
- /**
- * Parse a string and set our properties accordingly.
- * @param {string} str URI
- * @param {Object} options
- * @return {boolean} Success.
- */
- parse: function ( str, options ) {
- var q,
- uri = this,
- matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str );
- $.each( properties, function ( i, property ) {
- uri[ property ] = matches[ i + 1 ];
- } );
-
- // uri.query starts out as the query string; we will parse it into key-val pairs then make
- // that object the "query" property.
- // we overwrite query in uri way to make cloning easier, it can use the same list of properties.
- q = {};
- // using replace to iterate over a string
- if ( uri.query ) {
- uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ( $0, $1, $2, $3 ) {
- var k, v;
- if ( $1 ) {
- k = Uri.decode( $1 );
- v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 );
-
- // If overrideKeys, always (re)set top level value.
- // If not overrideKeys but this key wasn't set before, then we set it as well.
- if ( options.overrideKeys || q[ k ] === undefined ) {
- q[ k ] = v;
-
- // Use arrays if overrideKeys is false and key was already seen before
- } else {
- // Once before, still a string, turn into an array
- if ( typeof q[ k ] === 'string' ) {
- q[ k ] = [ q[ k ] ];
- }
- // Add to the array
- if ( $.isArray( q[ k ] ) ) {
- q[ k ].push( v );
- }
- }
- }
- } );
- }
- this.query = q;
- },
-
- /**
- * Returns user and password portion of a URI.
- * @return {string}
- */
- getUserInfo: function () {
- return cat( '', this.user, cat( ':', this.password, '' ) );
- },
-
- /**
- * Gets host and port portion of a URI.
- * @return {string}
- */
- getHostPort: function () {
- return this.host + cat( ':', this.port, '' );
- },
-
- /**
- * Returns the userInfo and host and port portion of the URI.
- * In most real-world URLs, this is simply the hostname, but it is more general.
- * @return {string}
- */
- getAuthority: function () {
- return cat( '', this.getUserInfo(), '@' ) + this.getHostPort();
- },
-
- /**
- * Returns the query arguments of the URL, encoded into a string
- * Does not preserve the order of arguments passed into the URI. Does handle escaping.
- * @return {string}
- */
- getQueryString: function () {
- var args = [];
- $.each( this.query, function ( key, val ) {
- var k = Uri.encode( key ),
- vals = $.isArray( val ) ? val : [ val ];
- $.each( vals, function ( i, v ) {
- if ( v === null ) {
- args.push( k );
- } else if ( k === 'title' ) {
- args.push( k + '=' + mw.util.wikiUrlencode( v ) );
- } else {
- args.push( k + '=' + Uri.encode( v ) );
- }
- } );
- } );
- return args.join( '&' );
- },
-
- /**
- * Returns everything after the authority section of the URI
- * @return {string}
- */
- getRelativePath: function () {
- return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' );
- },
-
- /**
- * Gets the entire URI string. May not be precisely the same as input due to order of query arguments.
- * @return {string} The URI string.
- */
- toString: function () {
- return this.protocol + '://' + this.getAuthority() + this.getRelativePath();
- },
-
- /**
- * Clone this URI
- * @return {Object} new URI object with same properties
- */
- clone: function () {
- return new Uri( this );
- },
-
- /**
- * Extend the query -- supply query parameters to override or add to ours
- * @param {Object} query parameters in key-val form to override or add
- * @return {Object} this URI object
- */
- extend: function ( parameters ) {
- $.extend( this.query, parameters );
- return this;
- }
- };
-
- defaultUri = new Uri( documentLocation );
-
- return Uri;
- };
-
- // if we are running in a browser, inject the current document location, for relative URLs
- if ( document && document.location && document.location.href ) {
- mw.Uri = mw.UriRelative( document.location.href );
- }
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.debug.css b/resources/mediawiki/mediawiki.debug.css
deleted file mode 100644
index 513cb847..00000000
--- a/resources/mediawiki/mediawiki.debug.css
+++ /dev/null
@@ -1,183 +0,0 @@
-.mw-debug {
- width: 100%;
- background-color: #eee;
- border-top: 1px solid #aaa;
-}
-
-.mw-debug pre {
- font-size: 11px;
- padding: 0;
- margin: 0;
- background: none;
- border: none;
-}
-
-.mw-debug table {
- border-spacing: 0;
- width: 100%;
- table-layout: fixed;
-}
-
-.mw-debug table tr {
- background-color: #fff;
-}
-
-.mw-debug table tr:nth-child(even) {
- background-color: #f9f9f9;
-}
-
-.mw-debug table td, .mw-debug table th {
- padding: 4px 10px;
-}
-
-.mw-debug table td {
- border-bottom: 1px solid #eee;
- word-wrap: break-word;
-}
-
-.mw-debug table td.nr {
- text-align: right;
-}
-
-.mw-debug table td span.stats {
- color: #808080;
-}
-
-.mw-debug ul {
- margin: 0;
- list-style: none;
-}
-
-.mw-debug li {
- padding: 4px 0;
- width: 100%;
-}
-
-.mw-debug-bits {
- text-align: center;
- border-bottom: 1px solid #aaa;
-}
-
-.mw-debug-bit {
- display: inline-block;
- padding: 10px 5px;
- font-size: 13px;
- /* IE-hack for display: inline-block */
- zoom: 1;
- *display:inline;
-}
-
-.mw-debug-panelink {
- background-color: #eee;
- border-right: 1px solid #ccc;
-}
-
-.mw-debug-panelink:first-child {
- border-left: 1px solid #ccc;
-}
-
-.mw-debug-panelink:hover {
- background-color: #fefefe;
- cursor: pointer;
-}
-.mw-debug-panelink.current {
- background-color: #dedede;
-
-}
-a.mw-debug-panelabel,
-a.mw-debug-panelabel:visited {
- color: #000;
-}
-
-.mw-debug-pane {
- height: 300px;
- overflow: scroll;
- display: none;
- font-size: 11px;
- background-color: #e1eff2;
- box-sizing: border-box;
-}
-
-#mw-debug-pane-debuglog,
-#mw-debug-pane-request {
- padding: 20px;
-}
-
-#mw-debug-pane-request table {
- width: 100%;
- margin: 10px 0 30px;
-}
-
-#mw-debug-pane-request tr,
-#mw-debug-pane-request th,
-#mw-debug-pane-request td,
-#mw-debug-pane-request table {
- border: 1px solid #D0DBB3;
- border-collapse: collapse;
- margin: 0;
-}
-
-#mw-debug-pane-request th,
-#mw-debug-pane-request td {
- font-size: 12px;
- padding: 8px 10px;
-}
-
-#mw-debug-pane-request th {
- background-color: #F1F7E2;
- font-weight: bold;
-}
-
-#mw-debug-pane-request td {
- background-color: white;
-}
-
-#mw-debug-console tr td:first-child {
- font-weight: bold;
- vertical-align: top;
-}
-
-#mw-debug-console tr td:last-child {
- vertical-align: top;
-}
-
-.mw-debug-console-log {
- background-color: #add8e6;
-}
-
-.mw-debug-console-warn {
- background-color: #ffa07a;
-}
-
-.mw-debug-console-deprecated {
- background-color: #ffb6c1;
-}
-
-.mw-debug-backtrace {
- padding: 5px 10px;
- margin: 5px;
- background-color: #dedede;
-}
-
-.mw-debug-backtrace span {
- font-weight: bold;
- color: #111;
-}
-
-.mw-debug-backtrace ul {
- padding-left: 10px;
-}
-
-.mw-debug-backtrace li {
- width: auto;
- padding: 0;
- color: #333;
- font-size: 10px;
- margin-bottom: 0;
- line-height: 1em;
-}
-
-/* Cheapo hack to hide the first 3 lines of the backtrace */
-.mw-debug-backtrace li:nth-child(-n+3) {
- display: none;
-}
diff --git a/resources/mediawiki/mediawiki.debug.init.js b/resources/mediawiki/mediawiki.debug.init.js
deleted file mode 100644
index 0f85e80d..00000000
--- a/resources/mediawiki/mediawiki.debug.init.js
+++ /dev/null
@@ -1,3 +0,0 @@
-jQuery( function () {
- mediaWiki.Debug.init();
-} );
diff --git a/resources/mediawiki/mediawiki.debug.js b/resources/mediawiki/mediawiki.debug.js
deleted file mode 100644
index 986917a1..00000000
--- a/resources/mediawiki/mediawiki.debug.js
+++ /dev/null
@@ -1,365 +0,0 @@
-/**
- * JavaScript for the new debug toolbar, enabled through $wgDebugToolbar.
- *
- * @author John Du Hart
- * @since 1.19
- */
-
-( function ( mw, $ ) {
- 'use strict';
-
- var debug,
- hovzer = $.getFootHovzer();
-
- debug = mw.Debug = {
- /**
- * Toolbar container element
- *
- * @var {jQuery}
- */
- $container: null,
-
- /**
- * Object containing data for the debug toolbar
- *
- * @var {Object}
- */
- data: {},
-
- /**
- * Initializes the debugging pane.
- * Shouldn't be called before the document is ready
- * (since it binds to elements on the page).
- *
- * @param {Object} data, defaults to 'debugInfo' from mw.config
- */
- init: function ( data ) {
-
- this.data = data || mw.config.get( 'debugInfo' );
- this.buildHtml();
-
- // Insert the container into the DOM
- hovzer.$.append( this.$container );
- hovzer.update();
-
- $( '.mw-debug-panelink' ).click( this.switchPane );
- },
-
- /**
- * Switches between panes
- *
- * @todo Store cookie for last pane open
- * @context {Element}
- * @param {jQuery.Event} e
- */
- switchPane: function ( e ) {
- var currentPaneId = debug.$container.data( 'currentPane' ),
- requestedPaneId = $(this).prop( 'id' ).substr( 9 ),
- $currentPane = $( '#mw-debug-pane-' + currentPaneId ),
- $requestedPane = $( '#mw-debug-pane-' + requestedPaneId ),
- hovDone = false;
-
- function updateHov() {
- if ( !hovDone ) {
- hovzer.update();
- hovDone = true;
- }
- }
-
- // Skip hash fragment handling. Prevents screen from jumping.
- e.preventDefault();
-
- $( this ).addClass( 'current ');
- $( '.mw-debug-panelink' ).not( this ).removeClass( 'current ');
-
- // Hide the current pane
- if ( requestedPaneId === currentPaneId ) {
- $currentPane.slideUp( updateHov );
- debug.$container.data( 'currentPane', null );
- return;
- }
-
- debug.$container.data( 'currentPane', requestedPaneId );
-
- if ( currentPaneId === undefined || currentPaneId === null ) {
- $requestedPane.slideDown( updateHov );
- } else {
- $currentPane.hide();
- $requestedPane.show();
- updateHov();
- }
- },
-
- /**
- * Constructs the HTML for the debugging toolbar
- */
- buildHtml: function () {
- var $container, $bits, panes, id, gitInfo;
-
- $container = $( '<div id="mw-debug-toolbar" class="mw-debug" lang="en" dir="ltr"></div>' );
-
- $bits = $( '<div class="mw-debug-bits"></div>' );
-
- /**
- * Returns a jQuery element for a debug-bit div
- *
- * @param id
- * @return {jQuery}
- */
- function bitDiv( id ) {
- return $( '<div>' ).prop({
- id: 'mw-debug-' + id,
- className: 'mw-debug-bit'
- })
- .appendTo( $bits );
- }
-
- /**
- * Returns a jQuery element for a pane link
- *
- * @param id
- * @param text
- * @return {jQuery}
- */
- function paneLabel( id, text ) {
- return $( '<a>' )
- .prop({
- className: 'mw-debug-panelabel',
- href: '#mw-debug-pane-' + id
- })
- .text( text );
- }
-
- /**
- * Returns a jQuery element for a debug-bit div with a for a pane link
- *
- * @param id CSS id snippet. Will be prefixed with 'mw-debug-'
- * @param text Text to show
- * @param count Optional count to show
- * @return {jQuery}
- */
- function paneTriggerBitDiv( id, text, count ) {
- if ( count ) {
- text = text + ' (' + count + ')';
- }
- return $( '<div>' ).prop({
- id: 'mw-debug-' + id,
- className: 'mw-debug-bit mw-debug-panelink'
- })
- .append( paneLabel( id, text ) )
- .appendTo( $bits );
- }
-
- paneTriggerBitDiv( 'console', 'Console', this.data.log.length );
-
- paneTriggerBitDiv( 'querylist', 'Queries', this.data.queries.length );
-
- paneTriggerBitDiv( 'debuglog', 'Debug log', this.data.debugLog.length );
-
- paneTriggerBitDiv( 'request', 'Request' );
-
- paneTriggerBitDiv( 'includes', 'PHP includes', this.data.includes.length );
-
- gitInfo = '';
- if ( this.data.gitRevision !== false ) {
- gitInfo = '(' + this.data.gitRevision.substring( 0, 7 ) + ')';
- if ( this.data.gitViewUrl !== false ) {
- gitInfo = $( '<a>' )
- .attr( 'href', this.data.gitViewUrl )
- .text( gitInfo );
- }
- }
-
- bitDiv( 'mwversion' )
- .append( $( '<a href="//www.mediawiki.org/">MediaWiki</a>' ) )
- .append( document.createTextNode( ': ' + this.data.mwVersion + ' ' ) )
- .append( gitInfo );
-
- if ( this.data.gitBranch !== false ) {
- bitDiv( 'gitbranch' ).text( 'Git branch: ' + this.data.gitBranch );
- }
-
- bitDiv( 'phpversion' )
- .append( $( '<a href="//www.php.net/"></a>' ).text( 'PHP' ) )
- .append( ': ' + this.data.phpVersion );
-
- bitDiv( 'time' )
- .text( 'Time: ' + this.data.time.toFixed( 5 ) );
-
- bitDiv( 'memory' )
- .text( 'Memory: ' + this.data.memory + ' (Peak: ' + this.data.memoryPeak + ')' );
-
- $bits.appendTo( $container );
-
- panes = {
- console: this.buildConsoleTable(),
- querylist: this.buildQueryTable(),
- debuglog: this.buildDebugLogTable(),
- request: this.buildRequestPane(),
- includes: this.buildIncludesPane()
- };
-
- for ( id in panes ) {
- if ( !panes.hasOwnProperty( id ) ) {
- continue;
- }
-
- $( '<div>' )
- .prop({
- className: 'mw-debug-pane',
- id: 'mw-debug-pane-' + id
- })
- .append( panes[id] )
- .appendTo( $container );
- }
-
- this.$container = $container;
- },
-
- /**
- * Builds the console panel
- */
- buildConsoleTable: function () {
- var $table, entryTypeText, i, length, entry;
-
- $table = $( '<table id="mw-debug-console">' );
-
- $( '<colgroup>' ).css( 'width', /* padding = */ 20 + ( 10 * /* fontSize = */ 11 ) ).appendTo( $table );
- $( '<colgroup>' ).appendTo( $table );
- $( '<colgroup>' ).css( 'width', 350 ).appendTo( $table );
-
-
- entryTypeText = function ( entryType ) {
- switch ( entryType ) {
- case 'log':
- return 'Log';
- case 'warn':
- return 'Warning';
- case 'deprecated':
- return 'Deprecated';
- default:
- return 'Unknown';
- }
- };
-
- for ( i = 0, length = this.data.log.length; i < length; i += 1 ) {
- entry = this.data.log[i];
- entry.typeText = entryTypeText( entry.type );
-
- $( '<tr>' )
- .append( $( '<td>' )
- .text( entry.typeText )
- .addClass( 'mw-debug-console-' + entry.type )
- )
- .append( $( '<td>' ).html( entry.msg ) )
- .append( $( '<td>' ).text( entry.caller ) )
- .appendTo( $table );
- }
-
- return $table;
- },
-
- /**
- * Query list pane
- */
- buildQueryTable: function () {
- var $table, i, length, query;
-
- $table = $( '<table id="mw-debug-querylist"></table>' );
-
- $( '<tr>' )
- .append( $( '<th>#</th>' ).css( 'width', '4em' ) )
- .append( $( '<th>SQL</th>' ) )
- .append( $( '<th>Time</th>' ).css( 'width', '8em' ) )
- .append( $( '<th>Call</th>' ).css( 'width', '18em' ) )
- .appendTo( $table );
-
- for ( i = 0, length = this.data.queries.length; i < length; i += 1 ) {
- query = this.data.queries[i];
-
- $( '<tr>' )
- .append( $( '<td>' ).text( i + 1 ) )
- .append( $( '<td>' ).text( query.sql ) )
- .append( $( '<td class="stats">' ).text( ( query.time * 1000 ).toFixed( 4 ) + 'ms' ) )
- .append( $( '<td>' ).text( query['function'] ) )
- .appendTo( $table );
- }
-
-
- return $table;
- },
-
- /**
- * Legacy debug log pane
- */
- buildDebugLogTable: function () {
- var $list, i, length, line;
- $list = $( '<ul>' );
-
- for ( i = 0, length = this.data.debugLog.length; i < length; i += 1 ) {
- line = this.data.debugLog[i];
- $( '<li>' )
- .html( mw.html.escape( line ).replace( /\n/g, '<br />\n' ) )
- .appendTo( $list );
- }
-
- return $list;
- },
-
- /**
- * Request information pane
- */
- buildRequestPane: function () {
-
- function buildTable( title, data ) {
- var $unit, $table, key;
-
- $unit = $( '<div>' ).append( $( '<h2>' ).text( title ) );
-
- $table = $( '<table>' ).appendTo( $unit );
-
- $( '<tr>' )
- .html( '<th>Key</th><th>Value</th>' )
- .appendTo( $table );
-
- for ( key in data ) {
- if ( !data.hasOwnProperty( key ) ) {
- continue;
- }
-
- $( '<tr>' )
- .append( $( '<th>' ).text( key ) )
- .append( $( '<td>' ).text( data[key] ) )
- .appendTo( $table );
- }
-
- return $unit;
- }
-
- return $( '<div>' )
- .text( this.data.request.method + ' ' + this.data.request.url )
- .append( buildTable( 'Headers', this.data.request.headers ) )
- .append( buildTable( 'Parameters', this.data.request.params ) );
- },
-
- /**
- * Included files pane
- */
- buildIncludesPane: function () {
- var $table, i, length, file;
-
- $table = $( '<table>' );
-
- for ( i = 0, length = this.data.includes.length; i < length; i += 1 ) {
- file = this.data.includes[i];
- $( '<tr>' )
- .append( $( '<td>' ).text( file.name ) )
- .append( $( '<td class="nr">' ).text( file.size ) )
- .appendTo( $table );
- }
-
- return $table;
- }
- };
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.feedback.css b/resources/mediawiki/mediawiki.feedback.css
deleted file mode 100644
index 6bd47bb2..00000000
--- a/resources/mediawiki/mediawiki.feedback.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.feedback-spinner {
- display: inline-block;
- zoom: 1;
- *display: inline; /* IE7 and below */
- /* @embed */
- background: url(mediawiki.feedback.spinner.gif);
- width: 18px;
- height: 18px;
-}
diff --git a/resources/mediawiki/mediawiki.feedback.js b/resources/mediawiki/mediawiki.feedback.js
deleted file mode 100644
index 1afe51ef..00000000
--- a/resources/mediawiki/mediawiki.feedback.js
+++ /dev/null
@@ -1,279 +0,0 @@
-/**
- * mediawiki.feedback
- *
- * @author Ryan Kaldari, 2010
- * @author Neil Kandalgaonkar, 2010-11
- * @since 1.19
- *
- * This is a way of getting simple feedback from users. It's useful
- * for testing new features -- users can give you feedback without
- * the difficulty of opening a whole new talk page. For this reason,
- * it also tends to collect a wider range of both positive and negative
- * comments. However you do need to tend to the feedback page. It will
- * get long relatively quickly, and you often get multiple messages
- * reporting the same issue.
- *
- * It takes the form of thing on your page which, when clicked, opens a small
- * dialog box. Submitting that dialog box appends its contents to a
- * wiki page that you specify, as a new section.
- *
- * Not compatible with LiquidThreads.
- *
- * Minimal example in how to use it:
- *
- * var feedback = new mw.Feedback();
- * $( '#myButton' ).click( function () { feedback.launch(); } );
- *
- * You can also launch the feedback form with a prefilled subject and body.
- * See the docs for the launch() method.
- */
-( function ( mw, $ ) {
- /**
- * Thingy for collecting user feedback on a wiki page
- * @param {Array} options -- optional, all properties optional.
- * api: {mw.Api} if omitted, will just create a standard API
- * title: {mw.Title} the title of the page where you collect feedback. Defaults to "Feedback".
- * dialogTitleMessageKey: {String} message key for the title of the dialog box
- * bugsLink: {mw.Uri|String} url where bugs can be posted
- * bugsListLink: {mw.Uri|String} url where bugs can be listed
- */
- mw.Feedback = function ( options ) {
- if ( options === undefined ) {
- options = {};
- }
-
- if ( options.api === undefined ) {
- options.api = new mw.Api();
- }
-
- if ( options.title === undefined ) {
- options.title = new mw.Title( 'Feedback' );
- }
-
- if ( options.dialogTitleMessageKey === undefined ) {
- options.dialogTitleMessageKey = 'feedback-submit';
- }
-
- if ( options.bugsLink === undefined ) {
- options.bugsLink = '//bugzilla.wikimedia.org/enter_bug.cgi';
- }
-
- if ( options.bugsListLink === undefined ) {
- options.bugsListLink = '//bugzilla.wikimedia.org/query.cgi';
- }
-
- $.extend( this, options );
- this.setup();
- };
-
- mw.Feedback.prototype = {
- setup: function () {
- var $feedbackPageLink,
- $bugNoteLink,
- $bugsListLink,
- fb = this;
-
- $feedbackPageLink = $( '<a>' )
- .attr( {
- href: fb.title.getUrl(),
- target: '_blank'
- } )
- .css( {
- whiteSpace: 'nowrap'
- } );
-
- $bugNoteLink = $( '<a>' ).attr( { href: '#' } ).click( function () {
- fb.displayBugs();
- } );
-
- $bugsListLink = $( '<a>' ).attr( {
- href: fb.bugsListLink,
- target: '_blank'
- } );
-
- // TODO: Use a stylesheet instead of these inline styles
- this.$dialog =
- $( '<div style="position: relative;"></div>' ).append(
- $( '<div class="feedback-mode feedback-form"></div>' ).append(
- $( '<small>' ).append(
- $( '<p>' ).msg(
- 'feedback-bugornote',
- $bugNoteLink,
- fb.title.getNameText(),
- $feedbackPageLink.clone()
- )
- ),
- $( '<div style="margin-top: 1em;"></div>' ).append(
- mw.msg( 'feedback-subject' ),
- $( '<br>' ),
- $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width: 99%;"/>' )
- ),
- $( '<div style="margin-top: 0.4em;"></div>' ).append(
- mw.msg( 'feedback-message' ),
- $( '<br>' ),
- $( '<textarea name="message" class="feedback-message" style="width: 99%;" rows="5" cols="60"></textarea>' )
- )
- ),
- $( '<div class="feedback-mode feedback-bugs"></div>' ).append(
- $( '<p>' ).msg( 'feedback-bugcheck', $bugsListLink )
- ),
- $( '<div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' ).append(
- mw.msg( 'feedback-adding' ),
- $( '<br>' ),
- $( '<span class="feedback-spinner"></span>' )
- ),
- $( '<div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>' ).msg(
- 'feedback-thanks', fb.title.getNameText(), $feedbackPageLink.clone()
- ),
- $( '<div class="feedback-mode feedback-error" style="position: relative;"></div>' ).append(
- $( '<div class="feedback-error-msg style="color: #990000; margin-top: 0.4em;"></div>' )
- )
- );
-
- // undo some damage from dialog css
- this.$dialog.find( 'a' ).css( {
- color: '#0645ad'
- } );
-
- this.$dialog.dialog({
- width: 500,
- autoOpen: false,
- title: mw.msg( this.dialogTitleMessageKey ),
- modal: true,
- buttons: fb.buttons
- });
-
- this.subjectInput = this.$dialog.find( 'input.feedback-subject' ).get(0);
- this.messageInput = this.$dialog.find( 'textarea.feedback-message' ).get(0);
-
- },
-
- display: function ( s ) {
- this.$dialog.dialog( { buttons:{} } ); // hide the buttons
- this.$dialog.find( '.feedback-mode' ).hide(); // hide everything
- this.$dialog.find( '.feedback-' + s ).show(); // show the desired div
- },
-
- displaySubmitting: function () {
- this.display( 'submitting' );
- },
-
- displayBugs: function () {
- var fb = this,
- bugsButtons = {};
- this.display( 'bugs' );
- bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function () {
- window.open( fb.bugsLink, '_blank' );
- };
- bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
- fb.cancel();
- };
- this.$dialog.dialog( {
- buttons: bugsButtons
- } );
- },
-
- displayThanks: function () {
- var fb = this,
- closeButton = {};
- this.display( 'thanks' );
- closeButton[ mw.msg( 'feedback-close' ) ] = function () {
- fb.$dialog.dialog( 'close' );
- };
- this.$dialog.dialog( {
- buttons: closeButton
- } );
- },
-
- /**
- * Display the feedback form
- * @param {Object} optional prefilled contents for the feedback form. Object with properties:
- * subject: {String}
- * message: {String}
- */
- displayForm: function ( contents ) {
- var fb = this,
- formButtons = {};
- this.subjectInput.value = ( contents && contents.subject ) ? contents.subject : '';
- this.messageInput.value = ( contents && contents.message ) ? contents.message : '';
-
- this.display( 'form' );
-
- // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized
- formButtons[ mw.msg( 'feedback-submit' ) ] = function () {
- fb.submit();
- };
- formButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
- fb.cancel();
- };
- this.$dialog.dialog( { buttons: formButtons } ); // put the buttons back
- },
-
- displayError: function ( message ) {
- var fb = this,
- closeButton = {};
- this.display( 'error' );
- this.$dialog.find( '.feedback-error-msg' ).msg( message );
- closeButton[ mw.msg( 'feedback-close' ) ] = function () {
- fb.$dialog.dialog( 'close' );
- };
- this.$dialog.dialog( { buttons: closeButton } );
- },
-
- cancel: function () {
- this.$dialog.dialog( 'close' );
- },
-
- submit: function () {
- var subject, message,
- fb = this;
-
- function ok( result ) {
- if ( result.edit !== undefined ) {
- if ( result.edit.result === 'Success' ) {
- fb.displayThanks();
- } else {
- // unknown API result
- fb.displayError( 'feedback-error1' );
- }
- } else {
- // edit failed
- fb.displayError( 'feedback-error2' );
- }
- }
-
- function err() {
- // ajax request failed
- fb.displayError( 'feedback-error3' );
- }
-
- // Get the values to submit.
- subject = this.subjectInput.value;
-
- // We used to include "mw.html.escape( navigator.userAgent )" but there are legal issues
- // with posting this without their explicit consent
- message = this.messageInput.value;
- if ( message.indexOf( '~~~' ) === -1 ) {
- message += ' ~~~~';
- }
-
- this.displaySubmitting();
-
- this.api.newSection( this.title, subject, message, ok, err );
- },
-
- /**
- * Modify the display form, and then open it, focusing interface on the subject.
- * @param {Object} optional prefilled contents for the feedback form. Object with properties:
- * subject: {String}
- * message: {String}
- */
- launch: function ( contents ) {
- this.displayForm( contents );
- this.$dialog.dialog( 'open' );
- this.subjectInput.focus();
- }
-
- };
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.feedback.spinner.gif b/resources/mediawiki/mediawiki.feedback.spinner.gif
deleted file mode 100644
index aed0ea41..00000000
--- a/resources/mediawiki/mediawiki.feedback.spinner.gif
+++ /dev/null
Binary files differ
diff --git a/resources/mediawiki/mediawiki.hidpi.js b/resources/mediawiki/mediawiki.hidpi.js
deleted file mode 100644
index ecee450c..00000000
--- a/resources/mediawiki/mediawiki.hidpi.js
+++ /dev/null
@@ -1,5 +0,0 @@
-jQuery( function ( $ ) {
- // Apply hidpi images on DOM-ready
- // Some may have already partly preloaded at low resolution.
- $( 'body' ).hidpi();
-} );
diff --git a/resources/mediawiki/mediawiki.htmlform.js b/resources/mediawiki/mediawiki.htmlform.js
deleted file mode 100644
index de068598..00000000
--- a/resources/mediawiki/mediawiki.htmlform.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/**
- * Utility functions for jazzing up HTMLForm elements.
- */
-( function ( mw, $ ) {
-
- /**
- * jQuery plugin to fade or snap to visible state.
- *
- * @param {boolean} instantToggle [optional]
- * @return {jQuery}
- */
- $.fn.goIn = function ( instantToggle ) {
- if ( instantToggle === true ) {
- return $(this).show();
- }
- return $(this).stop( true, true ).fadeIn();
- };
-
- /**
- * jQuery plugin to fade or snap to hiding state.
- *
- * @param {boolean} instantToggle [optional]
- * @return jQuery
- */
- $.fn.goOut = function ( instantToggle ) {
- if ( instantToggle === true ) {
- return $(this).hide();
- }
- return $(this).stop( true, true ).fadeOut();
- };
-
- /**
- * Bind a function to the jQuery object via live(), and also immediately trigger
- * the function on the objects with an 'instant' parameter set to true.
- * @param {Function} callback Takes one parameter, which is {true} when the
- * event is called immediately, and {jQuery.Event} when triggered from an event.
- */
- $.fn.liveAndTestAtStart = function ( callback ){
- $(this)
- .live( 'change', callback )
- .each( function () {
- callback.call( this, true );
- } );
- };
-
- $( function () {
-
- // Animate the SelectOrOther fields, to only show the text field when
- // 'other' is selected.
- $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) {
- var $other = $( '#' + $(this).attr( 'id' ) + '-other' );
- $other = $other.add( $other.siblings( 'br' ) );
- if ( $(this).val() === 'other' ) {
- $other.goIn( instant );
- } else {
- $other.goOut( instant );
- }
- });
-
- } );
-
- function addMulti( $oldContainer, $container ) {
- var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
- oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
- $select = $( '<select>' ),
- dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
- oldClass = $.trim( oldClass );
- $select.attr( {
- name: name,
- multiple: 'multiple',
- 'data-placeholder': dataPlaceholder.plain(),
- 'class': 'htmlform-chzn-select mw-input ' + oldClass
- } );
- $oldContainer.find( 'input' ).each( function () {
- var $oldInput = $(this),
- checked = $oldInput.prop( 'checked' ),
- $option = $( '<option>' );
- $option.prop( 'value', $oldInput.prop( 'value' ) );
- if ( checked ) {
- $option.prop( 'selected', true );
- }
- $option.text( $oldInput.prop( 'value' ) );
- $select.append( $option );
- } );
- $container.append( $select );
- }
-
- function convertCheckboxesToMulti( $oldContainer, type ) {
- var $fieldLabel = $( '<td>' ),
- $td = $( '<td>' ),
- $fieldLabelText = $( '<label>' ),
- $container;
- if ( type === 'tr' ) {
- addMulti( $oldContainer, $td );
- $container = $( '<tr>' );
- $container.append( $td );
- } else if ( type === 'div' ) {
- $fieldLabel = $( '<div>' );
- $container = $( '<div>' );
- addMulti( $oldContainer, $container );
- }
- $fieldLabel.attr( 'class', 'mw-label' );
- $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
- $fieldLabel.append( $fieldLabelText );
- $container.prepend( $fieldLabel );
- $oldContainer.replaceWith( $container );
- return $container;
- }
-
- if ( $( '.mw-chosen' ).length ) {
- mw.loader.using( 'jquery.chosen', function () {
- $( '.mw-chosen' ).each( function () {
- var type = this.nodeName.toLowerCase(),
- $converted = convertCheckboxesToMulti( $( this ), type );
- $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
- } );
- } );
- }
-
- $( function () {
- var $matrixTooltips = $( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
- if ( $matrixTooltips.length ) {
- mw.loader.using( 'jquery.tipsy', function () {
- $matrixTooltips.tipsy( { gravity: 's' } );
- } );
- }
- } );
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.icon.css b/resources/mediawiki/mediawiki.icon.css
deleted file mode 100644
index f61b7257..00000000
--- a/resources/mediawiki/mediawiki.icon.css
+++ /dev/null
@@ -1,15 +0,0 @@
-/* General-purpose icons via CSS. Classes here should be named "mw-icon-*". */
-
-/* For the collapsed and expanded arrows, we also provide selectors to make it
- * easy to use them with jquery.makeCollapsible. */
-.mw-icon-arrow-collapsed,
-.mw-collapsible-arrow.mw-collapsible-toggle-collapsed {
- /* @embed */
- background: url(images/arrow-collapsed-ltr.png) no-repeat left bottom;
-}
-
-.mw-icon-arrow-expanded,
-.mw-collapsible-arrow.mw-collapsible-toggle-expanded {
- /* @embed */
- background: url(images/arrow-expanded.png) no-repeat left bottom;
-}
diff --git a/resources/mediawiki/mediawiki.inspect.js b/resources/mediawiki/mediawiki.inspect.js
deleted file mode 100644
index 2f2ca335..00000000
--- a/resources/mediawiki/mediawiki.inspect.js
+++ /dev/null
@@ -1,204 +0,0 @@
-/*!
- * Tools for inspecting page composition and performance.
- *
- * @author Ori Livneh
- * @since 1.22
- */
-/*jshint devel:true */
-( function ( mw, $ ) {
-
- function sortByProperty( array, prop, descending ) {
- var order = descending ? -1 : 1;
- return array.sort( function ( a, b ) {
- return a[prop] > b[prop] ? order : a[prop] < b[prop] ? -order : 0;
- } );
- }
-
- /**
- * @class mw.inspect
- * @singleton
- */
- var inspect = {
-
- /**
- * Calculate the byte size of a ResourceLoader module.
- *
- * @param {string} moduleName The name of the module
- * @return {number|null} Module size in bytes or null
- */
- getModuleSize: function ( moduleName ) {
- var module = mw.loader.moduleRegistry[ moduleName ],
- payload = 0;
-
- if ( mw.loader.getState( moduleName ) !== 'ready' ) {
- return null;
- }
-
- if ( !module.style && !module.script ) {
- return null;
- }
-
- // Tally CSS
- if ( module.style && $.isArray( module.style.css ) ) {
- $.each( module.style.css, function ( i, stylesheet ) {
- payload += $.byteLength( stylesheet );
- } );
- }
-
- // Tally JavaScript
- if ( $.isFunction( module.script ) ) {
- payload += $.byteLength( module.script.toString() );
- }
-
- return payload;
- },
-
- /**
- * Given CSS source, count both the total number of selectors it
- * contains and the number which match some element in the current
- * document.
- *
- * @param {string} css CSS source
- * @return Selector counts
- * @return {number} return.selectors Total number of selectors
- * @return {number} return.matched Number of matched selectors
- */
- auditSelectors: function ( css ) {
- var selectors = { total: 0, matched: 0 },
- style = document.createElement( 'style' ),
- sheet, rules;
-
- style.textContent = css;
- document.body.appendChild( style );
- // Standards-compliant browsers use .sheet.cssRules, IE8 uses .styleSheet.rules…
- sheet = style.sheet || style.styleSheet;
- rules = sheet.cssRules || sheet.rules;
- $.each( rules, function ( index, rule ) {
- selectors.total++;
- if ( document.querySelector( rule.selectorText ) !== null ) {
- selectors.matched++;
- }
- } );
- document.body.removeChild( style );
- return selectors;
- },
-
- /**
- * Get a list of all loaded ResourceLoader modules.
- *
- * @return {Array} List of module names
- */
- getLoadedModules: function () {
- return $.grep( mw.loader.getModuleNames(), function ( module ) {
- return mw.loader.getState( module ) === 'ready';
- } );
- },
-
- /**
- * Print tabular data to the console, using console.table, console.log,
- * or mw.log (in declining order of preference).
- *
- * @param {Array} data Tabular data represented as an array of objects
- * with common properties.
- */
- dumpTable: function ( data ) {
- try {
- // Bartosz made me put this here.
- if ( window.opera ) { throw window.opera; }
- // Use Function.prototype#call to force an exception on Firefox,
- // which doesn't define console#table but doesn't complain if you
- // try to invoke it.
- console.table.call( console, data );
- return;
- } catch (e) {}
- try {
- console.log( $.toJSON( data, null, 2 ) );
- return;
- } catch (e) {}
- mw.log( data );
- },
-
- /**
- * Generate and print one more reports. When invoked with no arguments,
- * print all reports.
- *
- * @param {string...} [reports] Report names to run, or unset to print
- * all available reports.
- */
- runReports: function () {
- var reports = arguments.length > 0 ?
- Array.prototype.slice.call( arguments ) :
- $.map( inspect.reports, function ( v, k ) { return k; } );
-
- $.each( reports, function ( index, name ) {
- inspect.dumpTable( inspect.reports[name]() );
- } );
- },
-
- /**
- * @class mw.inspect.reports
- * @singleton
- */
- reports: {
- /**
- * Generate a breakdown of all loaded modules and their size in
- * kilobytes. Modules are ordered from largest to smallest.
- */
- size: function () {
- // Map each module to a descriptor object.
- var modules = $.map( inspect.getLoadedModules(), function ( module ) {
- return {
- name: module,
- size: inspect.getModuleSize( module )
- };
- } );
-
- // Sort module descriptors by size, largest first.
- sortByProperty( modules, 'size', true );
-
- // Convert size to human-readable string.
- $.each( modules, function ( i, module ) {
- module.size = module.size > 1024 ?
- ( module.size / 1024 ).toFixed( 2 ) + ' KB' :
- ( module.size !== null ? module.size + ' B' : null );
- } );
-
- return modules;
- },
-
- /**
- * For each module with styles, count the number of selectors, and
- * count how many match against some element currently in the DOM.
- */
- css: function () {
- var modules = [];
-
- $.each( inspect.getLoadedModules(), function ( index, name ) {
- var css, stats, module = mw.loader.moduleRegistry[name];
-
- try {
- css = module.style.css.join();
- } catch (e) { return; } // skip
-
- stats = inspect.auditSelectors( css );
- modules.push( {
- module: name,
- allSelectors: stats.total,
- matchedSelectors: stats.matched,
- percentMatched: stats.total !== 0 ?
- ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null
- } );
- } );
- sortByProperty( modules, 'allSelectors', true );
- return modules;
- },
- }
- };
-
- if ( mw.config.get( 'debug' ) ) {
- mw.log( 'mw.inspect: reports are not available in debug mode.' );
- }
-
- mw.inspect = inspect;
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.jqueryMsg.js b/resources/mediawiki/mediawiki.jqueryMsg.js
deleted file mode 100644
index 70b9be93..00000000
--- a/resources/mediawiki/mediawiki.jqueryMsg.js
+++ /dev/null
@@ -1,1148 +0,0 @@
-/**
-* Experimental advanced wikitext parser-emitter.
-* See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs
-*
-* @author neilk@wikimedia.org
-* @author mflaschen@wikimedia.org
-*/
-( function ( mw, $ ) {
- var oldParser,
- slice = Array.prototype.slice,
- parserDefaults = {
- magic : {
- 'SITENAME' : mw.config.get( 'wgSiteName' )
- },
- // This is a whitelist based on, but simpler than, Sanitizer.php.
- // Self-closing tags are not currently supported.
- allowedHtmlElements : [
- 'b',
- 'i'
- ],
- // Key tag name, value allowed attributes for that tag.
- // See Sanitizer::setupAttributeWhitelist
- allowedHtmlCommonAttributes : [
- // HTML
- 'id',
- 'class',
- 'style',
- 'lang',
- 'dir',
- 'title',
-
- // WAI-ARIA
- 'role'
- ],
-
- // Attributes allowed for specific elements.
- // Key is element name in lower case
- // Value is array of allowed attributes for that element
- allowedHtmlAttributesByElement : {},
- messages : mw.messages,
- language : mw.language,
-
- // Same meaning as in mediawiki.js.
- //
- // Only 'text', 'parse', and 'escaped' are supported, and the
- // actual escaping for 'escaped' is done by other code (generally
- // through mediawiki.js).
- //
- // However, note that this default only
- // applies to direct calls to jqueryMsg. The default for mediawiki.js itself
- // is 'text', including when it uses jqueryMsg.
- format: 'parse'
-
- };
-
- /**
- * Wrapper around jQuery append that converts all non-objects to TextNode so append will not
- * convert what it detects as an htmlString to an element.
- *
- * Object elements of children (jQuery, HTMLElement, TextNode, etc.) will be left as is.
- *
- * @param {jQuery} $parent Parent node wrapped by jQuery
- * @param {Object|string|Array} children What to append, with the same possible types as jQuery
- * @return {jQuery} $parent
- */
- function appendWithoutParsing( $parent, children ) {
- var i, len;
-
- if ( !$.isArray( children ) ) {
- children = [children];
- }
-
- for ( i = 0, len = children.length; i < len; i++ ) {
- if ( typeof children[i] !== 'object' ) {
- children[i] = document.createTextNode( children[i] );
- }
- }
-
- return $parent.append( children );
- }
-
- /**
- * Decodes the main HTML entities, those encoded by mw.html.escape.
- *
- * @param {string} encode Encoded string
- * @return {string} String with those entities decoded
- */
- function decodePrimaryHtmlEntities( encoded ) {
- return encoded
- .replace( /&#039;/g, '\'' )
- .replace( /&quot;/g, '"' )
- .replace( /&lt;/g, '<' )
- .replace( /&gt;/g, '>' )
- .replace( /&amp;/g, '&' );
- }
-
- /**
- * Given parser options, return a function that parses a key and replacements, returning jQuery object
- * @param {Object} parser options
- * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery}
- */
- function getFailableParserFn( options ) {
- var parser = new mw.jqueryMsg.parser( options );
- /**
- * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes.
- * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into
- * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it.
- *
- * @param {Array} first element is the key, replacements may be in array in 2nd element, or remaining elements.
- * @return {jQuery}
- */
- return function ( args ) {
- var key = args[0],
- argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 );
- try {
- return parser.parse( key, argsArray );
- } catch ( e ) {
- return $( '<span>' ).text( key + ': ' + e.message );
- }
- };
- }
-
- mw.jqueryMsg = {};
-
- /**
- * Class method.
- * Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements).
- * e.g.
- * window.gM = mediaWiki.parser.getMessageFunction( options );
- * $( 'p#headline' ).html( gM( 'hello-user', username ) );
- *
- * Like the old gM() function this returns only strings, so it destroys any bindings. If you want to preserve bindings use the
- * jQuery plugin version instead. This is only included for backwards compatibility with gM().
- *
- * @param {Array} parser options
- * @return {Function} function suitable for assigning to window.gM
- */
- mw.jqueryMsg.getMessageFunction = function ( options ) {
- var failableParserFn = getFailableParserFn( options ),
- format;
-
- if ( options && options.format !== undefined ) {
- format = options.format;
- } else {
- format = parserDefaults.format;
- }
-
- /**
- * N.B. replacements are variadic arguments or an array in second parameter. In other words:
- * somefunction(a, b, c, d)
- * is equivalent to
- * somefunction(a, [b, c, d])
- *
- * @param {string} key Message key.
- * @param {Array|mixed} replacements Optional variable replacements (variadically or an array).
- * @return {string} Rendered HTML.
- */
- return function () {
- var failableResult = failableParserFn( arguments );
- if ( format === 'text' || format === 'escaped' ) {
- return failableResult.text();
- } else {
- return failableResult.html();
- }
- };
- };
-
- /**
- * Class method.
- * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to
- * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links.
- * e.g.
- * $.fn.msg = mediaWiki.parser.getJqueryPlugin( options );
- * var userlink = $( '<a>' ).click( function () { alert( "hello!!") } );
- * $( 'p#headline' ).msg( 'hello-user', userlink );
- *
- * @param {Array} parser options
- * @return {Function} function suitable for assigning to jQuery plugin, such as $.fn.msg
- */
- mw.jqueryMsg.getPlugin = function ( options ) {
- var failableParserFn = getFailableParserFn( options );
- /**
- * N.B. replacements are variadic arguments or an array in second parameter. In other words:
- * somefunction(a, b, c, d)
- * is equivalent to
- * somefunction(a, [b, c, d])
- *
- * We append to 'this', which in a jQuery plugin context will be the selected elements.
- * @param {string} key Message key.
- * @param {Array|mixed} replacements Optional variable replacements (variadically or an array).
- * @return {jQuery} this
- */
- return function () {
- var $target = this.empty();
- // TODO: Simply appendWithoutParsing( $target, failableParserFn( arguments ).contents() )
- // or Simply appendWithoutParsing( $target, failableParserFn( arguments ) )
- $.each( failableParserFn( arguments ).contents(), function ( i, node ) {
- appendWithoutParsing( $target, node );
- } );
- return $target;
- };
- };
-
- /**
- * The parser itself.
- * Describes an object, whose primary duty is to .parse() message keys.
- * @param {Array} options
- */
- mw.jqueryMsg.parser = function ( options ) {
- this.settings = $.extend( {}, parserDefaults, options );
- this.settings.onlyCurlyBraceTransform = ( this.settings.format === 'text' || this.settings.format === 'escaped' );
-
- this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic );
- };
-
- mw.jqueryMsg.parser.prototype = {
- /**
- * Cache mapping MediaWiki message keys and the value onlyCurlyBraceTransform, to the AST of the message.
- *
- * In most cases, the message is a string so this is identical.
- * (This is why we would like to move this functionality server-side).
- *
- * The two parts of the key are separated by colon. For example:
- *
- * "message-key:true": ast
- *
- * if they key is "message-key" and onlyCurlyBraceTransform is true.
- *
- * This cache is shared by all instances of mw.jqueryMsg.parser.
- *
- * @static
- */
- astCache: {},
-
- /**
- * Where the magic happens.
- * Parses a message from the key, and swaps in replacements as necessary, wraps in jQuery
- * If an error is thrown, returns original key, and logs the error
- * @param {String} key Message key.
- * @param {Array} replacements Variable replacements for $1, $2... $n
- * @return {jQuery}
- */
- parse: function ( key, replacements ) {
- return this.emitter.emit( this.getAst( key ), replacements );
- },
- /**
- * Fetch the message string associated with a key, return parsed structure. Memoized.
- * Note that we pass '[' + key + ']' back for a missing message here.
- * @param {String} key
- * @return {String|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing
- */
- getAst: function ( key ) {
- var cacheKey = [key, this.settings.onlyCurlyBraceTransform].join( ':' ), wikiText;
-
- if ( this.astCache[ cacheKey ] === undefined ) {
- wikiText = this.settings.messages.get( key );
- if ( typeof wikiText !== 'string' ) {
- wikiText = '\\[' + key + '\\]';
- }
- this.astCache[ cacheKey ] = this.wikiTextToAst( wikiText );
- }
- return this.astCache[ cacheKey ];
- },
-
- /**
- * Parses the input wikiText into an abstract syntax tree, essentially an s-expression.
- *
- * CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already.
- * n.b. We want to move this functionality to the server. Nothing here is required to be on the client.
- *
- * @param {String} message string wikitext
- * @throws Error
- * @return {Mixed} abstract syntax tree
- */
- wikiTextToAst: function ( input ) {
- var pos, settings = this.settings, concat = Array.prototype.concat,
- regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, regularLiteralWithSquareBrackets,
- doubleQuote, singleQuote, backslash, anyCharacter, asciiAlphabetLiteral,
- escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral,
- whitespace, dollar, digits, htmlDoubleQuoteAttributeValue, htmlSingleQuoteAttributeValue,
- htmlAttributeEquals, openHtmlStartTag, optionalForwardSlash, openHtmlEndTag, closeHtmlTag,
- openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openWikilink, closeWikilink, templateName, pipe, colon,
- templateContents, openTemplate, closeTemplate,
- nonWhitespaceExpression, paramExpression, expression, curlyBraceTransformExpression, result;
-
- // Indicates current position in input as we parse through it.
- // Shared among all parsing functions below.
- pos = 0;
-
- // =========================================================
- // parsing combinators - could be a library on its own
- // =========================================================
- // Try parsers until one works, if none work return null
- function choice( ps ) {
- return function () {
- var i, result;
- for ( i = 0; i < ps.length; i++ ) {
- result = ps[i]();
- if ( result !== null ) {
- return result;
- }
- }
- return null;
- };
- }
- // try several ps in a row, all must succeed or return null
- // this is the only eager one
- function sequence( ps ) {
- var i, res,
- originalPos = pos,
- result = [];
- for ( i = 0; i < ps.length; i++ ) {
- res = ps[i]();
- if ( res === null ) {
- pos = originalPos;
- return null;
- }
- result.push( res );
- }
- return result;
- }
- // run the same parser over and over until it fails.
- // must succeed a minimum of n times or return null
- function nOrMore( n, p ) {
- return function () {
- var originalPos = pos,
- result = [],
- parsed = p();
- while ( parsed !== null ) {
- result.push( parsed );
- parsed = p();
- }
- if ( result.length < n ) {
- pos = originalPos;
- return null;
- }
- return result;
- };
- }
- // There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null.
- // But using this as a combinator seems to cause problems when combined with nOrMore().
- // May be some scoping issue
- function transform( p, fn ) {
- return function () {
- var result = p();
- return result === null ? null : fn( result );
- };
- }
- // Helpers -- just make ps out of simpler JS builtin types
- function makeStringParser( s ) {
- var len = s.length;
- return function () {
- var result = null;
- if ( input.substr( pos, len ) === s ) {
- result = s;
- pos += len;
- }
- return result;
- };
- }
-
- /**
- * Makes a regex parser, given a RegExp object.
- * The regex being passed in should start with a ^ to anchor it to the start
- * of the string.
- *
- * @param {RegExp} regex anchored regex
- * @return {Function} function to parse input based on the regex
- */
- function makeRegexParser( regex ) {
- return function () {
- var matches = input.substr( pos ).match( regex );
- if ( matches === null ) {
- return null;
- }
- pos += matches[0].length;
- return matches[0];
- };
- }
-
- /**
- * ===================================================================
- * General patterns above this line -- wikitext specific parsers below
- * ===================================================================
- */
- // Parsing functions follow. All parsing functions work like this:
- // They don't accept any arguments.
- // Instead, they just operate non destructively on the string 'input'
- // As they can consume parts of the string, they advance the shared variable pos,
- // and return tokens (or whatever else they want to return).
- // some things are defined as closures and other things as ordinary functions
- // converting everything to a closure makes it a lot harder to debug... errors pop up
- // but some debuggers can't tell you exactly where they come from. Also the mutually
- // recursive functions seem not to work in all browsers then. (Tested IE6-7, Opera, Safari, FF)
- // This may be because, to save code, memoization was removed
-
- regularLiteral = makeRegexParser( /^[^{}\[\]$<\\]/ );
- regularLiteralWithoutBar = makeRegexParser(/^[^{}\[\]$\\|]/);
- regularLiteralWithoutSpace = makeRegexParser(/^[^{}\[\]$\s]/);
- regularLiteralWithSquareBrackets = makeRegexParser( /^[^{}$\\]/ );
-
- backslash = makeStringParser( '\\' );
- doubleQuote = makeStringParser( '"' );
- singleQuote = makeStringParser( '\'' );
- anyCharacter = makeRegexParser( /^./ );
-
- openHtmlStartTag = makeStringParser( '<' );
- optionalForwardSlash = makeRegexParser( /^\/?/ );
- openHtmlEndTag = makeStringParser( '</' );
- htmlAttributeEquals = makeRegexParser( /^\s*=\s*/ );
- closeHtmlTag = makeRegexParser( /^\s*>/ );
-
- function escapedLiteral() {
- var result = sequence( [
- backslash,
- anyCharacter
- ] );
- return result === null ? null : result[1];
- }
- escapedOrLiteralWithoutSpace = choice( [
- escapedLiteral,
- regularLiteralWithoutSpace
- ] );
- escapedOrLiteralWithoutBar = choice( [
- escapedLiteral,
- regularLiteralWithoutBar
- ] );
- escapedOrRegularLiteral = choice( [
- escapedLiteral,
- regularLiteral
- ] );
- // Used to define "literals" without spaces, in space-delimited situations
- function literalWithoutSpace() {
- var result = nOrMore( 1, escapedOrLiteralWithoutSpace )();
- return result === null ? null : result.join('');
- }
- // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default
- // it is not a literal in the parameter
- function literalWithoutBar() {
- var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
- return result === null ? null : result.join('');
- }
-
- // Used for wikilink page names. Like literalWithoutBar, but
- // without allowing escapes.
- function unescapedLiteralWithoutBar() {
- var result = nOrMore( 1, regularLiteralWithoutBar )();
- return result === null ? null : result.join('');
- }
-
- function literal() {
- var result = nOrMore( 1, escapedOrRegularLiteral )();
- return result === null ? null : result.join('');
- }
-
- function curlyBraceTransformExpressionLiteral() {
- var result = nOrMore( 1, regularLiteralWithSquareBrackets )();
- return result === null ? null : result.join('');
- }
-
- asciiAlphabetLiteral = makeRegexParser( /[A-Za-z]+/ );
- htmlDoubleQuoteAttributeValue = makeRegexParser( /^[^"]*/ );
- htmlSingleQuoteAttributeValue = makeRegexParser( /^[^']*/ );
-
- whitespace = makeRegexParser( /^\s+/ );
- dollar = makeStringParser( '$' );
- digits = makeRegexParser( /^\d+/ );
-
- function replacement() {
- var result = sequence( [
- dollar,
- digits
- ] );
- if ( result === null ) {
- return null;
- }
- return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ];
- }
- openExtlink = makeStringParser( '[' );
- closeExtlink = makeStringParser( ']' );
- // this extlink MUST have inner contents, e.g. [foo] not allowed; [foo bar] [foo <i>bar</i>], etc. are allowed
- function extlink() {
- var result, parsedResult;
- result = null;
- parsedResult = sequence( [
- openExtlink,
- nonWhitespaceExpression,
- whitespace,
- nOrMore( 1, expression ),
- closeExtlink
- ] );
- if ( parsedResult !== null ) {
- result = [ 'EXTLINK', parsedResult[1] ];
- // TODO (mattflaschen, 2013-03-22): Clean this up if possible.
- // It's avoiding CONCAT for single nodes, so they at least doesn't get the htmlEmitter span.
- if ( parsedResult[3].length === 1 ) {
- result.push( parsedResult[3][0] );
- } else {
- result.push( ['CONCAT'].concat( parsedResult[3] ) );
- }
- }
- return result;
- }
- // this is the same as the above extlink, except that the url is being passed on as a parameter
- function extLinkParam() {
- var result = sequence( [
- openExtlink,
- dollar,
- digits,
- whitespace,
- expression,
- closeExtlink
- ] );
- if ( result === null ) {
- return null;
- }
- return [ 'EXTLINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ];
- }
- openWikilink = makeStringParser( '[[' );
- closeWikilink = makeStringParser( ']]' );
- pipe = makeStringParser( '|' );
-
- function template() {
- var result = sequence( [
- openTemplate,
- templateContents,
- closeTemplate
- ] );
- return result === null ? null : result[1];
- }
-
- wikilinkPage = choice( [
- unescapedLiteralWithoutBar,
- template
- ] );
-
- function pipedWikilink() {
- var result = sequence( [
- wikilinkPage,
- pipe,
- expression
- ] );
- return result === null ? null : [ result[0], result[2] ];
- }
-
- wikilinkContents = choice( [
- pipedWikilink,
- wikilinkPage // unpiped link
- ] );
-
- function wikilink() {
- var result, parsedResult, parsedLinkContents;
- result = null;
-
- parsedResult = sequence( [
- openWikilink,
- wikilinkContents,
- closeWikilink
- ] );
- if ( parsedResult !== null ) {
- parsedLinkContents = parsedResult[1];
- result = [ 'WIKILINK' ].concat( parsedLinkContents );
- }
- return result;
- }
-
- // TODO: Support data- if appropriate
- function doubleQuotedHtmlAttributeValue() {
- var parsedResult = sequence( [
- doubleQuote,
- htmlDoubleQuoteAttributeValue,
- doubleQuote
- ] );
- return parsedResult === null ? null : parsedResult[1];
- }
-
- function singleQuotedHtmlAttributeValue() {
- var parsedResult = sequence( [
- singleQuote,
- htmlSingleQuoteAttributeValue,
- singleQuote
- ] );
- return parsedResult === null ? null : parsedResult[1];
- }
-
- function htmlAttribute() {
- var parsedResult = sequence( [
- whitespace,
- asciiAlphabetLiteral,
- htmlAttributeEquals,
- choice( [
- doubleQuotedHtmlAttributeValue,
- singleQuotedHtmlAttributeValue
- ] )
- ] );
- return parsedResult === null ? null : [parsedResult[1], parsedResult[3]];
- }
-
- /**
- * Checks if HTML is allowed
- *
- * @param {string} startTagName HTML start tag name
- * @param {string} endTagName HTML start tag name
- * @param {Object} attributes array of consecutive key value pairs,
- * with index 2 * n being a name and 2 * n + 1 the associated value
- * @return {boolean} true if this is HTML is allowed, false otherwise
- */
- function isAllowedHtml( startTagName, endTagName, attributes ) {
- var i, len, attributeName;
-
- startTagName = startTagName.toLowerCase();
- endTagName = endTagName.toLowerCase();
- if ( startTagName !== endTagName || $.inArray( startTagName, settings.allowedHtmlElements ) === -1 ) {
- return false;
- }
-
- for ( i = 0, len = attributes.length; i < len; i += 2 ) {
- attributeName = attributes[i];
- if ( $.inArray( attributeName, settings.allowedHtmlCommonAttributes ) === -1 &&
- $.inArray( attributeName, settings.allowedHtmlAttributesByElement[startTagName] || [] ) === -1 ) {
- return false;
- }
- }
-
- return true;
- }
-
- function htmlAttributes() {
- var parsedResult = nOrMore( 0, htmlAttribute )();
- // Un-nest attributes array due to structure of jQueryMsg operations (see emit).
- return concat.apply( ['HTMLATTRIBUTES'], parsedResult );
- }
-
- // Subset of allowed HTML markup.
- // Most elements and many attributes allowed on the server are not supported yet.
- function html() {
- var result = null, parsedOpenTagResult, parsedHtmlContents,
- parsedCloseTagResult, wrappedAttributes, attributes,
- startTagName, endTagName, startOpenTagPos, startCloseTagPos,
- endOpenTagPos, endCloseTagPos;
-
- // Break into three sequence calls. That should allow accurate reconstruction of the original HTML, and requiring an exact tag name match.
- // 1. open through closeHtmlTag
- // 2. expression
- // 3. openHtmlEnd through close
- // This will allow recording the positions to reconstruct if HTML is to be treated as text.
-
- startOpenTagPos = pos;
- parsedOpenTagResult = sequence( [
- openHtmlStartTag,
- asciiAlphabetLiteral,
- htmlAttributes,
- optionalForwardSlash,
- closeHtmlTag
- ] );
-
- if ( parsedOpenTagResult === null ) {
- return null;
- }
-
- endOpenTagPos = pos;
- startTagName = parsedOpenTagResult[1];
-
- parsedHtmlContents = nOrMore( 0, expression )();
-
- startCloseTagPos = pos;
- parsedCloseTagResult = sequence( [
- openHtmlEndTag,
- asciiAlphabetLiteral,
- closeHtmlTag
- ] );
-
- if ( parsedCloseTagResult === null ) {
- // Closing tag failed. Return the start tag and contents.
- return [ 'CONCAT', input.substring( startOpenTagPos, endOpenTagPos ) ].concat( parsedHtmlContents );
- }
-
- endCloseTagPos = pos;
- endTagName = parsedCloseTagResult[1];
- wrappedAttributes = parsedOpenTagResult[2];
- attributes = wrappedAttributes.slice( 1 );
- if ( isAllowedHtml( startTagName, endTagName, attributes) ) {
- result = [ 'HTMLELEMENT', startTagName, wrappedAttributes ].concat( parsedHtmlContents );
- } else {
- // HTML is not allowed, so contents will remain how
- // it was, while HTML markup at this level will be
- // treated as text
- // E.g. assuming script tags are not allowed:
- //
- // <script>[[Foo|bar]]</script>
- //
- // results in '&lt;script&gt;' and '&lt;/script&gt;'
- // (not treated as an HTML tag), surrounding a fully
- // parsed HTML link.
- //
- // Concatenate everything from the tag, flattening the contents.
- result = [ 'CONCAT', input.substring( startOpenTagPos, endOpenTagPos ) ].concat( parsedHtmlContents, input.substring( startCloseTagPos, endCloseTagPos ) );
- }
-
- return result;
- }
-
- templateName = transform(
- // see $wgLegalTitleChars
- // not allowing : due to the need to catch "PLURAL:$1"
- makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ),
- function ( result ) { return result.toString(); }
- );
- function templateParam() {
- var expr, result;
- result = sequence( [
- pipe,
- nOrMore( 0, paramExpression )
- ] );
- if ( result === null ) {
- return null;
- }
- expr = result[1];
- // use a CONCAT operator if there are multiple nodes, otherwise return the first node, raw.
- return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[0];
- }
-
- function templateWithReplacement() {
- var result = sequence( [
- templateName,
- colon,
- replacement
- ] );
- return result === null ? null : [ result[0], result[2] ];
- }
- function templateWithOutReplacement() {
- var result = sequence( [
- templateName,
- colon,
- paramExpression
- ] );
- return result === null ? null : [ result[0], result[2] ];
- }
- colon = makeStringParser(':');
- templateContents = choice( [
- function () {
- var res = sequence( [
- // templates can have placeholders for dynamic replacement eg: {{PLURAL:$1|one car|$1 cars}}
- // or no placeholders eg: {{GRAMMAR:genitive|{{SITENAME}}}
- choice( [ templateWithReplacement, templateWithOutReplacement ] ),
- nOrMore( 0, templateParam )
- ] );
- return res === null ? null : res[0].concat( res[1] );
- },
- function () {
- var res = sequence( [
- templateName,
- nOrMore( 0, templateParam )
- ] );
- if ( res === null ) {
- return null;
- }
- return [ res[0] ].concat( res[1] );
- }
- ] );
- openTemplate = makeStringParser('{{');
- closeTemplate = makeStringParser('}}');
- nonWhitespaceExpression = choice( [
- template,
- wikilink,
- extLinkParam,
- extlink,
- replacement,
- literalWithoutSpace
- ] );
- paramExpression = choice( [
- template,
- wikilink,
- extLinkParam,
- extlink,
- replacement,
- literalWithoutBar
- ] );
-
- expression = choice( [
- template,
- wikilink,
- extLinkParam,
- extlink,
- replacement,
- html,
- literal
- ] );
-
- // Used when only {{-transformation is wanted, for 'text'
- // or 'escaped' formats
- curlyBraceTransformExpression = choice( [
- template,
- replacement,
- curlyBraceTransformExpressionLiteral
- ] );
-
-
- /**
- * Starts the parse
- *
- * @param {Function} rootExpression root parse function
- */
- function start( rootExpression ) {
- var result = nOrMore( 0, rootExpression )();
- if ( result === null ) {
- return null;
- }
- return [ 'CONCAT' ].concat( result );
- }
- // everything above this point is supposed to be stateless/static, but
- // I am deferring the work of turning it into prototypes & objects. It's quite fast enough
- // finally let's do some actual work...
-
- // If you add another possible rootExpression, you must update the astCache key scheme.
- result = start( this.settings.onlyCurlyBraceTransform ? curlyBraceTransformExpression : expression );
-
- /*
- * For success, the p must have gotten to the end of the input
- * and returned a non-null.
- * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
- */
- if ( result === null || pos !== input.length ) {
- throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + input );
- }
- return result;
- }
-
- };
- /**
- * htmlEmitter - object which primarily exists to emit HTML from parser ASTs
- */
- mw.jqueryMsg.htmlEmitter = function ( language, magic ) {
- this.language = language;
- var jmsg = this;
- $.each( magic, function ( key, val ) {
- jmsg[ key.toLowerCase() ] = function () {
- return val;
- };
- } );
- /**
- * (We put this method definition here, and not in prototype, to make sure it's not overwritten by any magic.)
- * Walk entire node structure, applying replacements and template functions when appropriate
- * @param {Mixed} abstract syntax tree (top node or subnode)
- * @param {Array} replacements for $1, $2, ... $n
- * @return {Mixed} single-string node or array of nodes suitable for jQuery appending
- */
- this.emit = function ( node, replacements ) {
- var ret, subnodes, operation,
- jmsg = this;
- switch ( typeof node ) {
- case 'string':
- case 'number':
- ret = node;
- break;
- // typeof returns object for arrays
- case 'object':
- // node is an array of nodes
- subnodes = $.map( node.slice( 1 ), function ( n ) {
- return jmsg.emit( n, replacements );
- } );
- operation = node[0].toLowerCase();
- if ( typeof jmsg[operation] === 'function' ) {
- ret = jmsg[ operation ]( subnodes, replacements );
- } else {
- throw new Error( 'Unknown operation "' + operation + '"' );
- }
- break;
- case 'undefined':
- // Parsing the empty string (as an entire expression, or as a paramExpression in a template) results in undefined
- // Perhaps a more clever parser can detect this, and return the empty string? Or is that useful information?
- // The logical thing is probably to return the empty string here when we encounter undefined.
- ret = '';
- break;
- default:
- throw new Error( 'Unexpected type in AST: ' + typeof node );
- }
- return ret;
- };
- };
- // For everything in input that follows double-open-curly braces, there should be an equivalent parser
- // function. For instance {{PLURAL ... }} will be processed by 'plural'.
- // If you have 'magic words' then configure the parser to have them upon creation.
- //
- // An emitter method takes the parent node, the array of subnodes and the array of replacements (the values that $1, $2... should translate to).
- // Note: all such functions must be pure, with the exception of referring to other pure functions via this.language (convertPlural and so on)
- mw.jqueryMsg.htmlEmitter.prototype = {
- /**
- * Parsing has been applied depth-first we can assume that all nodes here are single nodes
- * Must return a single node to parents -- a jQuery with synthetic span
- * However, unwrap any other synthetic spans in our children and pass them upwards
- * @param {Array} nodes - mixed, some single nodes, some arrays of nodes
- * @return {jQuery}
- */
- concat: function ( nodes ) {
- var $span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' );
- $.each( nodes, function ( i, node ) {
- if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) {
- $.each( node.contents(), function ( j, childNode ) {
- appendWithoutParsing( $span, childNode );
- } );
- } else {
- // Let jQuery append nodes, arrays of nodes and jQuery objects
- // other things (strings, numbers, ..) are appended as text nodes (not as HTML strings)
- appendWithoutParsing( $span, node );
- }
- } );
- return $span;
- },
-
- /**
- * Return escaped replacement of correct index, or string if unavailable.
- * Note that we expect the parsed parameter to be zero-based. i.e. $1 should have become [ 0 ].
- * if the specified parameter is not found return the same string
- * (e.g. "$99" -> parameter 98 -> not found -> return "$99" )
- * TODO: Throw error if nodes.length > 1 ?
- * @param {Array} of one element, integer, n >= 0
- * @return {String} replacement
- */
- replace: function ( nodes, replacements ) {
- var index = parseInt( nodes[0], 10 );
-
- if ( index < replacements.length ) {
- return replacements[index];
- } else {
- // index not found, fallback to displaying variable
- return '$' + ( index + 1 );
- }
- },
-
- /**
- * Transform wiki-link
- *
- * TODO:
- * It only handles basic cases, either no pipe, or a pipe with an explicit
- * anchor.
- *
- * It does not attempt to handle features like the pipe trick.
- * However, the pipe trick should usually not be present in wikitext retrieved
- * from the server, since the replacement is done at save time.
- * It may, though, if the wikitext appears in extension-controlled content.
- *
- * @param nodes
- */
- wikilink: function ( nodes ) {
- var page, anchor, url;
-
- page = nodes[0];
- url = mw.util.getUrl( page );
-
- // [[Some Page]] or [[Namespace:Some Page]]
- if ( nodes.length === 1 ) {
- anchor = page;
- }
-
- /*
- * [[Some Page|anchor text]] or
- * [[Namespace:Some Page|anchor]
- */
- else {
- anchor = nodes[1];
- }
-
- return $( '<a />' ).attr( {
- title: page,
- href: url
- } ).text( anchor );
- },
-
- /**
- * Converts array of HTML element key value pairs to object
- *
- * @param {Array} nodes array of consecutive key value pairs, with index 2 * n being a name and 2 * n + 1 the associated value
- * @return {Object} object mapping attribute name to attribute value
- */
- htmlattributes: function ( nodes ) {
- var i, len, mapping = {};
- for ( i = 0, len = nodes.length; i < len; i += 2 ) {
- mapping[nodes[i]] = decodePrimaryHtmlEntities( nodes[i + 1] );
- }
- return mapping;
- },
-
- /**
- * Handles an (already-validated) HTML element.
- *
- * @param {Array} nodes nodes to process when creating element
- * @return {jQuery|Array} jQuery node for valid HTML or array for disallowed element
- */
- htmlelement: function ( nodes ) {
- var tagName, attributes, contents, $element;
-
- tagName = nodes.shift();
- attributes = nodes.shift();
- contents = nodes;
- $element = $( document.createElement( tagName ) ).attr( attributes );
- return appendWithoutParsing( $element, contents );
- },
-
- /**
- * Transform parsed structure into external link
- * If the href is a jQuery object, treat it as "enclosing" the link text.
- * ... function, treat it as the click handler
- * ... string, treat it as a URI
- * TODO: throw an error if nodes.length > 2 ?
- * @param {Array} of two elements, {jQuery|Function|String} and {String}
- * @return {jQuery}
- */
- extlink: function ( nodes ) {
- var $el,
- arg = nodes[0],
- contents = nodes[1];
- if ( arg instanceof jQuery ) {
- $el = arg;
- } else {
- $el = $( '<a>' );
- if ( typeof arg === 'function' ) {
- $el.click( arg ).attr( 'href', '#' );
- } else {
- $el.attr( 'href', arg.toString() );
- }
- }
- return appendWithoutParsing( $el, contents );
- },
-
- /**
- * This is basically use a combination of replace + external link (link with parameter
- * as url), but we don't want to run the regular replace here-on: inserting a
- * url as href-attribute of a link will automatically escape it already, so
- * we don't want replace to (manually) escape it as well.
- * TODO throw error if nodes.length > 1 ?
- * @param {Array} of one element, integer, n >= 0
- * @return {String} replacement
- */
- extlinkparam: function ( nodes, replacements ) {
- var replacement,
- index = parseInt( nodes[0], 10 );
- if ( index < replacements.length) {
- replacement = replacements[index];
- } else {
- replacement = '$' + ( index + 1 );
- }
- return this.extlink( [ replacement, nodes[1] ] );
- },
-
- /**
- * Transform parsed structure into pluralization
- * n.b. The first node may be a non-integer (for instance, a string representing an Arabic number).
- * So convert it back with the current language's convertNumber.
- * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ]
- * @return {String} selected pluralized form according to current language
- */
- plural: function ( nodes ) {
- var forms, count;
- count = parseFloat( this.language.convertNumber( nodes[0], true ) );
- forms = nodes.slice(1);
- return forms.length ? this.language.convertPlural( count, forms ) : '';
- },
-
- /**
- * Transform parsed structure according to gender.
- * Usage {{gender:[ gender | mw.user object ] | masculine form|feminine form|neutral form}}.
- * The first node is either a string, which can be "male" or "female",
- * or a User object (not a username).
- *
- * @param {Array} of nodes, [ {String|mw.User}, {String}, {String}, {String} ]
- * @return {String} selected gender form according to current language
- */
- gender: function ( nodes ) {
- var gender, forms;
-
- if ( nodes[0] && nodes[0].options instanceof mw.Map ) {
- gender = nodes[0].options.get( 'gender' );
- } else {
- gender = nodes[0];
- }
-
- forms = nodes.slice( 1 );
-
- return this.language.gender( gender, forms );
- },
-
- /**
- * Transform parsed structure into grammar conversion.
- * Invoked by putting {{grammar:form|word}} in a message
- * @param {Array} of nodes [{Grammar case eg: genitive}, {String word}]
- * @return {String} selected grammatical form according to current language
- */
- grammar: function ( nodes ) {
- var form = nodes[0],
- word = nodes[1];
- return word && form && this.language.convertGrammar( word, form );
- },
-
- /**
- * Tranform parsed structure into a int: (interface language) message include
- * Invoked by putting {{int:othermessage}} into a message
- * @param {Array} of nodes
- * @return {string} Other message
- */
- int: function ( nodes ) {
- return mw.jqueryMsg.getMessageFunction()( nodes[0].toLowerCase() );
- },
-
- /**
- * Takes an unformatted number (arab, no group separators and . as decimal separator)
- * and outputs it in the localized digit script and formatted with decimal
- * separator, according to the current language
- * @param {Array} of nodes
- * @return {Number|String} formatted number
- */
- formatnum: function ( nodes ) {
- var isInteger = ( nodes[1] && nodes[1] === 'R' ) ? true : false,
- number = nodes[0];
-
- return this.language.convertNumber( number, isInteger );
- }
- };
- // Deprecated! don't rely on gM existing.
- // The window.gM ought not to be required - or if required, not required here.
- // But moving it to extensions breaks it (?!)
- // Need to fix plugin so it could do attributes as well, then will be okay to remove this.
- window.gM = mw.jqueryMsg.getMessageFunction();
- $.fn.msg = mw.jqueryMsg.getPlugin();
-
- // Replace the default message parser with jqueryMsg
- oldParser = mw.Message.prototype.parser;
- mw.Message.prototype.parser = function () {
- var messageFunction;
-
- // TODO: should we cache the message function so we don't create a new one every time? Benchmark this maybe?
- // Caching is somewhat problematic, because we do need different message functions for different maps, so
- // we'd have to cache the parser as a member of this.map, which sounds a bit ugly.
- // Do not use mw.jqueryMsg unless required
- if ( this.format === 'plain' || !/\{\{|[\[<>]/.test(this.map.get( this.key ) ) ) {
- // Fall back to mw.msg's simple parser
- return oldParser.apply( this );
- }
-
- messageFunction = mw.jqueryMsg.getMessageFunction( {
- 'messages': this.map,
- // For format 'escaped', escaping part is handled by mediawiki.js
- 'format': this.format
- } );
- return messageFunction( this.key, this.parameters );
- };
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.jqueryMsg.peg b/resources/mediawiki/mediawiki.jqueryMsg.peg
deleted file mode 100644
index 7879d6fa..00000000
--- a/resources/mediawiki/mediawiki.jqueryMsg.peg
+++ /dev/null
@@ -1,81 +0,0 @@
-/* PEG grammar for a subset of wikitext, useful in the MediaWiki frontend */
-
-start
- = e:expression* { return e.length > 1 ? [ "CONCAT" ].concat(e) : e[0]; }
-
-expression
- = template
- / link
- / extlink
- / replacement
- / literal
-
-paramExpression
- = template
- / link
- / extlink
- / replacement
- / literalWithoutBar
-
-template
- = "{{" t:templateContents "}}" { return t; }
-
-templateContents
- = twr:templateWithReplacement p:templateParam* { return twr.concat(p) }
- / twr:templateWithOutReplacement p:templateParam* { return twr.concat(p) }
- / t:templateName p:templateParam* { return p.length ? [ t, p ] : [ t ] }
-
-templateWithReplacement
- = t:templateName ":" r:replacement { return [ t, r ] }
-
-templateWithOutReplacement
- = t:templateName ":" p:paramExpression { return [ t, p ] }
-
-templateParam
- = "|" e:paramExpression* { return e.length > 1 ? [ "CONCAT" ].concat(e) : e[0]; }
-
-templateName
- = tn:[A-Za-z_]+ { return tn.join('').toUpperCase() }
-
-/* TODO: Update to reflect separate piped and unpiped handling */
-link
- = "[[" w:expression "]]" { return [ 'WLINK', w ]; }
-
-extlink
- = "[" url:url whitespace text:expression "]" { return [ 'LINK', url, text ] }
-
-url
- = url:[^ ]+ { return url.join(''); }
-
-whitespace
- = [ ]+
-
-replacement
- = '$' digits:digits { return [ 'REPLACE', parseInt( digits, 10 ) - 1 ] }
-
-digits
- = [0-9]+
-
-literal
- = lit:escapedOrRegularLiteral+ { return lit.join(''); }
-
-literalWithoutBar
- = lit:escapedOrLiteralWithoutBar+ { return lit.join(''); }
-
-escapedOrRegularLiteral
- = escapedLiteral
- / regularLiteral
-
-escapedOrLiteralWithoutBar
- = escapedLiteral
- / regularLiteralWithoutBar
-
-escapedLiteral
- = "\\" escaped:. { return escaped; }
-
-regularLiteral
- = [^{}\[\]$\\]
-
-regularLiteralWithoutBar
- = [^{}\[\]$\\|]
-
diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js
deleted file mode 100644
index 5a4ff1b5..00000000
--- a/resources/mediawiki/mediawiki.js
+++ /dev/null
@@ -1,1938 +0,0 @@
-/**
- * Base library for MediaWiki.
- *
- * @class mw
- * @alternateClassName mediaWiki
- * @singleton
- */
-
-var mw = ( function ( $, undefined ) {
- 'use strict';
-
- /* Private Members */
-
- var hasOwn = Object.prototype.hasOwnProperty,
- slice = Array.prototype.slice;
-
- /**
- * Log a message to window.console, if possible. Useful to force logging of some
- * errors that are otherwise hard to detect (I.e., this logs also in production mode).
- * Gets console references in each invocation, so that delayed debugging tools work
- * fine. No need for optimization here, which would only result in losing logs.
- *
- * @private
- * @method log_
- * @param {string} msg text for the log entry.
- * @param {Error} [e]
- */
- function log( msg, e ) {
- var console = window.console;
- if ( console && console.log ) {
- console.log( msg );
- // If we have an exception object, log it through .error() to trigger
- // proper stacktraces in browsers that support it. There are no (known)
- // browsers that don't support .error(), that do support .log() and
- // have useful exception handling through .log().
- if ( e && console.error ) {
- console.error( String( e ), e );
- }
- }
- }
-
- /* Object constructors */
-
- /**
- * Creates an object that can be read from or written to from prototype functions
- * that allow both single and multiple variables at once.
- *
- * @example
- *
- * var addies, wanted, results;
- *
- * // Create your address book
- * addies = new mw.Map();
- *
- * // This data could be coming from an external source (eg. API/AJAX)
- * addies.set( {
- * 'John Doe' : '10 Wall Street, New York, USA',
- * 'Jane Jackson' : '21 Oxford St, London, UK',
- * 'Dominique van Halen' : 'Kalverstraat 7, Amsterdam, NL'
- * } );
- *
- * wanted = ['Dominique van Halen', 'George Johnson', 'Jane Jackson'];
- *
- * // You can detect missing keys first
- * if ( !addies.exists( wanted ) ) {
- * // One or more are missing (in this case: "George Johnson")
- * mw.log( 'One or more names were not found in your address book' );
- * }
- *
- * // Or just let it give you what it can
- * results = addies.get( wanted, 'Middle of Nowhere, Alaska, US' );
- * mw.log( results['Jane Jackson'] ); // "21 Oxford St, London, UK"
- * mw.log( results['George Johnson'] ); // "Middle of Nowhere, Alaska, US"
- *
- * @class mw.Map
- *
- * @constructor
- * @param {boolean} [global=false] Whether to store the values in the global window
- * object or a exclusively in the object property 'values'.
- */
- function Map( global ) {
- this.values = global === true ? window : {};
- return this;
- }
-
- Map.prototype = {
- /**
- * Get the value of one or multiple a keys.
- *
- * If called with no arguments, all values will be returned.
- *
- * @param {string|Array} selection String key or array of keys to get values for.
- * @param {Mixed} [fallback] Value to use in case key(s) do not exist.
- * @return mixed If selection was a string returns the value or null,
- * If selection was an array, returns an object of key/values (value is null if not found),
- * If selection was not passed or invalid, will return the 'values' object member (be careful as
- * objects are always passed by reference in JavaScript!).
- * @return {string|Object|null} Values as a string or object, null if invalid/inexistant.
- */
- get: function ( selection, fallback ) {
- var results, i;
- // If we only do this in the `return` block, it'll fail for the
- // call to get() from the mutli-selection block.
- fallback = arguments.length > 1 ? fallback : null;
-
- if ( $.isArray( selection ) ) {
- selection = slice.call( selection );
- results = {};
- for ( i = 0; i < selection.length; i++ ) {
- results[selection[i]] = this.get( selection[i], fallback );
- }
- return results;
- }
-
- if ( typeof selection === 'string' ) {
- if ( !hasOwn.call( this.values, selection ) ) {
- return fallback;
- }
- return this.values[selection];
- }
-
- if ( selection === undefined ) {
- return this.values;
- }
-
- // invalid selection key
- return null;
- },
-
- /**
- * Sets one or multiple key/value pairs.
- *
- * @param {string|Object} selection String key to set value for, or object mapping keys to values.
- * @param {Mixed} [value] Value to set (optional, only in use when key is a string)
- * @return {Boolean} This returns true on success, false on failure.
- */
- set: function ( selection, value ) {
- var s;
-
- if ( $.isPlainObject( selection ) ) {
- for ( s in selection ) {
- this.values[s] = selection[s];
- }
- return true;
- }
- if ( typeof selection === 'string' && arguments.length > 1 ) {
- this.values[selection] = value;
- return true;
- }
- return false;
- },
-
- /**
- * Checks if one or multiple keys exist.
- *
- * @param {Mixed} selection String key or array of keys to check
- * @return {boolean} Existence of key(s)
- */
- exists: function ( selection ) {
- var s;
-
- if ( $.isArray( selection ) ) {
- for ( s = 0; s < selection.length; s++ ) {
- if ( typeof selection[s] !== 'string' || !hasOwn.call( this.values, selection[s] ) ) {
- return false;
- }
- }
- return true;
- }
- return typeof selection === 'string' && hasOwn.call( this.values, selection );
- }
- };
-
- /**
- * Object constructor for messages.
- *
- * Similar to the Message class in MediaWiki PHP.
- *
- * Format defaults to 'text'.
- *
- * @class mw.Message
- *
- * @constructor
- * @param {mw.Map} map Message storage
- * @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 = {
- /**
- * Simple message parser, does $N replacement and nothing else.
- *
- * This may be overridden to provide a more complex message parser.
- *
- * The primary override is in mediawiki.jqueryMsg.
- *
- * 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.
- *
- * @param {Array} parameters
- * @chainable
- */
- params: function ( parameters ) {
- var i;
- for ( i = 0; i < parameters.length; i += 1 ) {
- this.parameters.push( parameters[i] );
- }
- return this;
- },
-
- /**
- * Converts message object to it's string form based on the state of 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
- if ( this.format === 'escaped' || this.format === 'parse' ) {
- // format 'escaped' and 'parse' need to have the brackets and key html escaped
- return mw.html.escape( '<' + this.key + '>' );
- }
- return '<' + 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;
- },
-
- /**
- * Changes format to 'parse' and converts 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();
- },
-
- /**
- * Changes format to 'plain' and converts 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();
- },
-
- /**
- * Changes format to 'text' and converts message to string
- *
- * If jqueryMsg is loaded, {{-transformation is done where supported
- * (such as {{plural:}}, {{gender:}}, {{int:}}).
- *
- * Otherwise, it is equivalent to plain.
- */
- text: function () {
- this.format = 'text';
- return this.toString();
- },
-
- /**
- * Changes the format to 'escaped' and converts message to string
- *
- * This is equivalent to using the 'text' format (see text method), then
- * HTML-escaping the output.
- *
- * @return {string} String form of html escaped message
- */
- escaped: function () {
- this.format = 'escaped';
- return this.toString();
- },
-
- /**
- * Checks if message exists
- *
- * @see mw.Map#exists
- * @return {boolean}
- */
- exists: function () {
- return this.map.exists( this.key );
- }
- };
-
- /**
- * @class mw
- */
- return {
- /* Public Members */
-
- /**
- * Dummy placeholder for {@link mw.log}
- * @method
- */
- log: ( function () {
- var log = function () {};
- log.warn = function () {};
- log.deprecate = function ( obj, key, val ) {
- obj[key] = val;
- };
- return log;
- }() ),
-
- // Make the Map constructor publicly available.
- Map: Map,
-
- // Make the Message constructor publicly available.
- Message: Message,
-
- /**
- * Map of configuration values
- *
- * Check out [the complete list of configuration values](https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config)
- * on MediaWiki.org.
- *
- * If `$wgLegacyJavaScriptGlobals` is true, this Map will put its values in the
- * global window object.
- *
- * @property {mw.Map} config
- */
- // Dummy placeholder. Re-assigned in ResourceLoaderStartupModule with an instance of `mw.Map`.
- config: null,
-
- /**
- * Empty object that plugins can be installed in.
- * @property
- */
- libs: {},
-
- /**
- * Access container for deprecated functionality that can be moved from
- * from their legacy location and attached to this object (e.g. a global
- * function that is deprecated and as stop-gap can be exposed through here).
- *
- * This was reserved for future use but never ended up being used.
- *
- * @deprecated since 1.22: Let deprecated identifiers keep their original name
- * and use mw.log#deprecate to create an access container for tracking.
- * @property
- */
- legacy: {},
-
- /**
- * Localization system
- * @property {mw.Map}
- */
- messages: new Map(),
-
- /* Public Methods */
-
- /**
- * Get a message object.
- *
- * Similar to wfMessage() in MediaWiki PHP.
- *
- * @param {string} key Key of message to get
- * @param {Mixed...} parameters Parameters for the $N replacements in messages.
- * @return {mw.Message}
- */
- message: function ( key ) {
- // Variadic arguments
- var parameters = slice.call( arguments, 1 );
- return new Message( mw.messages, key, parameters );
- },
-
- /**
- * Get a message string using 'text' format.
- *
- * Similar to wfMsg() in MediaWiki PHP.
- *
- * @see mw.Message
- * @param {string} key Key of message to get
- * @param {Mixed...} parameters Parameters for the $N replacements in messages.
- * @return {string}
- */
- msg: function () {
- return mw.message.apply( mw.message, arguments ).toString();
- },
-
- /**
- * Client-side module loader which integrates with the MediaWiki ResourceLoader
- * @class mw.loader
- * @singleton
- */
- loader: ( function () {
-
- /* Private Members */
-
- /**
- * Mapping of registered modules
- *
- * The jquery module is pre-registered, because it must have already
- * been provided for this object to have been built, and in debug mode
- * jquery would have been provided through a unique loader request,
- * making it impossible to hold back registration of jquery until after
- * mediawiki.
- *
- * For exact details on support for script, style and messages, look at
- * mw.loader.implement.
- *
- * Format:
- * {
- * 'moduleName': {
- * 'version': ############## (unix timestamp),
- * 'dependencies': ['required.foo', 'bar.also', ...], (or) function () {}
- * 'group': 'somegroup', (or) null,
- * 'source': 'local', 'someforeignwiki', (or) null
- * 'state': 'registered', 'loaded', 'loading', 'ready', 'error' or 'missing'
- * 'script': ...,
- * 'style': ...,
- * 'messages': { 'key': 'value' },
- * }
- * }
- *
- * @property
- * @private
- */
- var registry = {},
- //
- // Mapping of sources, keyed by source-id, values are objects.
- // Format:
- // {
- // 'sourceId': {
- // 'loadScript': 'http://foo.bar/w/load.php'
- // }
- // }
- //
- sources = {},
- // List of modules which will be loaded as when ready
- batch = [],
- // List of modules to be loaded
- queue = [],
- // List of callback functions waiting for modules to be ready to be called
- jobs = [],
- // Selector cache for the marker element. Use getMarker() to get/use the marker!
- $marker = null,
- // Buffer for addEmbeddedCSS.
- cssBuffer = '',
- // Callbacks for addEmbeddedCSS.
- cssCallbacks = $.Callbacks();
-
- /* Private methods */
-
- function getMarker() {
- // Cached ?
- if ( $marker ) {
- return $marker;
- }
-
- $marker = $( 'meta[name="ResourceLoaderDynamicStyles"]' );
- if ( $marker.length ) {
- return $marker;
- }
- mw.log( 'getMarker> No <meta name="ResourceLoaderDynamicStyles"> found, inserting dynamically.' );
- $marker = $( '<meta>' ).attr( 'name', 'ResourceLoaderDynamicStyles' ).appendTo( 'head' );
-
- return $marker;
- }
-
- /**
- * Create a new style tag and add it to the DOM.
- *
- * @private
- * @param {string} text CSS text
- * @param {HTMLElement|jQuery} [nextnode=document.head] The element where the style tag should be
- * inserted before. Otherwise it will be appended to `<head>`.
- * @return {HTMLElement} Reference to the created `<style>` element.
- */
- function newStyleTag( text, nextnode ) {
- var s = document.createElement( 'style' );
- // Insert into document before setting cssText (bug 33305)
- if ( nextnode ) {
- // Must be inserted with native insertBefore, not $.fn.before.
- // When using jQuery to insert it, like $nextnode.before( s ),
- // then IE6 will throw "Access is denied" when trying to append
- // to .cssText later. Some kind of weird security measure.
- // http://stackoverflow.com/q/12586482/319266
- // Works: jsfiddle.net/zJzMy/1
- // Fails: jsfiddle.net/uJTQz
- // Works again: http://jsfiddle.net/Azr4w/ (diff: the next 3 lines)
- if ( nextnode.jquery ) {
- nextnode = nextnode.get( 0 );
- }
- nextnode.parentNode.insertBefore( s, nextnode );
- } else {
- document.getElementsByTagName( 'head' )[0].appendChild( s );
- }
- if ( s.styleSheet ) {
- // IE
- s.styleSheet.cssText = text;
- } else {
- // Other browsers.
- // (Safari sometimes borks on non-string values,
- // play safe by casting to a string, just in case.)
- s.appendChild( document.createTextNode( String( text ) ) );
- }
- return s;
- }
-
- /**
- * Checks whether it is safe to add this css to a stylesheet.
- *
- * @private
- * @param {string} cssText
- * @return {boolean} False if a new one must be created.
- */
- function canExpandStylesheetWith( cssText ) {
- // Makes sure that cssText containing `@import`
- // rules will end up in a new stylesheet (as those only work when
- // placed at the start of a stylesheet; bug 35562).
- return cssText.indexOf( '@import' ) === -1;
- }
-
- /**
- * Add a bit of CSS text to the current browser page.
- *
- * The CSS will be appended to an existing ResourceLoader-created `<style>` tag
- * or create a new one based on whether the given `cssText` is safe for extension.
- *
- * @param {string} [cssText=cssBuffer] If called without cssText,
- * the internal buffer will be inserted instead.
- * @param {Function} [callback]
- */
- function addEmbeddedCSS( cssText, callback ) {
- var $style, styleEl;
-
- if ( callback ) {
- cssCallbacks.add( callback );
- }
-
- // Yield once before inserting the <style> tag. There are likely
- // more calls coming up which we can combine this way.
- // Appending a stylesheet and waiting for the browser to repaint
- // is fairly expensive, this reduces it (bug 45810)
- if ( cssText ) {
- // Be careful not to extend the buffer with css that needs a new stylesheet
- if ( !cssBuffer || canExpandStylesheetWith( cssText ) ) {
- // Linebreak for somewhat distinguishable sections
- // (the rl-cachekey comment separating each)
- cssBuffer += '\n' + cssText;
- // TODO: Use requestAnimationFrame in the future which will
- // perform even better by not injecting styles while the browser
- // is paiting.
- setTimeout( function () {
- // Can't pass addEmbeddedCSS to setTimeout directly because Firefox
- // (below version 13) has the non-standard behaviour of passing a
- // numerical "lateness" value as first argument to this callback
- // http://benalman.com/news/2009/07/the-mysterious-firefox-settime/
- addEmbeddedCSS();
- } );
- return;
- }
-
- // This is a delayed call and we got a buffer still
- } else if ( cssBuffer ) {
- cssText = cssBuffer;
- cssBuffer = '';
- } else {
- // This is a delayed call, but buffer is already cleared by
- // another delayed call.
- return;
- }
-
- // By default, always create a new <style>. Appending text
- // to a <style> tag means the contents have to be re-parsed (bug 45810).
- // Except, of course, in IE below 9, in there we default to
- // re-using and appending to a <style> tag due to the
- // IE stylesheet limit (bug 31676).
- if ( 'documentMode' in document && document.documentMode <= 9 ) {
-
- $style = getMarker().prev();
- // Verify that the the element before Marker actually is a
- // <style> tag and one that came from ResourceLoader
- // (not some other style tag or even a `<meta>` or `<script>`).
- if ( $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
- // There's already a dynamic <style> tag present and
- // canExpandStylesheetWith() gave a green light to append more to it.
- styleEl = $style.get( 0 );
- if ( styleEl.styleSheet ) {
- try {
- styleEl.styleSheet.cssText += cssText; // IE
- } catch ( e ) {
- log( 'addEmbeddedCSS fail', e );
- }
- } else {
- styleEl.appendChild( document.createTextNode( String( cssText ) ) );
- }
- cssCallbacks.fire().empty();
- return;
- }
- }
-
- $( newStyleTag( cssText, getMarker() ) ).data( 'ResourceLoaderDynamicStyleTag', true );
-
- cssCallbacks.fire().empty();
- }
-
- /**
- * Generates an ISO8601 "basic" string from a UNIX timestamp
- * @private
- */
- function formatVersionNumber( timestamp ) {
- var d = new Date();
- function pad( a, b, c ) {
- return [a < 10 ? '0' + a : a, b < 10 ? '0' + b : b, c < 10 ? '0' + c : c].join( '' );
- }
- d.setTime( timestamp * 1000 );
- return [
- pad( d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() ), 'T',
- pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z'
- ].join( '' );
- }
-
- /**
- * Resolves dependencies and detects circular references.
- *
- * @private
- * @param {string} module Name of the top-level module whose dependencies shall be
- * resolved and sorted.
- * @param {Array} resolved Returns a topological sort of the given module and its
- * dependencies, such that later modules depend on earlier modules. The array
- * contains the module names. If the array contains already some module names,
- * this function appends its result to the pre-existing array.
- * @param {Object} [unresolved] Hash used to track the current dependency
- * chain; used to report loops in the dependency graph.
- * @throws {Error} If any unregistered module or a dependency loop is encountered
- */
- function sortDependencies( module, resolved, unresolved ) {
- var n, deps, len;
-
- if ( registry[module] === undefined ) {
- throw new Error( 'Unknown dependency: ' + module );
- }
- // Resolves dynamic loader function and replaces it with its own results
- if ( $.isFunction( registry[module].dependencies ) ) {
- registry[module].dependencies = registry[module].dependencies();
- // Ensures the module's dependencies are always in an array
- if ( typeof registry[module].dependencies !== 'object' ) {
- registry[module].dependencies = [registry[module].dependencies];
- }
- }
- if ( $.inArray( module, resolved ) !== -1 ) {
- // Module already resolved; nothing to do.
- return;
- }
- // unresolved is optional, supply it if not passed in
- if ( !unresolved ) {
- unresolved = {};
- }
- // Tracks down dependencies
- deps = registry[module].dependencies;
- len = deps.length;
- for ( n = 0; n < len; n += 1 ) {
- if ( $.inArray( deps[n], resolved ) === -1 ) {
- if ( unresolved[deps[n]] ) {
- throw new Error(
- 'Circular reference detected: ' + module +
- ' -> ' + deps[n]
- );
- }
-
- // Add to unresolved
- unresolved[module] = true;
- sortDependencies( deps[n], resolved, unresolved );
- delete unresolved[module];
- }
- }
- resolved[resolved.length] = module;
- }
-
- /**
- * Gets a list of module names that a module depends on in their proper dependency
- * order.
- *
- * @private
- * @param {string} module Module name or array of string module names
- * @return {Array} list of dependencies, including 'module'.
- * @throws {Error} If circular reference is detected
- */
- function resolve( module ) {
- var m, resolved;
-
- // Allow calling with an array of module names
- if ( $.isArray( module ) ) {
- resolved = [];
- for ( m = 0; m < module.length; m += 1 ) {
- sortDependencies( module[m], resolved );
- }
- return resolved;
- }
-
- if ( typeof module === 'string' ) {
- resolved = [];
- sortDependencies( module, resolved );
- return resolved;
- }
-
- throw new Error( 'Invalid module argument: ' + module );
- }
-
- /**
- * Narrows a list of module names down to those matching a specific
- * 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.
- *
- * @private
- * @param {string|string[]} states Module states to filter by
- * @param {Array} [modules] 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 ) {
- var list, module, s, m;
-
- // Allow states to be given as a string
- if ( typeof states === 'string' ) {
- states = [states];
- }
- // If called without a list of modules, build and use a list of all modules
- list = [];
- if ( modules === undefined ) {
- modules = [];
- for ( module in registry ) {
- modules[modules.length] = module;
- }
- }
- // Build a list of modules which are in one of the specified states
- for ( s = 0; s < states.length; s += 1 ) {
- for ( m = 0; m < modules.length; m += 1 ) {
- if ( registry[modules[m]] === undefined ) {
- // Module does not exist
- if ( states[s] === 'unregistered' ) {
- // OK, undefined
- list[list.length] = modules[m];
- }
- } else {
- // Module exists, check state
- if ( registry[modules[m]].state === states[s] ) {
- // OK, correct state
- list[list.length] = modules[m];
- }
- }
- }
- }
- return list;
- }
-
- /**
- * Determine whether all dependencies are in state 'ready', which means we may
- * execute the module or job now.
- *
- * @private
- * @param {Array} dependencies Dependencies (module names) to be checked.
- * @return {boolean} True if all dependencies are in state 'ready', false otherwise
- */
- function allReady( dependencies ) {
- return filter( 'ready', dependencies ).length === dependencies.length;
- }
-
- /**
- * A module has entered state 'ready', 'error', or 'missing'. Automatically update pending jobs
- * and modules that depend upon this module. if the given module failed, propagate the 'error'
- * state up the dependency tree; otherwise, execute all jobs/modules that now have all their
- * dependencies satisfied. On jobs depending on a failed module, run the error callback, if any.
- *
- * @private
- * @param {string} module Name of module that entered one of the states 'ready', 'error', or 'missing'.
- */
- function handlePending( module ) {
- var j, job, hasErrors, m, stateChange;
-
- // Modules.
- if ( $.inArray( registry[module].state, ['error', 'missing'] ) !== -1 ) {
- // If the current module failed, mark all dependent modules also as failed.
- // Iterate until steady-state to propagate the error state upwards in the
- // dependency tree.
- do {
- stateChange = false;
- for ( m in registry ) {
- if ( $.inArray( registry[m].state, ['error', 'missing'] ) === -1 ) {
- if ( filter( ['error', 'missing'], registry[m].dependencies ).length > 0 ) {
- registry[m].state = 'error';
- stateChange = true;
- }
- }
- }
- } while ( stateChange );
- }
-
- // Execute all jobs whose dependencies are either all satisfied or contain at least one failed module.
- for ( j = 0; j < jobs.length; j += 1 ) {
- hasErrors = filter( ['error', 'missing'], jobs[j].dependencies ).length > 0;
- if ( hasErrors || allReady( jobs[j].dependencies ) ) {
- // All dependencies satisfied, or some have errors
- job = jobs[j];
- jobs.splice( j, 1 );
- j -= 1;
- try {
- if ( hasErrors ) {
- if ( $.isFunction( job.error ) ) {
- job.error( new Error( 'Module ' + module + ' has failed dependencies' ), [module] );
- }
- } else {
- if ( $.isFunction( job.ready ) ) {
- job.ready();
- }
- }
- } catch ( e ) {
- // A user-defined callback raised an exception.
- // Swallow it to protect our state machine!
- log( 'Exception thrown by job.error', e );
- }
- }
- }
-
- if ( registry[module].state === 'ready' ) {
- // The current module became 'ready'. Recursively execute all dependent modules that are loaded
- // and now have all dependencies satisfied.
- for ( m in registry ) {
- if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) {
- execute( m );
- }
- }
- }
- }
-
- /**
- * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
- * depending on whether document-ready has occurred yet and whether we are in async mode.
- *
- * @private
- * @param {string} src URL to script, will be used as the src attribute in the script tag
- * @param {Function} [callback] Callback which will be run when the script is done
- */
- function addScript( src, callback, async ) {
- /*jshint evil:true */
- var script, head, done;
-
- // Using isReady directly instead of storing it locally from
- // a $.fn.ready callback (bug 31895).
- if ( $.isReady || async ) {
- // Can't use jQuery.getScript because that only uses <script> for cross-domain,
- // it uses XHR and eval for same-domain scripts, which we don't want because it
- // messes up line numbers.
- // The below is based on jQuery ([jquery@1.8.2]/src/ajax/script.js)
-
- // IE-safe way of getting the <head>. document.head isn't supported
- // in old IE, and doesn't work when in the <head>.
- done = false;
- head = document.getElementsByTagName( 'head' )[0] || document.body;
-
- script = document.createElement( 'script' );
- script.async = true;
- script.src = src;
- if ( $.isFunction( callback ) ) {
- script.onload = script.onreadystatechange = function () {
- if (
- !done
- && (
- !script.readyState
- || /loaded|complete/.test( script.readyState )
- )
- ) {
- done = true;
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
-
- // Detach the element from the document
- if ( script.parentNode ) {
- script.parentNode.removeChild( script );
- }
-
- // Dereference the element from javascript
- script = undefined;
-
- callback();
- }
- };
- }
-
- 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 {
- head.appendChild( script );
- }
- } else {
- document.write( mw.html.element( 'script', { '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();
- }
- }
- }
-
- /**
- * Executes a loaded module, making it ready to use
- *
- * @private
- * @param {string} module Module name to execute
- */
- function execute( module ) {
- var key, value, media, i, urls, cssHandle, checkCssHandles,
- cssHandlesRegistered = false;
-
- if ( registry[module] === undefined ) {
- throw new Error( 'Module has not been registered yet: ' + module );
- } else if ( registry[module].state === 'registered' ) {
- throw new Error( 'Module has not been requested from the server yet: ' + module );
- } else if ( registry[module].state === 'loading' ) {
- throw new Error( 'Module has not completed loading yet: ' + module );
- } else if ( registry[module].state === 'ready' ) {
- throw new Error( 'Module has already been executed: ' + module );
- }
-
- /**
- * Define loop-function here for efficiency
- * and to avoid re-using badly scoped variables.
- * @ignore
- */
- function addLink( media, url ) {
- var el = document.createElement( 'link' );
- getMarker().before( el ); // IE: Insert in dom before setting href
- el.rel = 'stylesheet';
- if ( media && media !== 'all' ) {
- el.media = media;
- }
- el.href = url;
- }
-
- function runScript() {
- var script, markModuleReady, nestedAddScript;
- try {
- script = registry[module].script;
- markModuleReady = function () {
- registry[module].state = 'ready';
- handlePending( module );
- };
- nestedAddScript = function ( arr, callback, async, i ) {
- // Recursively call addScript() in its own callback
- // for each element of arr.
- if ( i >= arr.length ) {
- // We're at the end of the array
- callback();
- return;
- }
-
- addScript( arr[i], function () {
- nestedAddScript( arr, callback, async, i + 1 );
- }, async );
- };
-
- if ( $.isArray( script ) ) {
- nestedAddScript( script, markModuleReady, registry[module].async, 0 );
- } else if ( $.isFunction( script ) ) {
- registry[module].state = 'ready';
- script( $ );
- handlePending( module );
- }
- } catch ( e ) {
- // This needs to NOT use mw.log because these errors are common in production mode
- // and not in debug mode, such as when a symbol that should be global isn't exported
- log( 'Exception thrown by ' + module, e );
- registry[module].state = 'error';
- handlePending( module );
- }
- }
-
- // This used to be inside runScript, but since that is now fired asychronously
- // (after CSS is loaded) we need to set it here right away. It is crucial that
- // when execute() is called this is set synchronously, otherwise modules will get
- // executed multiple times as the registry will state that it isn't loading yet.
- registry[module].state = 'loading';
-
- // Add localizations to message system
- if ( $.isPlainObject( registry[module].messages ) ) {
- mw.messages.set( registry[module].messages );
- }
-
- if ( $.isReady || registry[module].async ) {
- // Make sure we don't run the scripts until all (potentially asynchronous)
- // stylesheet insertions have completed.
- ( function () {
- var pending = 0;
- checkCssHandles = function () {
- // cssHandlesRegistered ensures we don't take off too soon, e.g. when
- // one of the cssHandles is fired while we're still creating more handles.
- if ( cssHandlesRegistered && pending === 0 && runScript ) {
- runScript();
- runScript = undefined; // Revoke
- }
- };
- cssHandle = function () {
- var check = checkCssHandles;
- pending++;
- return function () {
- if (check) {
- pending--;
- check();
- check = undefined; // Revoke
- }
- };
- };
- }() );
- } else {
- // We are in blocking mode, and so we can't afford to wait for CSS
- cssHandle = function () {};
- // Run immediately
- checkCssHandles = runScript;
- }
-
- // Process styles (see also mw.loader.implement)
- // * back-compat: { <media>: css }
- // * back-compat: { <media>: [url, ..] }
- // * { "css": [css, ..] }
- // * { "url": { <media>: [url, ..] } }
- if ( $.isPlainObject( registry[module].style ) ) {
- for ( key in registry[module].style ) {
- value = registry[module].style[key];
- media = undefined;
-
- if ( key !== 'url' && key !== 'css' ) {
- // Backwards compatibility, key is a media-type
- if ( typeof value === 'string' ) {
- // back-compat: { <media>: css }
- // Ignore 'media' because it isn't supported (nor was it used).
- // Strings are pre-wrapped in "@media". The media-type was just ""
- // (because it had to be set to something).
- // This is one of the reasons why this format is no longer used.
- addEmbeddedCSS( value, cssHandle() );
- } else {
- // back-compat: { <media>: [url, ..] }
- media = key;
- key = 'bc-url';
- }
- }
-
- // Array of css strings in key 'css',
- // or back-compat array of urls from media-type
- if ( $.isArray( value ) ) {
- for ( i = 0; i < value.length; i += 1 ) {
- if ( key === 'bc-url' ) {
- // back-compat: { <media>: [url, ..] }
- addLink( media, value[i] );
- } else if ( key === 'css' ) {
- // { "css": [css, ..] }
- addEmbeddedCSS( value[i], cssHandle() );
- }
- }
- // Not an array, but a regular object
- // Array of urls inside media-type key
- } else if ( typeof value === 'object' ) {
- // { "url": { <media>: [url, ..] } }
- for ( media in value ) {
- urls = value[media];
- for ( i = 0; i < urls.length; i += 1 ) {
- addLink( media, urls[i] );
- }
- }
- }
- }
- }
-
- // Kick off.
- cssHandlesRegistered = true;
- checkCssHandles();
- }
-
- /**
- * Adds a dependencies to the queue with optional callbacks to be run
- * when the dependencies are ready or fail
- *
- * @private
- * @param {string|string[]} dependencies Module name or array of string module names
- * @param {Function} [ready] Callback to execute when all dependencies are ready
- * @param {Function} [error] Callback to execute when any dependency fails
- * @param {boolean} [async] If true, load modules asynchronously even if
- * document ready has not yet occurred.
- */
- function request( dependencies, ready, error, async ) {
- var n;
-
- // Allow calling by single module name
- if ( typeof dependencies === 'string' ) {
- dependencies = [dependencies];
- }
-
- // Add ready and error callbacks if they were given
- if ( ready !== undefined || error !== undefined ) {
- jobs[jobs.length] = {
- 'dependencies': filter(
- ['registered', 'loading', 'loaded'],
- dependencies
- ),
- 'ready': ready,
- 'error': error
- };
- }
-
- // 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];
- if ( async ) {
- // Mark this module as async in the registry
- registry[dependencies[n]].async = true;
- }
- }
- }
-
- // Work the queue
- mw.loader.work();
- }
-
- function sortQuery(o) {
- var sorted = {}, key, a = [];
- for ( key in o ) {
- if ( hasOwn.call( o, key ) ) {
- a.push( key );
- }
- }
- a.sort();
- for ( key = 0; key < a.length; key += 1 ) {
- sorted[a[key]] = o[a[key]];
- }
- return sorted;
- }
-
- /**
- * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] }
- * to a query string of the form foo.bar,baz|bar.baz,quux
- * @private
- */
- function buildModulesString( moduleMap ) {
- var arr = [], p, prefix;
- for ( prefix in moduleMap ) {
- p = prefix === '' ? '' : prefix + '.';
- arr.push( p + moduleMap[prefix].join( ',' ) );
- }
- return arr.join( '|' );
- }
-
- /**
- * Asynchronously append a script tag to the end of the body
- * that invokes load.php
- * @private
- * @param {Object} moduleMap Module map, see #buildModulesString
- * @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request
- * @param {string} sourceLoadScript URL of load.php
- * @param {boolean} async If true, use an asynchronous request even if document ready has not yet occurred
- */
- function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
- var request = $.extend(
- { modules: buildModulesString( moduleMap ) },
- currReqBase
- );
- request = sortQuery( request );
- // Asynchronously append a script tag to the end of the body
- // Append &* to avoid triggering the IE6 extension check
- addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
- }
-
- /* Public Methods */
- return {
- /**
- * The module registry is exposed as an aid for debugging and inspecting page
- * state; it is not a public interface for modifying the registry.
- *
- * @see #registry
- * @property
- * @private
- */
- moduleRegistry: registry,
-
- /**
- * @inheritdoc #newStyleTag
- * @method
- */
- addStyleTag: newStyleTag,
-
- /**
- * Batch-request queued dependencies from the server.
- */
- work: function () {
- var reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
- source, group, g, i, modules, maxVersion, sourceLoadScript,
- currReqBase, currReqBaseLength, moduleMap, l,
- lastDotIndex, prefix, suffix, bytesAdded, async;
-
- // Build a list of request parameters common to all requests.
- reqBase = {
- skin: mw.config.get( 'skin' ),
- lang: mw.config.get( 'wgUserLanguage' ),
- debug: mw.config.get( 'debug' )
- };
- // Split module batch by source and by group.
- splits = {};
- maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 );
-
- // Appends a list of modules from the queue to the batch
- for ( q = 0; q < queue.length; q += 1 ) {
- // 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
- registry[queue[q]].state = 'loading';
- }
- }
- }
- // Early exit if there's nothing to load...
- if ( !batch.length ) {
- return;
- }
-
- // The queue has been processed into the batch, clear up the queue.
- queue = [];
-
- // Always order modules alphabetically to help reduce cache
- // misses for otherwise identical content.
- batch.sort();
-
- // Split batch by source and by group.
- for ( b = 0; b < batch.length; b += 1 ) {
- bSource = registry[batch[b]].source;
- bGroup = registry[batch[b]].group;
- if ( splits[bSource] === undefined ) {
- splits[bSource] = {};
- }
- if ( splits[bSource][bGroup] === undefined ) {
- splits[bSource][bGroup] = [];
- }
- bSourceGroup = splits[bSource][bGroup];
- bSourceGroup[bSourceGroup.length] = batch[b];
- }
-
- // Clear the batch - this MUST happen before we append any
- // script elements to the body or it's possible that a script
- // will be locally cached, instantly load, and work the batch
- // again, all before we've cleared it causing each request to
- // include modules which are already loaded.
- batch = [];
-
- for ( source in splits ) {
-
- sourceLoadScript = sources[source].loadScript;
-
- for ( group in splits[source] ) {
-
- // Cache access to currently selected list of
- // modules for this group from this source.
- modules = splits[source][group];
-
- // Calculate the highest timestamp
- maxVersion = 0;
- for ( g = 0; g < modules.length; g += 1 ) {
- if ( registry[modules[g]].version > maxVersion ) {
- maxVersion = registry[modules[g]].version;
- }
- }
-
- currReqBase = $.extend( { version: formatVersionNumber( maxVersion ) }, reqBase );
- // For user modules append a user name to the request.
- if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
- currReqBase.user = mw.config.get( 'wgUserName' );
- }
- currReqBaseLength = $.param( currReqBase ).length;
- async = true;
- // We may need to split up the request to honor the query string length limit,
- // so build it piece by piece.
- l = currReqBaseLength + 9; // '&modules='.length == 9
-
- moduleMap = {}; // { prefix: [ suffixes ] }
-
- for ( i = 0; i < modules.length; i += 1 ) {
- // Determine how many bytes this module would add to the query string
- lastDotIndex = modules[i].lastIndexOf( '.' );
- // Note that these substr() calls work even if lastDotIndex == -1
- prefix = modules[i].substr( 0, lastDotIndex );
- suffix = modules[i].substr( lastDotIndex + 1 );
- bytesAdded = moduleMap[prefix] !== undefined
- ? suffix.length + 3 // '%2C'.length == 3
- : modules[i].length + 3; // '%7C'.length == 3
-
- // If the request would become too long, create a new one,
- // but don't create empty requests
- if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
- // This request would become too long, create a new one
- // and fire off the old one
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
- moduleMap = {};
- async = true;
- l = currReqBaseLength + 9;
- }
- if ( moduleMap[prefix] === undefined ) {
- moduleMap[prefix] = [];
- }
- moduleMap[prefix].push( suffix );
- if ( !registry[modules[i]].async ) {
- // If this module is blocking, make the entire request blocking
- // This is slightly suboptimal, but in practice mixing of blocking
- // and async modules will only occur in debug mode.
- async = false;
- }
- l += bytesAdded;
- }
- // If there's anything left in moduleMap, request that too
- if ( !$.isEmptyObject( moduleMap ) ) {
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
- }
- }
- }
- },
-
- /**
- * Register a source.
- *
- * @param {string} id Short lowercase a-Z string representing a source, only used internally.
- * @param {Object} props Object containing only the loadScript property which is a url to
- * the load.php location of the source.
- * @return {boolean}
- */
- addSource: function ( id, props ) {
- var source;
- // Allow multiple additions
- if ( typeof id === 'object' ) {
- for ( source in id ) {
- mw.loader.addSource( source, id[source] );
- }
- return true;
- }
-
- if ( sources[id] !== undefined ) {
- throw new Error( 'source already registered: ' + id );
- }
-
- sources[id] = props;
-
- return true;
- },
-
- /**
- * Register a module, letting the system know about it and its
- * properties. Startup modules contain calls to this function.
- *
- * @param {string} module Module name
- * @param {number} version Module version number as a timestamp (falls backs to 0)
- * @param {string|Array|Function} dependencies One string or array of strings of module
- * names on which this module depends, or a function that returns that array.
- * @param {string} [group=null] Group which the module is in
- * @param {string} [source='local'] Name of the source
- */
- register: function ( module, version, dependencies, group, source ) {
- var m;
- // Allow multiple registration
- if ( typeof module === 'object' ) {
- for ( m = 0; m < module.length; m += 1 ) {
- // module is an array of module names
- if ( typeof module[m] === 'string' ) {
- mw.loader.register( module[m] );
- // module is an array of arrays
- } else if ( typeof module[m] === 'object' ) {
- mw.loader.register.apply( mw.loader, module[m] );
- }
- }
- return;
- }
- // Validate input
- if ( typeof module !== 'string' ) {
- throw new Error( 'module must be a string, not a ' + typeof module );
- }
- if ( registry[module] !== undefined ) {
- throw new Error( 'module already registered: ' + module );
- }
- // List the module as registered
- registry[module] = {
- version: version !== undefined ? parseInt( version, 10 ) : 0,
- dependencies: [],
- group: typeof group === 'string' ? group : null,
- source: typeof source === 'string' ? source: 'local',
- state: 'registered'
- };
- if ( typeof dependencies === 'string' ) {
- // Allow dependencies to be given as a single module name
- registry[module].dependencies = [ dependencies ];
- } else if ( typeof dependencies === 'object' || $.isFunction( dependencies ) ) {
- // Allow dependencies to be given as an array of module names
- // or a function which returns an array
- registry[module].dependencies = dependencies;
- }
- },
-
- /**
- * Implement a module given the components that make up the module.
- *
- * When #load or #using requests one or more modules, the server
- * response contain calls to this function.
- *
- * All arguments are required.
- *
- * @param {string} module Name of module
- * @param {Function|Array} script Function with module code or Array of URLs to
- * be used as the src attribute of a new `<script>` tag.
- * @param {Object} style Should follow one of the following patterns:
- *
- * { "css": [css, ..] }
- * { "url": { <media>: [url, ..] } }
- *
- * And for backwards compatibility (needs to be supported forever due to caching):
- *
- * { <media>: css }
- * { <media>: [url, ..] }
- *
- * The reason css strings are not concatenated anymore is bug 31676. We now check
- * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
- *
- * @param {Object} msgs List of key/value pairs to be added to mw#messages.
- */
- implement: function ( module, script, style, msgs ) {
- // Validate input
- if ( typeof module !== 'string' ) {
- throw new Error( 'module must be a string, not a ' + typeof module );
- }
- if ( !$.isFunction( script ) && !$.isArray( script ) ) {
- throw new Error( 'script must be a function or an array, not a ' + typeof script );
- }
- if ( !$.isPlainObject( style ) ) {
- throw new Error( 'style must be an object, not a ' + typeof style );
- }
- if ( !$.isPlainObject( msgs ) ) {
- throw new Error( 'msgs must be an object, not a ' + typeof msgs );
- }
- // Automatically register module
- if ( registry[module] === undefined ) {
- mw.loader.register( module );
- }
- // Check for duplicate implementation
- if ( registry[module] !== undefined && registry[module].script !== undefined ) {
- throw new Error( 'module already implemented: ' + module );
- }
- // Attach components
- registry[module].script = script;
- registry[module].style = style;
- registry[module].messages = msgs;
- // The module may already have been marked as erroneous
- if ( $.inArray( registry[module].state, ['error', 'missing'] ) === -1 ) {
- registry[module].state = 'loaded';
- if ( allReady( registry[module].dependencies ) ) {
- execute( module );
- }
- }
- },
-
- /**
- * Execute a function as soon as one or more required modules are ready.
- *
- * @param {string|Array} dependencies Module name or array of modules names the callback
- * dependends on to be ready before executing
- * @param {Function} [ready] callback to execute when all dependencies are ready
- * @param {Function} [error] callback to execute when if dependencies have a errors
- */
- using: function ( dependencies, ready, error ) {
- var tod = typeof dependencies;
- // Validate input
- if ( tod !== 'object' && tod !== 'string' ) {
- throw new Error( 'dependencies must be a string or an array, not a ' + tod );
- }
- // Allow calling with a single dependency as a string
- if ( tod === 'string' ) {
- dependencies = [ dependencies ];
- }
- // Resolve entire dependency map
- dependencies = resolve( dependencies );
- if ( allReady( dependencies ) ) {
- // Run ready immediately
- if ( $.isFunction( ready ) ) {
- ready();
- }
- } else if ( filter( ['error', 'missing'], dependencies ).length ) {
- // Execute error immediately if any dependencies have errors
- if ( $.isFunction( error ) ) {
- error( new Error( 'one or more dependencies have state "error" or "missing"' ),
- dependencies );
- }
- } else {
- // Not all dependencies are ready: queue up a request
- request( dependencies, ready, error );
- }
- },
-
- /**
- * Load an external script or one or more modules.
- *
- * @param {string|Array} modules Either the name of a module, array of modules,
- * or a URL of an external script or style
- * @param {string} [type='text/javascript'] mime-type to use if calling with a URL of an
- * external script or style; acceptable values are "text/css" and
- * "text/javascript"; if no type is provided, text/javascript is assumed.
- * @param {boolean} [async] If true, load modules asynchronously
- * even if document ready has not yet occurred. If false, block before
- * document ready and load async after. If not set, true will be
- * assumed if loading a URL, and false will be assumed otherwise.
- */
- load: function ( modules, type, async ) {
- var filtered, m, module, l;
-
- // 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 url or single dependency as a string
- if ( typeof modules === 'string' ) {
- // Support adding arbitrary external scripts
- if ( /^(https?:)?\/\//.test( modules ) ) {
- if ( async === undefined ) {
- // Assume async for bug 34542
- async = true;
- }
- if ( type === 'text/css' ) {
- // IE7-8 throws security warnings when inserting a <link> tag
- // with a protocol-relative URL set though attributes (instead of
- // properties) - when on HTTPS. See also bug #.
- l = document.createElement( 'link' );
- l.rel = 'stylesheet';
- l.href = modules;
- $( 'head' ).append( l );
- return;
- }
- if ( type === 'text/javascript' || type === undefined ) {
- addScript( modules, null, async );
- return;
- }
- // Unknown type
- 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 ) {
- module = registry[modules[m]];
- if ( module !== undefined ) {
- if ( $.inArray( module.state, ['error', 'missing'] ) === -1 ) {
- filtered[filtered.length] = modules[m];
- }
- }
- }
-
- if ( filtered.length === 0 ) {
- return;
- }
- // Resolve entire dependency map
- filtered = resolve( filtered );
- // If all modules are ready, nothing to be done
- if ( allReady( filtered ) ) {
- return;
- }
- // If any modules have errors: also quit.
- if ( filter( ['error', 'missing'], filtered ).length ) {
- return;
- }
- // Since some modules are not yet ready, queue up a request.
- request( filtered, undefined, undefined, async );
- },
-
- /**
- * Change the state of one or more modules.
- *
- * @param {string|Object} module module name or object of module name/state pairs
- * @param {string} state state name
- */
- state: function ( module, state ) {
- var m;
-
- if ( typeof module === 'object' ) {
- for ( m in module ) {
- mw.loader.state( m, module[m] );
- }
- return;
- }
- if ( registry[module] === undefined ) {
- mw.loader.register( module );
- }
- if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1
- && registry[module].state !== state ) {
- // Make sure pending modules depending on this one get executed if their
- // dependencies are now fulfilled!
- registry[module].state = state;
- handlePending( module );
- } else {
- registry[module].state = state;
- }
- },
-
- /**
- * Get the version of a module.
- *
- * @param {string} module Name of module to get version for
- */
- getVersion: function ( module ) {
- if ( registry[module] !== undefined && registry[module].version !== undefined ) {
- return formatVersionNumber( registry[module].version );
- }
- return null;
- },
-
- /**
- * @inheritdoc #getVersion
- * @deprecated since 1.18 use #getVersion instead
- */
- version: function () {
- return mw.loader.getVersion.apply( mw.loader, arguments );
- },
-
- /**
- * Get the state of a module.
- *
- * @param {string} module name of module to get state for
- */
- getState: function ( module ) {
- if ( registry[module] !== undefined && registry[module].state !== undefined ) {
- return registry[module].state;
- }
- return null;
- },
-
- /**
- * Get names of all registered modules.
- *
- * @return {Array}
- */
- getModuleNames: function () {
- return $.map( registry, function ( i, key ) {
- return key;
- } );
- },
-
- /**
- * Load the `mediawiki.user` module.
- *
- * For backwards-compatibility with cached pages from before 2013 where:
- *
- * - the `mediawiki.user` module didn't exist yet
- * - `mw.user` was still part of mediawiki.js
- * - `mw.loader.go` still existed and called after `mw.loader.load()`
- */
- go: function () {
- mw.loader.load( 'mediawiki.user' );
- },
-
- /**
- * @inheritdoc mw.inspect#runReports
- * @method
- */
- inspect: function () {
- var args = slice.call( arguments );
- mw.loader.using( 'mediawiki.inspect', function () {
- mw.inspect.runReports.apply( mw.inspect, args );
- } );
- }
-
- };
- }() ),
-
- /**
- * 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="&lt;"/></div>
- *
- * @class mw.html
- * @singleton
- */
- html: ( function () {
- function escapeCallback( s ) {
- switch ( s ) {
- case '\'':
- return '&#039;';
- case '"':
- return '&quot;';
- case '<':
- return '&lt;';
- case '>':
- return '&gt;';
- case '&':
- return '&amp;';
- }
- }
-
- return {
- /**
- * Escape a string for HTML. Converts special characters to HTML entities.
- * @param {string} s The string to escape
- */
- 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 {Mixed} contents The contents of the element. May be either:
- * - string: The string is escaped.
- * - null or undefined: The short closing form is used, e.g. <br/>.
- * - this.Raw: The value attribute is included without escaping.
- * - this.Cdata: The value attribute is included, and an exception is
- * thrown if it contains an illegal ETAGO delimiter.
- * See http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.3.2
- */
- element: function ( name, attrs, contents ) {
- var v, attrName, s = '<' + name;
-
- 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( 'mw.html.element: Illegal end tag found in CDATA' );
- }
- s += contents.value;
- } else {
- throw new Error( 'mw.html.element: Invalid type of contents' );
- }
- }
- s += '</' + name + '>';
- return s;
- },
-
- /**
- * Wrapper object for raw HTML passed to mw.html.element().
- * @class mw.html.Raw
- */
- Raw: function ( value ) {
- this.value = value;
- },
-
- /**
- * Wrapper object for CDATA element contents passed to mw.html.element()
- * @class mw.html.Cdata
- */
- Cdata: function ( value ) {
- this.value = value;
- }
- };
- }() ),
-
- // Skeleton user object. mediawiki.user.js extends this
- user: {
- options: new Map(),
- tokens: new Map()
- },
-
- /**
- * 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
- */
- hook: ( function () {
- var lists = {};
-
- /**
- * 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
- * @chainable
- */
- fire: function () {
- return list.fireWith( null, slice.call( arguments ) );
- }
- };
- };
- }() )
- };
-
-}( jQuery ) );
-
-// Alias $j to jQuery for backwards compatibility
-window.$j = jQuery;
-
-// Attach to window and globally alias
-window.mw = window.mediaWiki = mw;
-
-// Auto-register from pre-loaded startup scripts
-if ( jQuery.isFunction( window.startUp ) ) {
- window.startUp();
- window.startUp = undefined;
-}
diff --git a/resources/mediawiki/mediawiki.log.js b/resources/mediawiki/mediawiki.log.js
deleted file mode 100644
index 75e4c961..00000000
--- a/resources/mediawiki/mediawiki.log.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*!
- * Logger for MediaWiki javascript.
- * Implements the stub left by the main 'mediawiki' module.
- *
- * @author Michael Dale <mdale@wikimedia.org>
- * @author Trevor Parscal <tparscal@wikimedia.org>
- */
-
-( function ( mw, $ ) {
-
- /**
- * @class mw.log
- * @singleton
- */
-
- /**
- * Logs a message to the console.
- *
- * In the case the browser does not have a console API, a console is created on-the-fly by appending
- * a `<div id="mw-log-console">` element to the bottom of the body and then appending this and future
- * messages to that, instead of the console.
- *
- * @param {string...} msg Messages to output to console.
- */
- mw.log = function () {
- // Turn arguments into an array
- var args = Array.prototype.slice.call( arguments ),
- // Allow log messages to use a configured prefix to identify the source window (ie. frame)
- prefix = mw.config.exists( 'mw.log.prefix' ) ? mw.config.get( 'mw.log.prefix' ) + '> ' : '';
-
- // Try to use an existing console
- if ( window.console !== undefined && $.isFunction( window.console.log ) ) {
- args.unshift( prefix );
- window.console.log.apply( window.console, args );
- return;
- }
-
- // If there is no console, use our own log box
- mw.loader.using( 'jquery.footHovzer', function () {
-
- var hovzer,
- d = new Date(),
- // Create HH:MM:SS.MIL timestamp
- time = ( d.getHours() < 10 ? '0' + d.getHours() : d.getHours() ) +
- ':' + ( d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes() ) +
- ':' + ( d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds() ) +
- '.' + ( d.getMilliseconds() < 10 ? '00' + d.getMilliseconds() : ( d.getMilliseconds() < 100 ? '0' + d.getMilliseconds() : d.getMilliseconds() ) ),
- $log = $( '#mw-log-console' );
-
- if ( !$log.length ) {
- $log = $( '<div id="mw-log-console"></div>' ).css( {
- overflow: 'auto',
- height: '150px',
- backgroundColor: 'white',
- borderTop: 'solid 2px #ADADAD'
- } );
- hovzer = $.getFootHovzer();
- hovzer.$.append( $log );
- hovzer.update();
- }
- $log.append(
- $( '<div>' )
- .css( {
- borderBottom: 'solid 1px #DDDDDD',
- fontSize: 'small',
- fontFamily: 'monospace',
- whiteSpace: 'pre-wrap',
- padding: '0.125em 0.25em'
- } )
- .text( prefix + args.join( ', ' ) )
- .prepend( '<span style="float: right;">[' + time + ']</span>' )
- );
- } );
- };
-
- /**
- * Write a message the console's warning channel.
- * Also logs a stacktrace for easier debugging.
- * Each action is silently ignored if the browser doesn't support it.
- *
- * @param {string...} msg Messages to output to console
- */
- mw.log.warn = function () {
- var console = window.console;
- if ( console && console.warn ) {
- console.warn.apply( console, arguments );
- if ( console.trace ) {
- console.trace();
- }
- }
- };
-
- /**
- * Create a property in a host object that, when accessed, will produce
- * a deprecation warning in the console with backtrace.
- *
- * @param {Object} obj Host object of deprecated property
- * @param {string} key Name of property to create in `obj`
- * @param {Mixed} val The value this property should return when accessed
- * @param {string} [msg] Optional text to include in the deprecation message.
- */
- mw.log.deprecate = !Object.defineProperty ? function ( obj, key, val ) {
- obj[key] = val;
- } : function ( obj, key, val, msg ) {
- msg = 'MWDeprecationWarning: Use of "' + key + '" property is deprecated.' +
- ( msg ? ( ' ' + msg ) : '' );
- try {
- Object.defineProperty( obj, key, {
- configurable: true,
- enumerable: true,
- get: function () {
- mw.log.warn( msg );
- return val;
- },
- set: function ( newVal ) {
- mw.log.warn( msg );
- val = newVal;
- }
- } );
- } catch ( err ) {
- // IE8 can throw on Object.defineProperty
- obj[key] = val;
- }
- };
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.notification.css b/resources/mediawiki/mediawiki.notification.css
deleted file mode 100644
index 3aa358ac..00000000
--- a/resources/mediawiki/mediawiki.notification.css
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Stylesheet for mediawiki.notification module
- */
-
-.mw-notification-area {
- position: absolute;
- top: 0;
- right: 0;
- padding: 1em 1em 0 0;
- width: 20em;
- line-height: 1.35;
- z-index: 10000;
-}
-
-.mw-notification-area-floating {
- position: fixed;
-}
-
-* html .mw-notification-area-floating {
- /* Make it at least 'absolute' in IE6 since 'fixed' is not supported */
- position: absolute;
-}
-
-.mw-notification {
- padding: 0.25em 1em;
- margin-bottom: 0.5em;
- border: solid 1px #ddd;
- background-color: #fcfcfc;
- /* Message hides on-click */
- /* See also mediawiki.notification.js */
- cursor: pointer;
-}
-
-.mw-notification-title {
- font-weight: bold;
-}
diff --git a/resources/mediawiki/mediawiki.notification.js b/resources/mediawiki/mediawiki.notification.js
deleted file mode 100644
index 4ede8096..00000000
--- a/resources/mediawiki/mediawiki.notification.js
+++ /dev/null
@@ -1,504 +0,0 @@
-( function ( mw, $ ) {
- 'use strict';
-
- var notification,
- // The #mw-notification-area div that all notifications are contained inside.
- $area,
- isPageReady = false,
- preReadyNotifQueue = [];
-
- /**
- * Creates a Notification object for 1 message.
- * Does not insert anything into the document (see #start).
- *
- * The "_" in the name is to avoid a bug (http://github.com/senchalabs/jsduck/issues/304)
- * It is not part of the actual class name.
- *
- * @class mw.Notification_
- * @alternateClassName mw.Notification
- * @private
- *
- * @constructor
- */
- function Notification( message, options ) {
- var $notification, $notificationTitle, $notificationContent;
-
- $notification = $( '<div class="mw-notification"></div>' )
- .data( 'mw.notification', this )
- .addClass( options.autoHide ? 'mw-notification-autohide' : 'mw-notification-noautohide' );
-
- if ( options.tag ) {
- // Sanitize options.tag before it is used by any code. (Including Notification class methods)
- options.tag = options.tag.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
- if ( options.tag ) {
- $notification.addClass( 'mw-notification-tag-' + options.tag );
- } else {
- delete options.tag;
- }
- }
-
- if ( options.title ) {
- $notificationTitle = $( '<div class="mw-notification-title"></div>' )
- .text( options.title )
- .appendTo( $notification );
- }
-
- $notificationContent = $( '<div class="mw-notification-content"></div>' );
-
- if ( typeof message === 'object' ) {
- // Handle mw.Message objects separately from DOM nodes and jQuery objects
- if ( message instanceof mw.Message ) {
- $notificationContent.html( message.parse() );
- } else {
- $notificationContent.append( message );
- }
- } else {
- $notificationContent.text( message );
- }
-
- $notificationContent.appendTo( $notification );
-
- // Private state parameters, meant for internal use only
- // isOpen: Set to true after .start() is called to avoid double calls.
- // Set back to false after .close() to avoid duplicating the close animation.
- // isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts.
- // Set to true initially so .start() can call .resume().
- // message: The message passed to the notification. Unused now but may be used in the future
- // to stop replacement of a tagged notification with another notification using the same message.
- // options: The options passed to the notification with a little sanitization. Used by various methods.
- // $notification: jQuery object containing the notification DOM node.
- this.isOpen = false;
- this.isPaused = true;
- this.message = message;
- this.options = options;
- this.$notification = $notification;
- }
-
- /**
- * Start the notification.
- * This inserts it into the page, closes any matching tagged notifications,
- * handles the fadeIn animations and repacement transitions, and starts autoHide timers.
- */
- Notification.prototype.start = function () {
- var
- // Local references
- $notification, options,
- // Original opacity so that we can animate back to it later
- opacity,
- // Other notification elements matching the same tag
- $tagMatches,
- outerHeight,
- placeholderHeight,
- autohideCount,
- notif;
-
- if ( this.isOpen ) {
- return;
- }
-
- this.isOpen = true;
-
- options = this.options;
- $notification = this.$notification;
-
- opacity = this.$notification.css( 'opacity' );
-
- // Set the opacity to 0 so we can fade in later.
- $notification.css( 'opacity', 0 );
-
- if ( options.tag ) {
- // Check to see if there are any tagged notifications with the same tag as the new one
- $tagMatches = $area.find( '.mw-notification-tag-' + options.tag );
- }
-
- // If we found a tagged notification use the replacement pattern instead of the new
- // notification fade-in pattern.
- if ( options.tag && $tagMatches.length ) {
-
- // Iterate over the tag matches to find the outerHeight we should use
- // for the placeholder.
- outerHeight = 0;
- $tagMatches.each( function () {
- var notif = $( this ).data( 'mw.notification' );
- if ( notif ) {
- // Use the notification's height + padding + border + margins
- // as the placeholder height.
- outerHeight = notif.$notification.outerHeight( true );
- if ( notif.$replacementPlaceholder ) {
- // Grab the height of a placeholder that has not finished animating.
- placeholderHeight = notif.$replacementPlaceholder.height();
- // Remove any placeholders added by a previous tagged
- // notification that was in the middle of replacing another.
- // This also makes sure that we only grab the placeholderHeight
- // for the most recent notification.
- notif.$replacementPlaceholder.remove();
- delete notif.$replacementPlaceholder;
- }
- // Close the previous tagged notification
- // Since we're replacing it do this with a fast speed and don't output a placeholder
- // since we're taking care of that transition ourselves.
- notif.close( { speed: 'fast', placeholder: false } );
- }
- } );
- if ( placeholderHeight !== undefined ) {
- // If the other tagged notification was in the middle of replacing another
- // tagged notification, continue from the placeholder's height instead of
- // using the outerHeight of the notification.
- outerHeight = placeholderHeight;
- }
-
- $notification
- // Insert the new notification before the tagged notification(s)
- .insertBefore( $tagMatches.first() )
- .css( {
- // Use an absolute position so that we can use a placeholder to gracefully push other notifications
- // into the right spot.
- position: 'absolute',
- width: $notification.width()
- } )
- // Fade-in the notification
- .animate( { opacity: opacity },
- {
- duration: 'slow',
- complete: function () {
- // After we've faded in clear the opacity and let css take over
- $( this ).css( { opacity: '' } );
- }
- } );
-
- notif = this;
-
- // Create a clear placeholder we can use to make the notifications around the notification that is being
- // replaced expand or contract gracefully to fit the height of the new notification.
- notif.$replacementPlaceholder = $( '<div>' )
- // Set the height to the space the previous notification or placeholder took
- .css( 'height', outerHeight )
- // Make sure that this placeholder is at the very end of this tagged notification group
- .insertAfter( $tagMatches.eq( -1 ) )
- // Animate the placeholder height to the space that this new notification will take up
- .animate( { height: $notification.outerHeight( true ) },
- {
- // Do space animations fast
- speed: 'fast',
- complete: function () {
- // Reset the notification position after we've finished the space animation
- // However do not do it if the placeholder was removed because another tagged
- // notification went and closed this one.
- if ( notif.$replacementPlaceholder ) {
- $notification.css( 'position', '' );
- }
- // Finally, remove the placeholder from the DOM
- $( this ).remove();
- }
- } );
- } else {
- // Append to the notification area and fade in to the original opacity.
- $notification
- .appendTo( $area )
- .animate( { opacity: opacity },
- {
- duration: 'fast',
- complete: function () {
- // After we've faded in clear the opacity and let css take over
- $( this ).css( 'opacity', '' );
- }
- }
- );
- }
-
- // By default a notification is paused.
- // If this notification is within the first {autoHideLimit} notifications then
- // start the auto-hide timer as soon as it's created.
- autohideCount = $area.find( '.mw-notification-autohide' ).length;
- if ( autohideCount <= notification.autoHideLimit ) {
- this.resume();
- }
- };
-
- /**
- * Pause any running auto-hide timer for this notification
- */
- Notification.prototype.pause = function () {
- if ( this.isPaused ) {
- return;
- }
- this.isPaused = true;
-
- if ( this.timeout ) {
- clearTimeout( this.timeout );
- delete this.timeout;
- }
- };
-
- /**
- * Start autoHide timer if not already started.
- * Does nothing if autoHide is disabled.
- * Either to resume from pause or to make the first start.
- */
- Notification.prototype.resume = function () {
- var notif = this;
- if ( !notif.isPaused ) {
- return;
- }
- // Start any autoHide timeouts
- if ( notif.options.autoHide ) {
- notif.isPaused = false;
- notif.timeout = setTimeout( function () {
- // Already finished, so don't try to re-clear it
- delete notif.timeout;
- notif.close();
- }, notification.autoHideSeconds * 1000 );
- }
- };
-
- /**
- * Close/hide the notification.
- *
- * @param {Object} options An object containing options for the closing of the notification.
- * These are typically only used internally.
- *
- * - speed: Use a close speed different than the default 'slow'.
- * - placeholder: Set to false to disable the placeholder transition.
- */
- Notification.prototype.close = function ( options ) {
- if ( !this.isOpen ) {
- return;
- }
- this.isOpen = false;
- // Clear any remaining timeout on close
- this.pause();
-
- options = $.extend( {
- speed: 'slow',
- placeholder: true
- }, options );
-
- // Remove the mw-notification-autohide class from the notification to avoid
- // having a half-closed notification counted as a notification to resume
- // when handling {autoHideLimit}.
- this.$notification.removeClass( 'mw-notification-autohide' );
-
- // Now that a notification is being closed. Start auto-hide timers for any
- // notification that has now become one of the first {autoHideLimit} notifications.
- notification.resume();
-
- this.$notification
- .css( {
- // Don't trigger any mouse events while fading out, just in case the cursor
- // happens to be right above us when we transition upwards.
- pointerEvents: 'none',
- // Set an absolute position so we can move upwards in the animation.
- // Notification replacement doesn't look right unless we use an animation like this.
- position: 'absolute',
- // We must fix the width to avoid it shrinking horizontally.
- width: this.$notification.width()
- } )
- // Fix the top/left position to the current computed position from which we
- // can animate upwards.
- .css( this.$notification.position() );
-
- // This needs to be done *after* notification's position has been made absolute.
- if ( options.placeholder ) {
- // Insert a placeholder with a height equal to the height of the
- // notification plus it's vertical margins in place of the notification
- var $placeholder = $( '<div>' )
- .css( 'height', this.$notification.outerHeight( true ) )
- .insertBefore( this.$notification );
- }
-
- // Animate opacity and top to create fade upwards animation for notification closing
- this.$notification
- .animate( {
- opacity: 0,
- top: '-=35'
- }, {
- duration: options.speed,
- complete: function () {
- // Remove the notification
- $( this ).remove();
- if ( options.placeholder ) {
- // Use a fast slide up animation after closing to make it look like the notifications
- // below slide up into place when the notification disappears
- $placeholder.slideUp( 'fast', function () {
- // Remove the placeholder
- $( this ).remove();
- } );
- }
- }
- } );
- };
-
- /**
- * Helper function, take a list of notification divs and call
- * a function on the Notification instance attached to them.
- *
- * @param {jQuery} $notifications A jQuery object containing notification divs
- * @param {string} fn The name of the function to call on the Notification instance
- */
- function callEachNotification( $notifications, fn ) {
- $notifications.each( function () {
- var notif = $( this ).data( 'mw.notification' );
- if ( notif ) {
- notif[fn]();
- }
- } );
- }
-
- /**
- * Initialisation.
- * Must only be called once, and not before the document is ready.
- * @ignore
- */
- function init() {
- var offset, $window = $( window );
-
- $area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
- // Pause auto-hide timers when the mouse is in the notification area.
- .on( {
- mouseenter: notification.pause,
- mouseleave: notification.resume
- } )
- // When clicking on a notification close it.
- .on( 'click', '.mw-notification', function () {
- var notif = $( this ).data( 'mw.notification' );
- if ( notif ) {
- notif.close();
- }
- } )
- // Stop click events from <a> tags from propogating to prevent clicking.
- // on links from hiding a notification.
- .on( 'click', 'a', function ( e ) {
- e.stopPropagation();
- } );
-
- // Prepend the notification area to the content area and save it's object.
- mw.util.$content.prepend( $area );
- offset = $area.offset();
-
- function updateAreaMode() {
- var isFloating = $window.scrollTop() > offset.top;
- $area
- .toggleClass( 'mw-notification-area-floating', isFloating )
- .toggleClass( 'mw-notification-area-layout', !isFloating );
- }
-
- $window.on( 'scroll', updateAreaMode );
-
- // Initial mode
- updateAreaMode();
- }
-
- /**
- * @class mw.notification
- * @singleton
- */
- notification = {
- /**
- * Pause auto-hide timers for all notifications.
- * Notifications will not auto-hide until resume is called.
- * @see mw.Notification#pause
- */
- pause: function () {
- callEachNotification(
- $area.children( '.mw-notification' ),
- 'pause'
- );
- },
-
- /**
- * Resume any paused auto-hide timers from the beginning.
- * Only the first #autoHideLimit timers will be resumed.
- */
- resume: function () {
- callEachNotification(
- // Only call resume on the first #autoHideLimit notifications.
- // Exclude noautohide notifications to avoid bugs where #autoHideLimit
- // `{ autoHide: false }` notifications are at the start preventing any
- // auto-hide notifications from being autohidden.
- $area.children( '.mw-notification-autohide' ).slice( 0, notification.autoHideLimit ),
- 'resume'
- );
- },
-
- /**
- * Display a notification message to the user.
- *
- * @param {HTMLElement|jQuery|mw.Message|string} message
- * @param {Object} options The options to use for the notification.
- * See #defaults for details.
- * @return {Object} Object with a close function to close the notification
- */
- notify: function ( message, options ) {
- var notif;
- options = $.extend( {}, notification.defaults, options );
-
- notif = new Notification( message, options );
-
- if ( isPageReady ) {
- notif.start();
- } else {
- preReadyNotifQueue.push( notif );
- }
- return { close: $.proxy( notif.close, notif ) };
- },
-
- /**
- * @property {Object}
- * The defaults for #notify options parameter.
- *
- * - autoHide:
- * A boolean indicating whether the notifification should automatically
- * be hidden after shown. Or if it should persist.
- *
- * - tag:
- * An optional string. When a notification is tagged only one message
- * with that tag will be displayed. Trying to display a new notification
- * with the same tag as one already being displayed will cause the other
- * notification to be closed and this new notification to open up inside
- * the same place as the previous notification.
- *
- * - title:
- * An optional title for the notification. Will be displayed above the
- * content. Usually in bold.
- */
- defaults: {
- autoHide: true,
- tag: false,
- title: undefined
- },
-
- /**
- * @property {number}
- * Number of seconds to wait before auto-hiding notifications.
- */
- autoHideSeconds: 5,
-
- /**
- * @property {number}
- * Maximum number of notifications to count down auto-hide timers for.
- * Only the first #autoHideLimit notifications being displayed will
- * auto-hide. Any notifications further down in the list will only start
- * counting down to auto-hide after the first few messages have closed.
- *
- * This basically represents the number of notifications the user should
- * be able to process in #autoHideSeconds time.
- */
- autoHideLimit: 3
- };
-
- $( function () {
- var notif;
-
- init();
-
- // Handle pre-ready queue.
- isPageReady = true;
- while ( preReadyNotifQueue.length ) {
- notif = preReadyNotifQueue.shift();
- notif.start();
- }
- } );
-
- mw.notification = notification;
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.notify.js b/resources/mediawiki/mediawiki.notify.js
deleted file mode 100644
index 743d6517..00000000
--- a/resources/mediawiki/mediawiki.notify.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @class mw.plugin.notify
- */
-( function ( mw, $ ) {
- 'use strict';
-
- /**
- * @see mw.notification#notify
- * @param message
- * @param options
- * @return {jQuery.Promise}
- */
- mw.notify = function ( message, options ) {
- var d = $.Deferred();
- // Don't bother loading the whole notification system if we never use it.
- mw.loader.using( 'mediawiki.notification', function () {
- // Call notify with the notification the user requested of us.
- d.resolve( mw.notification.notify( message, options ) );
- }, d.reject );
- return d.promise();
- };
-
- /**
- * @class mw
- * @mixins mw.plugin.notify
- */
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.searchSuggest.css b/resources/mediawiki/mediawiki.searchSuggest.css
deleted file mode 100644
index 0fb862b9..00000000
--- a/resources/mediawiki/mediawiki.searchSuggest.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Make sure the links are not underlined or colored, ever. */
-/* There is already a :focus / :hover indication on the <div>. */
-.suggestions a.mw-searchSuggest-link,
-.suggestions a.mw-searchSuggest-link:hover,
-.suggestions a.mw-searchSuggest-link:active,
-.suggestions a.mw-searchSuggest-link:focus {
- text-decoration: none;
- color: black;
-}
-
-.suggestions-result-current a.mw-searchSuggest-link,
-.suggestions-result-current a.mw-searchSuggest-link:hover,
-.suggestions-result-current a.mw-searchSuggest-link:active,
-.suggestions-result-current a.mw-searchSuggest-link:focus {
- color: white;
-}
diff --git a/resources/mediawiki/mediawiki.searchSuggest.js b/resources/mediawiki/mediawiki.searchSuggest.js
deleted file mode 100644
index 7f078626..00000000
--- a/resources/mediawiki/mediawiki.searchSuggest.js
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * Add search suggestions to the search form.
- */
-( function ( mw, $ ) {
- $( function () {
- var map, resultRenderCache, searchboxesSelectors,
- // Region where the suggestions box will appear directly below
- // (using the same width). Can be a container element or the input
- // itself, depending on what suits best in the environment.
- // For Vector the suggestion box should align with the simpleSearch
- // container's borders, in other skins it should align with the input
- // element (not the search form, as that would leave the buttons
- // vertically between the input and the suggestions).
- $searchRegion = $( '#simpleSearch, #searchInput' ).first(),
- $searchInput = $( '#searchInput' );
-
- // Compatibility map
- map = {
- browsers: {
- // Left-to-right languages
- ltr: {
- // SimpleSearch is broken in Opera < 9.6
- opera: [['>=', 9.6]],
- docomo: false,
- blackberry: false,
- ipod: false,
- iphone: false
- },
- // Right-to-left languages
- rtl: {
- opera: [['>=', 9.6]],
- docomo: false,
- blackberry: false,
- ipod: false,
- iphone: false
- }
- }
- };
-
- if ( !$.client.test( map ) ) {
- return;
- }
-
- // Compute form data for search suggestions functionality.
- function computeResultRenderCache( context ) {
- var $form, formAction, baseHref, linkParams;
-
- // Compute common parameters for links' hrefs
- $form = context.config.$region.closest( 'form' );
-
- formAction = $form.attr( 'action' );
- baseHref = formAction + ( formAction.match(/\?/) ? '&' : '?' );
-
- linkParams = {};
- $.each( $form.serializeArray(), function ( idx, obj ) {
- linkParams[ obj.name ] = obj.value;
- } );
-
- return {
- textParam: context.data.$textbox.attr( 'name' ),
- linkParams: linkParams,
- baseHref: baseHref
- };
- }
-
- // The function used to render the suggestions.
- function renderFunction( text, context ) {
- if ( !resultRenderCache ) {
- resultRenderCache = computeResultRenderCache( context );
- }
-
- // linkParams object is modified and reused
- resultRenderCache.linkParams[ resultRenderCache.textParam ] = text;
-
- // this is the container <div>, jQueryfied
- this
- .append(
- // the <span> is needed for $.autoEllipsis to work
- $( '<span>' )
- .css( 'whiteSpace', 'nowrap' )
- .text( text )
- )
- .wrap(
- $( '<a>' )
- .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) )
- .addClass( 'mw-searchSuggest-link' )
- );
- }
-
- function specialRenderFunction( query, context ) {
- var $el = this;
-
- if ( !resultRenderCache ) {
- resultRenderCache = computeResultRenderCache( context );
- }
-
- // linkParams object is modified and reused
- resultRenderCache.linkParams[ resultRenderCache.textParam ] = query;
-
- if ( $el.children().length === 0 ) {
- $el
- .append(
- $( '<div>' )
- .addClass( 'special-label' )
- .text( mw.msg( 'searchsuggest-containing' ) ),
- $( '<div>' )
- .addClass( 'special-query' )
- .text( query )
- .autoEllipsis()
- )
- .show();
- } else {
- $el.find( '.special-query' )
- .text( query )
- .autoEllipsis();
- }
-
- if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
- $el.parent().attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' );
- } else {
- $el.wrap(
- $( '<a>' )
- .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' )
- .addClass( 'mw-searchSuggest-link' )
- );
- }
- }
-
- // General suggestions functionality for all search boxes
- searchboxesSelectors = [
- // Primary searchbox on every page in standard skins
- '#searchInput',
- // Special:Search
- '#powerSearchText',
- '#searchText',
- // Generic selector for skins with multiple searchboxes (used by CologneBlue)
- '.mw-searchInput'
- ];
- $( searchboxesSelectors.join(', ') )
- .suggestions( {
- fetch: function ( query ) {
- var $el;
-
- if ( query.length !== 0 ) {
- $el = $( this );
- $el.data( 'request', ( new mw.Api() ).get( {
- action: 'opensearch',
- search: query,
- namespace: 0,
- suggest: ''
- } ).done( function ( data ) {
- $el.suggestions( 'suggestions', data[1] );
- } ) );
- }
- },
- cancel: function () {
- var apiPromise = $( this ).data( 'request' );
- // If the delay setting has caused the fetch to have not even happened
- // yet, the apiPromise object will have never been set.
- if ( apiPromise && $.isFunction( apiPromise.abort ) ) {
- apiPromise.abort();
- $( this ).removeData( 'request' );
- }
- },
- result: {
- render: renderFunction,
- select: function ( $input ) {
- $input.closest( 'form' ).submit();
- }
- },
- delay: 120,
- highlightInput: true
- } )
- .bind( 'paste cut drop', function () {
- // make sure paste and cut events from the mouse and drag&drop events
- // trigger the keypress handler and cause the suggestions to update
- $( this ).trigger( 'keypress' );
- } );
-
- // Ensure that the thing is actually present!
- if ( $searchRegion.length === 0 ) {
- // Don't try to set anything up if simpleSearch is disabled sitewide.
- // The loader code loads us if the option is present, even if we're
- // not actually enabled (anymore).
- return;
- }
-
- // Special suggestions functionality for skin-provided search box
- $searchInput.suggestions( {
- result: {
- render: renderFunction,
- select: function ( $input ) {
- $input.closest( 'form' ).submit();
- }
- },
- special: {
- render: specialRenderFunction,
- select: function ( $input ) {
- $input.closest( 'form' ).append(
- $( '<input type="hidden" name="fulltext" value="1"/>' )
- );
- $input.closest( 'form' ).submit();
- }
- },
- $region: $searchRegion
- } );
-
- // In most skins (at least Monobook and Vector), the font-size is messed up in <body>.
- // (they use 2 elements to get a sane font-height). So, instead of making exceptions for
- // each skin or adding more stylesheets, just copy it from the active element so auto-fit.
- $searchInput
- .data( 'suggestions-context' )
- .data.$container
- .css( 'fontSize', $searchInput.css( 'fontSize' ) );
-
- } );
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.user.js b/resources/mediawiki/mediawiki.user.js
deleted file mode 100644
index 3e375fb6..00000000
--- a/resources/mediawiki/mediawiki.user.js
+++ /dev/null
@@ -1,255 +0,0 @@
-/**
- * @class mw.user
- * @singleton
- */
-( function ( mw, $ ) {
- var user,
- callbacks = {},
- // Extend the skeleton mw.user from mediawiki.js
- // This is kind of ugly but we're stuck with this for b/c reasons
- options = mw.user.options || new mw.Map(),
- tokens = mw.user.tokens || new mw.Map();
-
- /**
- * Get the current user's groups or rights
- *
- * @private
- * @param {string} info One of 'groups' or 'rights'
- * @param {Function} callback
- */
- function getUserInfo( info, callback ) {
- var api;
- if ( callbacks[info] ) {
- callbacks[info].add( callback );
- return;
- }
- callbacks.rights = $.Callbacks('once memory');
- callbacks.groups = $.Callbacks('once memory');
- callbacks[info].add( callback );
- api = new mw.Api();
- api.get( {
- action: 'query',
- meta: 'userinfo',
- uiprop: 'rights|groups'
- } ).always( function ( data ) {
- var rights, groups;
- if ( data.query && data.query.userinfo ) {
- rights = data.query.userinfo.rights;
- groups = data.query.userinfo.groups;
- }
- callbacks.rights.fire( rights || [] );
- callbacks.groups.fire( groups || [] );
- } );
- }
-
- mw.user = user = {
- options: options,
- tokens: tokens,
-
- /**
- * Generate a random user session ID (32 alpha-numeric characters)
- *
- * This information would potentially be stored in a cookie to identify a user during a
- * session or series of sessions. Its uniqueness should not be depended on.
- *
- * @return {string} Random set of 32 alpha-numeric characters
- */
- generateRandomSessionId: function () {
- var i, r,
- id = '',
- seed = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
- for ( i = 0; i < 32; i++ ) {
- r = Math.floor( Math.random() * seed.length );
- id += seed.substring( r, r + 1 );
- }
- return id;
- },
-
- /**
- * Get the current user's database id
- *
- * Not to be confused with #id.
- *
- * @return {number} Current user's id, or 0 if user is anonymous
- */
- getId: function () {
- return mw.config.get( 'wgUserId', 0 );
- },
-
- /**
- * Get the current user's name
- *
- * @return {string|null} User name string or null if user is anonymous
- */
- getName: function () {
- return mw.config.get( 'wgUserName' );
- },
-
- /**
- * @inheritdoc #getName
- * @deprecated since 1.20 use #getName instead
- */
- name: function () {
- return user.getName();
- },
-
- /**
- * Get date user registered, if available
- *
- * @return {Date|boolean|null} Date user registered, or false for anonymous users, or
- * null when data is not available
- */
- getRegistration: function () {
- var registration = mw.config.get( 'wgUserRegistration' );
- if ( user.isAnon() ) {
- return false;
- } else if ( registration === null ) {
- // Information may not be available if they signed up before
- // MW began storing this.
- return null;
- } else {
- return new Date( registration );
- }
- },
-
- /**
- * Whether the current user is anonymous
- *
- * @return {boolean}
- */
- isAnon: function () {
- return user.getName() === null;
- },
-
- /**
- * @inheritdoc #isAnon
- * @deprecated since 1.20 use #isAnon instead
- */
- anonymous: function () {
- return user.isAnon();
- },
-
- /**
- * Get an automatically generated random ID (stored in a session cookie)
- *
- * This ID is ephemeral for everyone, staying in their browser only until they close
- * their browser.
- *
- * @return {string} Random session ID
- */
- sessionId: function () {
- var sessionId = $.cookie( 'mediaWiki.user.sessionId' );
- if ( sessionId === undefined || sessionId === null ) {
- sessionId = user.generateRandomSessionId();
- $.cookie( 'mediaWiki.user.sessionId', sessionId, { expires: null, path: '/' } );
- }
- return sessionId;
- },
-
- /**
- * Get the current user's name or the session ID
- *
- * Not to be confused with #getId.
- *
- * @return {string} User name or random session ID
- */
- id: function () {
- return user.getName() || user.sessionId();
- },
-
- /**
- * Get the user's bucket (place them in one if not done already)
- *
- * mw.user.bucket( 'test', {
- * buckets: { ignored: 50, control: 25, test: 25 },
- * version: 1,
- * expires: 7
- * } );
- *
- * @param {string} key Name of bucket
- * @param {Object} options Bucket configuration options
- * @param {Object} options.buckets List of bucket-name/relative-probability pairs (required,
- * must have at least one pair)
- * @param {number} [options.version=0] Version of bucket test, changing this forces
- * rebucketing
- * @param {number} [options.expires=30] Length of time (in days) until the user gets
- * rebucketed
- * @return {string} Bucket name - the randomly chosen key of the `options.buckets` object
- */
- bucket: function ( key, options ) {
- var cookie, parts, version, bucket,
- range, k, rand, total;
-
- options = $.extend( {
- buckets: {},
- version: 0,
- expires: 30
- }, options || {} );
-
- cookie = $.cookie( 'mediaWiki.user.bucket:' + key );
-
- // Bucket information is stored as 2 integers, together as version:bucket like: "1:2"
- if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) !== -1 ) {
- parts = cookie.split( ':' );
- if ( parts.length > 1 && Number( parts[0] ) === options.version ) {
- version = Number( parts[0] );
- bucket = String( parts[1] );
- }
- }
-
- if ( bucket === undefined ) {
- if ( !$.isPlainObject( options.buckets ) ) {
- throw new Error( 'Invalid bucket. Object expected for options.buckets.' );
- }
-
- version = Number( options.version );
-
- // Find range
- range = 0;
- for ( k in options.buckets ) {
- range += options.buckets[k];
- }
-
- // Select random value within range
- rand = Math.random() * range;
-
- // Determine which bucket the value landed in
- total = 0;
- for ( k in options.buckets ) {
- bucket = k;
- total += options.buckets[k];
- if ( total >= rand ) {
- break;
- }
- }
-
- $.cookie(
- 'mediaWiki.user.bucket:' + key,
- version + ':' + bucket,
- { path: '/', expires: Number( options.expires ) }
- );
- }
-
- return bucket;
- },
-
- /**
- * Get the current user's groups
- *
- * @param {Function} callback
- */
- getGroups: function ( callback ) {
- getUserInfo( 'groups', callback );
- },
-
- /**
- * Get the current user's rights
- *
- * @param {Function} callback
- */
- getRights: function ( callback ) {
- getUserInfo( 'rights', callback );
- }
- };
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mediawiki/mediawiki.util.js b/resources/mediawiki/mediawiki.util.js
deleted file mode 100644
index 7383df2d..00000000
--- a/resources/mediawiki/mediawiki.util.js
+++ /dev/null
@@ -1,624 +0,0 @@
-( function ( mw, $ ) {
- 'use strict';
-
- /**
- * Utility library
- * @class mw.util
- * @singleton
- */
- var util = {
-
- /**
- * Initialisation
- * (don't call before document ready)
- */
- init: function () {
- var profile;
-
- /* Set tooltipAccessKeyPrefix */
- profile = $.client.profile();
-
- // Opera on any platform
- if ( profile.name === 'opera' ) {
- util.tooltipAccessKeyPrefix = 'shift-esc-';
-
- // Chrome on any platform
- } else if ( profile.name === 'chrome' ) {
-
- util.tooltipAccessKeyPrefix = (
- profile.platform === 'mac'
- // Chrome on Mac
- ? 'ctrl-option-'
- // Chrome on Windows or Linux
- // (both alt- and alt-shift work, but alt with E, D, F etc does not
- // work since they are browser shortcuts)
- : 'alt-shift-'
- );
-
- // Non-Windows Safari with webkit_version > 526
- } else if ( profile.platform !== 'win'
- && profile.name === 'safari'
- && profile.layoutVersion > 526 ) {
- util.tooltipAccessKeyPrefix = 'ctrl-alt-';
- // Firefox 14+ on Mac
- } else if ( profile.platform === 'mac'
- && profile.name === 'firefox'
- && profile.versionNumber >= 14 ) {
- util.tooltipAccessKeyPrefix = 'ctrl-option-';
- // Safari/Konqueror on any platform, or any browser on Mac
- // (but not Safari on Windows)
- } else if ( !( profile.platform === 'win' && profile.name === 'safari' )
- && ( profile.name === 'safari'
- || profile.platform === 'mac'
- || profile.name === 'konqueror' ) ) {
- util.tooltipAccessKeyPrefix = 'ctrl-';
-
- // Firefox/Iceweasel 2.x and later
- } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' )
- && profile.versionBase > '1' ) {
- util.tooltipAccessKeyPrefix = 'alt-shift-';
- }
-
- /* Fill $content var */
- util.$content = ( function () {
- var i, l, $content, selectors;
- selectors = [
- // The preferred standard for setting $content (class="mw-body")
- // You may also use (class="mw-body mw-body-primary") if you use
- // mw-body in multiple locations.
- // Or class="mw-body-primary" if you want $content to be deeper
- // in the dom than mw-body
- '.mw-body-primary',
- '.mw-body',
-
- /* Legacy fallbacks for setting the content */
- // Vector, Monobook, Chick, etc... based skins
- '#bodyContent',
-
- // Modern based skins
- '#mw_contentholder',
-
- // Standard, CologneBlue
- '#article',
-
- // #content is present on almost all if not all skins. Most skins (the above cases)
- // have #content too, but as an outer wrapper instead of the article text container.
- // The skins that don't have an outer wrapper do have #content for everything
- // so it's a good fallback
- '#content',
-
- // If nothing better is found fall back to our bodytext div that is guaranteed to be here
- '#mw-content-text',
-
- // Should never happen... well, it could if someone is not finished writing a skin and has
- // not inserted bodytext yet. But in any case <body> should always exist
- 'body'
- ];
- for ( i = 0, l = selectors.length; i < l; i++ ) {
- $content = $( selectors[i] ).first();
- if ( $content.length ) {
- return $content;
- }
- }
-
- // Make sure we don't unset util.$content if it was preset and we don't find anything
- return util.$content;
- } )();
-
- // Table of contents toggle
- mw.hook( 'wikipage.content' ).add( function () {
- var $tocTitle, $tocToggleLink, hideTocCookie;
- $tocTitle = $( '#toctitle' );
- $tocToggleLink = $( '#togglelink' );
- // Only add it if there is a TOC and there is no toggle added already
- if ( $( '#toc' ).length && $tocTitle.length && !$tocToggleLink.length ) {
- hideTocCookie = $.cookie( 'mw_hidetoc' );
- $tocToggleLink = $( '<a href="#" class="internal" id="togglelink"></a>' )
- .text( mw.msg( 'hidetoc' ) )
- .click( function ( e ) {
- e.preventDefault();
- util.toggleToc( $(this) );
- } );
- $tocTitle.append(
- $tocToggleLink
- .wrap( '<span class="toctoggle"></span>' )
- .parent()
- .prepend( '&nbsp;[' )
- .append( ']&nbsp;' )
- );
-
- if ( hideTocCookie === '1' ) {
- util.toggleToc( $tocToggleLink );
- }
- }
- } );
- },
-
- /* Main body */
-
- /**
- * Encode the string like PHP's rawurlencode
- *
- * @param {string} str String to be encoded.
- */
- rawurlencode: function ( str ) {
- str = String( str );
- return encodeURIComponent( str )
- .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
- .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
- },
-
- /**
- * Encode page titles for use in a URL
- * We want / and : to be included as literal characters in our title URLs
- * as they otherwise fatally break the title
- *
- * @param {string} str String to be encoded.
- */
- wikiUrlencode: function ( str ) {
- return util.rawurlencode( str )
- .replace( /%20/g, '_' ).replace( /%3A/g, ':' ).replace( /%2F/g, '/' );
- },
-
- /**
- * Get the link to a page name (relative to `wgServer`),
- *
- * @param {string} str Page name to get the link for.
- * @param {Object} params A mapping of query parameter names to values,
- * e.g. { action: 'edit' }. Optional.
- * @return {string} Location for a page with name of `str` or boolean false on error.
- */
- getUrl: function ( str, params ) {
- var url = mw.config.get( 'wgArticlePath' ).replace( '$1',
- util.wikiUrlencode( typeof str === 'string' ? str : mw.config.get( 'wgPageName' ) ) );
- if ( params && !$.isEmptyObject( params ) ) {
- url += url.indexOf( '?' ) !== -1 ? '&' : '?';
- url += $.param( params );
- }
- return url;
- },
-
- /**
- * Get address to a script in the wiki root.
- * For index.php use `mw.config.get( 'wgScript' )`.
- *
- * @since 1.18
- * @param str string Name of script (eg. 'api'), defaults to 'index'
- * @return string Address to script (eg. '/w/api.php' )
- */
- wikiScript: function ( str ) {
- str = str || 'index';
- if ( str === 'index' ) {
- return mw.config.get( 'wgScript' );
- } else if ( str === 'load' ) {
- return mw.config.get( 'wgLoadScript' );
- } else {
- return mw.config.get( 'wgScriptPath' ) + '/' + str +
- mw.config.get( 'wgScriptExtension' );
- }
- },
-
- /**
- * Append a new style block to the head and return the CSSStyleSheet object.
- * Use .ownerNode to access the `<style>` element, or use mw.loader#addStyleTag.
- * This function returns the styleSheet object for convience (due to cross-browsers
- * difference as to where it is located).
- *
- * var sheet = mw.util.addCSS('.foobar { display: none; }');
- * $(foo).click(function () {
- * // Toggle the sheet on and off
- * sheet.disabled = !sheet.disabled;
- * });
- *
- * @param {string} text CSS to be appended
- * @return {CSSStyleSheet} Use .ownerNode to get to the `<style>` element.
- */
- addCSS: function ( text ) {
- var s = mw.loader.addStyleTag( text );
- return s.sheet || s;
- },
-
- /**
- * Hide/show the table of contents element
- *
- * @param {jQuery} $toggleLink A jQuery object of the toggle link.
- * @param {Function} [callback] Function to be called after the toggle is
- * completed (including the animation).
- * @return {Mixed} Boolean visibility of the toc (true if it's visible)
- * or Null if there was no table of contents.
- */
- toggleToc: function ( $toggleLink, callback ) {
- var $tocList = $( '#toc ul:first' );
-
- // This function shouldn't be called if there's no TOC,
- // but just in case...
- if ( $tocList.length ) {
- if ( $tocList.is( ':hidden' ) ) {
- $tocList.slideDown( 'fast', callback );
- $toggleLink.text( mw.msg( 'hidetoc' ) );
- $( '#toc' ).removeClass( 'tochidden' );
- $.cookie( 'mw_hidetoc', null, {
- expires: 30,
- path: '/'
- } );
- return true;
- } else {
- $tocList.slideUp( 'fast', callback );
- $toggleLink.text( mw.msg( 'showtoc' ) );
- $( '#toc' ).addClass( 'tochidden' );
- $.cookie( 'mw_hidetoc', '1', {
- expires: 30,
- path: '/'
- } );
- return false;
- }
- } else {
- return null;
- }
- },
-
- /**
- * Grab the URL parameter value for the given parameter.
- * Returns null if not found.
- *
- * @param {string} param The parameter name.
- * @param {string} [url=document.location.href] URL to search through, defaulting to the current document's URL.
- * @return {Mixed} Parameter value or null.
- */
- getParamValue: function ( param, url ) {
- if ( url === undefined ) {
- url = document.location.href;
- }
- // Get last match, stop at hash
- var re = new RegExp( '^[^#]*[&?]' + $.escapeRE( param ) + '=([^&#]*)' ),
- m = re.exec( url );
- if ( m ) {
- // Beware that decodeURIComponent is not required to understand '+'
- // by spec, as encodeURIComponent does not produce it.
- return decodeURIComponent( m[1].replace( /\+/g, '%20' ) );
- }
- return null;
- },
-
- /**
- * @property {string}
- * Access key prefix. Will be re-defined based on browser/operating system
- * detection in mw.util#init.
- */
- tooltipAccessKeyPrefix: 'alt-',
-
- /**
- * @property {RegExp}
- * Regex to match accesskey tooltips.
- *
- * Should match:
- *
- * - "ctrl-option-"
- * - "alt-shift-"
- * - "ctrl-alt-"
- * - "ctrl-"
- *
- * The accesskey is matched in group $6.
- */
- tooltipAccessKeyRegexp: /\[(ctrl-)?(option-)?(alt-)?(shift-)?(esc-)?(.)\]$/,
-
- /**
- * Add the appropriate prefix to the accesskey shown in the tooltip.
- * If the nodeList parameter is given, only those nodes are updated;
- * otherwise, all the nodes that will probably have accesskeys by
- * default are updated.
- *
- * @param {Array|jQuery} [$nodes] A jQuery object, or array of nodes to update.
- */
- updateTooltipAccessKeys: function ( $nodes ) {
- if ( !$nodes ) {
- // Rather than going into a loop of all anchor tags, limit to few elements that
- // contain the relevant anchor tags.
- // Input and label are rare enough that no such optimization is needed
- $nodes = $( '#column-one a, #mw-head a, #mw-panel a, #p-logo a, input, label' );
- } else if ( !( $nodes instanceof $ ) ) {
- $nodes = $( $nodes );
- }
-
- $nodes.attr( 'title', function ( i, val ) {
- if ( val && util.tooltipAccessKeyRegexp.test( val ) ) {
- return val.replace( util.tooltipAccessKeyRegexp,
- '[' + util.tooltipAccessKeyPrefix + '$6]' );
- }
- return val;
- } );
- },
-
- /*
- * @property {jQuery}
- * A jQuery object that refers to the content area element.
- * Populated by #init.
- */
- $content: null,
-
- /**
- * Add a link to a portlet menu on the page, such as:
- *
- * p-cactions (Content actions), p-personal (Personal tools),
- * p-navigation (Navigation), p-tb (Toolbox)
- *
- * The first three paramters are required, the others are optional and
- * may be null. Though providing an id and tooltip is recommended.
- *
- * By default the new link will be added to the end of the list. To
- * add the link before a given existing item, pass the DOM node
- * (e.g. `document.getElementById( 'foobar' )`) or a jQuery-selector
- * (e.g. `'#foobar'`) for that item.
- *
- * mw.util.addPortletLink(
- * 'p-tb', 'http://mediawiki.org/',
- * 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', '#t-print'
- * );
- *
- * @param {string} portlet ID of the target portlet ( 'p-cactions' or 'p-personal' etc.)
- * @param {string} href Link URL
- * @param {string} text Link text
- * @param {string} [id] ID of the new item, should be unique and preferably have
- * the appropriate prefix ( 'ca-', 'pt-', 'n-' or 't-' )
- * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix
- * @param {string} [accesskey] Access key to activate this link (one character, try
- * to avoid conflicts. Use `$( '[accesskey=x]' ).get()` in the console to
- * see if 'x' is already used.
- * @param {HTMLElement|jQuery|string} [nextnode] Element or jQuery-selector string to the item that
- * the new item should be added before, should be another item in the same
- * list, it will be ignored otherwise
- *
- * @return {HTMLElement|null} The added element (a ListItem or Anchor element,
- * depending on the skin) or null if no element was added to the document.
- */
- addPortletLink: function ( portlet, href, text, id, tooltip, accesskey, nextnode ) {
- var $item, $link, $portlet, $ul;
-
- // Check if there's atleast 3 arguments to prevent a TypeError
- if ( arguments.length < 3 ) {
- return null;
- }
- // Setup the anchor tag
- $link = $( '<a>' ).attr( 'href', href ).text( text );
- if ( tooltip ) {
- $link.attr( 'title', tooltip );
- }
-
- // Select the specified portlet
- $portlet = $( '#' + portlet );
- if ( $portlet.length === 0 ) {
- return null;
- }
- // Select the first (most likely only) unordered list inside the portlet
- $ul = $portlet.find( 'ul' ).eq( 0 );
-
- // If it didn't have an unordered list yet, create it
- if ( $ul.length === 0 ) {
-
- $ul = $( '<ul>' );
-
- // If there's no <div> inside, append it to the portlet directly
- if ( $portlet.find( 'div:first' ).length === 0 ) {
- $portlet.append( $ul );
- } else {
- // otherwise if there's a div (such as div.body or div.pBody)
- // append the <ul> to last (most likely only) div
- $portlet.find( 'div' ).eq( -1 ).append( $ul );
- }
- }
- // Just in case..
- if ( $ul.length === 0 ) {
- return null;
- }
-
- // Unhide portlet if it was hidden before
- $portlet.removeClass( 'emptyPortlet' );
-
- // Wrap the anchor tag in a list item (and a span if $portlet is a Vector tab)
- // and back up the selector to the list item
- if ( $portlet.hasClass( 'vectorTabs' ) ) {
- $item = $link.wrap( '<li><span></span></li>' ).parent().parent();
- } else {
- $item = $link.wrap( '<li></li>' ).parent();
- }
-
- // Implement the properties passed to the function
- if ( id ) {
- $item.attr( 'id', id );
- }
-
- if ( tooltip ) {
- // Trim any existing accesskey hint and the trailing space
- tooltip = $.trim( tooltip.replace( util.tooltipAccessKeyRegexp, '' ) );
- if ( accesskey ) {
- tooltip += ' [' + accesskey + ']';
- }
- $link.attr( 'title', tooltip );
- if ( accesskey ) {
- util.updateTooltipAccessKeys( $link );
- }
- }
-
- if ( accesskey ) {
- $link.attr( 'accesskey', accesskey );
- }
-
- if ( nextnode ) {
- if ( nextnode.nodeType || typeof nextnode === 'string' ) {
- // nextnode is a DOM element (was the only option before MW 1.17, in wikibits.js)
- // or nextnode is a CSS selector for jQuery
- nextnode = $ul.find( nextnode );
- } else if ( !nextnode.jquery || ( nextnode.length && nextnode[0].parentNode !== $ul[0] ) ) {
- // Fallback
- $ul.append( $item );
- return $item[0];
- }
- if ( nextnode.length === 1 ) {
- // nextnode is a jQuery object that represents exactly one element
- nextnode.before( $item );
- return $item[0];
- }
- }
-
- // Fallback (this is the default behavior)
- $ul.append( $item );
- return $item[0];
-
- },
-
- /**
- * Add a little box at the top of the screen to inform the user of
- * something, replacing any previous message.
- * Calling with no arguments, with an empty string or null will hide the message
- *
- * @param {Mixed} message The DOM-element, jQuery object or HTML-string to be put inside the message box.
- * to allow CSS/JS to hide different boxes. null = no class used.
- * @deprecated since 1.20 Use mw#notify
- */
- jsMessage: function ( message ) {
- if ( !arguments.length || message === '' || message === null ) {
- return true;
- }
- if ( typeof message !== 'object' ) {
- message = $.parseHTML( message );
- }
- mw.notify( message, { autoHide: true, tag: 'legacy' } );
- return true;
- },
-
- /**
- * Validate a string as representing a valid e-mail address
- * according to HTML5 specification. Please note the specification
- * does not validate a domain with one character.
- *
- * FIXME: should be moved to or replaced by a validation module.
- *
- * @param {string} mailtxt E-mail address to be validated.
- * @return {boolean|null} Null if `mailtxt` was an empty string, otherwise true/false
- * as determined by validation.
- */
- validateEmail: function ( mailtxt ) {
- var rfc5322Atext, rfc1034LdhStr, html5EmailRegexp;
-
- if ( mailtxt === '' ) {
- return null;
- }
-
- // HTML5 defines a string as valid e-mail address if it matches
- // the ABNF:
- // 1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
- // With:
- // - atext : defined in RFC 5322 section 3.2.3
- // - ldh-str : defined in RFC 1034 section 3.5
- //
- // (see STD 68 / RFC 5234 http://tools.ietf.org/html/std68)
- // First, define the RFC 5322 'atext' which is pretty easy:
- // atext = ALPHA / DIGIT / ; Printable US-ASCII
- // "!" / "#" / ; characters not including
- // "$" / "%" / ; specials. Used for atoms.
- // "&" / "'" /
- // "*" / "+" /
- // "-" / "/" /
- // "=" / "?" /
- // "^" / "_" /
- // "`" / "{" /
- // "|" / "}" /
- // "~"
- rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~';
-
- // Next define the RFC 1034 'ldh-str'
- // <domain> ::= <subdomain> | " "
- // <subdomain> ::= <label> | <subdomain> "." <label>
- // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
- // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
- // <let-dig-hyp> ::= <let-dig> | "-"
- // <let-dig> ::= <letter> | <digit>
- rfc1034LdhStr = 'a-z0-9\\-';
-
- html5EmailRegexp = new RegExp(
- // start of string
- '^'
- +
- // User part which is liberal :p
- '[' + rfc5322Atext + '\\.]+'
- +
- // 'at'
- '@'
- +
- // Domain first part
- '[' + rfc1034LdhStr + ']+'
- +
- // Optional second part and following are separated by a dot
- '(?:\\.[' + rfc1034LdhStr + ']+)*'
- +
- // End of string
- '$',
- // RegExp is case insensitive
- 'i'
- );
- return (null !== mailtxt.match( html5EmailRegexp ) );
- },
-
- /**
- * Note: borrows from IP::isIPv4
- *
- * @param {string} address
- * @param {boolean} allowBlock
- * @return {boolean}
- */
- isIPv4Address: function ( address, allowBlock ) {
- if ( typeof address !== 'string' ) {
- return false;
- }
-
- var block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '',
- RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])',
- RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
-
- return address.search( new RegExp( '^' + RE_IP_ADD + block + '$' ) ) !== -1;
- },
-
- /**
- * Note: borrows from IP::isIPv6
- *
- * @param {string} address
- * @param {boolean} allowBlock
- * @return {boolean}
- */
- isIPv6Address: function ( address, allowBlock ) {
- if ( typeof address !== 'string' ) {
- return false;
- }
-
- var block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '',
- RE_IPV6_ADD =
- '(?:' + // starts with "::" (including "::")
- ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' +
- '|' + // ends with "::" (except "::")
- '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' +
- '|' + // contains no "::"
- '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' +
- ')';
-
- if ( address.search( new RegExp( '^' + RE_IPV6_ADD + block + '$' ) ) !== -1 ) {
- return true;
- }
-
- RE_IPV6_ADD = // contains one "::" in the middle (single '::' check below)
- '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}';
-
- return address.search( new RegExp( '^' + RE_IPV6_ADD + block + '$' ) ) !== -1
- && address.search( /::/ ) !== -1 && address.search( /::.*::/ ) === -1;
- }
- };
-
- /**
- * @method wikiGetlink
- * @inheritdoc #getUrl
- * @deprecated since 1.23 Use #getUrl instead.
- */
- mw.log.deprecate( util, 'wikiGetlink', util.getUrl, 'Use mw.util.getUrl instead.' );
-
- mw.util = util;
-
-}( mediaWiki, jQuery ) );