X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=skins%2Fcommon%2Fwikibits.js;h=b81196f33e3b3aced9931b753ec74c354180b01f;hb=1a52c36bf3feab3d7079554ee28f5daa48f658b0;hp=7889c396a38361542dd23d07c485aa73b714fbfe;hpb=a27abdde8b50318652c7e5758eed4ef13c3449b8;p=lhc%2Fweb%2Fwiklou.git diff --git a/skins/common/wikibits.js b/skins/common/wikibits.js index 7889c396a3..b81196f33e 100644 --- a/skins/common/wikibits.js +++ b/skins/common/wikibits.js @@ -1,17 +1,28 @@ // MediaWiki JavaScript support functions var clientPC = navigator.userAgent.toLowerCase(); // Get client info -var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) - && (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1)); -var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1)); -var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled )); -// For accesskeys -var is_ff2_win = (clientPC.indexOf('firefox/2')!=-1 || clientPC.indexOf('minefield/3')!=-1) && clientPC.indexOf('windows')!=-1; -var is_ff2_x11 = (clientPC.indexOf('firefox/2')!=-1 || clientPC.indexOf('minefield/3')!=-1) && clientPC.indexOf('x11')!=-1; +var is_gecko = /gecko/.test( clientPC ) && + !/khtml|spoofer|netscape\/7\.0/.test(clientPC); +var webkit_match = clientPC.match(/applewebkit\/(\d+)/); +if (webkit_match) { + var is_safari = clientPC.indexOf('applewebkit') != -1 && + clientPC.indexOf('spoofer') == -1; + var is_safari_win = is_safari && clientPC.indexOf('windows') != -1; + var webkit_version = parseInt(webkit_match[1]); +} +var is_khtml = navigator.vendor == 'KDE' || + ( document.childNodes && !document.all && !navigator.taintEnabled ); +// For accesskeys; note that FF3+ is included here! +var is_ff2 = /firefox\/[2-9]|minefield\/3/.test( clientPC ); +var is_ff2_ = /firefox\/2/.test( clientPC ); +// These aren't used here, but some custom scripts rely on them +var is_ff2_win = is_ff2 && clientPC.indexOf('windows') != -1; +var is_ff2_x11 = is_ff2 && clientPC.indexOf('x11') != -1; if (clientPC.indexOf('opera') != -1) { var is_opera = true; - var is_opera_preseven = (window.opera && !document.childNodes); - var is_opera_seven = (window.opera && document.childNodes); + var is_opera_preseven = window.opera && !document.childNodes; + var is_opera_seven = window.opera && document.childNodes; + var is_opera_95 = /opera\/(9.[5-9]|[1-9][0-9])/.test( clientPC ); } // Global external objects used by this script. @@ -26,25 +37,68 @@ if (!window.onloadFuncts) { function addOnloadHook(hookFunct) { // Allows add-on scripts to add onload functions - onloadFuncts[onloadFuncts.length] = hookFunct; -} - -function hookEvent(hookName, hookFunct) { - if (window.addEventListener) { - window.addEventListener(hookName, hookFunct, false); - } else if (window.attachEvent) { - window.attachEvent("on" + hookName, hookFunct); + if(!doneOnloadHook) { + onloadFuncts[onloadFuncts.length] = hookFunct; + } else { + hookFunct(); // bug in MSIE script loading } } -// document.write special stylesheet links +function hookEvent(hookName, hookFunct) { + addHandler(window, hookName, hookFunct); +} + +function importScript(page) { + // TODO: might want to introduce a utility function to match wfUrlencode() in PHP + var uri = wgScript + '?title=' + + encodeURIComponent(page.replace(/ /g,'_')).replace(/%2F/ig,'/').replace(/%3A/ig,':') + + '&action=raw&ctype=text/javascript'; + return importScriptURI(uri); +} + +var loadedScripts = {}; // included-scripts tracker +function importScriptURI(url) { + if (loadedScripts[url]) { + return null; + } + loadedScripts[url] = true; + var s = document.createElement('script'); + s.setAttribute('src',url); + s.setAttribute('type','text/javascript'); + document.getElementsByTagName('head')[0].appendChild(s); + return s; +} + +function importStylesheet(page) { + return importStylesheetURI(wgScript + '?action=raw&ctype=text/css&title=' + encodeURIComponent(page.replace(/ /g,'_'))); +} + +function importStylesheetURI(url) { + return document.createStyleSheet ? document.createStyleSheet(url) : appendCSS('@import "' + url + '";'); +} + +function appendCSS(text) { + var s = document.createElement('style'); + s.type = 'text/css'; + s.rel = 'stylesheet'; + if (s.styleSheet) s.styleSheet.cssText = text //IE + else s.appendChild(document.createTextNode(text + '')) //Safari sometimes borks on null + document.getElementsByTagName('head')[0].appendChild(s); + return s; +} + +// special stylesheet links if (typeof stylepath != 'undefined' && typeof skin != 'undefined') { if (is_opera_preseven) { - document.write(''); - } else if (is_opera_seven) { - document.write(''); + importStylesheetURI(stylepath+'/'+skin+'/Opera6Fixes.css'); + } else if (is_opera_seven && !is_opera_95) { + importStylesheetURI(stylepath+'/'+skin+'/Opera7Fixes.css'); + } else if (is_opera_95) { + importStylesheetURI(stylepath+'/'+skin+'/Opera9Fixes.css'); } else if (is_khtml) { - document.write(''); + importStylesheetURI(stylepath+'/'+skin+'/KHTMLFixes.css'); + } else if (is_ff2_) { + importStylesheetURI(stylepath+'/'+skin+'/FF2Fixes.css'); } } @@ -55,226 +109,14 @@ if (wgBreakFrames) { } } -// for enhanced RecentChanges -function toggleVisibility(_levelId, _otherId, _linkId) { - var thisLevel = document.getElementById(_levelId); - var otherLevel = document.getElementById(_otherId); - var linkLevel = document.getElementById(_linkId); - if (thisLevel.style.display == 'none') { - thisLevel.style.display = 'block'; - otherLevel.style.display = 'none'; - linkLevel.style.display = 'inline'; - } else { - thisLevel.style.display = 'none'; - otherLevel.style.display = 'inline'; - linkLevel.style.display = 'none'; - } -} - -function historyRadios(parent) { - var inputs = parent.getElementsByTagName('input'); - var radios = []; - for (var i = 0; i < inputs.length; i++) { - if (inputs[i].name == "diff" || inputs[i].name == "oldid") { - radios[radios.length] = inputs[i]; - } - } - return radios; -} - -// check selection and tweak visibility/class onclick -function diffcheck() { - var dli = false; // the li where the diff radio is checked - var oli = false; // the li where the oldid radio is checked - var hf = document.getElementById('pagehistory'); - if (!hf) { - return true; - } - var lis = hf.getElementsByTagName('li'); - for (var i=0;i= 0) ? "-" : "+") + ((tzHour < 10) ? "0" : "") + tzHour + ((tzMin < 10) ? "0" : "") + tzMin; - if (tz != tzString) { - var junk = msg.split('$1'); - document.write(junk[0] + "UTC" + tzString + junk[1]); - } -} - -function unhidetzbutton() { - var tzb = document.getElementById('guesstimezonebutton'); - if (tzb) { - tzb.style.display = 'inline'; - } -} - -// in [-]HH:MM format... -// won't yet work with non-even tzs -function fetchTimezone() { - // FIXME: work around Safari bug - var localclock = new Date(); - // returns negative offset from GMT in minutes - var tzRaw = localclock.getTimezoneOffset(); - var tzHour = Math.floor( Math.abs(tzRaw) / 60); - var tzMin = Math.abs(tzRaw) % 60; - var tzString = ((tzRaw >= 0) ? "-" : "") + ((tzHour < 10) ? "0" : "") + tzHour + - ":" + ((tzMin < 10) ? "0" : "") + tzMin; - return tzString; -} - -function guessTimezone(box) { - document.getElementsByName("wpHourDiff")[0].value = fetchTimezone(); -} - function showTocToggle() { if (document.createTextNode) { // Uses DOM calls to avoid document.write + XHTML issues var linkHolder = document.getElementById('toctitle'); - if (!linkHolder) { + var existingLink = document.getElementById('togglelink'); + if (!linkHolder || existingLink) { + // Don't add the toggle link twice return; } @@ -328,64 +170,6 @@ function toggleToc() { var mwEditButtons = []; var mwCustomEditButtons = []; // eg to add in MediaWiki:Common.js -// this function generates the actual toolbar buttons with localized text -// we use it to avoid creating the toolbar where javascript is not enabled -function addButton(imageId, imageFile, speedTip, tagOpen, tagClose, sampleText) { - // Don't generate buttons for browsers which don't fully - // support it. - mwEditButtons[mwEditButtons.length] = - {"imageId": imageId, - "imageFile": imageFile, - "speedTip": speedTip, - "tagOpen": tagOpen, - "tagClose": tagClose, - "sampleText": sampleText}; -} - -// this function generates the actual toolbar buttons with localized text -// we use it to avoid creating the toolbar where javascript is not enabled -function mwInsertEditButton(parent, item) { - var image = document.createElement("img"); - image.width = 23; - image.height = 22; - image.className = "mw-toolbar-editbutton"; - if (item.imageId) image.id = item.imageId; - image.src = item.imageFile; - image.border = 0; - image.alt = item.speedTip; - image.title = item.speedTip; - image.style.cursor = "pointer"; - image.onclick = function() { - insertTags(item.tagOpen, item.tagClose, item.sampleText); - return false; - }; - - parent.appendChild(image); - return true; -} - -function mwSetupToolbar() { - var toolbar = document.getElementById('toolbar'); - if (!toolbar) { return false; } - - var textbox = document.getElementById('wpTextbox1'); - if (!textbox) { return false; } - - // Don't generate buttons for browsers which don't fully - // support it. - if (!document.selection && textbox.selectionStart === null) { - return false; - } - - for (var i = 0; i < mwEditButtons.length; i++) { - mwInsertEditButton(toolbar, mwEditButtons[i]); - } - for (var i = 0; i < mwCustomEditButtons.length; i++) { - mwInsertEditButton(toolbar, mwCustomEditButtons[i]); - } - return true; -} - function escapeQuotes(text) { var re = new RegExp("'","g"); text = text.replace(re,"\\'"); @@ -406,76 +190,6 @@ function escapeQuotesHTML(text) { return text; } -// apply tagOpen/tagClose to selection in textarea, -// use sampleText instead of selection if there is none -// copied and adapted from phpBB -function insertTags(tagOpen, tagClose, sampleText) { - var txtarea; - if (document.editform) { - txtarea = document.editform.wpTextbox1; - } else { - // some alternate form? take the first one we can find - var areas = document.getElementsByTagName('textarea'); - txtarea = areas[0]; - } - - // IE - if (document.selection && !is_gecko) { - var theSelection = document.selection.createRange().text; - if (!theSelection) { - theSelection=sampleText; - } - txtarea.focus(); - if (theSelection.charAt(theSelection.length - 1) == " ") { // exclude ending space char, if any - theSelection = theSelection.substring(0, theSelection.length - 1); - document.selection.createRange().text = tagOpen + theSelection + tagClose + " "; - } else { - document.selection.createRange().text = tagOpen + theSelection + tagClose; - } - - // Mozilla - } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { - var replaced = false; - var startPos = txtarea.selectionStart; - var endPos = txtarea.selectionEnd; - if (endPos-startPos) { - replaced = true; - } - var scrollTop = txtarea.scrollTop; - var myText = (txtarea.value).substring(startPos, endPos); - if (!myText) { - myText=sampleText; - } - var subst; - if (myText.charAt(myText.length - 1) == " ") { // exclude ending space char, if any - subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; - } else { - subst = tagOpen + myText + tagClose; - } - txtarea.value = txtarea.value.substring(0, startPos) + subst + - txtarea.value.substring(endPos, txtarea.value.length); - txtarea.focus(); - //set new selection - if (replaced) { - var cPos = startPos+(tagOpen.length+myText.length+tagClose.length); - txtarea.selectionStart = cPos; - txtarea.selectionEnd = cPos; - } else { - txtarea.selectionStart = startPos+tagOpen.length; - txtarea.selectionEnd = startPos+tagOpen.length+myText.length; - } - txtarea.scrollTop = scrollTop; - - // All other browsers get no toolbar. - // There was previously support for a crippled "help" - // bar, but that caused more problems than it solved. - } - // reposition cursor if possible - if (txtarea.createTextRange) { - txtarea.caretPos = document.selection.createRange().duplicate(); - } -} - /** * Set the accesskey prefix based on browser detection. @@ -483,14 +197,16 @@ function insertTags(tagOpen, tagClose, sampleText) { var tooltipAccessKeyPrefix = 'alt-'; if (is_opera) { tooltipAccessKeyPrefix = 'shift-esc-'; -} else if (is_safari - || navigator.userAgent.toLowerCase().indexOf('mac') != -1 - || navigator.userAgent.toLowerCase().indexOf('konqueror') != -1 ) { +} else if (!is_safari_win && is_safari && webkit_version > 526) { + tooltipAccessKeyPrefix = 'ctrl-alt-'; +} else if (!is_safari_win && (is_safari + || clientPC.indexOf('mac') != -1 + || clientPC.indexOf('konqueror') != -1 )) { tooltipAccessKeyPrefix = 'ctrl-'; -} else if (is_ff2_x11 || is_ff2_win) { +} else if (is_ff2) { tooltipAccessKeyPrefix = 'alt-shift-'; } -var tooltipAccessKeyRegexp = /\[(ctrl-)?(alt-)?(shift-)?(esc-)?.\]$/; +var tooltipAccessKeyRegexp = /\[(ctrl-)?(alt-)?(shift-)?(esc-)?(.)\]$/; /** * Add the appropriate prefix to the accesskey shown in the tooltip. @@ -515,10 +231,9 @@ function updateTooltipAccessKeys( nodeList ) { for ( var i = 0; i < nodeList.length; i++ ) { var element = nodeList[i]; var tip = element.getAttribute("title"); - var key = element.getAttribute("accesskey"); - if ( key && tooltipAccessKeyRegexp.exec(tip) ) { + if ( tip && tooltipAccessKeyRegexp.exec(tip) ) { tip = tip.replace(tooltipAccessKeyRegexp, - "["+tooltipAccessKeyPrefix+key+"]"); + "["+tooltipAccessKeyPrefix+"$5]"); element.setAttribute("title", tip ); } } @@ -585,6 +300,28 @@ function addPortletLink(portlet, href, text, id, tooltip, accesskey, nextnode) { return item; } +function getInnerText(el) { + if (typeof el == "string") return el; + if (typeof el == "undefined") { return el }; + if (el.textContent) return el.textContent; // not needed but it is faster + if (el.innerText) return el.innerText; // IE doesn't have textContent + var str = ""; + + var cs = el.childNodes; + var l = cs.length; + for (var i = 0; i < l; i++) { + switch (cs[i].nodeType) { + case 1: //ELEMENT_NODE + str += ts_getInnerText(cs[i]); + break; + case 3: //TEXT_NODE + str += cs[i].nodeValue; + break; + } + } + return str; +} + /** * Set up accesskeys/tooltips from the deprecated ta array. If doId @@ -604,8 +341,7 @@ function akeytt( doId ) { // the original. var ta; if ( doId ) { - ta = new Array; - ta[doId] = window.ta[doId]; + ta = [doId]; } else { ta = window.ta; } @@ -645,53 +381,6 @@ function akeytt( doId ) { } } -function setupRightClickEdit() { - if (document.getElementsByTagName) { - var spans = document.getElementsByTagName('span'); - for (var i = 0; i < spans.length; i++) { - var el = spans[i]; - if(el.className == 'editsection') { - addRightClickEditHandler(el); - } - } - } -} - -function addRightClickEditHandler(el) { - for (var i = 0; i < el.childNodes.length; i++) { - var link = el.childNodes[i]; - if (link.nodeType == 1 && link.nodeName.toLowerCase() == 'a') { - var editHref = link.getAttribute('href'); - // find the enclosing (parent) header - var prev = el.parentNode; - if (prev && prev.nodeType == 1 && - prev.nodeName.match(/^[Hh][1-6]$/)) { - prev.oncontextmenu = function(e) { - if (!e) { e = window.event; } - // e is now the event in all browsers - var targ; - if (e.target) { targ = e.target; } - else if (e.srcElement) { targ = e.srcElement; } - if (targ.nodeType == 3) { // defeat Safari bug - targ = targ.parentNode; - } - // targ is now the target element - - // We don't want to deprive the noble reader of a context menu - // for the section edit link, do we? (Might want to extend this - // to all 's?) - if (targ.nodeName.toLowerCase() != 'a' - || targ.parentNode.className != 'editsection') { - document.location = editHref; - return false; - } - return true; - }; - } - } - } -} - var checkboxes; var lastCheckbox; @@ -713,8 +402,10 @@ function addCheckboxClickHandlers(inputs, start) { var cb = inputs[i]; if ( !cb.type || cb.type.toLowerCase() != 'checkbox' ) continue; - cb.index = checkboxes.push(cb) - 1; - cb.onmouseup = checkboxMouseupHandler; + var end = checkboxes.length; + checkboxes[end] = cb; + cb.index = end; + cb.onclick = checkboxClickHandler; } if ( finish < inputs.length ) { @@ -724,7 +415,7 @@ function addCheckboxClickHandlers(inputs, start) { } } -function checkboxMouseupHandler(e) { +function checkboxClickHandler(e) { if (typeof e == 'undefined') { e = window.event; } @@ -732,10 +423,7 @@ function checkboxMouseupHandler(e) { lastCheckbox = this.index; return true; } - var endState = !this.checked; - if ( is_opera ) { // opera has already toggled the checkbox by this point - endState = !endState; - } + var endState = this.checked; var start, finish; if ( this.index < lastCheckbox ) { start = this.index + 1; @@ -746,6 +434,8 @@ function checkboxMouseupHandler(e) { } for (var i = start; i <= finish; ++i ) { checkboxes[i].checked = endState; + if( i > start && typeof checkboxes[i].onchange == 'function' ) + checkboxes[i].onchange(); // fire triggers } lastCheckbox = this.index; return true; @@ -767,153 +457,6 @@ function toggle_element_check(ida,idb) { document.getElementById(idb).checked=false; } -function fillDestFilename(id) { - if (!document.getElementById) { - return; - } - var path = document.getElementById(id).value; - // Find trailing part - var slash = path.lastIndexOf('/'); - var backslash = path.lastIndexOf('\\'); - var fname; - if (slash == -1 && backslash == -1) { - fname = path; - } else if (slash > backslash) { - fname = path.substring(slash+1, 10000); - } else { - fname = path.substring(backslash+1, 10000); - } - - // Capitalise first letter and replace spaces by underscores - fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace(/ /g, '_'); - - // Output result - var destFile = document.getElementById('wpDestFile'); - if (destFile) { - destFile.value = fname; - } -} - - -function considerChangingExpiryFocus() { - if (!document.getElementById) { - return; - } - var drop = document.getElementById('wpBlockExpiry'); - if (!drop) { - return; - } - var field = document.getElementById('wpBlockOther'); - if (!field) { - return; - } - var opt = drop.value; - if (opt == 'other') { - field.style.display = ''; - } else { - field.style.display = 'none'; - } -} - -function scrollEditBox() { - var editBoxEl = document.getElementById("wpTextbox1"); - var scrollTopEl = document.getElementById("wpScrolltop"); - var editFormEl = document.getElementById("editform"); - - if (editBoxEl && scrollTopEl) { - if (scrollTopEl.value) { editBoxEl.scrollTop = scrollTopEl.value; } - editFormEl.onsubmit = function() { - document.getElementById("wpScrolltop").value = document.getElementById("wpTextbox1").scrollTop; - }; - } -} - -hookEvent("load", scrollEditBox); - -var allmessages_nodelist = false; -var allmessages_modified = false; -var allmessages_timeout = false; -var allmessages_running = false; - -function allmessagesmodified() { - allmessages_modified = !allmessages_modified; - allmessagesfilter(); -} - -function allmessagesfilter() { - if ( allmessages_timeout ) - window.clearTimeout( allmessages_timeout ); - - if ( !allmessages_running ) - allmessages_timeout = window.setTimeout( 'allmessagesfilter_do();', 500 ); -} - -function allmessagesfilter_do() { - if ( !allmessages_nodelist ) - return; - - var text = document.getElementById('allmessagesinput').value; - var nodef = allmessages_modified; - - allmessages_running = true; - - for ( var name in allmessages_nodelist ) { - var nodes = allmessages_nodelist[name]; - var display = ( name.indexOf( text ) == -1 ? 'none' : '' ); - - for ( var i = 0; i < nodes.length; i++) - nodes[i].style.display = - ( nodes[i].className == "def" && nodef - ? 'none' : display ); - } - - if ( text != document.getElementById('allmessagesinput').value || - nodef != allmessages_modified ) - allmessagesfilter_do(); // repeat - - allmessages_running = false; -} - -function allmessagesfilter_init() { - if ( allmessages_nodelist ) - return; - - var nodelist = new Array(); - var templist = new Array(); - - var table = document.getElementById('allmessagestable'); - if ( !table ) return; - - var rows = document.getElementsByTagName('tr'); - for ( var i = 0; i < rows.length; i++ ) { - var id = rows[i].getAttribute('id') - if ( id && id.substring(0,16) != 'sp-allmessages-r' ) continue; - templist[ id ] = rows[i]; - } - - var spans = table.getElementsByTagName('span'); - for ( var i = 0; i < spans.length; i++ ) { - var id = spans[i].getAttribute('id') - if ( id && id.substring(0,17) != 'sp-allmessages-i-' ) continue; - if ( !spans[i].firstChild || spans[i].firstChild.nodeType != 3 ) continue; - - var nodes = new Array(); - var row1 = templist[ id.replace('i', 'r1') ]; - var row2 = templist[ id.replace('i', 'r2') ]; - - if ( row1 ) nodes[nodes.length] = row1; - if ( row2 ) nodes[nodes.length] = row2; - nodelist[ spans[i].firstChild.nodeValue ] = nodes; - } - - var k = document.getElementById('allmessagesfilter'); - if (k) { k.style.display = ''; } - - allmessages_nodelist = nodelist; -} - -hookEvent( "load", allmessagesfilter_init ); - /* Written by Jonathan Snook, http://www.snook.ca/jonathan Add-ons by Robert Nyman, http://www.robertnyman.com @@ -921,16 +464,29 @@ hookEvent( "load", allmessagesfilter_init ); From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/ */ function getElementsByClassName(oElm, strTagName, oClassNames){ - var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName); var arrReturnElements = new Array(); + if ( typeof( oElm.getElementsByClassName ) == "function" ) { + /* Use a native implementation where possible FF3, Saf3.2, Opera 9.5 */ + var arrNativeReturn = oElm.getElementsByClassName( oClassNames ); + if ( strTagName == "*" ) + return arrNativeReturn; + for ( var h=0; h < arrNativeReturn.length; h++ ) { + if( arrNativeReturn[h].tagName.toLowerCase() == strTagName.toLowerCase() ) + arrReturnElements[arrReturnElements.length] = arrNativeReturn[h]; + } + return arrReturnElements; + } + var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName); var arrRegExpClassNames = new Array(); if(typeof oClassNames == "object"){ for(var i=0; i'; + cell.innerHTML += '  ' + + '' + + '' + + '↓'; } } if (ts_alternate_row_colors) { @@ -1029,24 +592,7 @@ function ts_makeSortable(table) { } function ts_getInnerText(el) { - if (typeof el == "string") return el; - if (typeof el == "undefined") { return el }; - if (el.innerText) return el.innerText; // Not needed but it is faster - var str = ""; - - var cs = el.childNodes; - var l = cs.length; - for (var i = 0; i < l; i++) { - switch (cs[i].nodeType) { - case 1: //ELEMENT_NODE - str += ts_getInnerText(cs[i]); - break; - case 3: //TEXT_NODE - str += cs[i].nodeValue; - break; - } - } - return str; + return getInnerText( el ); } function ts_resortTable(lnk) { @@ -1062,9 +608,14 @@ function ts_resortTable(lnk) { table = table.parentNode; if (!table) return; - // Work out a type for the column if (table.rows.length <= 1) return; + // Generate the number transform table if it's not done already + if (ts_number_transform_table == null) { + ts_initTransformTable(); + } + + // Work out a type for the column // Skip the first row if that's where the headings are var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1); @@ -1077,39 +628,52 @@ function ts_resortTable(lnk) { } } - sortfn = ts_sort_caseinsensitive; - if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/)) - sortfn = ts_sort_date; - if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/)) - sortfn = ts_sort_date; - if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/)) - sortfn = ts_sort_date; - if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro - sortfn = ts_sort_currency; - if (itm.match(/^[\d.,]+\%?$/)) - sortfn = ts_sort_numeric; + // TODO: bug 8226, localised date formats + var sortfn = ts_sort_generic; + var preprocessor = ts_toLowerCase; + if (/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/.test(itm)) { + preprocessor = ts_dateToSortKey; + } else if (/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/.test(itm)) { + preprocessor = ts_dateToSortKey; + } else if (/^\d\d[\/.-]\d\d[\/.-]\d\d$/.test(itm)) { + preprocessor = ts_dateToSortKey; + // pound dollar euro yen currency cents + } else if (/(^[\u00a3$\u20ac\u00a4\u00a5]|\u00a2$)/.test(itm)) { + preprocessor = ts_currencyToSortKey; + } else if (ts_number_regex.test(itm)) { + preprocessor = ts_parseFloat; + } var reverse = (span.getAttribute("sortdir") == 'down'); var newRows = new Array(); + var staticRows = new Array(); for (var j = rowStart; j < table.rows.length; j++) { var row = table.rows[j]; - var keyText = ts_getInnerText(row.cells[column]); - var oldIndex = (reverse ? -j : j); + if((" "+row.className+" ").indexOf(" unsortable ") < 0) { + var keyText = ts_getInnerText(row.cells[column]); + var oldIndex = (reverse ? -j : j); + var preprocessed = preprocessor( keyText ); - newRows[newRows.length] = new Array(row, keyText, oldIndex); + newRows[newRows.length] = new Array(row, preprocessed, oldIndex); + } else staticRows[staticRows.length] = new Array(row, false, j-rowStart); } newRows.sort(sortfn); var arrowHTML; if (reverse) { - arrowHTML = '↓'; - newRows.reverse(); - span.setAttribute('sortdir','up'); + arrowHTML = '↓'; + newRows.reverse(); + span.setAttribute('sortdir','up'); } else { - arrowHTML = '↑'; - span.setAttribute('sortdir','down'); + arrowHTML = '↑'; + span.setAttribute('sortdir','down'); + } + + for (var i = 0; i < staticRows.length; i++) { + var row = staticRows[i]; + newRows.splice(row[2], 0, row); } // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones @@ -1131,7 +695,66 @@ function ts_resortTable(lnk) { } span.innerHTML = arrowHTML; - ts_alternate(table); + if (ts_alternate_row_colors) { + ts_alternate(table); + } +} + +function ts_initTransformTable() { + if ( typeof wgSeparatorTransformTable == "undefined" + || ( wgSeparatorTransformTable[0] == '' && wgDigitTransformTable[2] == '' ) ) + { + digitClass = "[0-9,.]"; + ts_number_transform_table = false; + } else { + ts_number_transform_table = {}; + // Unpack the transform table + // Separators + ascii = wgSeparatorTransformTable[0].split("\t"); + localised = wgSeparatorTransformTable[1].split("\t"); + for ( var i = 0; i < ascii.length; i++ ) { + ts_number_transform_table[localised[i]] = ascii[i]; + } + // Digits + ascii = wgDigitTransformTable[0].split("\t"); + localised = wgDigitTransformTable[1].split("\t"); + for ( var i = 0; i < ascii.length; i++ ) { + ts_number_transform_table[localised[i]] = ascii[i]; + } + + // Construct regex for number identification + digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '\\.']; + maxDigitLength = 1; + for ( var digit in ts_number_transform_table ) { + // Escape regex metacharacters + digits.push( + digit.replace( /[\\\\$\*\+\?\.\(\)\|\{\}\[\]\-]/, + function( s ) { return '\\' + s; } ) + ); + if (digit.length > maxDigitLength) { + maxDigitLength = digit.length; + } + } + if ( maxDigitLength > 1 ) { + digitClass = '[' + digits.join( '', digits ) + ']'; + } else { + digitClass = '(' + digits.join( '|', digits ) + ')'; + } + } + + // We allow a trailing percent sign, which we just strip. This works fine + // if percents and regular numbers aren't being mixed. + ts_number_regex = new RegExp( + "^(" + + "[+-]?[0-9][0-9,]*(\\.[0-9,]*)?(E[+-]?[0-9][0-9,]*)?" + // Fortran-style scientific + "|" + + "[+-]?" + digitClass + "+%?" + // Generic localised + ")$", "i" + ); +} + +function ts_toLowerCase( s ) { + return s.toLowerCase(); } function ts_dateToSortKey(date) { @@ -1175,38 +798,34 @@ function ts_dateToSortKey(date) { return "00000000"; } -function ts_parseFloat(num) { - if (!num) return 0; - num = parseFloat(num.replace(/,/, "")); - return (isNaN(num) ? 0 : num); -} - -function ts_sort_date(a,b) { - var aa = ts_dateToSortKey(a[1]); - var bb = ts_dateToSortKey(b[1]); - return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]); -} - -function ts_sort_currency(a,b) { - var aa = ts_parseFloat(a[1].replace(/[^0-9.]/g,'')); - var bb = ts_parseFloat(b[1].replace(/[^0-9.]/g,'')); - return (aa != bb ? aa - bb : a[2] - b[2]); -} +function ts_parseFloat( s ) { + if ( !s ) { + return 0; + } + if (ts_number_transform_table != false) { + var newNum = '', c; + + for ( var p = 0; p < s.length; p++ ) { + c = s.charAt( p ); + if (c in ts_number_transform_table) { + newNum += ts_number_transform_table[c]; + } else { + newNum += c; + } + } + s = newNum; + } -function ts_sort_numeric(a,b) { - var aa = ts_parseFloat(a[1]); - var bb = ts_parseFloat(b[1]); - return (aa != bb ? aa - bb : a[2] - b[2]); + num = parseFloat(s.replace(/,/g, "")); + return (isNaN(num) ? 0 : num); } -function ts_sort_caseinsensitive(a,b) { - var aa = a[1].toLowerCase(); - var bb = b[1].toLowerCase(); - return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]); +function ts_currencyToSortKey( s ) { + return ts_parseFloat(s.replace(/[^0-9.,]/g,'')); } -function ts_sort_default(a,b) { - return (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2]); +function ts_sort_generic(a, b) { + return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2]; } function ts_alternate(table) { @@ -1234,6 +853,92 @@ function ts_alternate(table) { /* * End of table sorting code */ + + +/** + * Add a cute little box at the top of the screen to inform the user of + * something, replacing any preexisting message. + * + * @param String -or- Dom Object message HTML to be put inside the right div + * @param String className Used in adding a class; should be different for each + * call to allow CSS/JS to hide different boxes. null = no class used. + * @return Boolean True on success, false on failure + */ +function jsMsg( message, className ) { + if ( !document.getElementById ) { + return false; + } + // We special-case skin structures provided by the software. Skins that + // choose to abandon or significantly modify our formatting can just define + // an mw-js-message div to start with. + var messageDiv = document.getElementById( 'mw-js-message' ); + if ( !messageDiv ) { + messageDiv = document.createElement( 'div' ); + if ( document.getElementById( 'column-content' ) + && document.getElementById( 'content' ) ) { + // MonoBook, presumably + document.getElementById( 'content' ).insertBefore( + messageDiv, + document.getElementById( 'content' ).firstChild + ); + } else if ( document.getElementById('content') + && document.getElementById( 'article' ) ) { + // Non-Monobook but still recognizable (old-style) + document.getElementById( 'article').insertBefore( + messageDiv, + document.getElementById( 'article' ).firstChild + ); + } else { + return false; + } + } + + messageDiv.setAttribute( 'id', 'mw-js-message' ); + messageDiv.style.display = 'block'; + if( className ) { + messageDiv.setAttribute( 'class', 'mw-js-message-'+className ); + } + + if (typeof message === 'object') { + while (messageDiv.hasChildNodes()) // Remove old content + messageDiv.removeChild(messageDiv.firstChild); + messageDiv.appendChild (message); // Append new content + } + else { + messageDiv.innerHTML = message; + } + return true; +} + +/** + * Inject a cute little progress spinner after the specified element + * + * @param element Element to inject after + * @param id Identifier string (for use with removeSpinner(), below) + */ +function injectSpinner( element, id ) { + var spinner = document.createElement( "img" ); + spinner.id = "mw-spinner-" + id; + spinner.src = stylepath + "/common/images/spinner.gif"; + spinner.alt = spinner.title = "..."; + if( element.nextSibling ) { + element.parentNode.insertBefore( spinner, element.nextSibling ); + } else { + element.parentNode.appendChild( spinner ); + } +} + +/** + * Remove a progress spinner added with injectSpinner() + * + * @param id Identifier string + */ +function removeSpinner( id ) { + var spinner = document.getElementById( "mw-spinner-" + id ); + if( spinner ) { + spinner.parentNode.removeChild( spinner ); + } +} function runOnloadHook() { // don't run anything below this for non-dom browsers @@ -1245,12 +950,8 @@ function runOnloadHook() { // might cause the function to terminate prematurely doneOnloadHook = true; - histrowinit(); - unhidetzbutton(); - tabbedprefs(); updateTooltipAccessKeys( null ); akeytt( null ); - scrollEditBox(); setupCheckboxShiftClick(); sortables_init(); @@ -1260,8 +961,45 @@ function runOnloadHook() { } } +/** + * Add an event handler to an element + * + * @param Element element Element to add handler to + * @param String attach Event to attach to + * @param callable handler Event handler callback + */ +function addHandler( element, attach, handler ) { + if( window.addEventListener ) { + element.addEventListener( attach, handler, false ); + } else if( window.attachEvent ) { + element.attachEvent( 'on' + attach, handler ); + } +} + +/** + * Add a click event handler to an element + * + * @param Element element Element to add handler to + * @param callable handler Event handler callback + */ +function addClickHandler( element, handler ) { + addHandler( element, 'click', handler ); +} + +/** + * Removes an event handler from an element + * + * @param Element element Element to remove handler from + * @param String remove Event to remove + * @param callable handler Event handler callback to remove + */ +function removeHandler( element, remove, handler ) { + if( window.removeEventListener ) { + element.removeEventListener( remove, handler, false ); + } else if( window.detachEvent ) { + element.detachEvent( 'on' + remove, handler ); + } +} //note: all skins should call runOnloadHook() at the end of html output, // so the below should be redundant. It's there just in case. hookEvent("load", runOnloadHook); - -hookEvent("load", mwSetupToolbar);