xref: /dokuwiki/lib/scripts/script.js (revision 2a4cae0eb05d63ceb7fb0d9a5fd30269d1692a94)
1/**
2 * Some of these scripts were taken from wikipedia.org and were modified for DokuWiki
3 */
4
5/**
6 * Some browser detection
7 */
8var clientPC  = navigator.userAgent.toLowerCase(); // Get client info
9var is_macos  = navigator.appVersion.indexOf('Mac') != -1;
10var is_gecko  = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) &&
11                (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
12var is_safari = ((clientPC.indexOf('AppleWebKit')!=-1) && (clientPC.indexOf('spoofer')==-1));
13var is_khtml  = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
14if (clientPC.indexOf('opera')!=-1) {
15    var is_opera = true;
16    var is_opera_preseven = (window.opera && !document.childNodes);
17    var is_opera_seven = (window.opera && document.childNodes);
18}
19
20// prepare empty toolbar for checks by lazy plugins
21var toolbar = '';
22
23/**
24 * Rewrite the accesskey tooltips to be more browser and OS specific.
25 *
26 * Accesskey tooltips are still only a best-guess of what will work
27 * on well known systems.
28 *
29 * @author Ben Coburn <btcoburn@silicodon.net>
30 */
31function updateAccessKeyTooltip() {
32  // determin tooltip text (order matters)
33  var tip = 'ALT+'; //default
34  if (is_macos) { tip = 'CTRL+'; }
35  if (is_opera) { tip = 'SHIFT+ESC '; }
36  // add other cases here...
37
38  // do tooltip update
39  if (tip=='ALT+') { return; }
40  var exp = /\[ALT\+/i;
41  var rep = '['+tip;
42
43  var elements = document.getElementsByTagName('a');
44  for (var i=0; i<elements.length; i++) {
45    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
46      elements[i].title = elements[i].title.replace(exp, rep);
47    }
48  }
49
50  elements = document.getElementsByTagName('input');
51  for (var i=0; i<elements.length; i++) {
52    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
53      elements[i].title = elements[i].title.replace(exp, rep);
54    }
55  }
56
57  elements = document.getElementsByTagName('button');
58  for (var i=0; i<elements.length; i++) {
59    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
60      elements[i].title = elements[i].title.replace(exp, rep);
61    }
62  }
63}
64
65/**
66 * Handy shortcut to document.getElementById
67 *
68 * This function was taken from the prototype library
69 *
70 * @link http://prototype.conio.net/
71 */
72function $() {
73  var elements = new Array();
74
75  for (var i = 0; i < arguments.length; i++) {
76    var element = arguments[i];
77    if (typeof element == 'string')
78      element = document.getElementById(element);
79
80    if (arguments.length == 1)
81      return element;
82
83    elements.push(element);
84  }
85
86  return elements;
87}
88
89/**
90 * Simple function to check if a global var is defined
91 *
92 * @author Kae Verens
93 * @link http://verens.com/archives/2005/07/25/isset-for-javascript/#comment-2835
94 */
95function isset(varname){
96  return(typeof(window[varname])!='undefined');
97}
98
99/**
100 * Select elements by their class name
101 *
102 * @author Dustin Diaz <dustin [at] dustindiaz [dot] com>
103 * @link   http://www.dustindiaz.com/getelementsbyclass/
104 */
105function getElementsByClass(searchClass,node,tag) {
106    var classElements = new Array();
107    if ( node == null )
108        node = document;
109    if ( tag == null )
110        tag = '*';
111    var els = node.getElementsByTagName(tag);
112    var elsLen = els.length;
113    var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
114    for (i = 0, j = 0; i < elsLen; i++) {
115        if ( pattern.test(els[i].className) ) {
116            classElements[j] = els[i];
117            j++;
118        }
119    }
120    return classElements;
121}
122
123/**
124 * Get the X offset of the top left corner of the given object
125 *
126 * @link http://www.quirksmode.org/index.html?/js/findpos.html
127 */
128function findPosX(object){
129  var curleft = 0;
130  var obj = $(object);
131  if (obj.offsetParent){
132    while (obj.offsetParent){
133      curleft += obj.offsetLeft;
134      obj = obj.offsetParent;
135    }
136  }
137  else if (obj.x){
138    curleft += obj.x;
139  }
140  return curleft;
141} //end findPosX function
142
143/**
144 * Get the Y offset of the top left corner of the given object
145 *
146 * @link http://www.quirksmode.org/index.html?/js/findpos.html
147 */
148function findPosY(object){
149  var curtop = 0;
150  var obj = $(object);
151  if (obj.offsetParent){
152    while (obj.offsetParent){
153      curtop += obj.offsetTop;
154      obj = obj.offsetParent;
155    }
156  }
157  else if (obj.y){
158    curtop += obj.y;
159  }
160  return curtop;
161} //end findPosY function
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
249    var obj          = document.createElement('span');
250    obj.id           = 'toc__toggle';
251    obj.innerHTML    = '<span>&minus;</span>';
252    obj.className    = 'toc_close';
253    obj.style.cursor = 'pointer';
254
255    prependChild(header,obj);
256    obj.parentNode.onclick = toggleToc;
257    try {
258       obj.parentNode.style.cursor = 'pointer';
259       obj.parentNode.style.cursor = 'hand';
260    }catch(e){}
261}
262
263/**
264 * This toggles the visibility of the Table of Contents
265 */
266function toggleToc() {
267  var toc = $('toc__inside');
268  var obj = $('toc__toggle');
269  if(toc.style.display == 'none') {
270    toc.style.display   = '';
271    obj.innerHTML       = '<span>&minus;</span>';
272    obj.className       = 'toc_close';
273  } else {
274    toc.style.display   = 'none';
275    obj.innerHTML       = '<span>+</span>';
276    obj.className       = 'toc_open';
277  }
278}
279
280/**
281 * This enables/disables checkboxes for acl-administration
282 *
283 * @author Frank Schubert <frank@schokilade.de>
284 */
285function checkAclLevel(){
286  if(document.getElementById) {
287    var scope = $('acl_scope').value;
288
289    //check for namespace
290    if( (scope.indexOf(":*") > 0) || (scope == "*") ){
291      document.getElementsByName('acl_checkbox[4]')[0].disabled=false;
292      document.getElementsByName('acl_checkbox[8]')[0].disabled=false;
293    }else{
294      document.getElementsByName('acl_checkbox[4]')[0].checked=false;
295      document.getElementsByName('acl_checkbox[8]')[0].checked=false;
296
297      document.getElementsByName('acl_checkbox[4]')[0].disabled=true;
298      document.getElementsByName('acl_checkbox[8]')[0].disabled=true;
299    }
300  }
301}
302
303/**
304 * Display an insitu footnote popup
305 *
306 * @author Andreas Gohr <andi@splitbrain.org>
307 * @author Chris Smith <chris@jalakai.co.uk>
308 */
309function footnote(e){
310    var obj = e.target;
311    var id = obj.id.substr(5);
312
313    // get or create the footnote popup div
314    var fndiv = $('insitu__fn');
315    if(!fndiv){
316        fndiv = document.createElement('div');
317        fndiv.id        = 'insitu__fn';
318        fndiv.className = 'insitu-footnote JSpopup dokuwiki';
319
320        // autoclose on mouseout - ignoring bubbled up events
321        addEvent(fndiv,'mouseout',function(e){
322            if(e.target != fndiv){
323                e.stopPropagation();
324                return;
325            }
326            // check if the element was really left
327            if(e.pageX){        // Mozilla
328                var bx1 = findPosX(fndiv);
329                var bx2 = bx1 + fndiv.offsetWidth;
330                var by1 = findPosY(fndiv);
331                var by2 = by1 + fndiv.offsetHeight;
332                var x = e.pageX;
333                var y = e.pageY;
334                if(x > bx1 && x < bx2 && y > by1 && y < by2){
335                    // we're still inside boundaries
336                    e.stopPropagation();
337                    return;
338                }
339            }else{              // IE
340                if(e.offsetX > 0 && e.offsetX < fndiv.offsetWidth-1 &&
341                   e.offsetY > 0 && e.offsetY < fndiv.offsetHeight-1){
342                    // we're still inside boundaries
343                    e.stopPropagation();
344                    return;
345                }
346            }
347            // okay, hide it
348            fndiv.style.display='none';
349        });
350        document.body.appendChild(fndiv);
351    }
352
353    // locate the footnote anchor element
354    var a = $( "fn__"+id );
355    if (!a){ return; }
356
357    // anchor parent is the footnote container, get its innerHTML
358    var content = new String (a.parentNode.parentNode.innerHTML);
359
360    // strip the leading content anchors and their comma separators
361    content = content.replace(/<a\s.*?href=\".*\#fnt__\d+\".*?<\/a>/gi, '');
362    content = content.replace(/^\s+(,\s+)+/,'');
363
364    // prefix ids on any elements with "insitu__" to ensure they remain unique
365    content = content.replace(/\bid=\"(.*?)\"/gi,'id="insitu__$1');
366
367    // now put the content into the wrapper
368    fndiv.innerHTML = content;
369
370    // position the div and make it visible
371    var x; var y;
372    if(e.pageX){        // Mozilla
373        x = e.pageX;
374        y = e.pageY;
375    }else{              // IE
376        x = e.offsetX;
377        y = e.offsetY;
378    }
379    fndiv.style.position = 'absolute';
380    fndiv.style.left = (x+2)+'px';
381    fndiv.style.top  = (y+2)+'px';
382    fndiv.style.display = '';
383}
384
385/**
386 * Add the event handlers to footnotes
387 *
388 * @author Andreas Gohr <andi@splitbrain.org>
389 */
390addInitEvent(function(){
391    var elems = getElementsByClass('fn_top',null,'a');
392    for(var i=0; i<elems.length; i++){
393        addEvent(elems[i],'mouseover',function(e){footnote(e);});
394    }
395});
396
397/**
398 * Add the edit window size controls
399 */
400function initSizeCtl(ctlid,edid){
401    if(!document.getElementById){ return; }
402
403    var ctl      = $(ctlid);
404    var textarea = $(edid);
405    if(!ctl || !textarea) return;
406
407    var hgt = DokuCookie.getValue('sizeCtl');
408    if(hgt){
409      textarea.style.height = hgt;
410    }else{
411      textarea.style.height = '300px';
412    }
413
414    var l = document.createElement('img');
415    var s = document.createElement('img');
416    var w = document.createElement('img');
417    l.src = DOKU_BASE+'lib/images/larger.gif';
418    s.src = DOKU_BASE+'lib/images/smaller.gif';
419    w.src = DOKU_BASE+'lib/images/wrap.gif';
420    addEvent(l,'click',function(){sizeCtl(edid,100);});
421    addEvent(s,'click',function(){sizeCtl(edid,-100);});
422    addEvent(w,'click',function(){toggleWrap(edid);});
423    ctl.appendChild(l);
424    ctl.appendChild(s);
425    ctl.appendChild(w);
426}
427
428/**
429 * This sets the vertical size of the editbox
430 */
431function sizeCtl(edid,val){
432  var textarea = $(edid);
433  var height = parseInt(textarea.style.height.substr(0,textarea.style.height.length-2));
434  height += val;
435  textarea.style.height = height+'px';
436
437  DokuCookie.setValue('sizeCtl',textarea.style.height);
438}
439
440/**
441 * Toggle the wrapping mode of a textarea
442 *
443 * @author Fluffy Convict <fluffyconvict@hotmail.com>
444 * @link   http://news.hping.org/comp.lang.javascript.archive/12265.html
445 * @author <shutdown@flashmail.com>
446 * @link   https://bugzilla.mozilla.org/show_bug.cgi?id=302710#c2
447 */
448function toggleWrap(edid){
449    var txtarea = $(edid);
450    var wrap = txtarea.getAttribute('wrap');
451    if(wrap && wrap.toLowerCase() == 'off'){
452        txtarea.setAttribute('wrap', 'soft');
453    }else{
454        txtarea.setAttribute('wrap', 'off');
455    }
456    // Fix display for mozilla
457    var parNod = txtarea.parentNode;
458    var nxtSib = txtarea.nextSibling;
459    parNod.removeChild(txtarea);
460    parNod.insertBefore(txtarea, nxtSib);
461}
462
463/**
464 * Handler to close all open Popups
465 */
466function closePopups(){
467  if(!document.getElementById){ return; }
468
469  var divs = document.getElementsByTagName('div');
470  for(var i=0; i < divs.length; i++){
471    if(divs[i].className.indexOf('JSpopup') != -1){
472            divs[i].style.display = 'none';
473    }
474  }
475}
476
477/**
478 * Looks for an element with the ID scroll__here at scrolls to it
479 */
480function scrollToMarker(){
481    var obj = $('scroll__here');
482    if(obj) obj.scrollIntoView();
483}
484
485/**
486 * Looks for an element with the ID focus__this at sets focus to it
487 */
488function focusMarker(){
489    var obj = $('focus__this');
490    if(obj) obj.focus();
491}
492
493/**
494 * Remove messages
495 */
496function cleanMsgArea(){
497    var elems = getElementsByClass('(success|info|error)',document,'div');
498    if(elems){
499        for(var i=0; i<elems.length; i++){
500            elems[i].style.display = 'none';
501        }
502    }
503}
504
505/**
506 * Add the event handler to the actiondropdown
507 *
508 * @author Andreas Gohr <andi@splitbrain.org>
509 */
510addInitEvent(function(){
511    var selector = $('action__selector');
512    if(!selector) return;
513
514    addEvent(selector,'change',function(e){
515        this.form.submit();
516    });
517
518    $('action__selectorbtn').style.display = 'none';
519});
520
521