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