xref: /dokuwiki/lib/scripts/script.js (revision ddf8a04fe3281e871c5311235b08185a5ceab797)
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(!msg) msg = '';
18
19    var func;
20    if(arguments.callee) func = arguments.callee.caller.name;
21    if(func) func = ' '+func+'()';
22    var line = 'DEPRECATED function call'+func+'. '+msg;
23
24    if(console.warn){
25        console.warn(line);
26    }else{
27        console.log(line);
28    }
29
30    if(console.trace) console.trace();
31}
32
33/**
34 * Construct a wrapper function for deprecated function names
35 *
36 * This function returns a wrapper function which just calls DEPRECATED
37 * and the new function.
38 *
39 * @param func    The new function
40 * @param context Optional; The context (`this`) of the call
41 */
42function DEPRECATED_WRAP(func, context) {
43    return function () {
44        DEPRECATED();
45        return func.apply(context || this, arguments);
46    }
47}
48
49/**
50 * Some of these scripts were taken from wikipedia.org and were modified for DokuWiki
51 */
52
53/**
54 * Some browser detection
55 */
56var clientPC  = navigator.userAgent.toLowerCase(); // Get client info
57var is_macos  = navigator.appVersion.indexOf('Mac') != -1;
58var is_gecko  = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) &&
59                (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
60var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1));
61var is_khtml  = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
62if (clientPC.indexOf('opera')!=-1) {
63    var is_opera = true;
64    var is_opera_preseven = (window.opera && !document.childNodes);
65    var is_opera_seven = (window.opera && document.childNodes);
66}
67
68/**
69 * Handy shortcut to document.getElementById
70 *
71 * This function was taken from the prototype library
72 *
73 * @link http://prototype.conio.net/
74 */
75function $() {
76  DEPRECATED('Please use the JQuery() function instead.');
77
78  var elements = new Array();
79
80  for (var i = 0; i < arguments.length; i++) {
81    var element = arguments[i];
82    if (typeof element == 'string')
83      element = document.getElementById(element);
84
85    if (arguments.length == 1)
86      return element;
87
88    elements.push(element);
89  }
90
91  return elements;
92}
93
94/**
95 * Simple function to check if a global var is defined
96 *
97 * @author Kae Verens
98 * @link http://verens.com/archives/2005/07/25/isset-for-javascript/#comment-2835
99 */
100function isset(varname){
101  return(typeof(window[varname])!='undefined');
102}
103
104/**
105 * Get the computed style of a node.
106 *
107 * @link https://acidmartin.wordpress.com/2008/08/26/style-get-any-css-property-value-of-an-object/
108 * @link http://svn.dojotoolkit.org/src/dojo/trunk/_base/html.js
109 */
110function gcs(node){
111    if(node.currentStyle){
112        return node.currentStyle;
113    }else{
114        return node.ownerDocument.defaultView.getComputedStyle(node, null);
115    }
116}
117
118/**
119 * Escape special chars in JavaScript
120 *
121 * @author Andreas Gohr <andi@splitbrain.org>
122 */
123function jsEscape(text){
124    var re=new RegExp("\\\\","g");
125    text=text.replace(re,"\\\\");
126    re=new RegExp("'","g");
127    text=text.replace(re,"\\'");
128    re=new RegExp('"',"g");
129    text=text.replace(re,'&quot;');
130    re=new RegExp("\\\\\\\\n","g");
131    text=text.replace(re,"\\n");
132    return text;
133}
134
135/**
136 * This function escapes some special chars
137 * @deprecated by above function
138 */
139function escapeQuotes(text) {
140  var re=new RegExp("'","g");
141  text=text.replace(re,"\\'");
142  re=new RegExp('"',"g");
143  text=text.replace(re,'&quot;');
144  re=new RegExp("\\n","g");
145  text=text.replace(re,"\\n");
146  return text;
147}
148
149/**
150 * Adds a node as the first childenode to the given parent
151 *
152 * @see appendChild()
153 */
154function prependChild(parent,element) {
155    if(!parent.firstChild){
156        parent.appendChild(element);
157    }else{
158        parent.insertBefore(element,parent.firstChild);
159    }
160}
161
162/**
163 * Prints a animated gif to show the search is performed
164 *
165 * Because we need to modify the DOM here before the document is loaded
166 * and parsed completely we have to rely on document.write()
167 *
168 * @author Andreas Gohr <andi@splitbrain.org>
169 */
170function showLoadBar(){
171
172  document.write('<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
173                 'width="150" height="12" alt="..." />');
174
175  /* this does not work reliable in IE
176  obj = $(id);
177
178  if(obj){
179    obj.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
180                    'width="150" height="12" alt="..." />';
181    obj.style.display="block";
182  }
183  */
184}
185
186/**
187 * Disables the animated gif to show the search is done
188 *
189 * @author Andreas Gohr <andi@splitbrain.org>
190 */
191function hideLoadBar(id){
192  obj = $(id);
193  if(obj) obj.style.display="none";
194}
195
196/**
197 * Adds the toggle switch to the TOC
198 */
199function addTocToggle() {
200    if(!document.getElementById) return;
201    var header = $('toc__header');
202    if(!header) return;
203    var toc = $('toc__inside');
204
205    var obj          = document.createElement('span');
206    obj.id           = 'toc__toggle';
207    obj.style.cursor = 'pointer';
208    if (toc && toc.style.display == 'none') {
209        obj.innerHTML    = '<span>+</span>';
210        obj.className    = 'toc_open';
211    } else {
212        obj.innerHTML    = '<span>&minus;</span>';
213        obj.className    = 'toc_close';
214    }
215
216    prependChild(header,obj);
217    obj.parentNode.onclick = toggleToc;
218    obj.parentNode.style.cursor = 'pointer';
219}
220
221/**
222 * This toggles the visibility of the Table of Contents
223 */
224function toggleToc() {
225  var toc = $('toc__inside');
226  var obj = $('toc__toggle');
227  if(toc.style.display == 'none') {
228    toc.style.display   = '';
229    obj.innerHTML       = '<span>&minus;</span>';
230    obj.className       = 'toc_close';
231  } else {
232    toc.style.display   = 'none';
233    obj.innerHTML       = '<span>+</span>';
234    obj.className       = 'toc_open';
235  }
236}
237
238/**
239 * Create JavaScript mouseover popup
240 */
241function insitu_popup(target, popup_id) {
242
243    // get or create the popup div
244    var fndiv = $(popup_id);
245    if(!fndiv){
246        fndiv = document.createElement('div');
247        fndiv.id        = popup_id;
248        fndiv.className = 'insitu-footnote JSpopup dokuwiki';
249
250        // autoclose on mouseout - ignoring bubbled up events
251        addEvent(fndiv,'mouseout',function(e){
252            var p = e.relatedTarget || e.toElement;
253            while (p && p !== this) {
254                p = p.parentNode;
255            }
256            if (p === this) {
257                return;
258            }
259            // okay, hide it
260            this.style.display='none';
261        });
262        getElementsByClass('dokuwiki', document.body, 'div')[0].appendChild(fndiv);
263    }
264
265    var non_static_parent = fndiv.parentNode;
266    while (non_static_parent != document && gcs(non_static_parent)['position'] == 'static') {
267        non_static_parent = non_static_parent.parentNode;
268    }
269
270    var fixed_target_parent = target;
271    while (fixed_target_parent != document && gcs(fixed_target_parent)['position'] != 'fixed') {
272        fixed_target_parent = fixed_target_parent.parentNode;
273    }
274
275    // position the div and make it visible
276    if (fixed_target_parent != document) {
277        // the target has position fixed, that means the footnote needs to be fixed, too
278        fndiv.style.position = 'fixed';
279    } else {
280        fndiv.style.position = 'absolute';
281    }
282
283    if (fixed_target_parent != document || non_static_parent == document) {
284        fndiv.style.left = findPosX(target)+'px';
285        fndiv.style.top  = (findPosY(target)+target.offsetHeight * 1.5) + 'px';
286    } else {
287        fndiv.style.left = (findPosX(target) - findPosX(non_static_parent)) +'px';
288        fndiv.style.top  = (findPosY(target)+target.offsetHeight * 1.5 - findPosY(non_static_parent)) + 'px';
289    }
290
291    fndiv.style.display = '';
292    return fndiv;
293}
294
295/**
296 * Display an insitu footnote popup
297 *
298 * @author Andreas Gohr <andi@splitbrain.org>
299 * @author Chris Smith <chris@jalakai.co.uk>
300 */
301function footnote(e){
302    var fndiv = insitu_popup(e.target, 'insitu__fn');
303
304    // locate the footnote anchor element
305    var a = $("fn__" + e.target.id.substr(5));
306    if (!a){ return; }
307
308    // anchor parent is the footnote container, get its innerHTML
309    var content = new String (a.parentNode.parentNode.innerHTML);
310
311    // strip the leading content anchors and their comma separators
312    content = content.replace(/<sup>.*<\/sup>/gi, '');
313    content = content.replace(/^\s+(,\s+)+/,'');
314
315    // prefix ids on any elements with "insitu__" to ensure they remain unique
316    content = content.replace(/\bid=(['"])([^"']+)\1/gi,'id="insitu__$2');
317
318    // now put the content into the wrapper
319    fndiv.innerHTML = content;
320}
321
322/**
323 * Add the event handlers to footnotes
324 *
325 * @author Andreas Gohr <andi@splitbrain.org>
326 */
327addInitEvent(function(){
328    var elems = getElementsByClass('fn_top',null,'a');
329    for(var i=0; i<elems.length; i++){
330        addEvent(elems[i],'mouseover',function(e){footnote(e);});
331    }
332});
333
334
335/**
336 * Handler to close all open Popups
337 */
338function closePopups(){
339  if(!document.getElementById){ return; }
340
341  var divs = document.getElementsByTagName('div');
342  for(var i=0; i < divs.length; i++){
343    if(divs[i].className.indexOf('JSpopup') != -1){
344            divs[i].style.display = 'none';
345    }
346  }
347}
348
349/**
350 * disable multiple revisions checkboxes if two are checked
351 *
352 * @author Anika Henke <anika@selfthinker.org>
353 */
354addInitEvent(function(){
355    var revForm = $('page__revisions');
356    if (!revForm) return;
357    var elems = revForm.elements;
358    var countTicks = 0;
359    for (var i=0; i<elems.length; i++) {
360        var input1 = elems[i];
361        if (input1.type=='checkbox') {
362            addEvent(input1,'click',function(e){
363                if (this.checked) countTicks++;
364                else countTicks--;
365                for (var j=0; j<elems.length; j++) {
366                    var input2 = elems[j];
367                    if (countTicks >= 2) input2.disabled = (input2.type=='checkbox' && !input2.checked);
368                    else input2.disabled = (input2.type!='checkbox');
369                }
370            });
371            input1.checked = false; // chrome reselects on back button which messes up the logic
372        } else if(input1.type=='submit'){
373            input1.disabled = true;
374        }
375    }
376});
377
378
379/**
380 * Highlight the section when hovering over the appropriate section edit button
381 *
382 * @author Andreas Gohr <andi@splitbrain.org>
383 */
384addInitEvent(function(){
385    var btns = getElementsByClass('btn_secedit',document,'form');
386    for(var i=0; i<btns.length; i++){
387        addEvent(btns[i],'mouseover',function(e){
388            var tgt = this.parentNode;
389            var nr = tgt.className.match(/(\s+|^)editbutton_(\d+)(\s+|$)/)[2];
390            do {
391                tgt = tgt.previousSibling;
392            } while (tgt !== null && typeof tgt.tagName === 'undefined');
393            if (tgt === null) return;
394            while(typeof tgt.className === 'undefined' ||
395                  tgt.className.match('(\\s+|^)sectionedit' + nr + '(\\s+|$)') === null) {
396                if (typeof tgt.className !== 'undefined') {
397                    tgt.className += ' section_highlight';
398                }
399                tgt = (tgt.previousSibling !== null) ? tgt.previousSibling : tgt.parentNode;
400            }
401            if (typeof tgt.className !== 'undefined') tgt.className += ' section_highlight';
402        });
403
404        addEvent(btns[i],'mouseout',function(e){
405            var secs = getElementsByClass('section_highlight');
406            for(var j=0; j<secs.length; j++){
407                secs[j].className = secs[j].className.replace(/section_highlight/g,'');
408            }
409        });
410    }
411});
412
413