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