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