wikibits.js 30 KB

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