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