Merge "Make last row of new gallery not be huge."
[lhc/web/wiklou.git] / resources / mediawiki / mediawiki.Title.js
1 /*!
2 * @author Neil Kandalgaonkar, 2010
3 * @author Timo Tijhof, 2011
4 * @since 1.18
5 *
6 * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
7 */
8 ( function ( mw, $ ) {
9
10 /* Local space */
11
12 /**
13 * @class mw.Title
14 *
15 * @constructor
16 * @param {string} title Title of the page. If no second argument given,
17 * this will be searched for a namespace.
18 * @param {number} [namespace] Namespace id. If given, title will be taken as-is.
19 */
20 function Title( title, namespace ) {
21 this.ns = 0; // integer namespace id
22 this.name = null; // name in canonical 'database' form
23 this.ext = null; // extension
24
25 if ( arguments.length === 2 ) {
26 setNameAndExtension( this, title );
27 this.ns = fixNsId( namespace );
28 } else if ( arguments.length === 1 ) {
29 setAll( this, title );
30 }
31 return this;
32 }
33
34 var
35 /* Public methods (defined later) */
36 fn,
37
38 /**
39 * Strip some illegal chars: control chars, colon, less than, greater than,
40 * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
41 * intact, like unicode bidi chars, but it's a good start..
42 * @ignore
43 * @param {string} s
44 * @return {string}
45 */
46 clean = function ( s ) {
47 if ( s !== undefined ) {
48 return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
49 }
50 },
51
52 /**
53 * Convert db-key to readable text.
54 * @ignore
55 * @param {string} s
56 * @return {string}
57 */
58 text = function ( s ) {
59 if ( s !== null && s !== undefined ) {
60 return s.replace( /_/g, ' ' );
61 } else {
62 return '';
63 }
64 },
65
66 /**
67 * Sanitize name.
68 * @ignore
69 */
70 fixName = function ( s ) {
71 return clean( $.trim( s ) );
72 },
73
74 /**
75 * Sanitize extension.
76 * @ignore
77 */
78 fixExt = function ( s ) {
79 return clean( s );
80 },
81
82 /**
83 * Sanitize namespace id.
84 * @ignore
85 * @param id {Number} Namespace id.
86 * @return {Number|Boolean} The id as-is or boolean false if invalid.
87 */
88 fixNsId = function ( id ) {
89 // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
90 var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
91
92 // Check only undefined (may be false-y, such as '' (main namespace) ).
93 if ( ns === undefined ) {
94 return false;
95 } else {
96 return Number( id );
97 }
98 },
99
100 /**
101 * Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
102 * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
103 * @ignore
104 * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
105 * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
106 */
107 getNsIdByName = function ( ns ) {
108 // Don't cast non-strings to strings, because null or undefined
109 // should not result in returning the id of a potential namespace
110 // called "Null:" (e.g. on nullwiki.example.org)
111 // Also, toLowerCase throws exception on null/undefined, because
112 // it is a String.prototype method.
113 if ( typeof ns !== 'string' ) {
114 return false;
115 }
116 ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
117 var id = mw.config.get( 'wgNamespaceIds' )[ns];
118 if ( id === undefined ) {
119 mw.log( 'mw.Title: Unrecognized namespace: ' + ns );
120 return false;
121 }
122 return fixNsId( id );
123 },
124
125 /**
126 * Helper to extract namespace, name and extension from a string.
127 *
128 * @ignore
129 * @param {mw.Title} title
130 * @param {string} raw
131 * @return {mw.Title}
132 */
133 setAll = function ( title, s ) {
134 // In normal browsers the match-array contains null/undefined if there's no match,
135 // IE returns an empty string.
136 var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w+))?$/ ),
137 nsMatch = getNsIdByName( matches[1] );
138
139 // Namespace must be valid, and title must be a non-empty string.
140 if ( nsMatch && typeof matches[2] === 'string' && matches[2] !== '' ) {
141 title.ns = nsMatch;
142 title.name = fixName( matches[2] );
143 if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
144 title.ext = fixExt( matches[3] );
145 }
146 } else {
147 // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
148 title.ns = 0;
149 setNameAndExtension( title, s );
150 }
151 return title;
152 },
153
154 /**
155 * Helper to extract name and extension from a string.
156 *
157 * @ignore
158 * @param {mw.Title} title
159 * @param {string} raw
160 * @return {mw.Title}
161 */
162 setNameAndExtension = function ( title, raw ) {
163 // In normal browsers the match-array contains null/undefined if there's no match,
164 // IE returns an empty string.
165 var matches = raw.match( /^(?:)?(.*?)(?:\.(\w+))?$/ );
166
167 // Title must be a non-empty string.
168 if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
169 title.name = fixName( matches[1] );
170 if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
171 title.ext = fixExt( matches[2] );
172 }
173 } else {
174 throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
175 }
176 return title;
177 };
178
179
180 /* Static space */
181
182 /**
183 * Whether this title exists on the wiki.
184 * @static
185 * @param {Mixed} title prefixed db-key name (string) or instance of Title
186 * @return {Mixed} Boolean true/false if the information is available. Otherwise null.
187 */
188 Title.exists = function ( title ) {
189 var type = $.type( title ), obj = Title.exist.pages, match;
190 if ( type === 'string' ) {
191 match = obj[title];
192 } else if ( type === 'object' && title instanceof Title ) {
193 match = obj[title.toString()];
194 } else {
195 throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' );
196 }
197 if ( typeof match === 'boolean' ) {
198 return match;
199 }
200 return null;
201 };
202
203 /**
204 * @static
205 * @property
206 */
207 Title.exist = {
208 /**
209 * @static
210 * @property {Object} exist.pages Keyed by PrefixedDb title.
211 * Boolean true value indicates page does exist.
212 */
213 pages: {},
214 /**
215 * Example to declare existing titles:
216 * Title.exist.set(['User:John_Doe', ...]);
217 * Eample to declare titles nonexistent:
218 * Title.exist.set(['File:Foo_bar.jpg', ...], false);
219 *
220 * @static
221 * @property exist.set
222 * @param {string|Array} titles Title(s) in strict prefixedDb title form.
223 * @param {boolean} [state] State of the given titles. Defaults to true.
224 * @return {boolean}
225 */
226 set: function ( titles, state ) {
227 titles = $.isArray( titles ) ? titles : [titles];
228 state = state === undefined ? true : !!state;
229 var pages = this.pages, i, len = titles.length;
230 for ( i = 0; i < len; i++ ) {
231 pages[ titles[i] ] = state;
232 }
233 return true;
234 }
235 };
236
237 /* Public methods */
238
239 fn = {
240 constructor: Title,
241
242 /**
243 * Get the namespace number.
244 * @return {number}
245 */
246 getNamespaceId: function (){
247 return this.ns;
248 },
249
250 /**
251 * Get the namespace prefix (in the content-language).
252 * In NS_MAIN this is '', otherwise namespace name plus ':'
253 * @return {string}
254 */
255 getNamespacePrefix: function (){
256 return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':');
257 },
258
259 /**
260 * The name, like "Foo_bar"
261 * @return {string}
262 */
263 getName: function () {
264 if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
265 return this.name;
266 } else {
267 return $.ucFirst( this.name );
268 }
269 },
270
271 /**
272 * The name, like "Foo bar"
273 * @return {string}
274 */
275 getNameText: function () {
276 return text( this.getName() );
277 },
278
279 /**
280 * Get full name in prefixed DB form, like File:Foo_bar.jpg,
281 * most useful for API calls, anything that must identify the "title".
282 * @return {string}
283 */
284 getPrefixedDb: function () {
285 return this.getNamespacePrefix() + this.getMain();
286 },
287
288 /**
289 * Get full name in text form, like "File:Foo bar.jpg".
290 * @return {string}
291 */
292 getPrefixedText: function () {
293 return text( this.getPrefixedDb() );
294 },
295
296 /**
297 * The main title (without namespace), like "Foo_bar.jpg"
298 * @return {string}
299 */
300 getMain: function () {
301 return this.getName() + this.getDotExtension();
302 },
303
304 /**
305 * The "text" form, like "Foo bar.jpg"
306 * @return {string}
307 */
308 getMainText: function () {
309 return text( this.getMain() );
310 },
311
312 /**
313 * Get the extension (returns null if there was none)
314 * @return {string|null}
315 */
316 getExtension: function () {
317 return this.ext;
318 },
319
320 /**
321 * Convenience method: return string like ".jpg", or "" if no extension
322 * @return {string}
323 */
324 getDotExtension: function () {
325 return this.ext === null ? '' : '.' + this.ext;
326 },
327
328 /**
329 * Return the URL to this title
330 * @see mw.util#wikiGetlink
331 * @return {string}
332 */
333 getUrl: function () {
334 return mw.util.wikiGetlink( this.toString() );
335 },
336
337 /**
338 * Whether this title exists on the wiki.
339 * @see #static-method-exists
340 * @return {boolean|null} If the information is available. Otherwise null.
341 */
342 exists: function () {
343 return Title.exists( this );
344 }
345 };
346
347 // Alias
348 fn.toString = fn.getPrefixedDb;
349 fn.toText = fn.getPrefixedText;
350
351 // Assign
352 Title.prototype = fn;
353
354 // Expose
355 mw.Title = Title;
356
357 }( mediaWiki, jQuery ) );