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