whitespace clean up mw.util
[lhc/web/wiklou.git] / resources / mediawiki.util / mediawiki.util.js
1 /*
2 * Utilities
3 */
4
5 (function ($, mw) {
6
7 mediaWiki.util = {
8
9 /* Initialisation */
10 'initialised' : false,
11 'init' : function () {
12 if ( this.initialised === false ) {
13 this.initialised = true;
14
15 // Any initialisation after the DOM is ready
16 $(function () {
17
18 // Shortcut
19 var profile = $.client.profile();
20
21 // Set tooltipAccessKeyPrefix
22
23 // Opera on any platform
24 if ( profile.name == 'opera' ) {
25 mw.util.tooltipAccessKeyPrefix = 'shift-esc-';
26
27 // Chrome on any platform
28 } else if ( profile.name == 'chrome' ) {
29 // Chrome on Mac or Chrome on other platform ?
30 mw.util.tooltipAccessKeyPrefix = ( profile.platform == 'mac'
31 ? 'ctrl-option-' : 'alt-' );
32
33 // Non-Windows Safari with webkit_version > 526
34 } else if ( profile.platform !== 'win'
35 && profile.name == 'safari'
36 && profile.layoutVersion > 526 ) {
37 mw.util.tooltipAccessKeyPrefix = 'ctrl-alt-';
38
39 // Safari/Konqueror on any platform, or any browser on Mac
40 // (but not Safari on Windows)
41 } else if ( !( profile.platform == 'win' && profile.name == 'safari' )
42 && ( profile.name == 'safari'
43 || profile.platform == 'mac'
44 || profile.name == 'konqueror' ) ) {
45 mw.util.tooltipAccessKeyPrefix = 'ctrl-';
46
47 // Firefox 2.x
48 } else if ( profile.name == 'firefox' && profile.versionBase == '2' ) {
49 mw.util.tooltipAccessKeyPrefix = 'alt-shift-';
50 }
51
52 // Enable CheckboxShiftClick
53 $( 'input[type=checkbox]:not(.noshiftselect)' ).checkboxShiftClick();
54
55 // Emulate placeholder if not supported by browser
56 if ( !( 'placeholder' in document.createElement( 'input' ) ) ) {
57 $( 'input[placeholder]' ).placeholder();
58 }
59
60 // Fill $content var
61 if ( $( '#bodyContent' ).length ) {
62 mw.util.$content = $( '#bodyContent' );
63 } else if ( $( '#article' ).length ) {
64 mw.util.$content = $( '#article' );
65 } else {
66 mw.util.$content = $( '#content' );
67 }
68 } );
69
70 return true;
71 }
72 return false;
73 },
74
75 /* Main body */
76
77 /**
78 * Encode the string like PHP's rawurlencode
79 *
80 * @param str String to be encoded
81 */
82 'rawurlencode' : function( str ) {
83 str = ( str + '' ).toString();
84 return encodeURIComponent( str )
85 .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
86 .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
87 },
88
89 /**
90 * Encode page titles for use in a URL
91 * We want / and : to be included as literal characters in our title URLs
92 * as they otherwise fatally break the title
93 *
94 * @param str String to be encoded
95 */
96 'wikiUrlencode' : function( str ) {
97 return this.rawurlencode( str )
98 .replace( /%20/g, '_' ).replace( /%3A/g, ':' ).replace( /%2F/g, '/' );
99 },
100
101 /**
102 * Append a new style block to the head
103 *
104 * @param text String CSS to be appended
105 * @return the CSS stylesheet
106 */
107 'addCSS' : function( text ) {
108 var s = document.createElement( 'style' );
109 s.type = 'text/css';
110 s.rel = 'stylesheet';
111 if ( s.styleSheet ) {
112 s.styleSheet.cssText = text; // IE
113 } else {
114 s.appendChild( document.createTextNode( text + '' ) ); // Safari sometimes borks on null
115 }
116 document.getElementsByTagName("head")[0].appendChild( s );
117 return s.sheet || s;
118 },
119
120 /**
121 * Get the full URL to a page name
122 *
123 * @param str Page name to link to
124 */
125 'wikiGetlink' : function( str ) {
126 return wgServer + wgArticlePath.replace( '$1', this.wikiUrlencode( str ) );
127 },
128
129 /**
130 * Grab the URL parameter value for the given parameter.
131 * Returns null if not found.
132 *
133 * @param param The parameter name
134 * @param url URL to search through (optional)
135 */
136 'getParamValue' : function( param, url ) {
137 url = url ? url : document.location.href;
138 // Get last match, stop at hash
139 var re = new RegExp( '[^#]*[&?]' + $.escapeRE( param ) + '=([^&#]*)' );
140 var m = re.exec( url );
141 if ( m && m.length > 1 ) {
142 return decodeURIComponent( m[1] );
143 }
144 return null;
145 },
146
147 // Access key prefix.
148 // Will be re-defined based on browser/operating system detection in
149 // mw.util.init().
150 'tooltipAccessKeyPrefix' : 'alt-',
151
152 // Regex to match accesskey tooltips
153 'tooltipAccessKeyRegexp': /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/,
154
155 /**
156 * Add the appropriate prefix to the accesskey shown in the tooltip.
157 * If the nodeList parameter is given, only those nodes are updated;
158 * otherwise, all the nodes that will probably have accesskeys by
159 * default are updated.
160 *
161 * @param nodeList jQuery object, or array of elements
162 */
163 'updateTooltipAccessKeys' : function( nodeList ) {
164 var $nodes;
165 if ( nodeList instanceof jQuery ) {
166 $nodes = nodeList;
167 } else if ( nodeList ) {
168 $nodes = $( nodeList );
169 } else {
170 // Rather than scanning all links, just the elements that
171 // contain the relevant links
172 this.updateTooltipAccessKeys(
173 $( '#column-one a, #mw-head a, #mw-panel a, #p-logo a' ) );
174
175 // these are rare enough that no such optimization is needed
176 this.updateTooltipAccessKeys( $( 'input' ) );
177 this.updateTooltipAccessKeys( $( 'label' ) );
178 return;
179 }
180
181 $nodes.each( function ( i ) {
182 var tip = $(this).attr( 'title' );
183 if ( !!tip && mw.util.tooltipAccessKeyRegexp.exec( tip ) ) {
184 tip = tip.replace( mw.util.tooltipAccessKeyRegexp,
185 '[' + mw.util.tooltipAccessKeyPrefix + "$5]" );
186 $(this).attr( 'title', tip );
187 }
188 } );
189 },
190
191 // jQuery object that refers to the page-content element
192 // Populated by init()
193 '$content' : null,
194
195 /**
196 * Add a link to a portlet menu on the page, such as:
197 *
198 * p-cactions (Content actions), p-personal (Personal tools),
199 * p-navigation (Navigation), p-tb (Toolbox)
200 *
201 * The first three paramters are required, others are optionals. Though
202 * providing an id and tooltip is recommended.
203 *
204 * By default the new link will be added to the end of the list. To
205 * add the link before a given existing item, pass the DOM node
206 * (document.getElementById( 'foobar' )) or the jQuery-selector
207 * ( '#foobar' ) of that item.
208 *
209 * @example mw.util.addPortletLink(
210 * 'p-tb', 'http://mediawiki.org/',
211 * 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', '#t-print'
212 * )
213 *
214 * @param portlet ID of the target portlet ( 'p-cactions' or 'p-personal' etc.)
215 * @param href Link URL
216 * @param text Link text (will be automatically converted to lower
217 * case by CSS for p-cactions in Monobook)
218 * @param id ID of the new item, should be unique and preferably have
219 * the appropriate prefix ( 'ca-', 'pt-', 'n-' or 't-' )
220 * @param tooltip Text to show when hovering over the link, without accesskey suffix
221 * @param accesskey Access key to activate this link (one character, try
222 * to avoid conflicts. Use $( '[accesskey=x' ).get() in the console to
223 * see if 'x' is already used.
224 * @param nextnode DOM node or jQuery-selector of the item that the new
225 * item should be added before, should be another item in the same
226 * list will be ignored if not the so
227 *
228 * @return The DOM node of the new item (a LI element, or A element for
229 * older skins) or null.
230 */
231 'addPortletLink' : function( portlet, href, text, id, tooltip, accesskey, nextnode ) {
232
233 // Check if there's atleast 3 arguments to prevent a TypeError
234 if ( arguments.length < 3 ) {
235 return null;
236 }
237 // Setup the anchor tag
238 var $link = $( '<a />' ).attr( 'href', href ).text( text );
239
240 // Some skins don't have any portlets
241 // just add it to the bottom of their 'sidebar' element as a fallback
242 switch ( skin ) {
243 case 'standard' :
244 case 'cologneblue' :
245 $("#quickbar").append($link.after( '<br />' ));
246 return $link.get(0);
247 case 'nostalgia' :
248 $("#searchform").before($link).before( ' &#124; ' );
249 return $link.get(0);
250 default : // Skins like chick, modern, monobook, myskin, simple, vector...
251
252 // Select the specified portlet
253 var $portlet = $( '#' + portlet);
254 if ( $portlet.length === 0 ) {
255 return null;
256 }
257 // Select the first (most likely only) unordered list inside the portlet
258 var $ul = $portlet.find( 'ul' ).eq( 0 );
259
260 // If it didn't have an unordered list yet, create it
261 if ($ul.length === 0) {
262 // If there's no <div> inside, append it to the portlet directly
263 if ($portlet.find( 'div' ).length === 0) {
264 $portlet.append( '<ul/>' );
265 } else {
266 // otherwise if there's a div (such as div.body or div.pBody)
267 // append the <ul> to last (most likely only) div
268 $portlet.find( 'div' ).eq( -1 ).append( '<ul/>' );
269 }
270 // Select the created element
271 $ul = $portlet.find( 'ul' ).eq( 0 );
272 }
273 // Just in case..
274 if ( $ul.length === 0 ) {
275 return null;
276 }
277
278 // Unhide portlet if it was hidden before
279 $portlet.removeClass( 'emptyPortlet' );
280
281 // Wrap the anchor tag in a <span> and create a list item for it
282 // and back up the selector to the list item
283 var $item = $link.wrap( '<li><span /></li>' ).parent().parent();
284
285 // Implement the properties passed to the function
286 if ( id ) {
287 $item.attr( 'id', id );
288 }
289 if ( accesskey ) {
290 $link.attr( 'accesskey', accesskey );
291 tooltip += ' [' + accesskey + ']';
292 }
293 if ( tooltip ) {
294 $link.attr( 'title', tooltip );
295 }
296 if ( accesskey && tooltip ) {
297 this.updateTooltipAccessKeys( $link );
298 }
299
300 // Append using DOM-element passing
301 if ( nextnode && nextnode.parentNode == $ul.get( 0 ) ) {
302 $(nextnode).before( $item );
303 } else {
304 // If the jQuery selector isn't found within the <ul>, just
305 // append it at the end
306 if ( $ul.find( nextnode ).length === 0 ) {
307 $ul.append( $item );
308 } else {
309 // Append using jQuery CSS selector
310 $ul.find( nextnode ).eq( 0 ).before( $item );
311 }
312 }
313
314 return $item.get( 0 );
315 }
316 },
317
318 /**
319 * Add a little box at the top of the screen to inform the user of
320 * something, replacing any previous message.
321 *
322 * @param message mixed The DOM-element or HTML-string to be put inside the message box]
323 * Calling with no arguments, with an empty string or null will hide the message
324 * @param className string Used in adding a class; should be different for each
325 * call to allow CSS/JS to hide different boxes. null = no class used.
326 * @return Boolean True on success, false on failure
327 */
328 'jsMessage' : function( message, className ) {
329
330 if ( !arguments.length || message === '' || message === null ) {
331
332 $( '#mw-js-message' ).empty().hide();
333 return true; // Emptying and hiding message is intended behaviour, return true
334
335 } else {
336 // We special-case skin structures provided by the software. Skins that
337 // choose to abandon or significantly modify our formatting can just define
338 // an mw-js-message div to start with.
339 var $messageDiv = $( '#mw-js-message' );
340 if ( !$messageDiv.length ) {
341 $messageDiv = $( '<div id="mw-js-message">' );
342 if ( mw.util.$content.parent().length ) {
343 mw.util.$content.parent().prepend( $messageDiv );
344 } else {
345 return false;
346 }
347 }
348
349 $messageDiv.show();
350 if ( className ) {
351 $messageDiv.attr( 'class', 'mw-js-message-' + className );
352 }
353
354 if ( typeof message === 'object' ) {
355 $messageDiv.empty();
356 $messageDiv.append( message ); // Append new content
357 } else {
358 $messageDiv.html( message );
359 }
360 return true;
361 }
362 }
363
364 };
365
366 mediaWiki.util.init();
367
368 })(jQuery, mediaWiki);