diff options
Diffstat (limited to 'resources/mediawiki')
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 Binary files differdeleted file mode 100644 index b17e578b..00000000 --- a/resources/mediawiki/images/arrow-collapsed-ltr.png +++ /dev/null diff --git a/resources/mediawiki/images/arrow-collapsed-rtl.png b/resources/mediawiki/images/arrow-collapsed-rtl.png Binary files differdeleted file mode 100644 index a834548e..00000000 --- a/resources/mediawiki/images/arrow-collapsed-rtl.png +++ /dev/null diff --git a/resources/mediawiki/images/arrow-expanded.png b/resources/mediawiki/images/arrow-expanded.png Binary files differdeleted file mode 100644 index 2bec798e..00000000 --- a/resources/mediawiki/images/arrow-expanded.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index aed0ea41..00000000 --- a/resources/mediawiki/mediawiki.feedback.spinner.gif +++ /dev/null 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( /'/g, '\'' ) - .replace( /"/g, '"' ) - .replace( /</g, '<' ) - .replace( />/g, '>' ) - .replace( /&/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 '<script>' and '</script>' - // (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="<"/></div> - * - * @class mw.html - * @singleton - */ - html: ( function () { - function escapeCallback( s ) { - switch ( s ) { - case '\'': - return '''; - case '"': - return '"'; - case '<': - return '<'; - case '>': - return '>'; - case '&': - return '&'; - } - } - - return { - /** - * Escape a string for HTML. Converts special characters to HTML entities. - * @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( ' [' ) - .append( '] ' ) - ); - - 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 ) ); |