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