Merging resourceloader branch into trunk. Full documentation is at http://www.mediawi...
[lhc/web/wiklou.git] / resources / mediawiki / legacy / mediawiki.legacy.wikibits.js
1 /*
2 * Legacy emulation for the now depricated skins/common/wikibits.js
3 *
4 * MediaWiki JavaScript support functions
5 *
6 * Global external objects used by this script: ta, stylepath, skin
7 */
8
9 ( function( $, mw ) {
10
11 /* Extension */
12
13 /*
14 * Scary user-agent detection stuff
15 */
16 mw.legacy.clientPC = navigator.userAgent.toLowerCase(); // Get client info
17 mw.legacy.is_gecko = /gecko/.test( mw.legacy.clientPC ) && !/khtml|spoofer|netscape\/7\.0/.test(mw.legacy.clientPC);
18 mw.legacy.webkit_match = mw.legacy.clientPC.match(/applewebkit\/(\d+)/);
19 if (mw.legacy.webkit_match) {
20 mw.legacy.is_safari = mw.legacy.clientPC.indexOf('applewebkit') != -1 &&
21 mw.legacy.clientPC.indexOf('spoofer') == -1;
22 mw.legacy.is_safari_win = mw.legacy.is_safari && mw.legacy.clientPC.indexOf('windows') != -1;
23 mw.legacy.webkit_version = parseInt(mw.legacy.webkit_match[1]);
24 // Tests for chrome here, to avoid breaking old scripts safari left alone
25 // This is here for accesskeys
26 mw.legacy.is_chrome = mw.legacy.clientPC.indexOf('chrome') !== -1 &&
27 mw.legacy.clientPC.indexOf('spoofer') === -1;
28 mw.legacy.is_chrome_mac = mw.legacy.is_chrome && mw.legacy.clientPC.indexOf('mac') !== -1
29 }
30 // For accesskeys; note that FF3+ is included here!
31 mw.legacy.is_ff2 = /firefox\/[2-9]|minefield\/3/.test( mw.legacy.clientPC );
32 mw.legacy.ff2_bugs = /firefox\/2/.test( mw.legacy.clientPC );
33 // These aren't used here, but some custom scripts rely on them
34 mw.legacy.is_ff2_win = mw.legacy.is_ff2 && mw.legacy.clientPC.indexOf('windows') != -1;
35 mw.legacy.is_ff2_x11 = mw.legacy.is_ff2 && mw.legacy.clientPC.indexOf('x11') != -1;
36 if (mw.legacy.clientPC.indexOf('opera') != -1) {
37 mw.legacy.is_opera = true;
38 mw.legacy.is_opera_preseven = window.opera && !document.childNodes;
39 mw.legacy.is_opera_seven = window.opera && document.childNodes;
40 mw.legacy.is_opera_95 = /opera\/(9\.[5-9]|[1-9][0-9])/.test( mw.legacy.clientPC );
41 mw.legacy.opera6_bugs = mw.legacy.is_opera_preseven;
42 mw.legacy.opera7_bugs = mw.legacy.is_opera_seven && !mw.legacy.is_opera_95;
43 mw.legacy.opera95_bugs = /opera\/(9\.5)/.test( mw.legacy.clientPC );
44 }
45 // As recommended by <http://msdn.microsoft.com/en-us/library/ms537509.aspx>,
46 // avoiding false positives from moronic extensions that append to the IE UA
47 // string (bug 23171)
48 mw.legacy.ie6_bugs = false;
49 if ( /MSIE ([0-9]{1,}[\.0-9]{0,})/.exec( mw.legacy.clientPC ) != null && parseFloat( RegExp.$1 ) <= 6.0 ) {
50 mw.legacy.ie6_bugs = true;
51 }
52
53 $.extend( true, mw.legacy, {
54
55 /*
56 * Events
57 *
58 * Add any onload functions in this hook (please don't hard-code any events in the xhtml source)
59 */
60
61 /* Global Variables */
62
63 'doneOnloadHook': null,
64 'onloadFuncts': [],
65
66 /* Functions */
67
68 'addOnloadHook': function( hookFunct ) {
69 // Allows add-on scripts to add onload functions
70 if( !mw.legacy.doneOnloadHook ) {
71 mw.legacy.onloadFuncts[mw.legacy.onloadFuncts.length] = hookFunct;
72 } else {
73 hookFunct(); // bug in MSIE script loading
74 }
75 },
76 'hookEvent': function( hookName, hookFunct ) {
77 addHandler( window, hookName, hookFunct );
78 },
79 'killEvt': function( evt ) {
80 evt = evt || window.event || window.Event; // W3C, IE, Netscape
81 if ( typeof ( evt.preventDefault ) != 'undefined' ) {
82 evt.preventDefault(); // Don't follow the link
83 evt.stopPropagation();
84 } else {
85 evt.cancelBubble = true; // IE
86 }
87 return false; // Don't follow the link (IE)
88 },
89
90 /*
91 * Dynamic loading
92 */
93
94 /* Global Variables */
95
96 'loadedScripts': {},
97
98 /* Functions */
99
100 'importScript': function( page ) {
101 // TODO: might want to introduce a utility function to match wfUrlencode() in PHP
102 var uri = wgScript + '?title=' +
103 encodeURIComponent(page.replace(/ /g,'_')).replace(/%2F/ig,'/').replace(/%3A/ig,':') +
104 '&action=raw&ctype=text/javascript';
105 return importScriptURI( uri );
106 },
107 'importScriptURI': function( url ) {
108 if ( mw.legacy.loadedScripts[url] ) {
109 return null;
110 }
111 mw.legacy.loadedScripts[url] = true;
112 var s = document.createElement( 'script' );
113 s.setAttribute( 'src', url );
114 s.setAttribute( 'type', 'text/javascript' );
115 document.getElementsByTagName('head')[0].appendChild( s );
116 return s;
117 },
118 'importStylesheet': function( page ) {
119 return importStylesheetURI( wgScript + '?action=raw&ctype=text/css&title=' + encodeURIComponent( page.replace(/ /g,'_') ) );
120 },
121 'importStylesheetURI': function( url, media ) {
122 var l = document.createElement( 'link' );
123 l.type = 'text/css';
124 l.rel = 'stylesheet';
125 l.href = url;
126 if( media ) {
127 l.media = media;
128 }
129 document.getElementsByTagName('head')[0].appendChild( l );
130 return l;
131 },
132 'appendCSS': function( text ) {
133 var s = document.createElement( 'style' );
134 s.type = 'text/css';
135 s.rel = 'stylesheet';
136 if ( s.styleSheet ) {
137 s.styleSheet.cssText = text; // IE
138 } else {
139 s.appendChild( document.createTextNode( text + '' ) ); // Safari sometimes borks on null
140 }
141 document.getElementsByTagName('head')[0].appendChild( s );
142 return s;
143 },
144 'runOnloadHook': function() {
145 // don't run anything below this for non-dom browsers
146 if ( mw.legacy.doneOnloadHook || !( document.getElementById && document.getElementsByTagName ) ) {
147 return;
148 }
149 // set this before running any hooks, since any errors below
150 // might cause the function to terminate prematurely
151 mw.legacy.doneOnloadHook = true;
152 updateTooltipAccessKeys( null );
153 setupCheckboxShiftClick();
154 sortables_init();
155 // Run any added-on functions
156 for ( var i = 0; i < mw.legacy.onloadFuncts.length; i++ ) {
157 mw.legacy.onloadFuncts[i]();
158 }
159 },
160 /**
161 * Add an event handler to an element
162 *
163 * @param Element element Element to add handler to
164 * @param String attach Event to attach to
165 * @param callable handler Event handler callback
166 */
167 'addHandler': function( element, attach, handler ) {
168 if( window.addEventListener ) {
169 element.addEventListener( attach, handler, false );
170 } else if( window.attachEvent ) {
171 element.attachEvent( 'on' + attach, handler );
172 }
173 },
174 /**
175 * Add a click event handler to an element
176 *
177 * @param Element element Element to add handler to
178 * @param callable handler Event handler callback
179 */
180 'addClickHandler': function( element, handler ) {
181 addHandler( element, 'click', handler );
182 },
183 /**
184 * Removes an event handler from an element
185 *
186 * @param Element element Element to remove handler from
187 * @param String remove Event to remove
188 * @param callable handler Event handler callback to remove
189 */
190 'removeHandler': function( element, remove, handler ) {
191 if( window.removeEventListener ) {
192 element.removeEventListener( remove, handler, false );
193 } else if( window.detachEvent ) {
194 element.detachEvent( 'on' + remove, handler );
195 }
196 },
197
198 /*
199 * Toolbar
200 */
201
202 /* Global Variables */
203
204 'mwEditButtons': [],
205 'mwCustomEditButtons': [],
206
207 /**
208 * Tooltips and access-keys
209 */
210
211 /* Global Variables */
212
213 /**
214 * Set the accesskey prefix based on browser detection.
215 */
216 'tooltipAccessKeyPrefix': ( function() {
217 if ( mw.legacy.is_opera ) {
218 return 'shift-esc-';
219 } else if ( mw.legacy.is_chrome ) {
220 return mw.legacy.is_chrome_mac ? 'ctrl-option-' : 'alt-';
221 } else if ( !mw.legacy.is_safari_win && mw.legacy.is_safari && mw.legacy.webkit_version > 526 ) {
222 return 'ctrl-alt-';
223 } else if (
224 !mw.legacy.is_safari_win &&
225 (
226 mw.legacy.is_safari ||
227 mw.legacy.clientPC.indexOf( 'mac' ) != -1 ||
228 mw.legacy.clientPC.indexOf( 'konqueror' ) != -1
229 )
230 ) {
231 return 'ctrl-';
232 } else if ( mw.legacy.is_ff2 ) {
233 return 'alt-shift-';
234 }
235 return 'alt-';
236 } )(),
237 'tooltipAccessKeyRegexp': /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/,
238 // Dummy for deprecated function
239 'ta': [],
240
241 /* Functions */
242
243 /**
244 * Add the appropriate prefix to the accesskey shown in the tooltip.
245 * If the nodeList parameter is given, only those nodes are updated;
246 * otherwise, all the nodes that will probably have accesskeys by
247 * default are updated.
248 *
249 * @param Array nodeList -- list of elements to update
250 */
251 'updateTooltipAccessKeys': function( nodeList ) {
252 if ( !nodeList ) {
253 // Rather than scan all links on the whole page, we can just scan these
254 // containers which contain the relevant links. This is really just an
255 // optimization technique.
256 var linkContainers = [
257 'column-one', // Monobook and Modern
258 'mw-head', 'mw-panel', 'p-logo' // Vector
259 ];
260 for ( var i in linkContainers ) {
261 var linkContainer = document.getElementById( linkContainers[i] );
262 if ( linkContainer ) {
263 updateTooltipAccessKeys( linkContainer.getElementsByTagName( 'a' ) );
264 }
265 }
266 // these are rare enough that no such optimization is needed
267 updateTooltipAccessKeys( document.getElementsByTagName( 'input' ) );
268 updateTooltipAccessKeys( document.getElementsByTagName( 'label' ) );
269 return;
270 }
271 for ( var i = 0; i < nodeList.length; i++ ) {
272 var element = nodeList[i];
273 var tip = element.getAttribute( 'title' );
274 if ( tip && mw.legacy.tooltipAccessKeyRegexp.exec( tip ) ) {
275 tip = tip.replace(mw.legacy.tooltipAccessKeyRegexp,
276 '[' + mw.legacy.tooltipAccessKeyPrefix + '$5]');
277 element.setAttribute( 'title', tip );
278 }
279 }
280 },
281 // Dummy function for depricated feature
282 'akeytt': function( doId ) { },
283
284 /*
285 * Checkboxes
286 */
287
288 /* Global Varibles */
289
290 'checkboxes': null,
291 'lastCheckbox': null,
292
293 /* Functions */
294
295 'setupCheckboxShiftClick': function() {
296 mw.legacy.checkboxes = [];
297 mw.legacy.lastCheckbox = null;
298 var inputs = document.getElementsByTagName( 'input' );
299 addCheckboxClickHandlers( inputs );
300 },
301 'addCheckboxClickHandlers': function( inputs, start ) {
302 if ( !start ) {
303 start = 0;
304 }
305 var finish = start + 250;
306 if ( finish > inputs.length ) {
307 finish = inputs.length;
308 }
309 for ( var i = start; i < finish; i++ ) {
310 var cb = inputs[i];
311 if ( !cb.type || cb.type.toLowerCase() != 'checkbox' ) {
312 continue;
313 }
314 var end = mw.legacy.checkboxes.length;
315 mw.legacy.checkboxes[end] = cb;
316 cb.index = end;
317 addClickHandler( cb, checkboxClickHandler );
318 }
319 if ( finish < inputs.length ) {
320 setTimeout( function() {
321 addCheckboxClickHandlers( inputs, finish );
322 }, 200 );
323 }
324 },
325 'checkboxClickHandler': function( e ) {
326 if ( typeof e == 'undefined' ) {
327 e = window.event;
328 }
329 if ( !e.shiftKey || mw.legacy.lastCheckbox === null ) {
330 mw.legacy.lastCheckbox = this.index;
331 return true;
332 }
333 var endState = this.checked;
334 var start, finish;
335 if ( this.index < mw.legacy.lastCheckbox ) {
336 start = this.index + 1;
337 finish = mw.legacy.lastCheckbox;
338 } else {
339 start = mw.legacy.lastCheckbox;
340 finish = this.index - 1;
341 }
342 for ( var i = start; i <= finish; ++i ) {
343 mw.legacy.checkboxes[i].checked = endState;
344 if( i > start && typeof mw.legacy.checkboxes[i].onchange == 'function' ) {
345 mw.legacy.checkboxes[i].onchange(); // fire triggers
346 }
347 }
348 mw.legacy.lastCheckbox = this.index;
349 return true;
350 },
351
352 /*
353 * Table of contents
354 */
355
356 /* Functions */
357
358 'showTocToggle': function() {
359 if ( document.createTextNode ) {
360 // Uses DOM calls to avoid document.write + XHTML issues
361 var linkHolder = document.getElementById( 'toctitle' );
362 var existingLink = document.getElementById( 'togglelink' );
363 if ( !linkHolder || existingLink ) {
364 // Don't add the toggle link twice
365 return;
366 }
367 var outerSpan = document.createElement( 'span' );
368 outerSpan.className = 'toctoggle';
369 var toggleLink = document.createElement( 'a' );
370 toggleLink.id = 'togglelink';
371 toggleLink.className = 'internal';
372 toggleLink.href = '#';
373 addClickHandler( toggleLink, function( evt ) { toggleToc(); return killEvt( evt ); } );
374 toggleLink.appendChild( document.createTextNode( tocHideText ) );
375 outerSpan.appendChild( document.createTextNode( '[' ) );
376 outerSpan.appendChild( toggleLink );
377 outerSpan.appendChild( document.createTextNode( ']' ) );
378 linkHolder.appendChild( document.createTextNode( ' ' ) );
379 linkHolder.appendChild( outerSpan );
380 var cookiePos = document.cookie.indexOf( 'hidetoc=' );
381 if ( cookiePos > -1 && document.cookie.charAt( cookiePos + 8 ) == 1 ) {
382 toggleToc();
383 }
384 }
385 },
386 'toggleToc': function() {
387 var tocmain = document.getElementById( 'toc' );
388 var toc = document.getElementById('toc').getElementsByTagName('ul')[0];
389 var toggleLink = document.getElementById( 'togglelink' );
390
391 if ( toc && toggleLink && toc.style.display == 'none' ) {
392 changeText( toggleLink, tocHideText );
393 toc.style.display = 'block';
394 document.cookie = 'hidetoc=0';
395 tocmain.className = 'toc';
396 } else {
397 changeText( toggleLink, tocShowText );
398 toc.style.display = 'none';
399 document.cookie = 'hidetoc=1';
400 tocmain.className = 'toc tochidden';
401 }
402 return false;
403 },
404
405 /*
406 * Table sorting
407 *
408 * Script based on one (c) 1997-2006 Stuart Langridge and Joost de Valk:
409 * http://www.joostdevalk.nl/code/sortable-table/
410 * http://www.kryogenix.org/code/browser/sorttable/
411 *
412 * @todo don't break on colspans/rowspans (bug 8028)
413 * @todo language-specific digit grouping/decimals (bug 8063)
414 * @todo support all accepted date formats (bug 8226)
415 */
416
417 /* Global Variables */
418
419 'ts_image_path': mw.legacy.stylepath + '/common/images/',
420 'ts_image_up': 'sort_up.gif',
421 'ts_image_down': 'sort_down.gif',
422 'ts_image_none': 'sort_none.gif',
423 // The non-American-inclined can change to "true"
424 'ts_europeandate': mw.legacy.wgContentLanguage != 'en',
425 'ts_alternate_row_colors': false,
426 'ts_number_transform_table': null,
427 'ts_number_regex': null,
428
429 /* Functions */
430
431 'sortables_init': function() {
432 var idnum = 0;
433 // Find all tables with class sortable and make them sortable
434 var tables = getElementsByClassName( document, 'table', 'sortable' );
435 for ( var ti = 0; ti < tables.length ; ti++ ) {
436 if ( !tables[ti].id ) {
437 tables[ti].setAttribute( 'id', 'sortable_table_id_' + idnum );
438 ++idnum;
439 }
440 mw.legacy.ts_makeSortable( tables[ti] );
441 }
442 },
443 'ts_makeSortable': function( table ) {
444 var firstRow;
445 if ( table.rows && table.rows.length > 0 ) {
446 if ( table.tHead && table.tHead.rows.length > 0 ) {
447 firstRow = table.tHead.rows[table.tHead.rows.length-1];
448 } else {
449 firstRow = table.rows[0];
450 }
451 }
452 if ( !firstRow ) {
453 return;
454 }
455 // We have a first row: assume it's the header, and make its contents clickable links
456 for ( var i = 0; i < firstRow.cells.length; i++ ) {
457 var cell = firstRow.cells[i];
458 if ( (' ' + cell.className + ' ').indexOf(' unsortable ') == -1 ) {
459 cell.innerHTML += '<a href="#" class="sortheader" '
460 + 'onclick="mw.legacy.ts_resortTable(this);return false;">'
461 + '<span class="sortarrow">'
462 + '<img src="'
463 + mw.legacy.ts_image_path
464 + mw.legacy.ts_image_none
465 + '" alt="&darr;"/></span></a>';
466 }
467 }
468 if ( mw.legacy.ts_alternate_row_colors ) {
469 mw.legacy.ts_alternate( table );
470 }
471 },
472 'ts_getInnerText': function( el ) {
473 return getInnerText( el );
474 },
475 'ts_resortTable': function( lnk ) {
476 // get the span
477 var span = lnk.getElementsByTagName('span')[0];
478 var td = lnk.parentNode;
479 var tr = td.parentNode;
480 var column = td.cellIndex;
481 var table = tr.parentNode;
482 while ( table && !( table.tagName && table.tagName.toLowerCase() == 'table' ) ) {
483 table = table.parentNode;
484 }
485 if ( !table ) {
486 return;
487 }
488 if ( table.rows.length <= 1 ) {
489 return;
490 }
491 // Generate the number transform table if it's not done already
492 if ( mw.legacy.ts_number_transform_table === null ) {
493 mw.legacy.ts_initTransformTable();
494 }
495 // Work out a type for the column
496 // Skip the first row if that's where the headings are
497 var rowStart = ( table.tHead && table.tHead.rows.length > 0 ? 0 : 1 );
498 var bodyRows = 0;
499 if (rowStart == 0 && table.tBodies) {
500 for (var i=0; i < table.tBodies.length; i++ ) {
501 bodyRows += table.tBodies[i].rows.length;
502 }
503 if (bodyRows < table.rows.length)
504 rowStart = 1;
505 }
506 var itm = '';
507 for ( var i = rowStart; i < table.rows.length; i++ ) {
508 if ( table.rows[i].cells.length > column ) {
509 itm = mw.legacy.ts_getInnerText(table.rows[i].cells[column]);
510 itm = itm.replace(/^[\s\xa0]+/, '').replace(/[\s\xa0]+$/, '');
511 if ( itm != '' ) {
512 break;
513 }
514 }
515 }
516 // TODO: bug 8226, localised date formats
517 var sortfn = mw.legacy.ts_sort_generic;
518 var preprocessor = mw.legacy.ts_toLowerCase;
519 if ( /^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/.test( itm ) ) {
520 preprocessor = mw.legacy.ts_dateToSortKey;
521 } else if ( /^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/.test( itm ) ) {
522 preprocessor = mw.legacy.ts_dateToSortKey;
523 } else if ( /^\d\d[\/.-]\d\d[\/.-]\d\d$/.test( itm ) ) {
524 preprocessor = mw.legacy.ts_dateToSortKey;
525 // (minus sign)([pound dollar euro yen currency]|cents)
526 } else if ( /(^([-\u2212] *)?[\u00a3$\u20ac\u00a4\u00a5]|\u00a2$)/.test( itm ) ) {
527 preprocessor = mw.legacy.ts_currencyToSortKey;
528 } else if ( mw.legacy.ts_number_regex.test( itm ) ) {
529 preprocessor = mw.legacy.ts_parseFloat;
530 }
531 var reverse = ( span.getAttribute( 'sortdir' ) == 'down' );
532 var newRows = new Array();
533 var staticRows = new Array();
534 for ( var j = rowStart; j < table.rows.length; j++ ) {
535 var row = table.rows[j];
536 if( (' ' + row.className + ' ').indexOf(' unsortable ') < 0 ) {
537 var keyText = mw.legacy.ts_getInnerText( row.cells[column] );
538 if( keyText === undefined ) {
539 keyText = '';
540 }
541 var oldIndex = ( reverse ? -j : j );
542 var preprocessed = preprocessor( keyText.replace(/^[\s\xa0]+/, '').replace(/[\s\xa0]+$/, '') );
543 newRows[newRows.length] = new Array( row, preprocessed, oldIndex );
544 } else {
545 staticRows[staticRows.length] = new Array( row, false, j-rowStart );
546 }
547 }
548 newRows.sort( sortfn );
549 var arrowHTML;
550 if ( reverse ) {
551 arrowHTML = '<img src="' + mw.legacy.ts_image_path + mw.legacy.ts_image_down + '" alt="&darr;"/>';
552 newRows.reverse();
553 span.setAttribute( 'sortdir', 'up' );
554 } else {
555 arrowHTML = '<img src="' + mw.legacy.ts_image_path + mw.legacy.ts_image_up + '" alt="&uarr;"/>';
556 span.setAttribute( 'sortdir', 'down' );
557 }
558 for ( var i = 0; i < staticRows.length; i++ ) {
559 var row = staticRows[i];
560 newRows.splice( row[2], 0, row );
561 }
562 // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
563 // don't do sortbottom rows
564 for ( var i = 0; i < newRows.length; i++ ) {
565 if ( ( ' ' + newRows[i][0].className + ' ').indexOf(' sortbottom ') == -1 ) {
566 table.tBodies[0].appendChild( newRows[i][0] );
567 }
568 }
569 // do sortbottom rows only
570 for ( var i = 0; i < newRows.length; i++ ) {
571 if ( ( ' ' + newRows[i][0].className + ' ').indexOf(' sortbottom ') != -1 ) {
572 table.tBodies[0].appendChild( newRows[i][0] );
573 }
574 }
575 // Delete any other arrows there may be showing
576 var spans = getElementsByClassName( tr, 'span', 'sortarrow' );
577 for ( var i = 0; i < spans.length; i++ ) {
578 spans[i].innerHTML = '<img src="' + mw.legacy.ts_image_path + mw.legacy.ts_image_none + '" alt="&darr;"/>';
579 }
580 span.innerHTML = arrowHTML;
581
582 if ( mw.legacy.ts_alternate_row_colors ) {
583 mw.legacy.ts_alternate( table );
584 }
585 },
586 'ts_initTransformTable': function() {
587 if ( typeof wgSeparatorTransformTable == 'undefined'
588 || ( wgSeparatorTransformTable[0] == '' && wgDigitTransformTable[2] == '' ) )
589 {
590 var digitClass = '[0-9,.]';
591 mw.legacy.ts_number_transform_table = false;
592 } else {
593 mw.legacy.ts_number_transform_table = {};
594 // Unpack the transform table
595 // Separators
596 var ascii = wgSeparatorTransformTable[0].split('\t');
597 var localised = wgSeparatorTransformTable[1].split('\t');
598 for ( var i = 0; i < ascii.length; i++ ) {
599 mw.legacy.ts_number_transform_table[localised[i]] = ascii[i];
600 }
601 // Digits
602 ascii = wgDigitTransformTable[0].split('\t');
603 localised = wgDigitTransformTable[1].split('\t');
604 for ( var i = 0; i < ascii.length; i++ ) {
605 mw.legacy.ts_number_transform_table[localised[i]] = ascii[i];
606 }
607 // Construct regex for number identification
608 var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '\\.'];
609 var maxDigitLength = 1;
610 for ( var digit in mw.legacy.ts_number_transform_table ) {
611 // Escape regex metacharacters
612 digits.push(
613 digit.replace( /[\\\\$\*\+\?\.\(\)\|\{\}\[\]\-]/,
614 function( s ) { return '\\' + s; } )
615 );
616 if ( digit.length > maxDigitLength ) {
617 maxDigitLength = digit.length;
618 }
619 }
620 if ( maxDigitLength > 1 ) {
621 var digitClass = '[' + digits.join( '', digits ) + ']';
622 } else {
623 var digitClass = '(' + digits.join( '|', digits ) + ')';
624 }
625 }
626 // We allow a trailing percent sign, which we just strip. This works fine
627 // if percents and regular numbers aren't being mixed.
628 mw.legacy.ts_number_regex = new RegExp(
629 '^(' +
630 '[-+\u2212]?[0-9][0-9,]*(\\.[0-9,]*)?(E[-+\u2212]?[0-9][0-9,]*)?' + // Fortran-style scientific
631 '|' +
632 '[-+\u2212]?' + digitClass + '+%?' + // Generic localised
633 ')$', 'i'
634 );
635 },
636 'ts_toLowerCase': function( s ) {
637 return s.toLowerCase();
638 },
639 'ts_dateToSortKey': function( date ) {
640 // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
641 if ( date.length == 11 ) {
642 switch ( date.substr( 3, 3 ).toLowerCase() ) {
643 case 'jan':
644 var month = '01';
645 break;
646 case 'feb':
647 var month = '02';
648 break;
649 case 'mar':
650 var month = '03';
651 break;
652 case 'apr':
653 var month = '04';
654 break;
655 case 'may':
656 var month = '05';
657 break;
658 case 'jun':
659 var month = '06';
660 break;
661 case 'jul':
662 var month = '07';
663 break;
664 case 'aug':
665 var month = '08';
666 break;
667 case 'sep':
668 var month = '09';
669 break;
670 case 'oct':
671 var month = '10';
672 break;
673 case 'nov':
674 var month = '11';
675 break;
676 case 'dec':
677 var month = '12';
678 break;
679 // default: var month = '00';
680 }
681 return date.substr( 7, 4 ) + month + date.substr( 0, 2 );
682 } else if ( date.length == 10 ) {
683 if ( mw.legacy.ts_europeandate == false ) {
684 return date.substr( 6, 4 ) + date.substr( 0, 2 ) + date.substr( 3, 2 );
685 } else {
686 return date.substr( 6, 4 ) + date.substr( 3, 2 ) + date.substr( 0, 2 );
687 }
688 } else if ( date.length == 8 ) {
689 var yr = date.substr( 6, 2 );
690 if ( parseInt( yr ) < 50 ) {
691 yr = '20' + yr;
692 } else {
693 yr = '19' + yr;
694 }
695 if ( mw.legacy.ts_europeandate == true ) {
696 return yr + date.substr( 3, 2 ) + date.substr( 0, 2 );
697 } else {
698 return yr + date.substr( 0, 2 ) + date.substr( 3, 2 );
699 }
700 }
701 return '00000000';
702 },
703 'ts_parseFloat': function( s ) {
704 if ( !s ) {
705 return 0;
706 }
707 if ( mw.legacy.ts_number_transform_table != false ) {
708 var newNum = '', c;
709
710 for ( var p = 0; p < s.length; p++ ) {
711 c = s.charAt( p );
712 if ( c in mw.legacy.ts_number_transform_table ) {
713 newNum += mw.legacy.ts_number_transform_table[c];
714 } else {
715 newNum += c;
716 }
717 }
718 s = newNum;
719 }
720 var num = parseFloat( s.replace(/[, ]/g, '').replace('\u2212', '-') );
721 return ( isNaN( num ) ? -Infinity : num );
722 },
723 'ts_currencyToSortKey': function( s ) {
724 return mw.legacy.ts_parseFloat(s.replace(/[^-\u22120-9.,]/g,''));
725 },
726 'ts_sort_generic': function( a, b ) {
727 return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2];
728 },
729 'ts_alternate': function( table ) {
730 // Take object table and get all it's tbodies.
731 var tableBodies = table.getElementsByTagName( 'tbody' );
732 // Loop through these tbodies
733 for ( var i = 0; i < tableBodies.length; i++ ) {
734 // Take the tbody, and get all it's rows
735 var tableRows = tableBodies[i].getElementsByTagName( 'tr' );
736 // Loop through these rows
737 // Start at 1 because we want to leave the heading row untouched
738 for ( var j = 0; j < tableRows.length; j++ ) {
739 // Check if j is even, and apply classes for both possible results
740 var oldClasses = tableRows[j].className.split(' ');
741 var newClassName = '';
742 for ( var k = 0; k < oldClasses.length; k++ ) {
743 if ( oldClasses[k] != '' && oldClasses[k] != 'even' && oldClasses[k] != 'odd' ) {
744 newClassName += oldClasses[k] + ' ';
745 }
746 }
747 tableRows[j].className = newClassName + ( j % 2 == 0 ? 'even' : 'odd' );
748 }
749 }
750 },
751
752 /*
753 * Skins
754 */
755
756 /* Functions */
757
758 'changeText': function( el, newText ) {
759 // Safari work around
760 if ( el.innerText ) {
761 el.innerText = newText;
762 } else if ( el.firstChild && el.firstChild.nodeValue ) {
763 el.firstChild.nodeValue = newText;
764 }
765 },
766 'escapeQuotes': function( text ) {
767 var re = new RegExp( '\'', 'g' );
768 text = text.replace( re, '\\\'' );
769 re = new RegExp( '\\n', 'g' );
770 text = text.replace( re, '\\n' );
771 return escapeQuotesHTML( text );
772 },
773 'escapeQuotesHTML': function( text ) {
774 var re = new RegExp( '&', 'g' );
775 text = text.replace( re, '&amp;' );
776 re = new RegExp( '\'', 'g' );
777 text = text.replace( re, '&quot;' );
778 re = new RegExp( '<', 'g' );
779 text = text.replace( re, '&lt;' );
780 re = new RegExp( '>', 'g' );
781 text = text.replace( re, '&gt;' );
782 return text;
783 },
784 /**
785 * Add a link to one of the portlet menus on the page, including:
786 *
787 * p-cactions: Content actions (shown as tabs above the main content in Monobook)
788 * p-personal: Personal tools (shown at the top right of the page in Monobook)
789 * p-navigation: Navigation
790 * p-tb: Toolbox
791 *
792 * This function exists for the convenience of custom JS authors. All
793 * but the first three parameters are optional, though providing at
794 * least an id and a tooltip is recommended.
795 *
796 * By default the new link will be added to the end of the list. To
797 * add the link before a given existing item, pass the DOM node of
798 * that item (easily obtained with document.getElementById()) as the
799 * nextnode parameter; to add the link _after_ an existing item, pass
800 * the node's nextSibling instead.
801 *
802 * @param String portlet -- id of the target portlet ("p-cactions", "p-personal", "p-navigation" or "p-tb")
803 * @param String href -- link URL
804 * @param String text -- link text (will be automatically lowercased by CSS for p-cactions in Monobook)
805 * @param String id -- id of the new item, should be unique and preferably have the appropriate prefix ("ca-", "pt-", "n-" or "t-")
806 * @param String tooltip -- text to show when hovering over the link, without accesskey suffix
807 * @param String accesskey -- accesskey to activate this link (one character, try to avoid conflicts)
808 * @param Node nextnode -- the DOM node before which the new item should be added, should be another item in the same list
809 *
810 * @return Node -- the DOM node of the new item (an LI element) or null
811 */
812 'addPortletLink': function( portlet, href, text, id, tooltip, accesskey, nextnode ) {
813 var root = document.getElementById( portlet );
814 if ( !root ) {
815 return null;
816 }
817 var uls = root.getElementsByTagName( 'ul' );
818 var node;
819 if ( uls.length > 0 ) {
820 node = uls[0];
821 } else {
822 node = document.createElement( 'ul' );
823 var lastElementChild = null;
824 for ( var i = 0; i < root.childNodes.length; ++i ) { /* get root.lastElementChild */
825 if ( root.childNodes[i].nodeType == 1 ) {
826 lastElementChild = root.childNodes[i];
827 }
828 }
829 if ( lastElementChild && lastElementChild.nodeName.match( /div/i ) ) {
830 /* Insert into the menu divs */
831 lastElementChild.appendChild( node );
832 } else {
833 root.appendChild( node );
834 }
835 }
836 if ( !node ) {
837 return null;
838 }
839 // unhide portlet if it was hidden before
840 root.className = root.className.replace( /(^| )emptyPortlet( |$)/, '$2' );
841 var span = document.createElement( 'span' );
842 span.appendChild( document.createTextNode( text ) );
843 var link = document.createElement( 'a' );
844 link.appendChild( span );
845 link.href = href;
846 var item = document.createElement( 'li' );
847 item.appendChild( link );
848 if ( id ) {
849 item.id = id;
850 }
851 if ( accesskey ) {
852 link.setAttribute( 'accesskey', accesskey );
853 tooltip += ' [' + accesskey + ']';
854 }
855 if ( tooltip ) {
856 link.setAttribute( 'title', tooltip );
857 }
858 if ( accesskey && tooltip ) {
859 updateTooltipAccessKeys( new Array( link ) );
860 }
861 if ( nextnode && nextnode.parentNode == node ) {
862 node.insertBefore( item, nextnode );
863 } else {
864 node.appendChild( item ); // IE compatibility (?)
865 }
866 return item;
867 },
868 /**
869 * Add a cute little box at the top of the screen to inform the user of
870 * something, replacing any preexisting message.
871 *
872 * @param String -or- Dom Object message HTML to be put inside the right div
873 * @param String className Used in adding a class; should be different for each
874 * call to allow CSS/JS to hide different boxes. null = no class used.
875 * @return Boolean True on success, false on failure
876 */
877 'jsMsg': function( message, className ) {
878 if ( !document.getElementById ) {
879 return false;
880 }
881 // We special-case skin structures provided by the software. Skins that
882 // choose to abandon or significantly modify our formatting can just define
883 // an mw-js-message div to start with.
884 var messageDiv = document.getElementById( 'mw-js-message' );
885 if ( !messageDiv ) {
886 messageDiv = document.createElement( 'div' );
887 if ( document.getElementById( 'column-content' )
888 && document.getElementById( 'content' ) ) {
889 // MonoBook, presumably
890 document.getElementById( 'content' ).insertBefore(
891 messageDiv,
892 document.getElementById( 'content' ).firstChild
893 );
894 } else if ( document.getElementById( 'content' )
895 && document.getElementById( 'article' ) ) {
896 // Non-Monobook but still recognizable (old-style)
897 document.getElementById( 'article').insertBefore(
898 messageDiv,
899 document.getElementById( 'article' ).firstChild
900 );
901 } else {
902 return false;
903 }
904 }
905 messageDiv.setAttribute( 'id', 'mw-js-message' );
906 messageDiv.style.display = 'block';
907 if( className ) {
908 messageDiv.setAttribute( 'class', 'mw-js-message-' + className );
909 }
910 if ( typeof message === 'object' ) {
911 while ( messageDiv.hasChildNodes() ) { // Remove old content
912 messageDiv.removeChild( messageDiv.firstChild );
913 }
914 messageDiv.appendChild( message ); // Append new content
915 } else {
916 messageDiv.innerHTML = message;
917 }
918 return true;
919 },
920 /**
921 * Inject a cute little progress spinner after the specified element
922 *
923 * @param element Element to inject after
924 * @param id Identifier string (for use with removeSpinner(), below)
925 */
926 'injectSpinner': function( element, id ) {
927 var spinner = document.createElement( 'img' );
928 spinner.id = 'mw-spinner-' + id;
929 spinner.src = mw.legacy.stylepath + '/common/images/spinner.gif';
930 spinner.alt = spinner.title = '...';
931 if( element.nextSibling ) {
932 element.parentNode.insertBefore( spinner, element.nextSibling );
933 } else {
934 element.parentNode.appendChild( spinner );
935 }
936 },
937 /**
938 * Remove a progress spinner added with injectSpinner()
939 *
940 * @param id Identifier string
941 */
942 'removeSpinner': function( id ) {
943 var spinner = document.getElementById( 'mw-spinner-' + id );
944 if( spinner ) {
945 spinner.parentNode.removeChild( spinner );
946 }
947 },
948
949 /*
950 * DOM manipulation and traversal
951 */
952
953 /* Functions */
954
955 'getInnerText': function( el ) {
956 if ( typeof el == 'string' ) {
957 return el;
958 }
959 if ( typeof el == 'undefined' ) {
960 return el;
961 }
962 if ( el.textContent ) {
963 return el.textContent; // not needed but it is faster
964 }
965 if ( el.innerText ) {
966 return el.innerText; // IE doesn't have textContent
967 }
968 var str = '';
969 var cs = el.childNodes;
970 var l = cs.length;
971 for ( var i = 0; i < l; i++ ) {
972 switch ( cs[i].nodeType ) {
973 case 1: // ELEMENT_NODE
974 str += mw.legacy.ts_getInnerText( cs[i] );
975 break;
976 case 3: // TEXT_NODE
977 str += cs[i].nodeValue;
978 break;
979 }
980 }
981 return str;
982 },
983 /**
984 * Written by Jonathan Snook, http://www.snook.ca/jonathan
985 * Add-ons by Robert Nyman, http://www.robertnyman.com
986 * Author says "The credit comment is all it takes, no license. Go crazy with it!:-)"
987 * From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
988 */
989 'getElementsByClassName': function( oElm, strTagName, oClassNames ) {
990 var arrReturnElements = new Array();
991 if ( typeof( oElm.getElementsByClassName ) == 'function' ) {
992 /* Use a native implementation where possible FF3, Saf3.2, Opera 9.5 */
993 var arrNativeReturn = oElm.getElementsByClassName( oClassNames );
994 if ( strTagName == '*' ) {
995 return arrNativeReturn;
996 }
997 for ( var h = 0; h < arrNativeReturn.length; h++ ) {
998 if( arrNativeReturn[h].tagName.toLowerCase() == strTagName.toLowerCase() ) {
999 arrReturnElements[arrReturnElements.length] = arrNativeReturn[h];
1000 }
1001 }
1002 return arrReturnElements;
1003 }
1004 var arrElements = ( strTagName == '*' && oElm.all ) ? oElm.all : oElm.getElementsByTagName( strTagName );
1005 var arrRegExpClassNames = new Array();
1006 if( typeof oClassNames == 'object' ) {
1007 for( var i = 0; i < oClassNames.length; i++ ) {
1008 arrRegExpClassNames[arrRegExpClassNames.length] =
1009 new RegExp('(^|\\s)' + oClassNames[i].replace(/\-/g, '\\-') + '(\\s|$)');
1010 }
1011 } else {
1012 arrRegExpClassNames[arrRegExpClassNames.length] =
1013 new RegExp('(^|\\s)' + oClassNames.replace(/\-/g, '\\-') + '(\\s|$)');
1014 }
1015 var oElement;
1016 var bMatchesAll;
1017 for( var j = 0; j < arrElements.length; j++ ) {
1018 oElement = arrElements[j];
1019 bMatchesAll = true;
1020 for( var k = 0; k < arrRegExpClassNames.length; k++ ) {
1021 if( !arrRegExpClassNames[k].test( oElement.className ) ) {
1022 bMatchesAll = false;
1023 break;
1024 }
1025 }
1026 if( bMatchesAll ) {
1027 arrReturnElements[arrReturnElements.length] = oElement;
1028 }
1029 }
1030 return ( arrReturnElements );
1031 },
1032 'redirectToFragment': function( fragment ) {
1033 var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
1034 if ( match ) {
1035 var webKitVersion = parseInt( match[1] );
1036 if ( webKitVersion < 420 ) {
1037 // Released Safari w/ WebKit 418.9.1 messes up horribly
1038 // Nightlies of 420+ are ok
1039 return;
1040 }
1041 }
1042 if ( window.location.hash == '' ) {
1043 window.location.hash = fragment;
1044 // Mozilla needs to wait until after load, otherwise the window doesn't
1045 // scroll. See <https://bugzilla.mozilla.org/show_bug.cgi?id=516293>.
1046 // There's no obvious way to detect this programmatically, so we use
1047 // version-testing. If Firefox fixes the bug, they'll jump twice, but
1048 // better twice than not at all, so make the fix hit future versions as
1049 // well.
1050 if ( mw.legacy.is_gecko ) {
1051 addOnloadHook(function() {
1052 if ( window.location.hash == fragment ) {
1053 window.location.hash = fragment;
1054 }
1055 });
1056 }
1057 }
1058 }
1059 } );
1060
1061 /* Initialization */
1062
1063 $( document ).ready( function() {
1064 if ( wgBreakFrames ) {
1065 // Un-trap us from framesets
1066 if ( window.top != window ) {
1067 window.top.location = window.location;
1068 }
1069 }
1070 // Special stylesheet links for Monobook only (see bug 14717)
1071 if ( typeof stylepath != 'undefined' && skin == 'monobook' ) {
1072 if ( mw.legacy.opera6_bugs ) {
1073 importStylesheetURI( stylepath + '/' + skin + '/Opera6Fixes.css' );
1074 } else if ( mw.legacy.opera7_bugs ) {
1075 importStylesheetURI( stylepath + '/' + skin + '/Opera7Fixes.css' );
1076 } else if ( mw.legacy.opera95_bugs ) {
1077 importStylesheetURI( stylepath + '/' + skin + '/Opera9Fixes.css' );
1078 } else if ( mw.legacy.ff2_bugs ) {
1079 importStylesheetURI( stylepath + '/' + skin + '/FF2Fixes.css' );
1080 }
1081 }
1082 if ( mw.legacy.ie6_bugs ) {
1083 importScriptURI( mw.legacy.stylepath + '/common/IEFixes.js' );
1084 }
1085 // NOTE: All skins should call runOnloadHook() at the end of html output, so this should be redundant - it's here
1086 // just in case
1087 runOnloadHook();
1088 } );
1089
1090 } )( jQuery, mediaWiki );