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