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