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