xref: /dokuwiki/lib/scripts/script.js (revision e3776c06c37cc197709dac60892604dfea894ac2)
1/**
2 * Some of these scripts were taken from wikipedia.org and were modified for DokuWiki
3 */
4
5/**
6 * Some browser detection
7 */
8var clientPC  = navigator.userAgent.toLowerCase(); // Get client info
9var is_macos  = navigator.appVersion.indexOf('Mac') != -1;
10var is_gecko  = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) &&
11                (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
12var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1));
13var is_khtml  = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
14if (clientPC.indexOf('opera')!=-1) {
15    var is_opera = true;
16    var is_opera_preseven = (window.opera && !document.childNodes);
17    var is_opera_seven = (window.opera && document.childNodes);
18}
19
20/**
21 * Handy shortcut to document.getElementById
22 *
23 * This function was taken from the prototype library
24 *
25 * @link http://prototype.conio.net/
26 */
27function $() {
28  var elements = new Array();
29
30  for (var i = 0; i < arguments.length; i++) {
31    var element = arguments[i];
32    if (typeof element == 'string')
33      element = document.getElementById(element);
34
35    if (arguments.length == 1)
36      return element;
37
38    elements.push(element);
39  }
40
41  return elements;
42}
43
44/**
45 * Simple function to check if a global var is defined
46 *
47 * @author Kae Verens
48 * @link http://verens.com/archives/2005/07/25/isset-for-javascript/#comment-2835
49 */
50function isset(varname){
51  return(typeof(window[varname])!='undefined');
52}
53
54/**
55 * Select elements by their class name
56 *
57 * @author Dustin Diaz <dustin [at] dustindiaz [dot] com>
58 * @link   http://www.dustindiaz.com/getelementsbyclass/
59 */
60function getElementsByClass(searchClass,node,tag) {
61    var classElements = new Array();
62    if ( node == null )
63        node = document;
64    if ( tag == null )
65        tag = '*';
66    var els = node.getElementsByTagName(tag);
67    var elsLen = els.length;
68    var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
69    for (var i = 0, j = 0; i < elsLen; i++) {
70        if ( pattern.test(els[i].className) ) {
71            classElements[j] = els[i];
72            j++;
73        }
74    }
75    return classElements;
76}
77
78/**
79 * Get the X offset of the top left corner of the given object
80 *
81 * @link http://www.quirksmode.org/js/findpos.html
82 */
83function findPosX(object){
84  var curleft = 0;
85  var obj = $(object);
86  if (obj.offsetParent){
87    do {
88      curleft += obj.offsetLeft;
89    } while (obj = obj.offsetParent);
90  }
91  else if (obj.x){
92    curleft += obj.x;
93  }
94  return curleft;
95} //end findPosX function
96
97/**
98 * Get the Y offset of the top left corner of the given object
99 *
100 * @link http://www.quirksmode.org/js/findpos.html
101 */
102function findPosY(object){
103  var curtop = 0;
104  var obj = $(object);
105  if (obj.offsetParent){
106    do {
107      curtop += obj.offsetTop;
108    } while (obj = obj.offsetParent);
109  }
110  else if (obj.y){
111    curtop += obj.y;
112  }
113  return curtop;
114} //end findPosY function
115
116/**
117 * Escape special chars in JavaScript
118 *
119 * @author Andreas Gohr <andi@splitbrain.org>
120 */
121function jsEscape(text){
122    var re=new RegExp("\\\\","g");
123    text=text.replace(re,"\\\\");
124    re=new RegExp("'","g");
125    text=text.replace(re,"\\'");
126    re=new RegExp('"',"g");
127    text=text.replace(re,'&quot;');
128    re=new RegExp("\\\\\\\\n","g");
129    text=text.replace(re,"\\n");
130    return text;
131}
132
133/**
134 * This function escapes some special chars
135 * @deprecated by above function
136 */
137function escapeQuotes(text) {
138  var re=new RegExp("'","g");
139  text=text.replace(re,"\\'");
140  re=new RegExp('"',"g");
141  text=text.replace(re,'&quot;');
142  re=new RegExp("\\n","g");
143  text=text.replace(re,"\\n");
144  return text;
145}
146
147/**
148 * Adds a node as the first childenode to the given parent
149 *
150 * @see appendChild()
151 */
152function prependChild(parent,element) {
153    if(!parent.firstChild){
154        parent.appendChild(element);
155    }else{
156        parent.insertBefore(element,parent.firstChild);
157    }
158}
159
160/**
161 * Prints a animated gif to show the search is performed
162 *
163 * Because we need to modify the DOM here before the document is loaded
164 * and parsed completely we have to rely on document.write()
165 *
166 * @author Andreas Gohr <andi@splitbrain.org>
167 */
168function showLoadBar(){
169
170  document.write('<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
171                 'width="150" height="12" alt="..." />');
172
173  /* this does not work reliable in IE
174  obj = $(id);
175
176  if(obj){
177    obj.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
178                    'width="150" height="12" alt="..." />';
179    obj.style.display="block";
180  }
181  */
182}
183
184/**
185 * Disables the animated gif to show the search is done
186 *
187 * @author Andreas Gohr <andi@splitbrain.org>
188 */
189function hideLoadBar(id){
190  obj = $(id);
191  if(obj) obj.style.display="none";
192}
193
194/**
195 * Adds the toggle switch to the TOC
196 */
197function addTocToggle() {
198    if(!document.getElementById) return;
199    var header = $('toc__header');
200    if(!header) return;
201    var toc = $('toc__inside');
202
203    var obj          = document.createElement('span');
204    obj.id           = 'toc__toggle';
205    obj.style.cursor = 'pointer';
206    if (toc && toc.style.display == 'none') {
207        obj.innerHTML    = '<span>+</span>';
208        obj.className    = 'toc_open';
209    } else {
210        obj.innerHTML    = '<span>&minus;</span>';
211        obj.className    = 'toc_close';
212    }
213
214    prependChild(header,obj);
215    obj.parentNode.onclick = toggleToc;
216    obj.parentNode.style.cursor = 'pointer';
217}
218
219/**
220 * This toggles the visibility of the Table of Contents
221 */
222function toggleToc() {
223  var toc = $('toc__inside');
224  var obj = $('toc__toggle');
225  if(toc.style.display == 'none') {
226    toc.style.display   = '';
227    obj.innerHTML       = '<span>&minus;</span>';
228    obj.className       = 'toc_close';
229  } else {
230    toc.style.display   = 'none';
231    obj.innerHTML       = '<span>+</span>';
232    obj.className       = 'toc_open';
233  }
234}
235
236/**
237 * Create JavaScript mouseover popup
238 */
239function insitu_popup(target, popup_id) {
240
241    // get or create the popup div
242    var fndiv = $(popup_id);
243    if(!fndiv){
244        fndiv = document.createElement('div');
245        fndiv.id        = popup_id;
246        fndiv.className = 'insitu-footnote JSpopup dokuwiki';
247
248        // autoclose on mouseout - ignoring bubbled up events
249        addEvent(fndiv,'mouseout',function(e){
250            var p = e.relatedTarget || e.toElement;
251            while (p && p !== this) {
252                p = p.parentNode;
253            }
254            if (p === this) {
255                return;
256            }
257            // okay, hide it
258            this.style.display='none';
259        });
260        getElementsByClass('dokuwiki', document.body, 'div')[0].appendChild(fndiv);
261    }
262
263    // position the div and make it visible
264    fndiv.style.position = 'absolute';
265    fndiv.style.left = findPosX(target)+'px';
266    fndiv.style.top  = (findPosY(target)+target.offsetHeight * 1.5) + 'px';
267    fndiv.style.display = '';
268    return fndiv;
269}
270
271/**
272 * Display an insitu footnote popup
273 *
274 * @author Andreas Gohr <andi@splitbrain.org>
275 * @author Chris Smith <chris@jalakai.co.uk>
276 */
277function footnote(e){
278    var fndiv = insitu_popup(e.target, 'insitu__fn');
279
280    // locate the footnote anchor element
281    var a = $("fn__" + e.target.id.substr(5));
282    if (!a){ return; }
283
284    // anchor parent is the footnote container, get its innerHTML
285    var content = new String (a.parentNode.parentNode.innerHTML);
286
287    // strip the leading content anchors and their comma separators
288    content = content.replace(/<sup>.*<\/sup>/gi, '');
289    content = content.replace(/^\s+(,\s+)+/,'');
290
291    // prefix ids on any elements with "insitu__" to ensure they remain unique
292    content = content.replace(/\bid=(['"])([^"']+)\1/gi,'id="insitu__$2');
293
294    // now put the content into the wrapper
295    fndiv.innerHTML = content;
296}
297
298/**
299 * Add the event handlers to footnotes
300 *
301 * @author Andreas Gohr <andi@splitbrain.org>
302 */
303addInitEvent(function(){
304    var elems = getElementsByClass('fn_top',null,'a');
305    for(var i=0; i<elems.length; i++){
306        addEvent(elems[i],'mouseover',function(e){footnote(e);});
307    }
308});
309
310/**
311 * Add the edit window size controls
312 */
313function initSizeCtl(ctlid,edid){
314    if(!document.getElementById){ return; }
315
316    var ctl      = $(ctlid);
317    var textarea = $(edid);
318    if(!ctl || !textarea) return;
319
320    var hgt = DokuCookie.getValue('sizeCtl');
321    if(hgt){
322      textarea.style.height = hgt;
323    }else{
324      textarea.style.height = '300px';
325    }
326
327    var wrp = DokuCookie.getValue('wrapCtl');
328    if(wrp){
329      setWrap(textarea, wrp);
330    } // else use default value
331
332    var l = document.createElement('img');
333    var s = document.createElement('img');
334    var w = document.createElement('img');
335    l.src = DOKU_BASE+'lib/images/larger.gif';
336    s.src = DOKU_BASE+'lib/images/smaller.gif';
337    w.src = DOKU_BASE+'lib/images/wrap.gif';
338    addEvent(l,'click',function(){sizeCtl(edid,100);});
339    addEvent(s,'click',function(){sizeCtl(edid,-100);});
340    addEvent(w,'click',function(){toggleWrap(edid);});
341    ctl.appendChild(l);
342    ctl.appendChild(s);
343    ctl.appendChild(w);
344}
345
346/**
347 * This sets the vertical size of the editbox
348 */
349function sizeCtl(edid,val){
350  var textarea = $(edid);
351  var height = parseInt(textarea.style.height.substr(0,textarea.style.height.length-2));
352  height += val;
353  textarea.style.height = height+'px';
354
355  DokuCookie.setValue('sizeCtl',textarea.style.height);
356}
357
358/**
359 * Toggle the wrapping mode of a textarea
360 */
361function toggleWrap(edid){
362    var textarea = $(edid);
363    var wrap = textarea.getAttribute('wrap');
364    if(wrap && wrap.toLowerCase() == 'off'){
365        setWrap(textarea, 'soft');
366    }else{
367        setWrap(textarea, 'off');
368    }
369
370    DokuCookie.setValue('wrapCtl',textarea.getAttribute('wrap'));
371}
372
373/**
374 * Set the wrapping mode of a textarea
375 *
376 * @author Fluffy Convict <fluffyconvict@hotmail.com>
377 * @author <shutdown@flashmail.com>
378 * @link   http://news.hping.org/comp.lang.javascript.archive/12265.html
379 * @link   https://bugzilla.mozilla.org/show_bug.cgi?id=41464
380 */
381function setWrap(textarea, wrapAttrValue){
382    textarea.setAttribute('wrap', wrapAttrValue);
383
384    // Fix display for mozilla
385    var parNod = textarea.parentNode;
386    var nxtSib = textarea.nextSibling;
387    parNod.removeChild(textarea);
388    parNod.insertBefore(textarea, nxtSib);
389}
390
391/**
392 * Handler to close all open Popups
393 */
394function closePopups(){
395  if(!document.getElementById){ return; }
396
397  var divs = document.getElementsByTagName('div');
398  for(var i=0; i < divs.length; i++){
399    if(divs[i].className.indexOf('JSpopup') != -1){
400            divs[i].style.display = 'none';
401    }
402  }
403}
404
405/**
406 * Looks for an element with the ID scroll__here at scrolls to it
407 */
408function scrollToMarker(){
409    var obj = $('scroll__here');
410    if(obj) obj.scrollIntoView();
411}
412
413/**
414 * Looks for an element with the ID focus__this at sets focus to it
415 */
416function focusMarker(){
417    var obj = $('focus__this');
418    if(obj) obj.focus();
419}
420
421/**
422 * Remove messages
423 */
424function cleanMsgArea(){
425    var elems = getElementsByClass('(success|info|error)',document,'div');
426    if(elems){
427        for(var i=0; i<elems.length; i++){
428            elems[i].style.display = 'none';
429        }
430    }
431}
432
433/**
434 * disable multiple revisions checkboxes if two are checked
435 *
436 * @author Anika Henke <anika@selfthinker.org>
437 */
438addInitEvent(function(){
439    var revForm = $('page__revisions');
440    if (!revForm) return;
441    var elems = revForm.elements;
442    var countTicks = 0;
443    for (var i=0; i<elems.length; i++) {
444        var input1 = elems[i];
445        if (input1.type=='checkbox') {
446            addEvent(input1,'click',function(e){
447                if (this.checked) countTicks++;
448                else countTicks--;
449                for (var j=0; j<elems.length; j++) {
450                    var input2 = elems[j];
451                    if (countTicks >= 2) input2.disabled = (input2.type=='checkbox' && !input2.checked);
452                    else input2.disabled = (input2.type!='checkbox');
453                }
454            });
455            input1.checked = false; // chrome reselects on back button which messes up the logic
456        } else if(input1.type=='submit'){
457            input1.disabled = true;
458        }
459    }
460});
461
462/**
463 * Add the event handler to the actiondropdown
464 *
465 * @author Andreas Gohr <andi@splitbrain.org>
466 */
467addInitEvent(function(){
468    var selector = $('action__selector');
469    if(!selector) return;
470
471    addEvent(selector,'change',function(e){
472        this.form.submit();
473    });
474
475    $('action__selectorbtn').style.display = 'none';
476});
477
478/**
479 * Display error for Windows Shares on browsers other than IE
480 *
481 * @author Michael Klier <chi@chimeric.de>
482 */
483function checkWindowsShares() {
484    if(!LANG['nosmblinks']) return true;
485    if(document.all != null) return true;
486
487    var elems = getElementsByClass('windows',document,'a');
488    if(elems){
489        for(var i=0; i<elems.length; i++){
490            var share = elems[i];
491            addEvent(share,'click',function(){
492                alert(LANG['nosmblinks']);
493            });
494        }
495    }
496}
497
498/**
499 * Add the event handler for the Windows Shares check
500 *
501 * @author Michael Klier <chi@chimeric.de>
502 */
503addInitEvent(function(){
504    checkWindowsShares();
505});
506
507/**
508 * Highlight the section when hovering over the appropriate section edit button
509 *
510 * @author Andreas Gohr <andi@splitbrain.org>
511 */
512addInitEvent(function(){
513    var btns = getElementsByClass('btn_secedit',document,'form');
514    for(var i=0; i<btns.length; i++){
515        addEvent(btns[i],'mouseover',function(e){
516            var tgt = this.parentNode;
517            var nr = tgt.className.match(/(\s+|^)editbutton_(\d+)(\s+|$)/)[2];
518            do {
519                tgt = tgt.previousSibling;
520            } while (tgt !== null && typeof tgt.tagName === 'undefined');
521            if (tgt === null) return;
522            while(typeof tgt.className === 'undefined' ||
523                  tgt.className.match('(\\s+|^)sectionedit' + nr + '(\\s+|$)') === null) {
524                if (typeof tgt.className !== 'undefined') {
525                    tgt.className += ' section_highlight';
526                }
527                tgt = (tgt.previousSibling !== null) ? tgt.previousSibling : tgt.parentNode;
528            }
529            if (typeof tgt.className !== 'undefined') tgt.className += ' section_highlight';
530        });
531
532        addEvent(btns[i],'mouseout',function(e){
533            var secs = getElementsByClass('section_highlight');
534            for(var j=0; j<secs.length; j++){
535                secs[j].className = secs[j].className.replace(/section_highlight/g,'');
536            }
537        });
538    }
539});
540
541