Rewrite of JS onload hook code; allows add-on scripts to run functions as soon as...
[lhc/web/wiklou.git] / skins / common / wikibits.js
1 // Wikipedia JavaScript support functions
2
3 var clientPC = navigator.userAgent.toLowerCase(); // Get client info
4 var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1)
5 && (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
6 var is_safari = ((clientPC.indexOf('AppleWebKit')!=-1) && (clientPC.indexOf('spoofer')==-1));
7 var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
8 if (clientPC.indexOf('opera')!=-1) {
9 var is_opera = true;
10 var is_opera_preseven = (window.opera && !document.childNodes);
11 var is_opera_seven = (window.opera && document.childNodes);
12 }
13
14 // add any onload functions in this hook (please don't hard-code any events in the xhtml source)
15
16 var doneOnloadHook;
17 var onloadFuncts = [];
18
19 function addOnloadHook( hookFunct )
20 {
21 // Allows add-on scripts to add onload functions
22 onloadFuncts[onloadFuncts.length] = hookFunct;
23 }
24
25 function runOnloadHook()
26 {
27 // don't run anything below this for non-dom browsers
28 if ( doneOnloadHook || !( document.getElementById && document.getElementsByTagName ) )
29 return;
30
31 histrowinit();
32 unhidetzbutton();
33 tabbedprefs();
34 akeytt();
35
36 // Run any added-on functions
37 for ( var i = 0; i < onloadFuncts.length; i++ )
38 onloadFuncts[i]();
39
40 doneOnloadHook = true;
41 }
42
43 function hookEvent( hookName, hookFunct )
44 {
45 if ( window.addEventListener )
46 addEventListener( hookName, hookFunct, false );
47 else if ( window.attachEvent )
48 attachEvent( "on" + hookName, hookFunct );
49 }
50
51 hookEvent( "load", runOnloadHook );
52
53 // document.write special stylesheet links
54 if(typeof stylepath != 'undefined' && typeof skin != 'undefined') {
55 if (is_opera_preseven) {
56 document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera6Fixes.css">');
57 } else if (is_opera_seven) {
58 document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera7Fixes.css">');
59 } else if (is_khtml) {
60 document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/KHTMLFixes.css">');
61 }
62 }
63 // Un-trap us from framesets
64 if( window.top != window ) window.top.location = window.location;
65
66 // for enhanced RecentChanges
67 function toggleVisibility( _levelId, _otherId, _linkId) {
68 var thisLevel = document.getElementById( _levelId );
69 var otherLevel = document.getElementById( _otherId );
70 var linkLevel = document.getElementById( _linkId );
71 if ( thisLevel.style.display == 'none' ) {
72 thisLevel.style.display = 'block';
73 otherLevel.style.display = 'none';
74 linkLevel.style.display = 'inline';
75 } else {
76 thisLevel.style.display = 'none';
77 otherLevel.style.display = 'inline';
78 linkLevel.style.display = 'none';
79 }
80 }
81
82 // page history stuff
83 // attach event handlers to the input elements on history page
84 function histrowinit () {
85 hf = document.getElementById('pagehistory');
86 if(!hf) return;
87 lis = hf.getElementsByTagName('li');
88 for (i=0;i<lis.length;i++) {
89 inputs=lis[i].getElementsByTagName('input');
90 if(inputs[0] && inputs[1]) {
91 inputs[0].onclick = diffcheck;
92 inputs[1].onclick = diffcheck;
93 }
94 }
95 diffcheck();
96 }
97 // check selection and tweak visibility/class onclick
98 function diffcheck() {
99 var dli = false; // the li where the diff radio is checked
100 var oli = false; // the li where the oldid radio is checked
101 hf = document.getElementById('pagehistory');
102 if(!hf) return;
103 lis = hf.getElementsByTagName('li');
104 for (i=0;i<lis.length;i++) {
105 inputs=lis[i].getElementsByTagName('input');
106 if(inputs[1] && inputs[0]) {
107 if(inputs[1].checked || inputs[0].checked) { // this row has a checked radio button
108 if(inputs[1].checked && inputs[0].checked && inputs[0].value == inputs[1].value) return false;
109 if(oli) { // it's the second checked radio
110 if(inputs[1].checked) {
111 oli.className = "selected";
112 return false
113 }
114 } else if (inputs[0].checked) {
115 return false;
116 }
117 if(inputs[0].checked) dli = lis[i];
118 if(!oli) inputs[0].style.visibility = 'hidden';
119 if(dli) inputs[1].style.visibility = 'hidden';
120 lis[i].className = "selected";
121 oli = lis[i];
122 } else { // no radio is checked in this row
123 if(!oli) inputs[0].style.visibility = 'hidden';
124 else inputs[0].style.visibility = 'visible';
125 if(dli) inputs[1].style.visibility = 'hidden';
126 else inputs[1].style.visibility = 'visible';
127 lis[i].className = "";
128 }
129 }
130 }
131 }
132
133 // generate toc from prefs form, fold sections
134 // XXX: needs testing on IE/Mac and safari
135 // more comments to follow
136 function tabbedprefs() {
137 var prefform = document.getElementById('preferences');
138 if(!prefform || !document.createElement) return;
139 if(prefform.nodeName.toLowerCase() == 'a') return; // Occasional IE problem
140 prefform.className = prefform.className + 'jsprefs';
141 var sections = new Array();
142 children = prefform.childNodes;
143 var seci = 0;
144 for(i=0;i<children.length;i++) {
145 if(children[i].nodeName.toLowerCase() == 'fieldset') {
146 children[i].id = 'prefsection-' + seci;
147 children[i].className = 'prefsection';
148 if(is_opera || is_khtml) children[i].className = 'prefsection operaprefsection';
149 legends = children[i].getElementsByTagName('legend');
150 sections[seci] = new Object();
151 legends[0].className = 'mainLegend';
152 if(legends[0] && legends[0].firstChild.nodeValue)
153 sections[seci].text = legends[0].firstChild.nodeValue;
154 else
155 sections[seci].text = '# ' + seci;
156 sections[seci].secid = children[i].id;
157 seci++;
158 if(sections.length != 1) children[i].style.display = 'none';
159 else var selectedid = children[i].id;
160 }
161 }
162 var toc = document.createElement('ul');
163 toc.id = 'preftoc';
164 toc.selectedid = selectedid;
165 for(i=0;i<sections.length;i++) {
166 var li = document.createElement('li');
167 if(i == 0) li.className = 'selected';
168 var a = document.createElement('a');
169 a.href = '#' + sections[i].secid;
170 a.onmousedown = a.onclick = uncoversection;
171 a.appendChild(document.createTextNode(sections[i].text));
172 a.secid = sections[i].secid;
173 li.appendChild(a);
174 toc.appendChild(li);
175 }
176 prefform.parentNode.insertBefore(toc, prefform.parentNode.childNodes[0]);
177 document.getElementById('prefsubmit').id = 'prefcontrol';
178 }
179 function uncoversection() {
180 oldsecid = this.parentNode.parentNode.selectedid;
181 newsec = document.getElementById(this.secid);
182 if(oldsecid != this.secid) {
183 ul = document.getElementById('preftoc');
184 document.getElementById(oldsecid).style.display = 'none';
185 newsec.style.display = 'block';
186 ul.selectedid = this.secid;
187 lis = ul.getElementsByTagName('li');
188 for(i=0;i< lis.length;i++) {
189 lis[i].className = '';
190 }
191 this.parentNode.className = 'selected';
192 }
193 return false;
194 }
195
196 // Timezone stuff
197 // tz in format [+-]HHMM
198 function checkTimezone( tz, msg ) {
199 var localclock = new Date();
200 // returns negative offset from GMT in minutes
201 var tzRaw = localclock.getTimezoneOffset();
202 var tzHour = Math.floor( Math.abs(tzRaw) / 60);
203 var tzMin = Math.abs(tzRaw) % 60;
204 var tzString = ((tzRaw >= 0) ? "-" : "+") + ((tzHour < 10) ? "0" : "") + tzHour + ((tzMin < 10) ? "0" : "") + tzMin;
205 if( tz != tzString ) {
206 var junk = msg.split( '$1' );
207 document.write( junk[0] + "UTC" + tzString + junk[1] );
208 }
209 }
210 function unhidetzbutton() {
211 tzb = document.getElementById('guesstimezonebutton')
212 if(tzb) tzb.style.display = 'inline';
213 }
214
215 // in [-]HH:MM format...
216 // won't yet work with non-even tzs
217 function fetchTimezone() {
218 // FIXME: work around Safari bug
219 var localclock = new Date();
220 // returns negative offset from GMT in minutes
221 var tzRaw = localclock.getTimezoneOffset();
222 var tzHour = Math.floor( Math.abs(tzRaw) / 60);
223 var tzMin = Math.abs(tzRaw) % 60;
224 var tzString = ((tzRaw >= 0) ? "-" : "") + ((tzHour < 10) ? "0" : "") + tzHour +
225 ":" + ((tzMin < 10) ? "0" : "") + tzMin;
226 return tzString;
227 }
228
229 function guessTimezone(box) {
230 document.getElementsByName("wpHourDiff")[0].value = fetchTimezone();
231 }
232
233 function showTocToggle() {
234 if (document.createTextNode) {
235 // Uses DOM calls to avoid document.write + XHTML issues
236
237 var linkHolder = document.getElementById('toctitle')
238 if (!linkHolder) return;
239
240 var outerSpan = document.createElement('span');
241 outerSpan.className = 'toctoggle';
242
243 var toggleLink = document.createElement('a');
244 toggleLink.id = 'togglelink';
245 toggleLink.className = 'internal';
246 toggleLink.href = 'javascript:toggleToc()';
247 toggleLink.appendChild(document.createTextNode(tocHideText));
248
249 outerSpan.appendChild(document.createTextNode('['));
250 outerSpan.appendChild(toggleLink);
251 outerSpan.appendChild(document.createTextNode(']'));
252
253 linkHolder.appendChild(document.createTextNode(' '));
254 linkHolder.appendChild(outerSpan);
255
256 var cookiePos = document.cookie.indexOf("hidetoc=");
257 if (cookiePos > -1 && document.cookie.charAt(cookiePos + 8) == 1)
258 toggleToc();
259 }
260 }
261
262 function changeText(el, newText) {
263 // Safari work around
264 if (el.innerText)
265 el.innerText = newText;
266 else if (el.firstChild && el.firstChild.nodeValue)
267 el.firstChild.nodeValue = newText;
268 }
269
270 function toggleToc() {
271 var toc = document.getElementById('toc').getElementsByTagName('ul')[0];
272 var toggleLink = document.getElementById('togglelink')
273
274 if(toc && toggleLink && toc.style.display == 'none') {
275 changeText(toggleLink, tocHideText);
276 toc.style.display = 'block';
277 document.cookie = "hidetoc=0";
278 } else {
279 changeText(toggleLink, tocShowText);
280 toc.style.display = 'none';
281 document.cookie = "hidetoc=1";
282 }
283 }
284
285 // this function generates the actual toolbar buttons with localized text
286 // we use it to avoid creating the toolbar where javascript is not enabled
287 function addButton(imageFile, speedTip, tagOpen, tagClose, sampleText) {
288
289 // Don't generate buttons for browsers which don't fully
290 // support it.
291 if(!document.selection && !is_gecko) {
292 return false;
293 }
294 imageFile=escapeQuotesHTML(imageFile);
295 speedTip=escapeQuotesHTML(speedTip);
296 tagOpen=escapeQuotes(tagOpen);
297 tagClose=escapeQuotes(tagClose);
298 sampleText=escapeQuotes(sampleText);
299 var mouseOver="";
300
301 document.write("<a href=\"javascript:insertTags");
302 document.write("('"+tagOpen+"','"+tagClose+"','"+sampleText+"');\">");
303 document.write("<img width=\"23\" height=\"22\" src=\""+imageFile+"\" border=\"0\" alt=\""+speedTip+"\" title=\""+speedTip+"\""+mouseOver+">");
304 document.write("</a>");
305 return;
306 }
307
308 function escapeQuotes(text) {
309 var re=new RegExp("'","g");
310 text=text.replace(re,"\\'");
311 re=new RegExp("\\n","g");
312 text=text.replace(re,"\\n");
313 return escapeQuotesHTML(text);
314 }
315
316 function escapeQuotesHTML(text) {
317 var re=new RegExp('&',"g");
318 text=text.replace(re,"&amp;");
319 var re=new RegExp('"',"g");
320 text=text.replace(re,"&quot;");
321 var re=new RegExp('<',"g");
322 text=text.replace(re,"&lt;");
323 var re=new RegExp('>',"g");
324 text=text.replace(re,"&gt;");
325 return text;
326 }
327
328 // apply tagOpen/tagClose to selection in textarea,
329 // use sampleText instead of selection if there is none
330 // copied and adapted from phpBB
331 function insertTags(tagOpen, tagClose, sampleText) {
332
333 var txtarea = document.editform.wpTextbox1;
334 // IE
335 if(document.selection && !is_gecko) {
336 var theSelection = document.selection.createRange().text;
337 if(!theSelection) { theSelection=sampleText;}
338 txtarea.focus();
339 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any
340 theSelection = theSelection.substring(0, theSelection.length - 1);
341 document.selection.createRange().text = tagOpen + theSelection + tagClose + " ";
342 } else {
343 document.selection.createRange().text = tagOpen + theSelection + tagClose;
344 }
345
346 // Mozilla
347 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') {
348 var replaced = false;
349 var startPos = txtarea.selectionStart;
350 var endPos = txtarea.selectionEnd;
351 if(endPos-startPos) replaced=true;
352 var scrollTop=txtarea.scrollTop;
353 var myText = (txtarea.value).substring(startPos, endPos);
354 if(!myText) { myText=sampleText;}
355 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any
356 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " ";
357 } else {
358 subst = tagOpen + myText + tagClose;
359 }
360 txtarea.value = txtarea.value.substring(0, startPos) + subst +
361 txtarea.value.substring(endPos, txtarea.value.length);
362 txtarea.focus();
363 //set new selection
364 if(replaced){
365 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length);
366 txtarea.selectionStart=cPos;
367 txtarea.selectionEnd=cPos;
368 }else{
369 txtarea.selectionStart=startPos+tagOpen.length;
370 txtarea.selectionEnd=startPos+tagOpen.length+myText.length;
371 }
372 txtarea.scrollTop=scrollTop;
373
374 // All other browsers get no toolbar.
375 // There was previously support for a crippled "help"
376 // bar, but that caused more problems than it solved.
377 }
378 // reposition cursor if possible
379 if (txtarea.createTextRange) txtarea.caretPos = document.selection.createRange().duplicate();
380 }
381
382 function akeytt() {
383 if(typeof ta == "undefined" || !ta) return;
384 pref = 'alt-';
385 if(is_safari || navigator.userAgent.toLowerCase().indexOf( 'mac' ) + 1
386 || navigator.userAgent.toLowerCase().indexOf( 'konqueror' ) + 1 ) pref = 'control-';
387 if(is_opera) pref = 'shift-esc-';
388
389 for(id in ta) {
390 n = document.getElementById(id);
391 if(n){
392 // Are we putting accesskey in it
393 if(ta[id][0].length > 0) {
394 // Is this object a object? If not assume it's the next child.
395
396 if ( n.nodeName.toLowerCase() == "a" ) {
397 a = n;
398 } else {
399 a = n.childNodes[0];
400 }
401
402 if(a){
403 a.accessKey = ta[id][0];
404 ak = ' ['+pref+ta[id][0]+']';
405 }
406 } else {
407 // We don't care what type the object is when assigning tooltip
408 a = n;
409 ak = '';
410 }
411
412 if (a) {
413 a.title = ta[id][1]+ak;
414 }
415 }
416 }
417 }
418
419 function setupRightClickEdit() {
420 if( document.getElementsByTagName ) {
421 var divs = document.getElementsByTagName( 'div' );
422 for( var i = 0; i < divs.length; i++ ) {
423 var el = divs[i];
424 if( el.className == 'editsection' ) {
425 addRightClickEditHandler( el );
426 }
427 }
428 }
429 }
430
431 function addRightClickEditHandler( el ) {
432 for( var i = 0; i < el.childNodes.length; i++ ) {
433 var link = el.childNodes[i];
434 if( link.nodeType == 1 && link.nodeName.toLowerCase() == 'a' ) {
435 var editHref = link.getAttribute( 'href' );
436
437 // find the following a
438 var next = el.nextSibling;
439 while( next.nodeType != 1 )
440 next = next.nextSibling;
441
442 // find the following header
443 next = next.nextSibling;
444 while( next.nodeType != 1 )
445 next = next.nextSibling;
446
447 if( next && next.nodeType == 1 &&
448 next.nodeName.match( /^[Hh][1-6]$/ ) ) {
449 next.oncontextmenu = function() {
450 document.location = editHref;
451 return false;
452 }
453 }
454 }
455 }
456 }
457
458 function fillDestFilename() {
459 if (!document.getElementById) return;
460 var path = document.getElementById('wpUploadFile').value;
461 // Find trailing part
462 var slash = path.lastIndexOf( '/' );
463 var backslash = path.lastIndexOf( '\\' );
464 var fname;
465 if ( slash == -1 && backslash == -1 ) {
466 fname = path;
467 } else if ( slash > backslash ) {
468 fname = path.substring( slash+1, 10000 );
469 } else {
470 fname = path.substring( backslash+1, 10000 );
471 }
472
473 // Capitalise first letter and replace spaces by underscores
474 fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace( / /g, '_' );
475
476 // Output result
477 var destFile = document.getElementById('wpDestFile');
478 if (destFile) destFile.value = fname;
479 }
480
481
482 function considerChangingExpiryFocus() {
483 if (!document.getElementById) return;
484 var drop = document.getElementById('wpBlockExpiry');
485 if (!drop) return;
486 var field = document.getElementById('wpBlockOther');
487 if (!field) return;
488 var opt = drop.value;
489 if (opt == 'other')
490 field.style.display = '';
491 else
492 field.style.display = 'none';
493 }