Merge "SpecialPreferences: Remove invalid <strong> tag in successbox"
[lhc/web/wiklou.git] / skins / common / wikibits.js
1 /**
2 * MediaWiki legacy wikibits
3 */
4 ( function ( mw ) {
5
6 window.clientPC = navigator.userAgent.toLowerCase(); // Get client info
7 window.is_gecko = /gecko/.test( clientPC ) &&
8 !/khtml|spoofer|netscape\/7\.0/.test(clientPC);
9
10 window.is_safari = window.is_safari_win = window.webkit_version =
11 window.is_chrome = window.is_chrome_mac = false;
12 window.webkit_match = clientPC.match(/applewebkit\/(\d+)/);
13 if (webkit_match) {
14 window.is_safari = clientPC.indexOf('applewebkit') != -1 &&
15 clientPC.indexOf('spoofer') == -1;
16 window.is_safari_win = is_safari && clientPC.indexOf('windows') != -1;
17 window.webkit_version = parseInt(webkit_match[1]);
18 // Tests for chrome here, to avoid breaking old scripts safari left alone
19 // This is here for accesskeys
20 window.is_chrome = clientPC.indexOf('chrome') !== -1 &&
21 clientPC.indexOf('spoofer') === -1;
22 window.is_chrome_mac = is_chrome && clientPC.indexOf('mac') !== -1
23 }
24
25 // For accesskeys; note that FF3+ is included here!
26 window.is_ff2 = /firefox\/[2-9]|minefield\/3/.test( clientPC );
27 window.ff2_bugs = /firefox\/2/.test( clientPC );
28 // These aren't used here, but some custom scripts rely on them
29 window.is_ff2_win = is_ff2 && clientPC.indexOf('windows') != -1;
30 window.is_ff2_x11 = is_ff2 && clientPC.indexOf('x11') != -1;
31
32 window.is_opera = window.is_opera_preseven = window.is_opera_95 =
33 window.opera6_bugs = window.opera7_bugs = window.opera95_bugs = false;
34 if (clientPC.indexOf('opera') != -1) {
35 window.is_opera = true;
36 window.is_opera_preseven = window.opera && !document.childNodes;
37 window.is_opera_seven = window.opera && document.childNodes;
38 window.is_opera_95 = /opera\/(9\.[5-9]|[1-9][0-9])/.test( clientPC );
39 window.opera6_bugs = is_opera_preseven;
40 window.opera7_bugs = is_opera_seven && !is_opera_95;
41 window.opera95_bugs = /opera\/(9\.5)/.test( clientPC );
42 }
43 // As recommended by <http://msdn.microsoft.com/en-us/library/ms537509.aspx>,
44 // avoiding false positives from moronic extensions that append to the IE UA
45 // string (bug 23171)
46 window.ie6_bugs = false;
47 if ( /msie ([0-9]{1,}[\.0-9]{0,})/.exec( clientPC ) != null
48 && parseFloat( RegExp.$1 ) <= 6.0 ) {
49 ie6_bugs = true;
50 }
51
52 // add any onload functions in this hook (please don't hard-code any events in the xhtml source)
53 window.doneOnloadHook = undefined;
54
55 if (!window.onloadFuncts) {
56 window.onloadFuncts = [];
57 }
58
59 window.addOnloadHook = function( hookFunct ) {
60 // Allows add-on scripts to add onload functions
61 if( !doneOnloadHook ) {
62 onloadFuncts[onloadFuncts.length] = hookFunct;
63 } else {
64 hookFunct(); // bug in MSIE script loading
65 }
66 };
67
68 window.importScript = function( page ) {
69 var uri = mw.config.get( 'wgScript' ) + '?title=' +
70 mw.util.wikiUrlencode( page ) +
71 '&action=raw&ctype=text/javascript';
72 return importScriptURI( uri );
73 };
74
75 window.loadedScripts = {}; // included-scripts tracker
76 window.importScriptURI = function( url ) {
77 if ( loadedScripts[url] ) {
78 return null;
79 }
80 loadedScripts[url] = true;
81 var s = document.createElement( 'script' );
82 s.setAttribute( 'src', url );
83 s.setAttribute( 'type', 'text/javascript' );
84 document.getElementsByTagName('head')[0].appendChild( s );
85 return s;
86 };
87
88 window.importStylesheet = function( page ) {
89 return importStylesheetURI( mw.config.get( 'wgScript' ) + '?action=raw&ctype=text/css&title=' + mw.util.wikiUrlencode( page ) );
90 };
91
92 window.importStylesheetURI = function( url, media ) {
93 var l = document.createElement( 'link' );
94 l.rel = 'stylesheet';
95 l.href = url;
96 if ( media ) {
97 l.media = media;
98 }
99 document.getElementsByTagName('head')[0].appendChild( l );
100 return l;
101 };
102
103 window.appendCSS = function( text ) {
104 var s = document.createElement( 'style' );
105 s.type = 'text/css';
106 s.rel = 'stylesheet';
107 if ( s.styleSheet ) {
108 s.styleSheet.cssText = text; // IE
109 } else {
110 s.appendChild( document.createTextNode( text + '' ) ); // Safari sometimes borks on null
111 }
112 document.getElementsByTagName('head')[0].appendChild( s );
113 return s;
114 };
115
116 // Special stylesheet links for Monobook only (see bug 14717)
117 var skinpath = mw.config.get( 'stylepath' ) + '/' + mw.config.get( 'skin' );
118 if ( mw.config.get( 'skin' ) === 'monobook' ) {
119 if ( opera6_bugs ) {
120 importStylesheetURI( skinpath + '/Opera6Fixes.css' );
121 } else if ( opera7_bugs ) {
122 importStylesheetURI( skinpath + '/Opera7Fixes.css' );
123 } else if ( opera95_bugs ) {
124 importStylesheetURI( skinpath + '/Opera9Fixes.css' );
125 }
126 }
127
128 if ( mw.config.get( 'wgBreakFrames' ) ) {
129 // Un-trap us from framesets
130 if ( window.top != window ) {
131 window.top.location = window.location;
132 }
133 }
134
135 window.changeText = function( el, newText ) {
136 // Safari work around
137 if ( el.innerText ) {
138 el.innerText = newText;
139 } else if ( el.firstChild && el.firstChild.nodeValue ) {
140 el.firstChild.nodeValue = newText;
141 }
142 };
143
144 window.killEvt = function( evt ) {
145 evt = evt || window.event || window.Event; // W3C, IE, Netscape
146 if ( typeof ( evt.preventDefault ) != 'undefined' ) {
147 evt.preventDefault(); // Don't follow the link
148 evt.stopPropagation();
149 } else {
150 evt.cancelBubble = true; // IE
151 }
152 return false; // Don't follow the link (IE)
153 };
154
155 window.mwEditButtons = [];
156 window.mwCustomEditButtons = []; // eg to add in MediaWiki:Common.js
157
158 window.escapeQuotes = function( text ) {
159 var re = new RegExp( "'", "g" );
160 text = text.replace( re, "\\'" );
161 re = new RegExp( "\\n", "g" );
162 text = text.replace( re, "\\n" );
163 return escapeQuotesHTML( text );
164 };
165
166 window.escapeQuotesHTML = function( text ) {
167 var re = new RegExp( '&', "g" );
168 text = text.replace( re, "&amp;" );
169 re = new RegExp( '"', "g" );
170 text = text.replace( re, "&quot;" );
171 re = new RegExp( '<', "g" );
172 text = text.replace( re, "&lt;" );
173 re = new RegExp( '>', "g" );
174 text = text.replace( re, "&gt;" );
175 return text;
176 };
177
178 /**
179 * Set the accesskey prefix based on browser detection.
180 */
181 window.tooltipAccessKeyPrefix = 'alt-';
182 if ( is_opera ) {
183 tooltipAccessKeyPrefix = 'shift-esc-';
184 } else if ( is_chrome ) {
185 tooltipAccessKeyPrefix = is_chrome_mac ? 'ctrl-option-' : 'alt-';
186 } else if ( !is_safari_win && is_safari && webkit_version > 526 ) {
187 tooltipAccessKeyPrefix = 'ctrl-alt-';
188 } else if ( !is_safari_win && ( is_safari
189 || clientPC.indexOf('mac') != -1
190 || clientPC.indexOf('konqueror') != -1 ) ) {
191 tooltipAccessKeyPrefix = 'ctrl-';
192 } else if ( is_ff2 ) {
193 tooltipAccessKeyPrefix = 'alt-shift-';
194 }
195 window.tooltipAccessKeyRegexp = /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/;
196
197 /**
198 * Add the appropriate prefix to the accesskey shown in the tooltip.
199 * If the nodeList parameter is given, only those nodes are updated;
200 * otherwise, all the nodes that will probably have accesskeys by
201 * default are updated.
202 *
203 * @param nodeList Array list of elements to update
204 */
205 window.updateTooltipAccessKeys = function( nodeList ) {
206 if ( !nodeList ) {
207 // Rather than scan all links on the whole page, we can just scan these
208 // containers which contain the relevant links. This is really just an
209 // optimization technique.
210 var linkContainers = [
211 'column-one', // Monobook and Modern
212 'mw-head', 'mw-panel', 'p-logo' // Vector
213 ];
214 for ( var i in linkContainers ) {
215 var linkContainer = document.getElementById( linkContainers[i] );
216 if ( linkContainer ) {
217 updateTooltipAccessKeys( linkContainer.getElementsByTagName( 'a' ) );
218 }
219 }
220 // these are rare enough that no such optimization is needed
221 updateTooltipAccessKeys( document.getElementsByTagName( 'input' ) );
222 updateTooltipAccessKeys( document.getElementsByTagName( 'label' ) );
223 return;
224 }
225
226 for ( var i = 0; i < nodeList.length; i++ ) {
227 var element = nodeList[i];
228 var tip = element.getAttribute( 'title' );
229 if ( tip && tooltipAccessKeyRegexp.exec( tip ) ) {
230 tip = tip.replace(tooltipAccessKeyRegexp,
231 '[' + tooltipAccessKeyPrefix + "$5]");
232 element.setAttribute( 'title', tip );
233 }
234 }
235 };
236
237 /**
238 * Add a link to one of the portlet menus on the page, including:
239 *
240 * p-cactions: Content actions (shown as tabs above the main content in Monobook)
241 * p-personal: Personal tools (shown at the top right of the page in Monobook)
242 * p-navigation: Navigation
243 * p-tb: Toolbox
244 *
245 * This function exists for the convenience of custom JS authors. All
246 * but the first three parameters are optional, though providing at
247 * least an id and a tooltip is recommended.
248 *
249 * By default the new link will be added to the end of the list. To
250 * add the link before a given existing item, pass the DOM node of
251 * that item (easily obtained with document.getElementById()) as the
252 * nextnode parameter; to add the link _after_ an existing item, pass
253 * the node's nextSibling instead.
254 *
255 * @param portlet String id of the target portlet ("p-cactions", "p-personal", "p-navigation" or "p-tb")
256 * @param href String link URL
257 * @param text String link text (will be automatically lowercased by CSS for p-cactions in Monobook)
258 * @param id String id of the new item, should be unique and preferably have the appropriate prefix ("ca-", "pt-", "n-" or "t-")
259 * @param tooltip String text to show when hovering over the link, without accesskey suffix
260 * @param accesskey String accesskey to activate this link (one character, try to avoid conflicts)
261 * @param nextnode Node the DOM node before which the new item should be added, should be another item in the same list
262 *
263 * @return Node -- the DOM node of the new item (an LI element) or null
264 */
265 window.addPortletLink = function( portlet, href, text, id, tooltip, accesskey, nextnode ) {
266 var root = document.getElementById( portlet );
267 if ( !root ) {
268 return null;
269 }
270 var uls = root.getElementsByTagName( 'ul' );
271 var node;
272 if ( uls.length > 0 ) {
273 node = uls[0];
274 } else {
275 node = document.createElement( 'ul' );
276 var lastElementChild = null;
277 for ( var i = 0; i < root.childNodes.length; ++i ) { /* get root.lastElementChild */
278 if ( root.childNodes[i].nodeType == 1 ) {
279 lastElementChild = root.childNodes[i];
280 }
281 }
282 if ( lastElementChild && lastElementChild.nodeName.match( /div/i ) ) {
283 /* Insert into the menu divs */
284 lastElementChild.appendChild( node );
285 } else {
286 root.appendChild( node );
287 }
288 }
289 if ( !node ) {
290 return null;
291 }
292
293 // unhide portlet if it was hidden before
294 root.className = root.className.replace( /(^| )emptyPortlet( |$)/, "$2" );
295
296 var link = document.createElement( 'a' );
297 link.appendChild( document.createTextNode( text ) );
298 link.href = href;
299
300 // Wrap in a span - make it work with vector tabs and has no effect on any other portlets
301 var span = document.createElement( 'span' );
302 span.appendChild( link );
303
304 var item = document.createElement( 'li' );
305 item.appendChild( span );
306 if ( id ) {
307 item.id = id;
308 }
309
310 if ( accesskey ) {
311 link.setAttribute( 'accesskey', accesskey );
312 tooltip += ' [' + accesskey + ']';
313 }
314 if ( tooltip ) {
315 link.setAttribute( 'title', tooltip );
316 }
317 if ( accesskey && tooltip ) {
318 updateTooltipAccessKeys( [link] );
319 }
320
321 if ( nextnode && nextnode.parentNode == node ) {
322 node.insertBefore( item, nextnode );
323 } else {
324 node.appendChild( item ); // IE compatibility (?)
325 }
326
327 return item;
328 };
329
330 window.getInnerText = function( el ) {
331 if ( typeof el == 'string' ) {
332 return el;
333 }
334 if ( typeof el == 'undefined' ) {
335 return el;
336 }
337 // Custom sort value through 'data-sort-value' attribute
338 // (no need to prepend hidden text to change sort value)
339 if ( el.nodeType && el.getAttribute( 'data-sort-value' ) !== null ) {
340 // Make sure it's a valid DOM element (.nodeType) and that the attribute is set (!null)
341 return el.getAttribute( 'data-sort-value' );
342 }
343 if ( el.textContent ) {
344 return el.textContent; // not needed but it is faster
345 }
346 if ( el.innerText ) {
347 return el.innerText; // IE doesn't have textContent
348 }
349 var str = '';
350
351 var cs = el.childNodes;
352 var l = cs.length;
353 for ( var i = 0; i < l; i++ ) {
354 switch ( cs[i].nodeType ) {
355 case 1: // ELEMENT_NODE
356 str += getInnerText( cs[i] );
357 break;
358 case 3: // TEXT_NODE
359 str += cs[i].nodeValue;
360 break;
361 }
362 }
363 return str;
364 };
365
366 window.checkboxes = undefined;
367 window.lastCheckbox = undefined;
368
369 window.setupCheckboxShiftClick = function() {
370 checkboxes = [];
371 lastCheckbox = null;
372 var inputs = document.getElementsByTagName( 'input' );
373 addCheckboxClickHandlers( inputs );
374 };
375
376 window.addCheckboxClickHandlers = function( inputs, start ) {
377 if ( !start ) {
378 start = 0;
379 }
380
381 var finish = start + 250;
382 if ( finish > inputs.length ) {
383 finish = inputs.length;
384 }
385
386 for ( var i = start; i < finish; i++ ) {
387 var cb = inputs[i];
388 if ( !cb.type || cb.type.toLowerCase() != 'checkbox' || ( ' ' + cb.className + ' ' ).indexOf( ' noshiftselect ' ) != -1 ) {
389 continue;
390 }
391 var end = checkboxes.length;
392 checkboxes[end] = cb;
393 cb.index = end;
394 addClickHandler( cb, checkboxClickHandler );
395 }
396
397 if ( finish < inputs.length ) {
398 setTimeout( function() {
399 addCheckboxClickHandlers( inputs, finish );
400 }, 200 );
401 }
402 };
403
404 window.checkboxClickHandler = function( e ) {
405 if ( typeof e == 'undefined' ) {
406 e = window.event;
407 }
408 if ( !e.shiftKey || lastCheckbox === null ) {
409 lastCheckbox = this.index;
410 return true;
411 }
412 var endState = this.checked;
413 var start, finish;
414 if ( this.index < lastCheckbox ) {
415 start = this.index + 1;
416 finish = lastCheckbox;
417 } else {
418 start = lastCheckbox;
419 finish = this.index - 1;
420 }
421 for ( var i = start; i <= finish; ++i ) {
422 checkboxes[i].checked = endState;
423 if( i > start && typeof checkboxes[i].onchange == 'function' ) {
424 checkboxes[i].onchange(); // fire triggers
425 }
426 }
427 lastCheckbox = this.index;
428 return true;
429 };
430
431
432 /*
433 Written by Jonathan Snook, http://www.snook.ca/jonathan
434 Add-ons by Robert Nyman, http://www.robertnyman.com
435 Author says "The credit comment is all it takes, no license. Go crazy with it!:-)"
436 From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
437 */
438 window.getElementsByClassName = function( oElm, strTagName, oClassNames ) {
439 var arrReturnElements = [];
440 if ( typeof( oElm.getElementsByClassName ) == 'function' ) {
441 /* Use a native implementation where possible FF3, Saf3.2, Opera 9.5 */
442 var arrNativeReturn = oElm.getElementsByClassName( oClassNames );
443 if ( strTagName == '*' ) {
444 return arrNativeReturn;
445 }
446 for ( var h = 0; h < arrNativeReturn.length; h++ ) {
447 if( arrNativeReturn[h].tagName.toLowerCase() == strTagName.toLowerCase() ) {
448 arrReturnElements[arrReturnElements.length] = arrNativeReturn[h];
449 }
450 }
451 return arrReturnElements;
452 }
453 var arrElements = ( strTagName == '*' && oElm.all ) ? oElm.all : oElm.getElementsByTagName( strTagName );
454 var arrRegExpClassNames = [];
455 if( typeof oClassNames == 'object' ) {
456 for( var i = 0; i < oClassNames.length; i++ ) {
457 arrRegExpClassNames[arrRegExpClassNames.length] =
458 new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)");
459 }
460 } else {
461 arrRegExpClassNames[arrRegExpClassNames.length] =
462 new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)");
463 }
464 var oElement;
465 var bMatchesAll;
466 for( var j = 0; j < arrElements.length; j++ ) {
467 oElement = arrElements[j];
468 bMatchesAll = true;
469 for( var k = 0; k < arrRegExpClassNames.length; k++ ) {
470 if( !arrRegExpClassNames[k].test( oElement.className ) ) {
471 bMatchesAll = false;
472 break;
473 }
474 }
475 if( bMatchesAll ) {
476 arrReturnElements[arrReturnElements.length] = oElement;
477 }
478 }
479 return ( arrReturnElements );
480 };
481
482 window.redirectToFragment = function( fragment ) {
483 var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
484 if ( match ) {
485 var webKitVersion = parseInt( match[1] );
486 if ( webKitVersion < 420 ) {
487 // Released Safari w/ WebKit 418.9.1 messes up horribly
488 // Nightlies of 420+ are ok
489 return;
490 }
491 }
492 if ( window.location.hash == '' ) {
493 window.location.hash = fragment;
494
495 // Mozilla needs to wait until after load, otherwise the window doesn't
496 // scroll. See <https://bugzilla.mozilla.org/show_bug.cgi?id=516293>.
497 // There's no obvious way to detect this programmatically, so we use
498 // version-testing. If Firefox fixes the bug, they'll jump twice, but
499 // better twice than not at all, so make the fix hit future versions as
500 // well.
501 if ( is_gecko ) {
502 addOnloadHook(function() {
503 if ( window.location.hash == fragment ) {
504 window.location.hash = fragment;
505 }
506 });
507 }
508 }
509 };
510
511 /**
512 * Add a cute little box at the top of the screen to inform the user of
513 * something, replacing any preexisting message.
514 *
515 * @deprecated since 1.17 Use the 'mediawiki.notify' module instead.
516 * @param {String|HTMLElement} message To be put inside the message box.
517 */
518 window.jsMsg = function () {
519 return mw.util.jsMessage.apply( mw.util, arguments );
520 };
521
522 /**
523 * Inject a cute little progress spinner after the specified element
524 *
525 * @param element Element to inject after
526 * @param id Identifier string (for use with removeSpinner(), below)
527 */
528 window.injectSpinner = function( element, id ) {
529 var spinner = document.createElement( 'img' );
530 spinner.id = 'mw-spinner-' + id;
531 spinner.src = mw.config.get( 'stylepath' ) + '/common/images/spinner.gif';
532 spinner.alt = spinner.title = '...';
533 if( element.nextSibling ) {
534 element.parentNode.insertBefore( spinner, element.nextSibling );
535 } else {
536 element.parentNode.appendChild( spinner );
537 }
538 };
539
540 /**
541 * Remove a progress spinner added with injectSpinner()
542 *
543 * @param id Identifier string
544 */
545 window.removeSpinner = function( id ) {
546 var spinner = document.getElementById( 'mw-spinner-' + id );
547 if( spinner ) {
548 spinner.parentNode.removeChild( spinner );
549 }
550 };
551
552 window.runOnloadHook = function() {
553 // don't run anything below this for non-dom browsers
554 if ( doneOnloadHook || !( document.getElementById && document.getElementsByTagName ) ) {
555 return;
556 }
557
558 // set this before running any hooks, since any errors below
559 // might cause the function to terminate prematurely
560 doneOnloadHook = true;
561
562 // Run any added-on functions
563 for ( var i = 0; i < onloadFuncts.length; i++ ) {
564 onloadFuncts[i]();
565 }
566 };
567
568 /**
569 * Add an event handler to an element
570 *
571 * @param element Element to add handler to
572 * @param attach String Event to attach to
573 * @param handler callable Event handler callback
574 */
575 window.addHandler = function( element, attach, handler ) {
576 if( element.addEventListener ) {
577 element.addEventListener( attach, handler, false );
578 } else if( element.attachEvent ) {
579 element.attachEvent( 'on' + attach, handler );
580 }
581 };
582
583 window.hookEvent = function( hookName, hookFunct ) {
584 addHandler( window, hookName, hookFunct );
585 };
586
587 /**
588 * Add a click event handler to an element
589 *
590 * @param element Element to add handler to
591 * @param handler callable Event handler callback
592 */
593 window.addClickHandler = function( element, handler ) {
594 addHandler( element, 'click', handler );
595 };
596
597 /**
598 * Removes an event handler from an element
599 *
600 * @param element Element to remove handler from
601 * @param remove String Event to remove
602 * @param handler callable Event handler callback to remove
603 */
604 window.removeHandler = function( element, remove, handler ) {
605 if( window.removeEventListener ) {
606 element.removeEventListener( remove, handler, false );
607 } else if( window.detachEvent ) {
608 element.detachEvent( 'on' + remove, handler );
609 }
610 };
611 // note: all skins should call runOnloadHook() at the end of html output,
612 // so the below should be redundant. It's there just in case.
613 hookEvent( 'load', runOnloadHook );
614
615 if ( ie6_bugs ) {
616 importScriptURI( mw.config.get( 'stylepath' ) + '/common/IEFixes.js' );
617 }
618
619 }( mediaWiki ) );