xref: /dokuwiki/lib/scripts/script.js (revision 5aa52fafe8be8e728c0d2c9ff12c999e80766127)
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/**
21 * Rewrite the accesskey tooltips to be more browser and OS specific.
22 *
23 * Accesskey tooltips are still only a best-guess of what will work
24 * on well known systems.
25 *
26 * @author Ben Coburn <btcoburn@silicodon.net>
27 */
28function updateAccessKeyTooltip() {
29  // determin tooltip text (order matters)
30  var tip = 'ALT+'; //default
31  if (is_macos) { tip = 'CTRL+'; }
32  if (is_opera) { tip = 'SHIFT+ESC '; }
33  // add other cases here...
34
35  // do tooltip update
36  if (tip=='ALT+') { return; }
37  var exp = /\[ALT\+/i;
38  var rep = '['+tip;
39
40  var elements = document.getElementsByTagName('a');
41  for (var i=0; i<elements.length; i++) {
42    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
43      elements[i].title = elements[i].title.replace(exp, rep);
44    }
45  }
46
47  elements = document.getElementsByTagName('input');
48  for (var i=0; i<elements.length; i++) {
49    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
50      elements[i].title = elements[i].title.replace(exp, rep);
51    }
52  }
53
54  elements = document.getElementsByTagName('button');
55  for (var i=0; i<elements.length; i++) {
56    if (elements[i].accessKey.length==1 && elements[i].title.length>0) {
57      elements[i].title = elements[i].title.replace(exp, rep);
58    }
59  }
60}
61
62/**
63 * Handy shortcut to document.getElementById
64 *
65 * This function was taken from the prototype library
66 *
67 * @link http://prototype.conio.net/
68 */
69function $() {
70  var elements = new Array();
71
72  for (var i = 0; i < arguments.length; i++) {
73    var element = arguments[i];
74    if (typeof element == 'string')
75      element = document.getElementById(element);
76
77    if (arguments.length == 1)
78      return element;
79
80    elements.push(element);
81  }
82
83  return elements;
84}
85
86/**
87 * Simple function to check if a global var is defined
88 *
89 * @author Kae Verens
90 * @link http://verens.com/archives/2005/07/25/isset-for-javascript/#comment-2835
91 */
92function isset(varname){
93  return(typeof(window[varname])!='undefined');
94}
95
96/**
97 * Select elements by their class name
98 *
99 * @author Dustin Diaz <dustin [at] dustindiaz [dot] com>
100 * @link   http://www.dustindiaz.com/getelementsbyclass/
101 */
102function getElementsByClass(searchClass,node,tag) {
103    var classElements = new Array();
104    if ( node == null )
105        node = document;
106    if ( tag == null )
107        tag = '*';
108    var els = node.getElementsByTagName(tag);
109    var elsLen = els.length;
110    var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
111    for (i = 0, j = 0; i < elsLen; i++) {
112        if ( pattern.test(els[i].className) ) {
113            classElements[j] = els[i];
114            j++;
115        }
116    }
117    return classElements;
118}
119
120/**
121 * Get the X offset of the top left corner of the given object
122 *
123 * @link http://www.quirksmode.org/index.html?/js/findpos.html
124 */
125function findPosX(object){
126  var curleft = 0;
127  var obj = $(object);
128  if (obj.offsetParent){
129    while (obj.offsetParent){
130      curleft += obj.offsetLeft;
131      obj = obj.offsetParent;
132    }
133  }
134  else if (obj.x){
135    curleft += obj.x;
136  }
137  return curleft;
138} //end findPosX function
139
140/**
141 * Get the Y offset of the top left corner of the given object
142 *
143 * @link http://www.quirksmode.org/index.html?/js/findpos.html
144 */
145function findPosY(object){
146  var curtop = 0;
147  var obj = $(object);
148  if (obj.offsetParent){
149    while (obj.offsetParent){
150      curtop += obj.offsetTop;
151      obj = obj.offsetParent;
152    }
153  }
154  else if (obj.y){
155    curtop += obj.y;
156  }
157  return curtop;
158} //end findPosY function
159
160/**
161 * Escape special chars in JavaScript
162 *
163 * @author Andreas Gohr <andi@splitbrain.org>
164 */
165function jsEscape(text){
166    var re=new RegExp("\\\\","g");
167    text=text.replace(re,"\\\\");
168    re=new RegExp("'","g");
169    text=text.replace(re,"\\'");
170    re=new RegExp('"',"g");
171    text=text.replace(re,'&quot;');
172    re=new RegExp("\\\\\\\\n","g");
173    text=text.replace(re,"\\n");
174    return text;
175}
176
177/**
178 * This function escapes some special chars
179 * @deprecated by above function
180 */
181function escapeQuotes(text) {
182  var re=new RegExp("'","g");
183  text=text.replace(re,"\\'");
184  re=new RegExp('"',"g");
185  text=text.replace(re,'&quot;');
186  re=new RegExp("\\n","g");
187  text=text.replace(re,"\\n");
188  return text;
189}
190
191/**
192 * Adds a node as the first childenode to the given parent
193 *
194 * @see appendChild()
195 */
196function prependChild(parent,element) {
197    if(!parent.firstChild){
198        parent.appendChild(element);
199    }else{
200        parent.insertBefore(element,parent.firstChild);
201    }
202}
203
204/**
205 * Prints a animated gif to show the search is performed
206 *
207 * Because we need to modify the DOM here before the document is loaded
208 * and parsed completely we have to rely on document.write()
209 *
210 * @author Andreas Gohr <andi@splitbrain.org>
211 */
212function showLoadBar(){
213
214  document.write('<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
215                 'width="150" height="12" alt="..." />');
216
217  /* this does not work reliable in IE
218  obj = $(id);
219
220  if(obj){
221    obj.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
222                    'width="150" height="12" alt="..." />';
223    obj.style.display="block";
224  }
225  */
226}
227
228/**
229 * Disables the animated gif to show the search is done
230 *
231 * @author Andreas Gohr <andi@splitbrain.org>
232 */
233function hideLoadBar(id){
234  obj = $(id);
235  if(obj) obj.style.display="none";
236}
237
238/**
239 * Adds the toggle switch to the TOC
240 */
241function addTocToggle() {
242    if(!document.getElementById) return;
243    var header = $('toc__header');
244    if(!header) return;
245
246    var showimg     = document.createElement('img');
247    showimg.id      = 'toc__show';
248    showimg.src     = DOKU_BASE+'lib/images/arrow_down.gif';
249    showimg.alt     = '+';
250    showimg.onclick = toggleToc;
251    showimg.style.display = 'none';
252
253    var hideimg     = document.createElement('img');
254    hideimg.id      = 'toc__hide';
255    hideimg.src     = DOKU_BASE+'lib/images/arrow_up.gif';
256    hideimg.alt     = '-';
257    hideimg.onclick = toggleToc;
258
259    prependChild(header,showimg);
260    prependChild(header,hideimg);
261}
262
263/**
264 * This toggles the visibility of the Table of Contents
265 */
266function toggleToc() {
267  var toc = $('toc__inside');
268  var showimg = $('toc__show');
269  var hideimg = $('toc__hide');
270  if(toc.style.display == 'none') {
271    toc.style.display      = '';
272    hideimg.style.display = '';
273    showimg.style.display = 'none';
274  } else {
275    toc.style.display      = 'none';
276    hideimg.style.display = 'none';
277    showimg.style.display = '';
278  }
279}
280
281/**
282 * This enables/disables checkboxes for acl-administration
283 *
284 * @author Frank Schubert <frank@schokilade.de>
285 */
286function checkAclLevel(){
287  if(document.getElementById) {
288    var scope = $('acl_scope').value;
289
290    //check for namespace
291    if( (scope.indexOf(":*") > 0) || (scope == "*") ){
292      document.getElementsByName('acl_checkbox[4]')[0].disabled=false;
293      document.getElementsByName('acl_checkbox[8]')[0].disabled=false;
294    }else{
295      document.getElementsByName('acl_checkbox[4]')[0].checked=false;
296      document.getElementsByName('acl_checkbox[8]')[0].checked=false;
297
298      document.getElementsByName('acl_checkbox[4]')[0].disabled=true;
299      document.getElementsByName('acl_checkbox[8]')[0].disabled=true;
300    }
301  }
302}
303
304/**
305 * Display an insitu footnote popup
306 *
307 * @author Andreas Gohr <andi@splitbrain.org>
308 * @author Chris Smith <chris@jalakai.co.uk>
309 */
310function footnote(e){
311    var obj = e.target;
312    var id = obj.id.substr(5);
313
314    // get or create the footnote popup div
315    var fndiv = $('insitu__fn');
316    if(!fndiv){
317        fndiv = document.createElement('div');
318        fndiv.id        = 'insitu__fn';
319        fndiv.className = 'insitu-footnote JSpopup dokuwiki';
320
321        // autoclose on mouseout - ignoring bubbled up events
322        addEvent(fndiv,'mouseout',function(e){
323            if(e.target != fndiv){
324                e.stopPropagation();
325                return;
326            }
327            // check if the element was really left
328            if(e.pageX){        // Mozilla
329                var bx1 = findPosX(fndiv);
330                var bx2 = bx1 + fndiv.offsetWidth;
331                var by1 = findPosY(fndiv);
332                var by2 = by1 + fndiv.offsetHeight;
333                var x = e.pageX;
334                var y = e.pageY;
335                if(x > bx1 && x < bx2 && y > by1 && y < by2){
336                    // we're still inside boundaries
337                    e.stopPropagation();
338                    return;
339                }
340            }else{              // IE
341                if(e.offsetX > 0 && e.offsetX < fndiv.offsetWidth-1 &&
342                   e.offsetY > 0 && e.offsetY < fndiv.offsetHeight-1){
343                    // we're still inside boundaries
344                    e.stopPropagation();
345                    return;
346                }
347            }
348            // okay, hide it
349            fndiv.style.display='none';
350        });
351        document.body.appendChild(fndiv);
352    }
353
354    // locate the footnote anchor element
355    var a = $( "fn__"+id );
356    if (!a){ return; }
357
358    // anchor parent is the footnote container, get its innerHTML
359    var content = new String (a.parentNode.innerHTML);
360
361    // strip the leading content anchors and their comma separators
362    content = content.replace(/<a\s.*?href=\".*\#fnt__\d+\".*?<\/a>/gi, '');
363    content = content.replace(/^\s+(,\s+)+/,'');
364
365    // prefix ids on any elements with "insitu__" to ensure they remain unique
366    content = content.replace(/\bid=\"(.*?)\"/gi,'id="insitu__$1');
367
368    // now put the content into the wrapper
369    fndiv.innerHTML = content;
370
371    // position the div and make it visible
372    var x; var y;
373    if(e.pageX){        // Mozilla
374        x = e.pageX;
375        y = e.pageY;
376    }else{              // IE
377        x = e.offsetX;
378        y = e.offsetY;
379    }
380    fndiv.style.position = 'absolute';
381    fndiv.style.left = (x+2)+'px';
382    fndiv.style.top  = (y+2)+'px';
383    fndiv.style.display = '';
384}
385
386/**
387 * Add the event handlers to footnotes
388 *
389 * @author Andreas Gohr <andi@splitbrain.org>
390 */
391addInitEvent(function(){
392    var elems = getElementsByClass('fn_top',null,'a');
393    for(var i=0; i<elems.length; i++){
394        addEvent(elems[i],'mouseover',function(e){footnote(e);});
395    }
396});
397
398/**
399 * Add the edit window size controls
400 */
401function initSizeCtl(ctlid,edid){
402    if(!document.getElementById){ return; }
403
404    var ctl      = $(ctlid);
405    var textarea = $(edid);
406    if(!ctl || !textarea) return;
407
408    var hgt = DokuCookie.getValue('sizeCtl');
409    if(hgt){
410      textarea.style.height = hgt;
411    }else{
412      textarea.style.height = '300px';
413    }
414
415    var l = document.createElement('img');
416    var s = document.createElement('img');
417    var w = document.createElement('img');
418    l.src = DOKU_BASE+'lib/images/larger.gif';
419    s.src = DOKU_BASE+'lib/images/smaller.gif';
420    w.src = DOKU_BASE+'lib/images/wrap.gif';
421    addEvent(l,'click',function(){sizeCtl(edid,100);});
422    addEvent(s,'click',function(){sizeCtl(edid,-100);});
423    addEvent(w,'click',function(){toggleWrap(edid);});
424    ctl.appendChild(l);
425    ctl.appendChild(s);
426    ctl.appendChild(w);
427}
428
429/**
430 * This sets the vertical size of the editbox
431 */
432function sizeCtl(edid,val){
433  var textarea = $(edid);
434  var height = parseInt(textarea.style.height.substr(0,textarea.style.height.length-2));
435  height += val;
436  textarea.style.height = height+'px';
437
438  DokuCookie.setValue('sizeCtl',textarea.style.height);
439}
440
441/**
442 * Toggle the wrapping mode of a textarea
443 *
444 * @author Fluffy Convict <fluffyconvict@hotmail.com>
445 * @link   http://news.hping.org/comp.lang.javascript.archive/12265.html
446 * @author <shutdown@flashmail.com>
447 * @link   https://bugzilla.mozilla.org/show_bug.cgi?id=302710#c2
448 */
449function toggleWrap(edid){
450    var txtarea = $(edid);
451    var wrap = txtarea.getAttribute('wrap');
452    if(wrap && wrap.toLowerCase() == 'off'){
453        txtarea.setAttribute('wrap', 'soft');
454    }else{
455        txtarea.setAttribute('wrap', 'off');
456    }
457    // Fix display for mozilla
458    var parNod = txtarea.parentNode;
459    var nxtSib = txtarea.nextSibling;
460    parNod.removeChild(txtarea);
461    parNod.insertBefore(txtarea, nxtSib);
462}
463
464/**
465 * Handler to close all open Popups
466 */
467function closePopups(){
468  if(!document.getElementById){ return; }
469
470  var divs = document.getElementsByTagName('div');
471  for(var i=0; i < divs.length; i++){
472    if(divs[i].className.indexOf('JSpopup') != -1){
473            divs[i].style.display = 'none';
474    }
475  }
476}
477
478/**
479 * Looks for an element with the ID scroll__here at scrolls to it
480 */
481function scrollToMarker(){
482    var obj = $('scroll__here');
483    if(obj) obj.scrollIntoView();
484}
485
486/**
487 * Looks for an element with the ID focus__this at sets focus to it
488 */
489function focusMarker(){
490    var obj = $('focus__this');
491    if(obj) obj.focus();
492}
493
494/**
495 * Remove messages
496 */
497function cleanMsgArea(){
498    var elems = getElementsByClass('(success|info|error)',document,'div');
499    if(elems){
500        for(var i=0; i<elems.length; i++){
501            elems[i].style.display = 'none';
502        }
503    }
504}
505