'scripts' => 'resources/mediawiki/mediawiki.htmlform.js',
),
'mediawiki.Title' => array(
- 'scripts' => 'resources/mediawiki/mediawiki.title.js',
+ 'scripts' => 'resources/mediawiki/mediawiki.Title.js',
'dependencies' => 'mediawiki.util',
),
'mediawiki.Uri' => array(
- 'scripts' => 'resources/mediawiki/mediawiki.uri.js',
+ 'scripts' => 'resources/mediawiki/mediawiki.Uri.js',
),
'mediawiki.user' => array(
'scripts' => 'resources/mediawiki/mediawiki.user.js',
--- /dev/null
+/**
+ * mediaWiki.Title
+ *
+ * @author Neil Kandalgaonkar, 2010
+ * @author Timo Tijhof, 2011
+ * @since 1.18
+ *
+ * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
+ */
+( function( $ ) {
+
+ /* Local space */
+
+ /**
+ * Title
+ * @constructor
+ *
+ * @param title {String} Title of the page. If no second argument given,
+ * this will be searched for a namespace.
+ * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is.
+ * @return {Title} this
+ */
+var Title = function( title, namespace ) {
+ this._ns = 0; // integer namespace id
+ this._name = null; // name in canonical 'database' form
+ this._ext = null; // extension
+
+ if ( arguments.length === 2 ) {
+ setNameAndExtension( this, title );
+ this._ns = fixNsId( namespace );
+ } else if ( arguments.length === 1 ) {
+ setAll( this, title );
+ }
+ return this;
+ },
+
+ /**
+ * Strip some illegal chars: control chars, colon, less than, greater than,
+ * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
+ * intact, like unicode bidi chars, but it's a good start..
+ * @param s {String}
+ * @return {String}
+ */
+ clean = function( s ) {
+ if ( s !== undefined ) {
+ return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
+ }
+ },
+
+ /**
+ * Convert db-key to readable text.
+ * @param s {String}
+ * @return {String}
+ */
+ text = function ( s ) {
+ if ( s !== null && s !== undefined ) {
+ return s.replace( /_/g, ' ' );
+ } else {
+ return '';
+ }
+ },
+
+ /**
+ * Sanitize name.
+ */
+ fixName = function( s ) {
+ return clean( $.trim( s ) );
+ },
+
+ /**
+ * Sanitize name.
+ */
+ fixExt = function( s ) {
+ return clean( s.toLowerCase() );
+ },
+
+ /**
+ * Sanitize namespace id.
+ * @param id {Number} Namespace id.
+ * @return {Number|Boolean} The id as-is or boolean false if invalid.
+ */
+ fixNsId = function( id ) {
+ // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
+ var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
+
+ // Check only undefined (may be false-y, such as '' (main namespace) ).
+ if ( ns === undefined ) {
+ return false;
+ } else {
+ return Number( id );
+ }
+ },
+
+ /**
+ * Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
+ *
+ * @example On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
+ * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
+ * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
+ */
+ getNsIdByName = function( ns ) {
+ // toLowerCase throws exception on null/undefined. Return early.
+ if ( ns == null ) {
+ return false;
+ }
+ ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
+ var id = mw.config.get( 'wgNamespaceIds' )[ns];
+ if ( id === undefined ) {
+ mw.log( 'mw.Title: Unrecognized namespace: ' + ns );
+ return false;
+ }
+ return fixNsId( id );
+ },
+
+ /**
+ * Helper to extract namespace, name and extension from a string.
+ *
+ * @param title {mw.Title}
+ * @param raw {String}
+ * @return {mw.Title}
+ */
+ setAll = function( title, s ) {
+ // In normal browsers the match-array contains null/undefined if there's no match,
+ // IE returns an empty string.
+ var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w{1,5}))?$/ ),
+ ns_match = getNsIdByName( matches[1] );
+
+ // Namespace must be valid, and title must be a non-empty string.
+ if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) {
+ title._ns = ns_match;
+ title._name = fixName( matches[2] );
+ if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
+ title._ext = fixExt( matches[3] );
+ }
+ } else {
+ // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
+ title._ns = 0;
+ setNameAndExtension( title, s );
+ }
+ return title;
+ },
+
+ /**
+ * Helper to extract name and extension from a string.
+ *
+ * @param title {mw.Title}
+ * @param raw {String}
+ * @return {mw.Title}
+ */
+ setNameAndExtension = function( title, raw ) {
+ // In normal browsers the match-array contains null/undefined if there's no match,
+ // IE returns an empty string.
+ var matches = raw.match( /^(?:)?(.*?)(?:\.(\w{1,5}))?$/ );
+
+ // Title must be a non-empty string.
+ if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
+ title._name = fixName( matches[1] );
+ if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
+ title._ext = fixExt( matches[2] );
+ }
+ } else {
+ throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
+ }
+ return title;
+ };
+
+
+ /* Static space */
+
+ /**
+ * Whether this title exists on the wiki.
+ * @param title {mixed} prefixed db-key name (string) or instance of Title
+ * @return {mixed} Boolean true/false if the information is available. Otherwise null.
+ */
+ Title.exists = function( title ) {
+ var type = $.type( title ), obj = Title.exist.pages, match;
+ 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;
+ };
+
+ /**
+ * @var Title.exist {Object}
+ */
+ Title.exist = {
+ /**
+ * @var Title.exist.pages {Object} Keyed by PrefixedDb title.
+ * Boolean true value indicates page does exist.
+ */
+ pages: {},
+ /**
+ * @example Declare existing titles: Title.exist.set(['User:John_Doe', ...]);
+ * @example Declare titles inexisting: Title.exist.set(['File:Foo_bar.jpg', ...], false);
+ * @param titles {String|Array} Title(s) in strict prefixedDb title form.
+ * @param state {Boolean} (optional) State of the given titles. Defaults to true.
+ * @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 methods */
+
+ var fn = {
+ constructor: Title,
+
+ /**
+ * Get the namespace number.
+ * @return {Number}
+ */
+ getNamespaceId: function(){
+ return this._ns;
+ },
+
+ /**
+ * Get the namespace prefix (in the content-language).
+ * In NS_MAIN this is '', otherwise namespace name plus ':'
+ * @return {String}
+ */
+ getNamespacePrefix: function(){
+ return mw.config.get( 'wgFormattedNamespaces' )[this._ns].replace( / /g, '_' ) + (this._ns === 0 ? '' : ':');
+ },
+
+ /**
+ * The name, like "Foo_bar"
+ * @return {String}
+ */
+ getName: function() {
+ if ( $.inArray( this._ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
+ return this._name;
+ } else {
+ return $.ucFirst( this._name );
+ }
+ },
+
+ /**
+ * The name, like "Foo bar"
+ * @return {String}
+ */
+ getNameText: function() {
+ return text( this.getName() );
+ },
+
+ /**
+ * Get full name in prefixed DB form, like File:Foo_bar.jpg,
+ * most useful for API calls, anything that must identify the "title".
+ */
+ getPrefixedDb: function() {
+ return this.getNamespacePrefix() + this.getMain();
+ },
+
+ /**
+ * Get full name in text form, like "File:Foo bar.jpg".
+ * @return {String}
+ */
+ getPrefixedText: function() {
+ return text( this.getPrefixedDb() );
+ },
+
+ /**
+ * The main title (without namespace), like "Foo_bar.jpg"
+ * @return {String}
+ */
+ getMain: function() {
+ return this.getName() + this.getDotExtension();
+ },
+
+ /**
+ * The "text" form, like "Foo bar.jpg"
+ * @return {String}
+ */
+ getMainText: function() {
+ return text( this.getMain() );
+ },
+
+ /**
+ * Get the extension (returns null if there was none)
+ * @return {String|null} extension
+ */
+ getExtension: function() {
+ return this._ext;
+ },
+
+ /**
+ * Convenience method: return string like ".jpg", or "" if no extension
+ * @return {String}
+ */
+ getDotExtension: function() {
+ return this._ext === null ? '' : '.' + this._ext;
+ },
+
+ /**
+ * Return the URL to this title
+ * @return {String}
+ */
+ getUrl: function() {
+ return mw.util.wikiGetlink( this.toString() );
+ },
+
+ /**
+ * Whether this title exists on the wiki.
+ * @return {mixed} Boolean true/false if the information is available. Otherwise null.
+ */
+ exists: function() {
+ return Title.exists( this );
+ }
+ };
+
+ // Alias
+ fn.toString = fn.getPrefixedDb;
+ fn.toText = fn.getPrefixedText;
+
+ // Assign
+ Title.prototype = fn;
+
+ // Expose
+ mw.Title = Title;
+
+})(jQuery);
--- /dev/null
+/**
+ * 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( $ ) {
+
+ /**
+ * 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} to prepend, if value not empty
+ * @param {String} value to include, if not empty
+ * @param {String} to append, if value not empty
+ * @param {Boolean} raw -- if true, do not URI encode
+ * @return {String}
+ */
+ function cat( pre, val, post, raw ) {
+ if ( val === undefined || val === null || val === '' ) {
+ return '';
+ } else {
+ 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
+ ];
+
+ /**
+ * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
+ * @constructor
+ * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties.
+ * @param {Boolean} strict mode (when parsing a string)
+ */
+ mw.Uri = function( uri, strictMode ) {
+ strictMode = !!strictMode;
+ if ( uri !== undefined && uri !== null || uri !== '' ) {
+ if ( typeof uri === 'string' ) {
+ this._parse( uri, strictMode );
+ } else if ( typeof uri === 'object' ) {
+ var _this = this;
+ $.each( properties, function( i, property ) {
+ _this[property] = uri[property];
+ } );
+ if ( this.query === undefined ) {
+ this.query = {};
+ }
+ }
+ }
+ 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} string
+ * @return {String} encoded for URI
+ */
+ mw.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} string encoded for URI
+ * @return {String} decoded string
+ */
+ mw.Uri.decode = function( s ) {
+ return decodeURIComponent( s ).replace( /\+/g, ' ' );
+ };
+
+ mw.Uri.prototype = {
+
+ /**
+ * Parse a string and set our properties accordingly.
+ * @param {String} URI
+ * @param {Boolean} strictness
+ * @return {Boolean} success
+ */
+ _parse: function( str, strictMode ) {
+ var matches = parser[ strictMode ? 'strict' : 'loose' ].exec( str );
+ var uri = this;
+ $.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.
+ var q = {};
+ // using replace to iterate over a string
+ if ( uri.query ) {
+ uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) {
+ if ( $1 ) {
+ var k = mw.Uri.decode( $1 );
+ var v = ( $2 === '' || $2 === undefined ) ? null : mw.Uri.decode( $3 );
+ if ( typeof q[ k ] === 'string' ) {
+ q[ k ] = [ q[ k ] ];
+ }
+ if ( typeof q[ k ] === 'object' ) {
+ q[ k ].push( v );
+ } else {
+ q[ k ] = 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 = mw.Uri.encode( key );
+ var vals = val === null ? [ null ] : $.makeArray( val );
+ $.each( vals, function( i, v ) {
+ args.push( k + ( v === null ? '' : '=' + mw.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 mw.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;
+ }
+ };
+
+} )( jQuery );
+++ /dev/null
-/**
- * mediaWiki.Title
- *
- * @author Neil Kandalgaonkar, 2010
- * @author Timo Tijhof, 2011
- * @since 1.18
- *
- * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
- */
-( function( $ ) {
-
- /* Local space */
-
- /**
- * Title
- * @constructor
- *
- * @param title {String} Title of the page. If no second argument given,
- * this will be searched for a namespace.
- * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is.
- * @return {Title} this
- */
-var Title = function( title, namespace ) {
- this._ns = 0; // integer namespace id
- this._name = null; // name in canonical 'database' form
- this._ext = null; // extension
-
- if ( arguments.length === 2 ) {
- setNameAndExtension( this, title );
- this._ns = fixNsId( namespace );
- } else if ( arguments.length === 1 ) {
- setAll( this, title );
- }
- return this;
- },
-
- /**
- * Strip some illegal chars: control chars, colon, less than, greater than,
- * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
- * intact, like unicode bidi chars, but it's a good start..
- * @param s {String}
- * @return {String}
- */
- clean = function( s ) {
- if ( s !== undefined ) {
- return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
- }
- },
-
- /**
- * Convert db-key to readable text.
- * @param s {String}
- * @return {String}
- */
- text = function ( s ) {
- if ( s !== null && s !== undefined ) {
- return s.replace( /_/g, ' ' );
- } else {
- return '';
- }
- },
-
- /**
- * Sanitize name.
- */
- fixName = function( s ) {
- return clean( $.trim( s ) );
- },
-
- /**
- * Sanitize name.
- */
- fixExt = function( s ) {
- return clean( s.toLowerCase() );
- },
-
- /**
- * Sanitize namespace id.
- * @param id {Number} Namespace id.
- * @return {Number|Boolean} The id as-is or boolean false if invalid.
- */
- fixNsId = function( id ) {
- // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
- var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
-
- // Check only undefined (may be false-y, such as '' (main namespace) ).
- if ( ns === undefined ) {
- return false;
- } else {
- return Number( id );
- }
- },
-
- /**
- * Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
- *
- * @example On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
- * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
- * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
- */
- getNsIdByName = function( ns ) {
- // toLowerCase throws exception on null/undefined. Return early.
- if ( ns == null ) {
- return false;
- }
- ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
- var id = mw.config.get( 'wgNamespaceIds' )[ns];
- if ( id === undefined ) {
- mw.log( 'mw.Title: Unrecognized namespace: ' + ns );
- return false;
- }
- return fixNsId( id );
- },
-
- /**
- * Helper to extract namespace, name and extension from a string.
- *
- * @param title {mw.Title}
- * @param raw {String}
- * @return {mw.Title}
- */
- setAll = function( title, s ) {
- // In normal browsers the match-array contains null/undefined if there's no match,
- // IE returns an empty string.
- var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w{1,5}))?$/ ),
- ns_match = getNsIdByName( matches[1] );
-
- // Namespace must be valid, and title must be a non-empty string.
- if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) {
- title._ns = ns_match;
- title._name = fixName( matches[2] );
- if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
- title._ext = fixExt( matches[3] );
- }
- } else {
- // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
- title._ns = 0;
- setNameAndExtension( title, s );
- }
- return title;
- },
-
- /**
- * Helper to extract name and extension from a string.
- *
- * @param title {mw.Title}
- * @param raw {String}
- * @return {mw.Title}
- */
- setNameAndExtension = function( title, raw ) {
- // In normal browsers the match-array contains null/undefined if there's no match,
- // IE returns an empty string.
- var matches = raw.match( /^(?:)?(.*?)(?:\.(\w{1,5}))?$/ );
-
- // Title must be a non-empty string.
- if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
- title._name = fixName( matches[1] );
- if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
- title._ext = fixExt( matches[2] );
- }
- } else {
- throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
- }
- return title;
- };
-
-
- /* Static space */
-
- /**
- * Whether this title exists on the wiki.
- * @param title {mixed} prefixed db-key name (string) or instance of Title
- * @return {mixed} Boolean true/false if the information is available. Otherwise null.
- */
- Title.exists = function( title ) {
- var type = $.type( title ), obj = Title.exist.pages, match;
- 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;
- };
-
- /**
- * @var Title.exist {Object}
- */
- Title.exist = {
- /**
- * @var Title.exist.pages {Object} Keyed by PrefixedDb title.
- * Boolean true value indicates page does exist.
- */
- pages: {},
- /**
- * @example Declare existing titles: Title.exist.set(['User:John_Doe', ...]);
- * @example Declare titles inexisting: Title.exist.set(['File:Foo_bar.jpg', ...], false);
- * @param titles {String|Array} Title(s) in strict prefixedDb title form.
- * @param state {Boolean} (optional) State of the given titles. Defaults to true.
- * @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 methods */
-
- var fn = {
- constructor: Title,
-
- /**
- * Get the namespace number.
- * @return {Number}
- */
- getNamespaceId: function(){
- return this._ns;
- },
-
- /**
- * Get the namespace prefix (in the content-language).
- * In NS_MAIN this is '', otherwise namespace name plus ':'
- * @return {String}
- */
- getNamespacePrefix: function(){
- return mw.config.get( 'wgFormattedNamespaces' )[this._ns].replace( / /g, '_' ) + (this._ns === 0 ? '' : ':');
- },
-
- /**
- * The name, like "Foo_bar"
- * @return {String}
- */
- getName: function() {
- if ( $.inArray( this._ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
- return this._name;
- } else {
- return $.ucFirst( this._name );
- }
- },
-
- /**
- * The name, like "Foo bar"
- * @return {String}
- */
- getNameText: function() {
- return text( this.getName() );
- },
-
- /**
- * Get full name in prefixed DB form, like File:Foo_bar.jpg,
- * most useful for API calls, anything that must identify the "title".
- */
- getPrefixedDb: function() {
- return this.getNamespacePrefix() + this.getMain();
- },
-
- /**
- * Get full name in text form, like "File:Foo bar.jpg".
- * @return {String}
- */
- getPrefixedText: function() {
- return text( this.getPrefixedDb() );
- },
-
- /**
- * The main title (without namespace), like "Foo_bar.jpg"
- * @return {String}
- */
- getMain: function() {
- return this.getName() + this.getDotExtension();
- },
-
- /**
- * The "text" form, like "Foo bar.jpg"
- * @return {String}
- */
- getMainText: function() {
- return text( this.getMain() );
- },
-
- /**
- * Get the extension (returns null if there was none)
- * @return {String|null} extension
- */
- getExtension: function() {
- return this._ext;
- },
-
- /**
- * Convenience method: return string like ".jpg", or "" if no extension
- * @return {String}
- */
- getDotExtension: function() {
- return this._ext === null ? '' : '.' + this._ext;
- },
-
- /**
- * Return the URL to this title
- * @return {String}
- */
- getUrl: function() {
- return mw.util.wikiGetlink( this.toString() );
- },
-
- /**
- * Whether this title exists on the wiki.
- * @return {mixed} Boolean true/false if the information is available. Otherwise null.
- */
- exists: function() {
- return Title.exists( this );
- }
- };
-
- // Alias
- fn.toString = fn.getPrefixedDb;
- fn.toText = fn.getPrefixedText;
-
- // Assign
- Title.prototype = fn;
-
- // Expose
- mw.Title = Title;
-
-})(jQuery);
+++ /dev/null
-/**
- * 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( $ ) {
-
- /**
- * 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} to prepend, if value not empty
- * @param {String} value to include, if not empty
- * @param {String} to append, if value not empty
- * @param {Boolean} raw -- if true, do not URI encode
- * @return {String}
- */
- function cat( pre, val, post, raw ) {
- if ( val === undefined || val === null || val === '' ) {
- return '';
- } else {
- 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
- ];
-
- /**
- * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
- * @constructor
- * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties.
- * @param {Boolean} strict mode (when parsing a string)
- */
- mw.Uri = function( uri, strictMode ) {
- strictMode = !!strictMode;
- if ( uri !== undefined && uri !== null || uri !== '' ) {
- if ( typeof uri === 'string' ) {
- this._parse( uri, strictMode );
- } else if ( typeof uri === 'object' ) {
- var _this = this;
- $.each( properties, function( i, property ) {
- _this[property] = uri[property];
- } );
- if ( this.query === undefined ) {
- this.query = {};
- }
- }
- }
- 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} string
- * @return {String} encoded for URI
- */
- mw.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} string encoded for URI
- * @return {String} decoded string
- */
- mw.Uri.decode = function( s ) {
- return decodeURIComponent( s ).replace( /\+/g, ' ' );
- };
-
- mw.Uri.prototype = {
-
- /**
- * Parse a string and set our properties accordingly.
- * @param {String} URI
- * @param {Boolean} strictness
- * @return {Boolean} success
- */
- _parse: function( str, strictMode ) {
- var matches = parser[ strictMode ? 'strict' : 'loose' ].exec( str );
- var uri = this;
- $.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.
- var q = {};
- // using replace to iterate over a string
- if ( uri.query ) {
- uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) {
- if ( $1 ) {
- var k = mw.Uri.decode( $1 );
- var v = ( $2 === '' || $2 === undefined ) ? null : mw.Uri.decode( $3 );
- if ( typeof q[ k ] === 'string' ) {
- q[ k ] = [ q[ k ] ];
- }
- if ( typeof q[ k ] === 'object' ) {
- q[ k ].push( v );
- } else {
- q[ k ] = 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 = mw.Uri.encode( key );
- var vals = val === null ? [ null ] : $.makeArray( val );
- $.each( vals, function( i, v ) {
- args.push( k + ( v === null ? '' : '=' + mw.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 mw.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;
- }
- };
-
-} )( jQuery );
<!-- include source files here... -->
<script type="text/javascript" src="../../load.php?debug=true&lang=en&modules=jquery%7Cmediawiki&only=scripts&skin=vector"></script>
- <script type="text/javascript" src="../../resources/mediawiki/mediawiki.uri.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.Uri.js"></script>
<!-- include spec files here... -->
- <script type="text/javascript" src="spec/mediawiki.uri.spec.js"></script>
+ <script type="text/javascript" src="spec/mediawiki.Uri.spec.js"></script>
</head>
<body>
<script type="text/javascript">
- jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
+ jasmine.getEnv().addReporter( new jasmine.TrivialReporter() );
jasmine.getEnv().execute();
</script>
</body>
</html>
-
--- /dev/null
+( function() {
+
+ describe( "mw.Uri", function() {
+
+ describe( "should work well in loose and strict mode", function() {
+
+ function basicTests( strict ) {
+
+ describe( "should parse a simple HTTP URI correctly", function() {
+
+ var uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
+ var uri;
+ if ( strict ) {
+ uri = new mw.Uri( uriString, strict );
+ } else {
+ uri = new mw.Uri( uriString );
+ }
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.ietf.org' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/rfc/rfc2396.txt' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have empty userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( '' );
+ } );
+
+ it( "should have authority equal to host", function() {
+ expect( uri.getAuthority() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have hostport equal to host", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have empty string as query string", function() {
+ expect( uri.getQueryString() ).toEqual( '' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' );
+ } );
+
+ it( "should return a uri string equivalent to original", function() {
+ expect( uri.toString() ).toEqual( uriString );
+ } );
+ } );
+ } );
+ }
+
+ describe( "should work in loose mode", function() {
+ basicTests( false );
+ } );
+
+ describe( "should work in strict mode", function() {
+ basicTests( true );
+ } );
+
+ } );
+
+ it( "should parse a simple ftp URI correctly with user and password", function() {
+ var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' );
+ expect( uri.protocol ).toEqual( 'ftp' );
+ expect( uri.user ).toEqual( 'usr' );
+ expect( uri.password ).toEqual( 'pwd' );
+ expect( uri.host ).toEqual( '192.0.2.16' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ it( "should parse a simple querystring", function() {
+ var uri = new mw.Uri( 'http://www.google.com/?q=uri' );
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.google.com' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toBeDefined();
+ expect( uri.query ).toEqual( { q: 'uri' } );
+ expect( uri.fragment ).not.toBeDefined();
+ expect( uri.getQueryString() ).toEqual( 'q=uri' );
+ } );
+
+ describe( "should handle multiple value query args", function() {
+ var uri = new mw.Uri( 'http://www.sample.com/dir/?m=foo&m=bar&n=1' );
+ it ( "should parse with multiple values", function() {
+ expect( uri.query.m.length ).toEqual( 2 );
+ expect( uri.query.m[0] ).toEqual( 'foo' );
+ expect( uri.query.m[1] ).toEqual( 'bar' );
+ expect( uri.query.n ).toEqual( '1' );
+ } );
+ it ( "should accept multiple values", function() {
+ uri.query.n = [ "x", "y", "z" ];
+ expect( uri.toString() ).toContain( 'm=foo&m=bar' );
+ expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
+ expect( uri.toString().length ).toEqual( 'http://www.sample.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length );
+ } );
+ it ( "should be okay with removing values", function() {
+ uri.query.m.splice( 0, 1 );
+ delete uri.query.n;
+ expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/?m=bar' );
+ uri.query.m.splice( 0, 1 );
+ expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/' );
+ } );
+ } );
+
+ describe( "should deal with an all-dressed URI with everything", function() {
+ var uri = new mw.Uri( 'http://auth@www.sample.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' );
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.user ).toEqual( 'auth' );
+ expect( uri.password ).not.toBeDefined();
+ expect( uri.host ).toEqual( 'www.sample.com' );
+ expect( uri.port ).toEqual( '81' );
+ expect( uri.path ).toEqual( '/dir/dir.2/index.htm' );
+ expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } );
+ expect( uri.fragment ).toEqual( 'top' );
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( 'auth' );
+ } );
+
+ it( "should have authority equal to auth@hostport", function() {
+ expect( uri.getAuthority() ).toEqual( 'auth@www.sample.com:81' );
+ } );
+
+ it( "should have hostport equal to host:port", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.sample.com:81' );
+ } );
+
+ it( "should have query string which contains all components", function() {
+ var queryString = uri.getQueryString();
+ expect( queryString ).toContain( 'q1=0' );
+ expect( queryString ).toContain( 'test1' );
+ expect( queryString ).not.toContain( 'test1=' );
+ expect( queryString ).toContain( 'test2=value+%28escaped%29' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toContain( uri.path );
+ expect( uri.getRelativePath() ).toContain( uri.getQueryString() );
+ expect( uri.getRelativePath() ).toContain( uri.fragment );
+ } );
+
+ } );
+ } );
+
+ describe( "should be able to clone itself", function() {
+ var original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' );
+ var clone = original.clone();
+
+ it( "should make clones equivalent", function() {
+ expect( original ).toEqual( clone );
+ expect( original.toString() ).toEqual( clone.toString() );
+ } );
+
+ it( "should be able to manipulate clones independently", function() {
+ // but they are still different objects
+ expect( original ).not.toBe( clone );
+ // and can diverge
+ clone.host = 'fr.wiki.local';
+ expect( original.host ).not.toEqual( clone.host );
+ expect( original.toString() ).not.toEqual( clone.toString() );
+ } );
+ } );
+
+ describe( "should be able to construct URL from object", function() {
+ it ( "should construct given basic arguments", function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this' );
+ } );
+
+ it ( "should construct given more complex arguments", function() {
+ var uri = new mw.Uri( {
+ protocol: 'http',
+ host: 'www.foo.local',
+ path: '/this',
+ query: { hi: 'there' },
+ fragment: 'blah'
+ } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' );
+ } );
+
+ it ( "should fail to construct without required properties", function() {
+ expect( function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local' } );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+ } );
+
+ describe( "should be able to manipulate properties", function() {
+ var uri;
+
+ beforeEach( function() {
+ uri = new mw.Uri( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ it( "can add a fragment", function() {
+ uri.fragment = 'frag';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' );
+ } );
+
+ it( "can change host and port", function() {
+ uri.host = 'fr.wiki.local';
+ uri.port = '8080';
+ expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' );
+ } );
+
+ it ( "can add query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ } );
+
+ it ( "can extend query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ uri.extend( { foo: 'quux', pif: 'paf' } );
+ expect( uri.toString() ).toContain( 'foo=quux' );
+ expect( uri.toString() ).not.toContain( 'foo=bar' );
+ expect( uri.toString() ).toContain( 'pif=paf' );
+ } );
+
+ it ( "can remove query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ delete( uri.query.foo );
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ } );
+
+ it( "should throw error on no arguments to constructor", function() {
+ expect( function() {
+ uri = new mw.Uri();
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on empty string as argument to constructor", function() {
+ expect( function() {
+ uri = new mw.Uri( '' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on non-URI as argument to constructor", function() {
+ expect( function() {
+ uri = new mw.Uri( 'glaswegian penguins' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on improper URI as argument to constructor", function() {
+ expect( function() {
+ uri = new mw.Uri( 'http:/foo.com' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on URI without protocol as argument to constructor", function() {
+ expect( function() {
+ uri = new mw.Uri( 'foo.com/bar/baz' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+
+ } );
+
+} )();
+++ /dev/null
-( function() {
-
- describe( "mw.Uri", function() {
-
- describe( "should work well in loose and strict mode", function() {
-
- function basicTests( strict ) {
-
- describe( "should parse a simple HTTP URI correctly", function() {
-
- var uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
- var uri;
- if ( strict ) {
- uri = new mw.Uri( uriString, strict );
- } else {
- uri = new mw.Uri( uriString );
- }
-
- it( "should have basic object properties", function() {
- expect( uri.protocol ).toEqual( 'http' );
- expect( uri.host ).toEqual( 'www.ietf.org' );
- expect( uri.port ).not.toBeDefined();
- expect( uri.path ).toEqual( '/rfc/rfc2396.txt' );
- expect( uri.query ).toEqual( {} );
- expect( uri.fragment ).not.toBeDefined();
- } );
-
- describe( "should construct composite components of URI on request", function() {
- it( "should have empty userinfo", function() {
- expect( uri.getUserInfo() ).toEqual( '' );
- } );
-
- it( "should have authority equal to host", function() {
- expect( uri.getAuthority() ).toEqual( 'www.ietf.org' );
- } );
-
- it( "should have hostport equal to host", function() {
- expect( uri.getHostPort() ).toEqual( 'www.ietf.org' );
- } );
-
- it( "should have empty string as query string", function() {
- expect( uri.getQueryString() ).toEqual( '' );
- } );
-
- it( "should have path as relative path", function() {
- expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' );
- } );
-
- it( "should return a uri string equivalent to original", function() {
- expect( uri.toString() ).toEqual( uriString );
- } );
- } );
- } );
- }
-
- describe( "should work in loose mode", function() {
- basicTests( false );
- } );
-
- describe( "should work in strict mode", function() {
- basicTests( true );
- } );
-
- } );
-
- it( "should parse a simple ftp URI correctly with user and password", function() {
- var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' );
- expect( uri.protocol ).toEqual( 'ftp' );
- expect( uri.user ).toEqual( 'usr' );
- expect( uri.password ).toEqual( 'pwd' );
- expect( uri.host ).toEqual( '192.0.2.16' );
- expect( uri.port ).not.toBeDefined();
- expect( uri.path ).toEqual( '/' );
- expect( uri.query ).toEqual( {} );
- expect( uri.fragment ).not.toBeDefined();
- } );
-
- it( "should parse a simple querystring", function() {
- var uri = new mw.Uri( 'http://www.google.com/?q=uri' );
- expect( uri.protocol ).toEqual( 'http' );
- expect( uri.host ).toEqual( 'www.google.com' );
- expect( uri.port ).not.toBeDefined();
- expect( uri.path ).toEqual( '/' );
- expect( uri.query ).toBeDefined();
- expect( uri.query ).toEqual( { q: 'uri' } );
- expect( uri.fragment ).not.toBeDefined();
- expect( uri.getQueryString() ).toEqual( 'q=uri' );
- } );
-
- describe( "should handle multiple value query args", function() {
- var uri = new mw.Uri( 'http://www.sample.com/dir/?m=foo&m=bar&n=1' );
- it ( "should parse with multiple values", function() {
- expect( uri.query.m.length ).toEqual( 2 );
- expect( uri.query.m[0] ).toEqual( 'foo' );
- expect( uri.query.m[1] ).toEqual( 'bar' );
- expect( uri.query.n ).toEqual( '1' );
- } );
- it ( "should accept multiple values", function() {
- uri.query.n = [ "x", "y", "z" ];
- expect( uri.toString() ).toContain( 'm=foo&m=bar' );
- expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
- expect( uri.toString().length ).toEqual( 'http://www.sample.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length );
- } );
- it ( "should be okay with removing values", function() {
- uri.query.m.splice( 0, 1 );
- delete uri.query.n;
- expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/?m=bar' );
- uri.query.m.splice( 0, 1 );
- expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/' );
- } );
- } );
-
- describe( "should deal with an all-dressed URI with everything", function() {
- var uri = new mw.Uri( 'http://auth@www.sample.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' );
-
- it( "should have basic object properties", function() {
- expect( uri.protocol ).toEqual( 'http' );
- expect( uri.user ).toEqual( 'auth' );
- expect( uri.password ).not.toBeDefined();
- expect( uri.host ).toEqual( 'www.sample.com' );
- expect( uri.port ).toEqual( '81' );
- expect( uri.path ).toEqual( '/dir/dir.2/index.htm' );
- expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } );
- expect( uri.fragment ).toEqual( 'top' );
- } );
-
- describe( "should construct composite components of URI on request", function() {
- it( "should have userinfo", function() {
- expect( uri.getUserInfo() ).toEqual( 'auth' );
- } );
-
- it( "should have authority equal to auth@hostport", function() {
- expect( uri.getAuthority() ).toEqual( 'auth@www.sample.com:81' );
- } );
-
- it( "should have hostport equal to host:port", function() {
- expect( uri.getHostPort() ).toEqual( 'www.sample.com:81' );
- } );
-
- it( "should have query string which contains all components", function() {
- var queryString = uri.getQueryString();
- expect( queryString ).toContain( 'q1=0' );
- expect( queryString ).toContain( 'test1' );
- expect( queryString ).not.toContain( 'test1=' );
- expect( queryString ).toContain( 'test2=value+%28escaped%29' );
- } );
-
- it( "should have path as relative path", function() {
- expect( uri.getRelativePath() ).toContain( uri.path );
- expect( uri.getRelativePath() ).toContain( uri.getQueryString() );
- expect( uri.getRelativePath() ).toContain( uri.fragment );
- } );
-
- } );
- } );
-
- describe( "should be able to clone itself", function() {
- var original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' );
- var clone = original.clone();
-
- it( "should make clones equivalent", function() {
- expect( original ).toEqual( clone );
- expect( original.toString() ).toEqual( clone.toString() );
- } );
-
- it( "should be able to manipulate clones independently", function() {
- // but they are still different objects
- expect( original ).not.toBe( clone );
- // and can diverge
- clone.host = 'fr.wiki.local';
- expect( original.host ).not.toEqual( clone.host );
- expect( original.toString() ).not.toEqual( clone.toString() );
- } );
- } );
-
- describe( "should be able to construct URL from object", function() {
- it ( "should construct given basic arguments", function() {
- var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } );
- expect( uri.toString() ).toEqual( 'http://www.foo.local/this' );
- } );
-
- it ( "should construct given more complex arguments", function() {
- var uri = new mw.Uri( {
- protocol: 'http',
- host: 'www.foo.local',
- path: '/this',
- query: { hi: 'there' },
- fragment: 'blah'
- } );
- expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' );
- } );
-
- it ( "should fail to construct without required properties", function() {
- expect( function() {
- var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local' } );
- } ).toThrow( "Bad constructor arguments" );
- } );
- } );
-
- describe( "should be able to manipulate properties", function() {
- var uri;
-
- beforeEach( function() {
- uri = new mw.Uri( 'http://en.wiki.local/w/api.php' );
- } );
-
- it( "can add a fragment", function() {
- uri.fragment = 'frag';
- expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' );
- } );
-
- it( "can change host and port", function() {
- uri.host = 'fr.wiki.local';
- uri.port = '8080';
- expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' );
- } );
-
- it ( "can add query arguments", function() {
- uri.query.foo = 'bar';
- expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
- } );
-
- it ( "can extend query arguments", function() {
- uri.query.foo = 'bar';
- expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
- uri.extend( { foo: 'quux', pif: 'paf' } );
- expect( uri.toString() ).toContain( 'foo=quux' );
- expect( uri.toString() ).not.toContain( 'foo=bar' );
- expect( uri.toString() ).toContain( 'pif=paf' );
- } );
-
- it ( "can remove query arguments", function() {
- uri.query.foo = 'bar';
- expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
- delete( uri.query.foo );
- expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' );
- } );
-
- } );
-
- it( "should throw error on no arguments to constructor", function() {
- expect( function() {
- uri = new mw.Uri();
- } ).toThrow( "Bad constructor arguments" );
- } );
-
- it( "should throw error on empty string as argument to constructor", function() {
- expect( function() {
- uri = new mw.Uri( '' );
- } ).toThrow( "Bad constructor arguments" );
- } );
-
- it( "should throw error on non-URI as argument to constructor", function() {
- expect( function() {
- uri = new mw.Uri( 'glaswegian penguins' );
- } ).toThrow( "Bad constructor arguments" );
- } );
-
- it( "should throw error on improper URI as argument to constructor", function() {
- expect( function() {
- uri = new mw.Uri( 'http:/foo.com' );
- } ).toThrow( "Bad constructor arguments" );
- } );
-
- it( "should throw error on URI without protocol as argument to constructor", function() {
- expect( function() {
- uri = new mw.Uri( 'foo.com/bar/baz' );
- } ).toThrow( "Bad constructor arguments" );
- } );
-
-
- } );
-
-} )();
<script src="../../resources/jquery/jquery.tabIndex.js"></script>
<script src="../../resources/jquery/jquery.tablesorter.js"></script>
<script src="../../resources/jquery/jquery.textSelection.js"></script>
- <script src="../../resources/mediawiki/mediawiki.title.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.Title.js"></script>
<script src="../../resources/mediawiki.special/mediawiki.special.js"></script>
<script src="../../resources/mediawiki.special/mediawiki.special.recentchanges.js"></script>
<script src="suites/resources/jquery/jquery.tabIndex.test.js"></script>
<script src="suites/resources/jquery/jquery.tablesorter.test.js" charset="UTF-8"></script>
<script src="suites/resources/jquery/jquery.textSelection.test.js" charset="UTF-8"></script>
- <script src="suites/resources/mediawiki/mediawiki.title.test.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.Title.test.js"></script>
<script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js"></script>
</head>
<body>
--- /dev/null
+module( 'mediawiki.Title' );
+
+// mw.Title relies on these three config vars
+// Restore them after each test run
+var _titleConfig = function() {
+
+ mw.config.set({
+ "wgFormattedNamespaces": {
+ "-2": "Media",
+ "-1": "Special",
+ "0": "",
+ "1": "Talk",
+ "2": "User",
+ "3": "User talk",
+ "4": "Wikipedia",
+ "5": "Wikipedia talk",
+ "6": "File",
+ "7": "File talk",
+ "8": "MediaWiki",
+ "9": "MediaWiki talk",
+ "10": "Template",
+ "11": "Template talk",
+ "12": "Help",
+ "13": "Help talk",
+ "14": "Category",
+ "15": "Category talk",
+ /* testing custom / localized */
+ "100": "Penguins"
+ },
+ "wgNamespaceIds": {
+ "media": -2,
+ "special": -1,
+ "": 0,
+ "talk": 1,
+ "user": 2,
+ "user_talk": 3,
+ "wikipedia": 4,
+ "wikipedia_talk": 5,
+ "file": 6,
+ "file_talk": 7,
+ "mediawiki": 8,
+ "mediawiki_talk": 9,
+ "template": 10,
+ "template_talk": 11,
+ "help": 12,
+ "help_talk": 13,
+ "category": 14,
+ "category_talk": 15,
+ "image": 6,
+ "image_talk": 7,
+ "project": 4,
+ "project_talk": 5,
+ /* testing custom / alias */
+ "penguins": 100,
+ "antarctic_waterfowl": 100
+ },
+ "wgCaseSensitiveNamespaces": []
+ });
+};
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( mw.Title, 'mw.Title defined' );
+});
+
+test( 'Transform between Text and Db', function() {
+ expect(2);
+ _titleConfig();
+
+ var title;
+
+ title = new mw.Title( 'File:quux pif.jpg' );
+ equal( title.getName(), 'Quux_pif' );
+
+ title = new mw.Title( 'File:Glarg_foo_glang.jpg' );
+ equal( title.getNameText(), 'Glarg foo glang' );
+});
+
+test( 'Main text for filename', function() {
+ expect(8);
+ _titleConfig();
+
+ var title = new mw.Title( 'File:foo_bar.JPG' );
+
+ equal( title.getNamespaceId(), 6 );
+ equal( title.getNamespacePrefix(), 'File:' );
+ equal( title.getName(), 'Foo_bar' );
+ equal( title.getNameText(), 'Foo bar' );
+ equal( title.getMain(), 'Foo_bar.jpg' );
+ equal( title.getMainText(), 'Foo bar.jpg' );
+ equal( title.getExtension(), 'jpg' );
+ equal( title.getDotExtension(), '.jpg' );
+});
+
+test( 'Namespace detection and conversion', function() {
+ expect(6);
+ _titleConfig();
+
+ var title;
+
+ title = new mw.Title( 'something.PDF', 6 );
+ equal( title.toString(), 'File:Something.pdf' );
+
+ title = new mw.Title( 'NeilK', 3 );
+ equal( title.toString(), 'User_talk:NeilK' );
+ equal( title.toText(), 'User talk:NeilK' );
+
+ title = new mw.Title( 'Frobisher', 100 );
+ equal( title.toString(), 'Penguins:Frobisher' );
+
+ title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' );
+ equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
+
+ title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' );
+ equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
+});
+
+test( 'Throw error on invalid title', function() {
+ expect(1);
+ _titleConfig();
+
+ raises(function() {
+ var title = new mw.Title( '' );
+ }, 'Throw error on empty string' );
+});
+
+test( 'Case-sensivity', function() {
+ expect(3);
+ _titleConfig();
+
+ var title;
+
+ // Default config
+ mw.config.set( 'wgCaseSensitiveNamespaces', [] );
+
+ title = new mw.Title( 'article' );
+ equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' );
+
+ // $wgCapitalLinks = false;
+ mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] );
+
+ title = new mw.Title( 'article' );
+ equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' );
+
+ title = new mw.Title( 'john', 2 );
+ equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' );
+});
+
+test( 'toString / toText', function() {
+ expect(2);
+ _titleConfig();
+
+ var title = new mw.Title( 'Some random page' );
+
+ equal( title.toString(), title.getPrefixedDb() );
+ equal( title.toText(), title.getPrefixedText() );
+});
+
+test( 'Exists', function() {
+ expect(3);
+ _titleConfig();
+
+ var title;
+
+ // Empty registry, checks default to null
+
+ title = new mw.Title( 'Some random page', 4 );
+ strictEqual( title.exists(), null, 'Return null with empty existance registry' );
+
+ // Basic registry, checks default to boolean
+ mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true );
+ mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false );
+
+ title = new mw.Title( 'Project:Sandbox rules' );
+ assertTrue( title.exists(), 'Return true for page titles marked as existing' );
+ title = new mw.Title( 'Foobar' );
+ assertFalse( title.exists(), 'Return false for page titles marked as inexisting' );
+
+});
+
+test( 'Url', function() {
+ expect(2);
+ _titleConfig();
+
+ var title;
+
+ // Config
+ mw.config.set( 'wgArticlePath', '/wiki/$1' );
+
+ title = new mw.Title( 'Foobar' );
+ equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, toString passing to wikiGetlink' );
+
+ title = new mw.Title( 'John Doe', 3 );
+ equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' );
+});
+++ /dev/null
-module( 'mediawiki.title' );
-
-// mw.Title relies on these three config vars
-// Restore them after each test run
-var _titleConfig = function() {
-
- mw.config.set({
- "wgFormattedNamespaces": {
- "-2": "Media",
- "-1": "Special",
- "0": "",
- "1": "Talk",
- "2": "User",
- "3": "User talk",
- "4": "Wikipedia",
- "5": "Wikipedia talk",
- "6": "File",
- "7": "File talk",
- "8": "MediaWiki",
- "9": "MediaWiki talk",
- "10": "Template",
- "11": "Template talk",
- "12": "Help",
- "13": "Help talk",
- "14": "Category",
- "15": "Category talk",
- /* testing custom / localized */
- "100": "Penguins"
- },
- "wgNamespaceIds": {
- "media": -2,
- "special": -1,
- "": 0,
- "talk": 1,
- "user": 2,
- "user_talk": 3,
- "wikipedia": 4,
- "wikipedia_talk": 5,
- "file": 6,
- "file_talk": 7,
- "mediawiki": 8,
- "mediawiki_talk": 9,
- "template": 10,
- "template_talk": 11,
- "help": 12,
- "help_talk": 13,
- "category": 14,
- "category_talk": 15,
- "image": 6,
- "image_talk": 7,
- "project": 4,
- "project_talk": 5,
- /* testing custom / alias */
- "penguins": 100,
- "antarctic_waterfowl": 100
- },
- "wgCaseSensitiveNamespaces": []
- });
-};
-
-test( '-- Initial check', function() {
- expect(1);
- ok( mw.Title, 'mw.Title defined' );
-});
-
-test( 'Transform between Text and Db', function() {
- expect(2);
- _titleConfig();
-
- var title;
-
- title = new mw.Title( 'File:quux pif.jpg' );
- equal( title.getName(), 'Quux_pif' );
-
- title = new mw.Title( 'File:Glarg_foo_glang.jpg' );
- equal( title.getNameText(), 'Glarg foo glang' );
-});
-
-test( 'Main text for filename', function() {
- expect(8);
- _titleConfig();
-
- var title = new mw.Title( 'File:foo_bar.JPG' );
-
- equal( title.getNamespaceId(), 6 );
- equal( title.getNamespacePrefix(), 'File:' );
- equal( title.getName(), 'Foo_bar' );
- equal( title.getNameText(), 'Foo bar' );
- equal( title.getMain(), 'Foo_bar.jpg' );
- equal( title.getMainText(), 'Foo bar.jpg' );
- equal( title.getExtension(), 'jpg' );
- equal( title.getDotExtension(), '.jpg' );
-});
-
-test( 'Namespace detection and conversion', function() {
- expect(6);
- _titleConfig();
-
- var title;
-
- title = new mw.Title( 'something.PDF', 6 );
- equal( title.toString(), 'File:Something.pdf' );
-
- title = new mw.Title( 'NeilK', 3 );
- equal( title.toString(), 'User_talk:NeilK' );
- equal( title.toText(), 'User talk:NeilK' );
-
- title = new mw.Title( 'Frobisher', 100 );
- equal( title.toString(), 'Penguins:Frobisher' );
-
- title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' );
- equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
-
- title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' );
- equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
-});
-
-test( 'Throw error on invalid title', function() {
- expect(1);
- _titleConfig();
-
- raises(function() {
- var title = new mw.Title( '' );
- }, 'Throw error on empty string' );
-});
-
-test( 'Case-sensivity', function() {
- expect(3);
- _titleConfig();
-
- var title;
-
- // Default config
- mw.config.set( 'wgCaseSensitiveNamespaces', [] );
-
- title = new mw.Title( 'article' );
- equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' );
-
- // $wgCapitalLinks = false;
- mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] );
-
- title = new mw.Title( 'article' );
- equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' );
-
- title = new mw.Title( 'john', 2 );
- equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' );
-});
-
-test( 'toString / toText', function() {
- expect(2);
- _titleConfig();
-
- var title = new mw.Title( 'Some random page' );
-
- equal( title.toString(), title.getPrefixedDb() );
- equal( title.toText(), title.getPrefixedText() );
-});
-
-test( 'Exists', function() {
- expect(3);
- _titleConfig();
-
- var title;
-
- // Empty registry, checks default to null
-
- title = new mw.Title( 'Some random page', 4 );
- strictEqual( title.exists(), null, 'Return null with empty existance registry' );
-
- // Basic registry, checks default to boolean
- mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true );
- mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false );
-
- title = new mw.Title( 'Project:Sandbox rules' );
- assertTrue( title.exists(), 'Return true for page titles marked as existing' );
- title = new mw.Title( 'Foobar' );
- assertFalse( title.exists(), 'Return false for page titles marked as inexisting' );
-
-});
-
-test( 'Url', function() {
- expect(2);
- _titleConfig();
-
- var title;
-
- // Config
- mw.config.set( 'wgArticlePath', '/wiki/$1' );
-
- title = new mw.Title( 'Foobar' );
- equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, toString passing to wikiGetlink' );
-
- title = new mw.Title( 'John Doe', 3 );
- equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' );
-});
\ No newline at end of file