var DOKU_BASE   = '/wiki/';var DOKU_TPL    = '/wiki/lib/tpl/unpriviliged/';var DOKU_UHN    = 0;var DOKU_UHC    = 0;LANG = {"searchmedia":"Suche Dateien","keepopen":"Fenster nach Auswahl nicht schlie\u00dfen","hidedetails":"Details ausblenden","nosmblinks":"Das Verlinken von Windows-Freigaben funktioniert nur im Microsoft Internet Explorer.\nDer Link kann jedoch durch Kopieren und Einf\u00fcgen verwendet werden.","linkwiz":"Link-Assistent","linkto":"Link nach:","del_confirm":"Eintrag wirklich l\u00f6schen?","mu_btn":"Mehrere Dateien gleichzeitig hochladen","plugins":[]};
var toolbar = [{"type":"format","title":"Fetter Text","icon":"bold.png","key":"b","open":"**","close":"**"},{"type":"format","title":"Kursiver Text","icon":"italic.png","key":"i","open":"\/\/","close":"\/\/"},{"type":"format","title":"Unterstrichener Text","icon":"underline.png","key":"u","open":"__","close":"__"},{"type":"format","title":"Code Text","icon":"mono.png","key":"c","open":"''","close":"''"},{"type":"format","title":"Durchgestrichener Text","icon":"strike.png","key":"d","open":"<del>","close":"<\/del>"},{"type":"autohead","title":"Gleichzeilige \u00dcberschrift","icon":"hequal.png","key":"8","text":"\u00dcberschrift","mod":0},{"type":"autohead","title":"Untere \u00dcberschrift","icon":"hminus.png","key":"9","text":"\u00dcberschrift","mod":1},{"type":"autohead","title":"Obere \u00dcberschrift","icon":"hplus.png","key":"0","text":"\u00dcberschrift","mod":-1},{"type":"picker","title":"W\u00e4hle die \u00dcberschrift","icon":"h.png","class":"pk_hl","list":[{"type":"format","title":"Level 1 \u00dcberschrift","icon":"h1.png","key":"1","open":"====== ","close":" ======\\n"},{"type":"format","title":"Level 2 \u00dcberschrift","icon":"h2.png","key":"2","open":"===== ","close":" =====\\n"},{"type":"format","title":"Level 3 \u00dcberschrift","icon":"h3.png","key":"3","open":"==== ","close":" ====\\n"},{"type":"format","title":"Level 4 \u00dcberschrift","icon":"h4.png","key":"4","open":"=== ","close":" ===\\n"},{"type":"format","title":"Level 5 \u00dcberschrift","icon":"h5.png","key":"5","open":"== ","close":" ==\\n"}]},{"type":"linkwiz","title":"Interner Link","icon":"link.png","key":"l","open":"[[","close":"]]"},{"type":"format","title":"Externer Link","icon":"linkextern.png","open":"[[","close":"]]","sample":"http:\/\/example.com|Externer Link"},{"type":"formatln","title":"Nummerierter Listenpunkt","icon":"ol.png","open":"  - ","close":"","key":"-"},{"type":"formatln","title":"Listenpunkt","icon":"ul.png","open":"  * ","close":"","key":"."},{"type":"insert","title":"Horizontale Linie","icon":"hr.png","insert":"\\n----\\n"},{"type":"mediapopup","title":"Bilder und andere Dateien hinzuf\u00fcgen","icon":"image.png","url":"lib\/exe\/mediamanager.php?ns=","name":"mediaselect","options":"width=750,height=500,left=20,top=20,scrollbars=yes,resizable=yes"},{"type":"picker","title":"Smileys","icon":"smiley.png","list":{"8-)":"icon_cool.gif","8-O":"icon_eek.gif","8-o":"icon_eek.gif",":-(":"icon_sad.gif",":-)":"icon_smile.gif","=)":"icon_smile2.gif",":-\/":"icon_doubt.gif",":-\\":"icon_doubt2.gif",":-?":"icon_confused.gif",":-D":"icon_biggrin.gif",":-P":"icon_razz.gif",":-o":"icon_surprised.gif",":-O":"icon_surprised.gif",":-x":"icon_silenced.gif",":-X":"icon_silenced.gif",":-|":"icon_neutral.gif",";-)":"icon_wink.gif","^_^":"icon_fun.gif",":?:":"icon_question.gif",":!:":"icon_exclaim.gif","LOL":"icon_lol.gif","FIXME":"fixme.gif","DELETEME":"delete.gif"},"icobase":"smileys"},{"type":"picker","title":"Sonderzeichen","icon":"chars.png","list":["\u00c0","\u00e0","\u00c1","\u00e1","\u00c2","\u00e2","\u00c3","\u00e3","\u00c4","\u00e4","\u01cd","\u01ce","\u0102","\u0103","\u00c5","\u00e5","\u0100","\u0101","\u0104","\u0105","\u00c6","\u00e6","\u0106","\u0107","\u00c7","\u00e7","\u010c","\u010d","\u0108","\u0109","\u010a","\u010b","\u00d0","\u0111","\u00f0","\u010e","\u010f","\u00c8","\u00e8","\u00c9","\u00e9","\u00ca","\u00ea","\u00cb","\u00eb","\u011a","\u011b","\u0112","\u0113","\u0116","\u0117","\u0118","\u0119","\u0122","\u0123","\u011c","\u011d","\u011e","\u011f","\u0120","\u0121","\u0124","\u0125","\u00cc","\u00ec","\u00cd","\u00ed","\u00ce","\u00ee","\u00cf","\u00ef","\u01cf","\u01d0","\u012a","\u012b","\u0130","\u0131","\u012e","\u012f","\u0134","\u0135","\u0136","\u0137","\u0139","\u013a","\u013b","\u013c","\u013d","\u013e","\u0141","\u0142","\u013f","\u0140","\u0143","\u0144","\u00d1","\u00f1","\u0145","\u0146","\u0147","\u0148","\u00d2","\u00f2","\u00d3","\u00f3","\u00d4","\u00f4","\u00d5","\u00f5","\u00d6","\u00f6","\u01d1","\u01d2","\u014c","\u014d","\u0150","\u0151","\u0152","\u0153","\u00d8","\u00f8","\u0154","\u0155","\u0156","\u0157","\u0158","\u0159","\u015a","\u015b","\u015e","\u015f","\u0160","\u0161","\u015c","\u015d","\u0162","\u0163","\u0164","\u0165","\u00d9","\u00f9","\u00da","\u00fa","\u00db","\u00fb","\u00dc","\u00fc","\u01d3","\u01d4","\u016c","\u016d","\u016a","\u016b","\u016e","\u016f","\u01d6","\u01d8","\u01da","\u01dc","\u0172","\u0173","\u0170","\u0171","\u0174","\u0175","\u00dd","\u00fd","\u0178","\u00ff","\u0176","\u0177","\u0179","\u017a","\u017d","\u017e","\u017b","\u017c","\u00de","\u00fe","\u00df","\u0126","\u0127","\u00bf","\u00a1","\u00a2","\u00a3","\u00a4","\u00a5","\u20ac","\u00a6","\u00a7","\u00aa","\u00ac","\u00af","\u00b0","\u00b1","\u00f7","\u2030","\u00bc","\u00bd","\u00be","\u00b9","\u00b2","\u00b3","\u00b5","\u00b6","\u2020","\u2021","\u00b7","\u2022","\u00ba","\u2200","\u2202","\u2203","\u018f","\u0259","\u2205","\u2207","\u2208","\u2209","\u220b","\u220f","\u2211","\u203e","\u2212","\u2217","\u221a","\u221d","\u221e","\u2220","\u2227","\u2228","\u2229","\u222a","\u222b","\u2234","\u223c","\u2245","\u2248","\u2260","\u2261","\u2264","\u2265","\u2282","\u2283","\u2284","\u2286","\u2287","\u2295","\u2297","\u22a5","\u22c5","\u25ca","\u2118","\u2111","\u211c","\u2135","\u2660","\u2663","\u2665","\u2666","\u03b1","\u03b2","\u0393","\u03b3","\u0394","\u03b4","\u03b5","\u03b6","\u03b7","\u0398","\u03b8","\u03b9","\u03ba","\u039b","\u03bb","\u03bc","\u039e","\u03be","\u03a0","\u03c0","\u03c1","\u03a3","\u03c3","\u03a4","\u03c4","\u03c5","\u03a6","\u03c6","\u03c7","\u03a8","\u03c8","\u03a9","\u03c9","\u2605","\u2606","\u260e","\u261a","\u261b","\u261c","\u261d","\u261e","\u261f","\u2639","\u263a","\u2714","\u2718","\u00d7","\u201e","\u201c","\u201d","\u201a","\u2018","\u2019","\u00ab","\u00bb","\u2039","\u203a","\u2014","\u2013","\u2026","\u2190","\u2191","\u2192","\u2193","\u2194","\u21d0","\u21d1","\u21d2","\u21d3","\u21d4","\u00a9","\u2122","\u00ae","\u2032","\u2033","[","]","{","}","~","(",")","%","\u00a7","$","#","|","@"]},{"type":"signature","title":"Unterschrift einf\u00fcgen","icon":"sig.png","key":"y"}];


/* XXXXXXXXXX begin of lib/scripts/helpers.js XXXXXXXXXX */

/**
 * Differrent helper functions
 *
 * @author Ilya Lebedev <ilya@lebedev.net>
 * @license LGPL
 */
//-----------------------------------------------------------------------------
//  Variable/property checks
//-----------------------------------------------------------------------------
/**
 *  Checks if property is undefined
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isUndefined (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'undefined');
}
/**
 *  Checks if property is function
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isFunction (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'function');
}
/**
 *  Checks if property is string
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isString (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'string');
}
/**
 *  Checks if property is number
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isNumber (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'number');
}
/**
 *  Checks if property is the calculable number
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isNumeric (prop /* :Object */) /* :Boolean */ {
  return isNumber(prop)&&!isNaN(prop)&&isFinite(prop);
}
/**
 *  Checks if property is array
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isArray (prop /* :Object */) /* :Boolean */ {
  return (prop instanceof Array);
}
/**
 *  Checks if property is regexp
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isRegExp (prop /* :Object */) /* :Boolean */ {
  return (prop instanceof RegExp);
}
/**
 *  Checks if property is a boolean value
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isBoolean (prop /* :Object */) /* :Boolean */ {
  return ('boolean' == typeof prop);
}
/**
 *  Checks if property is a scalar value (value that could be used as the hash key)
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isScalar (prop /* :Object */) /* :Boolean */ {
  return isNumeric(prop)||isString(prop);
}
/**
 *  Checks if property is empty
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isEmpty (prop /* :Object */) /* :Boolean */ {
  if (isBoolean(prop)) return false;
  if (isRegExp(prop) && new RegExp("").toString() == prop.toString()) return true;
  if (isString(prop) || isNumber(prop)) return !prop;
  if (Boolean(prop)&&false != prop) {
    for (var i in prop) if(prop.hasOwnProperty(i)) return false
  }
  return true;
}

/**
 *  Checks if property is derived from prototype, applies method if it is not exists
 *
 *  @param string property name
 *  @return bool true if prototyped
 *  @access public
 */
if ('undefined' == typeof Object.hasOwnProperty) {
  Object.prototype.hasOwnProperty = function (prop) {
    return !('undefined' == typeof this[prop] || this.constructor && this.constructor.prototype[prop] && this[prop] === this.constructor.prototype[prop]);
  }
}

/**
 * Very simplistic Flash plugin check, probably works for Flash 8 and higher only
 */
function hasFlash(version){
    var ver = 0;
    try{
        if(navigator.plugins != null && navigator.plugins.length > 0){
           ver = navigator.plugins["Shockwave Flash"].description.split(' ')[2].split('.')[0];
        }else{
           var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
           ver = axo.GetVariable("$version").split(' ')[1].split(',')[0];
        }
    }catch(e){ }

    if(ver >= version) return true;
    return false;
}


/* XXXXXXXXXX end of lib/scripts/helpers.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/events.js XXXXXXXXXX */

// written by Dean Edwards, 2005
// with input from Tino Zijdel

// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
    // assign each event handler a unique ID
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
    // create a hash table of event types for the element
    if (!element.events) element.events = {};
    // create a hash table of event handlers for each element/event pair
    var handlers = element.events[type];
    if (!handlers) {
        handlers = element.events[type] = {};
        // store the existing event handler (if there is one)
        if (element["on" + type]) {
            handlers[0] = element["on" + type];
        }
    }
    // store the event handler in the hash table
    handlers[handler.$$guid] = handler;
    // assign a global event handler to do all the work
    element["on" + type] = handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
    // delete the event handler from the hash table
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};

function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(window.event);
    // get a reference to the hash table of event handlers
    var handlers = this.events[event.type];
    // execute each event handler
    for (var i in handlers) {
        if (!handlers.hasOwnProperty(i)) continue;
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

function fixEvent(event) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    // fix target
    event.target = event.srcElement;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};


/**
 * Pseudo event handler to be fired after the DOM was parsed or
 * on window load at last.
 *
 * @author based upon some code by Dean Edwards
 * @author Dean Edwards
 * @link   http://dean.edwards.name/weblog/2006/06/again/
 */
window.fireoninit = function() {
  // quit if this function has already been called
  if (arguments.callee.done) return;
  // flag this function so we don't do the same thing twice
  arguments.callee.done = true;
  // kill the timer
  if (_timer) {
     clearInterval(_timer);
     _timer = null;
  }

  if (typeof window.oninit == 'function') {
        window.oninit();
  }
};

/**
 * Run the fireoninit function as soon as possible after
 * the DOM was loaded, using different methods for different
 * Browsers
 *
 * @author Dean Edwards
 * @link   http://dean.edwards.name/weblog/2006/06/again/
 */
  // for Mozilla
  if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", window.fireoninit, null);
  }

  // for Internet Explorer (using conditional comments)
  /*@cc_on @*/
  /*@if (@_win32)
    document.write("<scr" + "ipt id=\"__ie_init\" defer=\"true\" src=\"//:\"><\/script>");
    var script = document.getElementById("__ie_init");
    script.onreadystatechange = function() {
        if (this.readyState == "complete") {
            window.fireoninit(); // call the onload handler
        }
    };
  /*@end @*/

  // for Safari
  if (/WebKit/i.test(navigator.userAgent)) { // sniff
    var _timer = setInterval(function() {
        if (/loaded|complete/.test(document.readyState)) {
            window.fireoninit(); // call the onload handler
        }
    }, 10);
  }

  // for other browsers
  window.onload = window.fireoninit;


/**
 * This is a pseudo Event that will be fired by the fireoninit
 * function above.
 *
 * Use addInitEvent to bind to this event!
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @see fireoninit()
 */
window.oninit = function(){};

/**
 * Bind a function to the window.init pseudo event
 *
 * @author Simon Willison
 * @see http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 */
function addInitEvent(func) {
  var oldoninit = window.oninit;
  if (typeof window.oninit != 'function') {
    window.oninit = func;
  } else {
    window.oninit = function() {
      oldoninit();
      func();
    };
  }
}

/**
 * Bind variables to a function call creating a closure
 *
 * Use this to circumvent variable scope problems when creating closures
 * inside a loop
 *
 * @author  Adrian Lang <lang@cosmocode.de>
 * @link    http://www.cosmocode.de/en/blog/gohr/2009-10/15-javascript-fixing-the-closure-scope-in-loops
 * @param   functionref fnc - the function to be called
 * @param   mixed - any arguments to be passed to the function
 * @returns functionref
 */
function bind (fnc) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        return fnc.apply(this, args);
    }
}


/* XXXXXXXXXX end of lib/scripts/events.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/cookie.js XXXXXXXXXX */

/**
 * Handles the cookie used by several JavaScript functions
 *
 * Only a single cookie is written and read. You may only save
 * sime name-value pairs - no complex types!
 *
 * You should only use the getValue and setValue methods
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
DokuCookie = {
    data: Array(),
    name: 'DOKU_PREFS',

    /**
     * Save a value to the cookie
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    setValue: function(key,val){
        DokuCookie.init();
        DokuCookie.data[key] = val;

        // prepare expire date
        var now = new Date();
        DokuCookie.fixDate(now);
        now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000); //expire in a year

        //save the whole data array
        var text = '';
        for(var key in DokuCookie.data){
            if (!DokuCookie.data.hasOwnProperty(key)) continue;
            text += '#'+escape(key)+'#'+DokuCookie.data[key];
        }
        DokuCookie.setCookie(DokuCookie.name,text.substr(1),now,DOKU_BASE);
    },

    /**
     * Get a Value from the Cookie
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    getValue: function(key){
        DokuCookie.init();
        return DokuCookie.data[key];
    },

    /**
     * Loads the current set cookie
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    init: function(){
        if(DokuCookie.data.length) return;
        var text  = DokuCookie.getCookie(DokuCookie.name);
        if(text){
            var parts = text.split('#');
            for(var i=0; i<parts.length; i+=2){
                DokuCookie.data[unescape(parts[i])] = unescape(parts[i+1]);
            }
        }
    },

    /**
     * This sets a cookie by JavaScript
     *
     * @link http://www.webreference.com/js/column8/functions.html
     */
    setCookie: function(name, value, expires, path, domain, secure) {
        var curCookie = name + "=" + escape(value) +
            ((expires) ? "; expires=" + expires.toGMTString() : "") +
            ((path) ? "; path=" + path : "") +
            ((domain) ? "; domain=" + domain : "") +
            ((secure) ? "; secure" : "");
        document.cookie = curCookie;
    },

    /**
     * This reads a cookie by JavaScript
     *
     * @link http://www.webreference.com/js/column8/functions.html
     */
    getCookie: function(name) {
        var dc = document.cookie;
        var prefix = name + "=";
        var begin = dc.indexOf("; " + prefix);
        if (begin == -1) {
            begin = dc.indexOf(prefix);
            if (begin !== 0){ return null; }
        } else {
            begin += 2;
        }
        var end = document.cookie.indexOf(";", begin);
        if (end == -1){
            end = dc.length;
        }
        return unescape(dc.substring(begin + prefix.length, end));
    },

    /**
     * This is needed for the cookie functions
     *
     * @link http://www.webreference.com/js/column8/functions.html
     */
    fixDate: function(date) {
        var base = new Date(0);
        var skew = base.getTime();
        if (skew > 0){
            date.setTime(date.getTime() - skew);
        }
    }
};


/* XXXXXXXXXX end of lib/scripts/cookie.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/script.js XXXXXXXXXX */

/**
 * Some of these scripts were taken from wikipedia.org and were modified for DokuWiki
 */

/**
 * Some browser detection
 */
var clientPC  = navigator.userAgent.toLowerCase(); // Get client info
var is_macos  = navigator.appVersion.indexOf('Mac') != -1;
var is_gecko  = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) &&
                (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
var is_safari = ((clientPC.indexOf('AppleWebKit')!=-1) && (clientPC.indexOf('spoofer')==-1));
var is_khtml  = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
if (clientPC.indexOf('opera')!=-1) {
    var is_opera = true;
    var is_opera_preseven = (window.opera && !document.childNodes);
    var is_opera_seven = (window.opera && document.childNodes);
}

/**
 * Handy shortcut to document.getElementById
 *
 * This function was taken from the prototype library
 *
 * @link http://prototype.conio.net/
 */
function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
}

/**
 * Simple function to check if a global var is defined
 *
 * @author Kae Verens
 * @link http://verens.com/archives/2005/07/25/isset-for-javascript/#comment-2835
 */
function isset(varname){
  return(typeof(window[varname])!='undefined');
}

/**
 * Select elements by their class name
 *
 * @author Dustin Diaz <dustin [at] dustindiaz [dot] com>
 * @link   http://www.dustindiaz.com/getelementsbyclass/
 */
function getElementsByClass(searchClass,node,tag) {
    var classElements = new Array();
    if ( node == null )
        node = document;
    if ( tag == null )
        tag = '*';
    var els = node.getElementsByTagName(tag);
    var elsLen = els.length;
    var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
    for (var i = 0, j = 0; i < elsLen; i++) {
        if ( pattern.test(els[i].className) ) {
            classElements[j] = els[i];
            j++;
        }
    }
    return classElements;
}

/**
 * Get the X offset of the top left corner of the given object
 *
 * @link http://www.quirksmode.org/index.html?/js/findpos.html
 */
function findPosX(object){
  var curleft = 0;
  var obj = $(object);
  if (obj.offsetParent){
    while (obj.offsetParent){
      curleft += obj.offsetLeft;
      obj = obj.offsetParent;
    }
  }
  else if (obj.x){
    curleft += obj.x;
  }
  return curleft;
} //end findPosX function

/**
 * Get the Y offset of the top left corner of the given object
 *
 * @link http://www.quirksmode.org/index.html?/js/findpos.html
 */
function findPosY(object){
  var curtop = 0;
  var obj = $(object);
  if (obj.offsetParent){
    while (obj.offsetParent){
      curtop += obj.offsetTop;
      obj = obj.offsetParent;
    }
  }
  else if (obj.y){
    curtop += obj.y;
  }
  return curtop;
} //end findPosY function

/**
 * Escape special chars in JavaScript
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function jsEscape(text){
    var re=new RegExp("\\\\","g");
    text=text.replace(re,"\\\\");
    re=new RegExp("'","g");
    text=text.replace(re,"\\'");
    re=new RegExp('"',"g");
    text=text.replace(re,'&quot;');
    re=new RegExp("\\\\\\\\n","g");
    text=text.replace(re,"\\n");
    return text;
}

/**
 * This function escapes some special chars
 * @deprecated by above function
 */
function escapeQuotes(text) {
  var re=new RegExp("'","g");
  text=text.replace(re,"\\'");
  re=new RegExp('"',"g");
  text=text.replace(re,'&quot;');
  re=new RegExp("\\n","g");
  text=text.replace(re,"\\n");
  return text;
}

/**
 * Adds a node as the first childenode to the given parent
 *
 * @see appendChild()
 */
function prependChild(parent,element) {
    if(!parent.firstChild){
        parent.appendChild(element);
    }else{
        parent.insertBefore(element,parent.firstChild);
    }
}

/**
 * Prints a animated gif to show the search is performed
 *
 * Because we need to modify the DOM here before the document is loaded
 * and parsed completely we have to rely on document.write()
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function showLoadBar(){

  document.write('<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
                 'width="150" height="12" alt="..." />');

  /* this does not work reliable in IE
  obj = $(id);

  if(obj){
    obj.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" '+
                    'width="150" height="12" alt="..." />';
    obj.style.display="block";
  }
  */
}

/**
 * Disables the animated gif to show the search is done
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function hideLoadBar(id){
  obj = $(id);
  if(obj) obj.style.display="none";
}

/**
 * Adds the toggle switch to the TOC
 */
function addTocToggle() {
    if(!document.getElementById) return;
    var header = $('toc__header');
    if(!header) return;
    var toc = $('toc__inside');

    var obj          = document.createElement('span');
    obj.id           = 'toc__toggle';
    obj.style.cursor = 'pointer';
    if (toc && toc.style.display == 'none') {
        obj.innerHTML    = '<span>+</span>';
        obj.className    = 'toc_open';
    } else {
        obj.innerHTML    = '<span>&minus;</span>';
        obj.className    = 'toc_close';
    }

    prependChild(header,obj);
    obj.parentNode.onclick = toggleToc;
    try {
       obj.parentNode.style.cursor = 'pointer';
       obj.parentNode.style.cursor = 'hand';
    }catch(e){}
}

/**
 * This toggles the visibility of the Table of Contents
 */
function toggleToc() {
  var toc = $('toc__inside');
  var obj = $('toc__toggle');
  if(toc.style.display == 'none') {
    toc.style.display   = '';
    obj.innerHTML       = '<span>&minus;</span>';
    obj.className       = 'toc_close';
  } else {
    toc.style.display   = 'none';
    obj.innerHTML       = '<span>+</span>';
    obj.className       = 'toc_open';
  }
}

/**
 * Display an insitu footnote popup
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @author Chris Smith <chris@jalakai.co.uk>
 */
function footnote(e){
    var obj = e.target;
    var id = obj.id.substr(5);

    // get or create the footnote popup div
    var fndiv = $('insitu__fn');
    if(!fndiv){
        fndiv = document.createElement('div');
        fndiv.id        = 'insitu__fn';
        fndiv.className = 'insitu-footnote JSpopup dokuwiki';

        // autoclose on mouseout - ignoring bubbled up events
        addEvent(fndiv,'mouseout',function(e){
            if(e.target != fndiv){
                e.stopPropagation();
                return;
            }
            // check if the element was really left
            if(e.pageX){        // Mozilla
                var bx1 = findPosX(fndiv);
                var bx2 = bx1 + fndiv.offsetWidth;
                var by1 = findPosY(fndiv);
                var by2 = by1 + fndiv.offsetHeight;
                var x = e.pageX;
                var y = e.pageY;
                if(x > bx1 && x < bx2 && y > by1 && y < by2){
                    // we're still inside boundaries
                    e.stopPropagation();
                    return;
                }
            }else{              // IE
                if(e.offsetX > 0 && e.offsetX < fndiv.offsetWidth-1 &&
                   e.offsetY > 0 && e.offsetY < fndiv.offsetHeight-1){
                    // we're still inside boundaries
                    e.stopPropagation();
                    return;
                }
            }
            // okay, hide it
            fndiv.style.display='none';
        });
        document.body.appendChild(fndiv);
    }

    // locate the footnote anchor element
    var a = $( "fn__"+id );
    if (!a){ return; }

    // anchor parent is the footnote container, get its innerHTML
    var content = new String (a.parentNode.parentNode.innerHTML);

    // strip the leading content anchors and their comma separators
    content = content.replace(/<sup>.*<\/sup>/gi, '');
    content = content.replace(/^\s+(,\s+)+/,'');

    // prefix ids on any elements with "insitu__" to ensure they remain unique
    content = content.replace(/\bid=\"(.*?)\"/gi,'id="insitu__$1');

    // now put the content into the wrapper
    fndiv.innerHTML = content;

    // position the div and make it visible
    var x; var y;
    if(e.pageX){        // Mozilla
        x = e.pageX;
        y = e.pageY;
    }else{              // IE
        x = e.offsetX;
        y = e.offsetY;
    }
    fndiv.style.position = 'absolute';
    fndiv.style.left = (x+2)+'px';
    fndiv.style.top  = (y+2)+'px';
    fndiv.style.display = '';
}

/**
 * Add the event handlers to footnotes
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
addInitEvent(function(){
    var elems = getElementsByClass('fn_top',null,'a');
    for(var i=0; i<elems.length; i++){
        addEvent(elems[i],'mouseover',function(e){footnote(e);});
    }
});

/**
 * Add the edit window size controls
 */
function initSizeCtl(ctlid,edid){
    if(!document.getElementById){ return; }

    var ctl      = $(ctlid);
    var textarea = $(edid);
    if(!ctl || !textarea) return;

    var hgt = DokuCookie.getValue('sizeCtl');
    if(hgt){
      textarea.style.height = hgt;
    }else{
      textarea.style.height = '300px';
    }

    var wrp = DokuCookie.getValue('wrapCtl');
    if(wrp){
      setWrap(textarea, wrp);
    } // else use default value

    var l = document.createElement('img');
    var s = document.createElement('img');
    var w = document.createElement('img');
    l.src = DOKU_BASE+'lib/images/larger.gif';
    s.src = DOKU_BASE+'lib/images/smaller.gif';
    w.src = DOKU_BASE+'lib/images/wrap.gif';
    addEvent(l,'click',function(){sizeCtl(edid,100);});
    addEvent(s,'click',function(){sizeCtl(edid,-100);});
    addEvent(w,'click',function(){toggleWrap(edid);});
    ctl.appendChild(l);
    ctl.appendChild(s);
    ctl.appendChild(w);
}

/**
 * This sets the vertical size of the editbox
 */
function sizeCtl(edid,val){
  var textarea = $(edid);
  var height = parseInt(textarea.style.height.substr(0,textarea.style.height.length-2));
  height += val;
  textarea.style.height = height+'px';

  DokuCookie.setValue('sizeCtl',textarea.style.height);
}

/**
 * Toggle the wrapping mode of a textarea
 */
function toggleWrap(edid){
    var textarea = $(edid);
    var wrap = textarea.getAttribute('wrap');
    if(wrap && wrap.toLowerCase() == 'off'){
        setWrap(textarea, 'soft');
    }else{
        setWrap(textarea, 'off');
    }

    DokuCookie.setValue('wrapCtl',textarea.getAttribute('wrap'));
}

/**
 * Set the wrapping mode of a textarea
 *
 * @author Fluffy Convict <fluffyconvict@hotmail.com>
 * @author <shutdown@flashmail.com>
 * @link   http://news.hping.org/comp.lang.javascript.archive/12265.html
 * @link   https://bugzilla.mozilla.org/show_bug.cgi?id=41464
 */
function setWrap(textarea, wrapAttrValue){
    textarea.setAttribute('wrap', wrapAttrValue);

    // Fix display for mozilla
    var parNod = textarea.parentNode;
    var nxtSib = textarea.nextSibling;
    parNod.removeChild(textarea);
    parNod.insertBefore(textarea, nxtSib);
}

/**
 * Handler to close all open Popups
 */
function closePopups(){
  if(!document.getElementById){ return; }

  var divs = document.getElementsByTagName('div');
  for(var i=0; i < divs.length; i++){
    if(divs[i].className.indexOf('JSpopup') != -1){
            divs[i].style.display = 'none';
    }
  }
}

/**
 * Looks for an element with the ID scroll__here at scrolls to it
 */
function scrollToMarker(){
    var obj = $('scroll__here');
    if(obj) obj.scrollIntoView();
}

/**
 * Looks for an element with the ID focus__this at sets focus to it
 */
function focusMarker(){
    var obj = $('focus__this');
    if(obj) obj.focus();
}

/**
 * Remove messages
 */
function cleanMsgArea(){
    var elems = getElementsByClass('(success|info|error)',document,'div');
    if(elems){
        for(var i=0; i<elems.length; i++){
            elems[i].style.display = 'none';
        }
    }
}

/**
 * disable multiple revisions checkboxes if two are checked
 *
 * @author Anika Henke <anika@selfthinker.org>
 */
addInitEvent(function(){
    var revForm = $('page__revisions');
    if (!revForm) return;
    var elems = revForm.elements;
    var countTicks = 0;
    for (var i=0; i<elems.length; i++) {
        var input1 = elems[i];
        if (input1.type=='checkbox') {
            addEvent(input1,'click',function(e){
                if (this.checked) countTicks++;
                else countTicks--;
                for (var j=0; j<elems.length; j++) {
                    var input2 = elems[j];
                    if (countTicks >= 2) input2.disabled = (input2.type=='checkbox' && !input2.checked);
                    else input2.disabled = (input2.type!='checkbox');
                }
            });
            input1.checked = false; // chrome reselects on back button which messes up the logic
        } else if(input1.type=='submit'){
            input1.disabled = true;
        }
    }
});

/**
 * Add the event handler to the actiondropdown
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
addInitEvent(function(){
    var selector = $('action__selector');
    if(!selector) return;

    addEvent(selector,'change',function(e){
        this.form.submit();
    });

    $('action__selectorbtn').style.display = 'none';
});

/**
 * Display error for Windows Shares on browsers other than IE
 *
 * @author Michael Klier <chi@chimeric.de>
 */
function checkWindowsShares() {
    if(!LANG['nosmblinks']) return true;
    var elems = getElementsByClass('windows',document,'a');
    if(elems){
        for(var i=0; i<elems.length; i++){
            var share = elems[i];
            addEvent(share,'click',function(){
                if(document.all == null) {
                    alert(LANG['nosmblinks']);
                }
            });
        }
    }
}

/**
 * Add the event handler for the Windows Shares check
 *
 * @author Michael Klier <chi@chimeric.de>
 */
addInitEvent(function(){
    checkWindowsShares();
});

/**
 * Highlight the section when hovering over the appropriate section edit button
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
addInitEvent(function(){
    var break_classes = new RegExp('secedit|toc|page');
    var btns = getElementsByClass('btn_secedit',document,'form');
    for(var i=0; i<btns.length; i++){
        addEvent(btns[i],'mouseover',function(e){
            var tgt = e.target;
            if(tgt.form) tgt = tgt.form;
            tgt = tgt.parentNode.previousSibling;
            if(tgt.nodeName != "DIV") tgt = tgt.previousSibling;
            while(!break_classes.test(tgt.className)) {
                tgt.className += ' section_highlight';
                if (tgt.tagName == 'H1') break;
                tgt = (tgt.previousSibling != null) ? tgt.previousSibling : tgt.parentNode;
            }
        });

        addEvent(btns[i],'mouseout',function(e){
            var secs = getElementsByClass('section_highlight');
            for(var j=0; j<secs.length; j++){
                secs[j].className = secs[j].className.replace(/section_highlight/,'');
            }
            var secs = getElementsByClass('section_highlight');
        });
    }
});


/* XXXXXXXXXX end of lib/scripts/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/tw-sack.js XXXXXXXXXX */

/* Simple AJAX Code-Kit (SACK) */
/* ©2005 Gregory Wild-Smith */
/* www.twilightuniverse.com */
/* Software licenced under a modified X11 licence, see documentation or authors website for more details */

function sack(file){
  this.AjaxFailedAlert = "Your browser does not support the enhanced functionality of this website, and therefore you will have an experience that differs from the intended one.\n";
  this.requestFile = file;
  this.method = "POST";
  this.URLString = "";
  this.encodeURIString = true;
  this.execute = false;

  this.onLoading = function() { };
  this.onLoaded = function() { };
  this.onInteractive = function() { };
  this.onCompletion = function() { };
  this.afterCompletion = function() { };

  this.createAJAX = function() {
    try {
      this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (err) {
        this.xmlhttp = null;
      }
    }
    if(!this.xmlhttp && typeof XMLHttpRequest != "undefined"){
      this.xmlhttp = new XMLHttpRequest();
    }
    if (!this.xmlhttp){
      this.failed = true;
    }
  };

  this.setVar = function(name, value){
    if (this.URLString.length < 3){
      this.URLString = name + "=" + value;
    } else {
      this.URLString += "&" + name + "=" + value;
    }
  };

  this.encVar = function(name, value){
    var varString = encodeURIComponent(name) + "=" + encodeURIComponent(value);
  return varString;
  };

  this.encodeURLString = function(string){
    varArray = string.split('&');
    for (i = 0; i < varArray.length; i++){
      urlVars = varArray[i].split('=');
      if (urlVars[0].indexOf('amp;') != -1){
        urlVars[0] = urlVars[0].substring(4);
      }
      varArray[i] = this.encVar(urlVars[0],urlVars[1]);
    }
  return varArray.join('&');
  };

  this.runResponse = function(){
    eval(this.response);
  };

  this.runAJAX = function(urlstring){
    this.responseStatus = new Array(2);
    if(this.failed && this.AjaxFailedAlert){
      alert(this.AjaxFailedAlert);
    } else {
      if (urlstring){
        if (this.URLString.length){
          this.URLString = this.URLString + "&" + urlstring;
        } else {
          this.URLString = urlstring;
        }
      }
      if (this.encodeURIString){
        var timeval = new Date().getTime();
        this.URLString = this.encodeURLString(this.URLString);
        this.setVar("rndval", timeval);
      }
      if (this.element) { this.elementObj = document.getElementById(this.element); }
      if (this.xmlhttp) {
        var self = this;
        if (this.method == "GET") {
          var totalurlstring = this.requestFile + "?" + this.URLString;
          this.xmlhttp.open(this.method, totalurlstring, true);
        } else {
          this.xmlhttp.open(this.method, this.requestFile, true);
        }
        if (this.method == "POST"){
          try {
             this.xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
          } catch (e) {}
        }

        this.xmlhttp.onreadystatechange = function() {
          switch (self.xmlhttp.readyState){
            case 1:
              self.onLoading();
            break;
            case 2:
              self.onLoaded();
            break;
            case 3:
              self.onInteractive();
            break;
            case 4:
              self.response = self.xmlhttp.responseText;
              self.responseXML = self.xmlhttp.responseXML;
              self.responseStatus[0] = self.xmlhttp.status;
              self.responseStatus[1] = self.xmlhttp.statusText;
              self.onCompletion();
              if(self.execute){ self.runResponse(); }
              if (self.elementObj) {
                var elemNodeName = self.elementObj.nodeName;
                elemNodeName.toLowerCase();
                if (elemNodeName == "input" || elemNodeName == "select" || elemNodeName == "option" || elemNodeName == "textarea"){
                  self.elementObj.value = self.response;
                } else {
                  self.elementObj.innerHTML = self.response;
                }
              }
              self.afterCompletion();
              self.URLString = "";
            break;
          }
        };
        this.xmlhttp.send(this.URLString);
      }
    }
  };
this.createAJAX();
}


/* XXXXXXXXXX end of lib/scripts/tw-sack.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/ajax.js XXXXXXXXXX */

/**
 * AJAX functions for the pagename quicksearch
 *
 * We're using a global object with self referencing methods
 * here to make callbacks work
 *
 * @license  GPL2 (http://www.gnu.org/licenses/gpl.html)
 * @author   Andreas Gohr <andi@splitbrain.org>
 */

//prepare class
function ajax_qsearch_class(){
  this.sack = null;
  this.inObj = null;
  this.outObj = null;
  this.timer = null;
}

//create global object and add functions
var ajax_qsearch = new ajax_qsearch_class();
ajax_qsearch.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
ajax_qsearch.sack.AjaxFailedAlert = '';
ajax_qsearch.sack.encodeURIString = false;

ajax_qsearch.init = function(inID,outID){
  ajax_qsearch.inObj  = document.getElementById(inID);
  ajax_qsearch.outObj = document.getElementById(outID);

  // objects found?
  if(ajax_qsearch.inObj === null){ return; }
  if(ajax_qsearch.outObj === null){ return; }

  // attach eventhandler to search field
  addEvent(ajax_qsearch.inObj,'keyup',ajax_qsearch.call);

  // attach eventhandler to output field
  addEvent(ajax_qsearch.outObj,'click',function(){ ajax_qsearch.outObj.style.display='none'; });
};

ajax_qsearch.clear = function(){
  ajax_qsearch.outObj.style.display = 'none';
  ajax_qsearch.outObj.innerHTML = '';
  if(ajax_qsearch.timer !== null){
    window.clearTimeout(ajax_qsearch.timer);
    ajax_qsearch.timer = null;
  }
};

ajax_qsearch.exec = function(){
  ajax_qsearch.clear();
  var value = ajax_qsearch.inObj.value;
  if(value === ''){ return; }
  ajax_qsearch.sack.runAJAX('call=qsearch&q='+encodeURI(value));
};

ajax_qsearch.sack.onCompletion = function(){
  var data = ajax_qsearch.sack.response;
  if(data === ''){ return; }

  ajax_qsearch.outObj.innerHTML = data;
  ajax_qsearch.outObj.style.display = 'block';
};

ajax_qsearch.call = function(){
  ajax_qsearch.clear();
  ajax_qsearch.timer = window.setTimeout("ajax_qsearch.exec()",500);
};



/* XXXXXXXXXX end of lib/scripts/ajax.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/index.js XXXXXXXXXX */

/**
 * Javascript for index view
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */

var index = {

     /**
     * Delay in ms before showing the throbber.
     * Used to skip the throbber for fast AJAX calls.
     */
    throbber_delay: 500,

    /**
     * Attach event handlers to all "folders" below the given element
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    treeattach: function(obj){
        if(!obj) return;

        var items = getElementsByClass('idx_dir',obj,'a');
        for(var i=0; i<items.length; i++){
            var elem = items[i];

            // attach action to make the link clickable by AJAX
            addEvent(elem,'click',function(e){ return index.toggle(e,this); });

            // get the listitem the elem belongs to
            var listitem = elem.parentNode;
            while (listitem.tagName != 'LI') {
              listitem = listitem.parentNode;
            }
            //when there are uls under this listitem mark this listitem as opened
            if (listitem.getElementsByTagName('ul').length) {
              listitem.open = true;
            }
        }
    },

    /**
     * Open or close a subtree using AJAX
     * The contents of subtrees are "cached" untill the page is reloaded.
     * A "loading" indicator is shown only when the AJAX call is slow.
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     * @author Ben Coburn <btcoburn@silicodon.net>
     */
    toggle: function(e,clicky){
        var listitem = clicky.parentNode.parentNode;

        listitem.open = !listitem.open;
        // listitem.open represents now the action to be done

        // if already open, close by removing the sublist
        var sublists = listitem.getElementsByTagName('ul');
        if(!listitem.open){
            if (sublists.length) {
              sublists[0].style.display='none';
            }
            listitem.className='closed';
            e.preventDefault();
            return false;
        }

        // just show if already loaded
        if(sublists.length && listitem.open){
            sublists[0].style.display='';
            listitem.className='open';
            e.preventDefault();
            return false;
        }

        // prepare an AJAX call to fetch the subtree
        var ajax = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        //prepare the new ul
        var ul = document.createElement('ul');
        ul.className = 'idx';
        timeout = window.setTimeout(function(){
            // show the throbber as needed
            if (listitem.open) {
              ul.innerHTML = '<li><img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="loading..." title="loading..." /></li>';
              listitem.appendChild(ul);
              listitem.className='open';
            }
        }, this.throbber_delay);
        ajax.elementObj = ul;
        ajax.afterCompletion = function(){
            window.clearTimeout(timeout);
            index.treeattach(ul);
            if (listitem.className!='open') {
              if (!listitem.open) {
                ul.style.display='none';
              }
              listitem.appendChild(ul);
              if (listitem.open) {
                listitem.className='open';
              }
            }
        };
        ajax.runAJAX(clicky.search.substr(1)+'&call=index');
        e.preventDefault();
        return false;
    }
};


addInitEvent(function(){
    index.treeattach($('index__tree'));
});


/* XXXXXXXXXX end of lib/scripts/index.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/drag.js XXXXXXXXXX */

/**
 * Makes a DOM object draggable
 *
 * This is currently for movable DOM dialogs only. If needed it could be
 * extended to execute callbacks on special events...
 *
 * @link http://nofunc.org/Drag_Drop/
 */
var drag = {
    obj: null,
    handle: null,
    oX: 0,  // object X position
    oY: 0,  // object Y position
    eX: 0,  // event X delta
    eY: 0,  // event Y delta

    /**
     * Attaches the needed handlers to the given object
     *
     * This can be called for multiple objects, the right one is later
     * determined from the event itself. The handle is optional
     *
     * @param DOMObject obj    The object that should be draggable
     * @param DOMObject handle A handle on which the obj can be dragged
     */
    attach: function (obj,handle) {
        if(handle){
            handle.dragobject = obj;
            addEvent($(handle),'mousedown',drag.start);
        }else{
            addEvent($(obj),'mousedown',drag.start);
        }
    },

    /**
     * Starts the dragging operation
     */
    start: function (e){
        drag.handle = e.target;
        if(drag.handle.dragobject){
            drag.obj = drag.handle.dragobject;
        }else{
            drag.obj = drag.handle;
        }

        drag.handle.className += ' ondrag';
        drag.obj.className    += ' ondrag';

        drag.oX = parseInt(drag.obj.style.left);
        drag.oY = parseInt(drag.obj.style.top);
        drag.eX = drag.evX(e);
        drag.eY = drag.evY(e);

        addEvent(document,'mousemove',drag.drag);
        addEvent(document,'mouseup',drag.stop);

        e.preventDefault();
        e.stopPropagation();
        return false;
    },

    /**
     * Ends the dragging operation
     */
    stop: function(){
        drag.handle.className = drag.handle.className.replace(/ ?ondrag/,'');
        drag.obj.className    = drag.obj.className.replace(/ ?ondrag/,'');
        removeEvent(document,'mousemove',drag.drag);
        removeEvent(document,'mouseup',drag.stop);
        drag.obj = null;
        drag.handle = null;
    },

    /**
     * Moves the object during the dragging operation
     */
    drag: function(e) {
        if(drag.obj) {
            drag.obj.style.top  = (drag.evY(e)+drag.oY-drag.eY+'px');
            drag.obj.style.left = (drag.evX(e)+drag.oX-drag.eX+'px');
        }
    },

    /**
     * Returns the X position of the given event.
     */
    evX: function(e){
        return (e.pageX) ? e.pageX : e.clientX + document.body.scrollTop; //fixme shouldn't this be scrollLeft?
    },

    /**
     * Returns the Y position of the given event.
     */
    evY: function(e){
        return (e.pageY) ? e.pageY : e.clientY + document.body.scrollTop;
    }

};



/* XXXXXXXXXX end of lib/scripts/drag.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/textselection.js XXXXXXXXXX */

/**
 * Text selection related functions.
 */

/**
 * selection prototype
 *
 * Object that capsulates the selection in a textarea. Returned by getSelection.
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function selection_class(){
    this.start     = 0;
    this.end       = 0;
    this.obj       = null;
    this.rangeCopy = null;
    this.scroll    = 0;
    this.fix       = 0;

    this.getLength = function(){
        return this.end - this.start;
    };

    this.getText = function(){
        if(!this.obj) return '';
        return this.obj.value.substring(this.start,this.end);
    }
}

/**
 * Get current selection/cursor position in a given textArea
 *
 * @link   http://groups.drupal.org/node/1210
 * @author Andreas Gohr <andi@splitbrain.org>
 * @link   http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html
 * @returns object - a selection object
 */
function getSelection(textArea) {
    var sel = new selection_class();

    sel.obj   = textArea;
    sel.start = textArea.value.length;
    sel.end   = textArea.value.length;

    textArea.focus();
    if(document.getSelection) {          // Mozilla et al.
        sel.start  = textArea.selectionStart;
        sel.end    = textArea.selectionEnd;
        sel.scroll = textArea.scrollTop;
    } else if(document.selection) {      // MSIE
        /*
         * This huge lump of code is neccessary to work around two MSIE bugs:
         *
         * 1. Selections trim newlines at the end of the code
         * 2. Selections count newlines as two characters
         */

        // The current selection
        sel.rangeCopy = document.selection.createRange().duplicate();

        var before_range = document.body.createTextRange();
        before_range.moveToElementText(textArea);                    // Selects all the text
        before_range.setEndPoint("EndToStart", sel.rangeCopy);     // Moves the end where we need it

        var before_finished = false, selection_finished = false;
        var before_text, selection_text;
        // Load the text values we need to compare
        before_text  = before_range.text;
        selection_text = sel.rangeCopy.text;

        sel.start = before_text.length;
        sel.end   = sel.start + selection_text.length;

        // Check each range for trimmed newlines by shrinking the range by 1 character and seeing
        // if the text property has changed.  If it has not changed then we know that IE has trimmed
        // a \r\n from the end.
        do {
            if (!before_finished) {
                if (before_range.compareEndPoints("StartToEnd", before_range) == 0) {
                    before_finished = true;
                } else {
                    before_range.moveEnd("character", -1);
                    if (before_range.text == before_text) {
                        sel.start += 2;
                        sel.end += 2;
                    } else {
                        before_finished = true;
                    }
                }
            }
            if (!selection_finished) {
                if (sel.rangeCopy.compareEndPoints("StartToEnd", sel.rangeCopy) == 0) {
                    selection_finished = true;
                } else {
                    sel.rangeCopy.moveEnd("character", -1);
                    if (sel.rangeCopy.text == selection_text) {
                        sel.end += 2;
                    } else {
                        selection_finished = true;
                    }
                }
            }
        } while ((!before_finished || !selection_finished));


        // count number of newlines in str to work around stupid IE selection bug
        var countNL = function(str) {
            var m = str.split("\r\n");
            if (!m || !m.length) return 0;
            return m.length-1;
        };
        sel.fix = countNL(sel.obj.value.substring(0,sel.start));

    }
    return sel;
}

/**
 * Set the selection
 *
 * You need to get a selection object via getSelection() first, then modify the
 * start and end properties and pass it back to this function.
 *
 * @link http://groups.drupal.org/node/1210
 * @author Andreas Gohr <andi@splitbrain.org>
 * @param object selection - a selection object as returned by getSelection()
 */
function setSelection(selection){
    if(document.getSelection){ // FF
        // what a pleasure in FF ;)
        selection.obj.setSelectionRange(selection.start,selection.end);
        if(selection.scroll) selection.obj.scrollTop = selection.scroll;
    } else if(document.selection) { // IE
        selection.rangeCopy.collapse(true);
        selection.rangeCopy.moveStart('character',selection.start - selection.fix);
        selection.rangeCopy.moveEnd('character',selection.end - selection.start);
        selection.rangeCopy.select();
    }
}

/**
 * Inserts the given text at the current cursor position or replaces the current
 * selection
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @param string text          - the new text to be pasted
 * @param objct  selecttion    - selection object returned by getSelection
 * @param int    opts.startofs - number of charcters at the start to skip from new selection
 * @param int    opts.endofs   - number of characters at the end to skip from new selection
 * @param bool   opts.nosel    - set true if new text should not be selected
 */
function pasteText(selection,text,opts){
    if(!opts) opts = {};
    // replace the content

    selection.obj.value =
        selection.obj.value.substring(0, selection.start) + text +
        selection.obj.value.substring(selection.end, selection.obj.value.length);

    // set new selection
    selection.end = selection.start + text.length;

    // modify the new selection if wanted
    if(opts.startofs) selection.start += opts.startofs;
    if(opts.endofs)   selection.end   -= opts.endofs;

    // no selection wanted? set cursor to end position
    if(opts.nosel) selection.start = selection.end;

    setSelection(selection);
}


/**
 * Format selection
 *
 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead
 * of selection if there is none.
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function insertTags(textAreaID, tagOpen, tagClose, sampleText){
    var txtarea = $(textAreaID);

    var selection = getSelection(txtarea);
    var text = selection.getText();
    var opts;

    // don't include trailing space in selection
    if(text.charAt(text.length - 1) == ' '){
        selection.end--;
        text = selection.getText();
    }

    if(!text){
        // nothing selected, use the sample text and select it
        text = sampleText;
        opts = {
            startofs: tagOpen.length,
            endofs: tagClose.length
        };
    }else{
        // place cursor at the end
        opts = {
            nosel: true
        };
    }

    // surround with tags
    text = tagOpen + text + tagClose;

    // do it
    pasteText(selection,text,opts);
}

/**
 * Wraps around pasteText() for backward compatibility
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function insertAtCarret(textAreaID, text){
    var txtarea = $(textAreaID);
    var selection = getSelection(txtarea);
    pasteText(selection,text,{nosel: true});
}



/* XXXXXXXXXX end of lib/scripts/textselection.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/toolbar.js XXXXXXXXXX */


// used to identify pickers
var pickercounter=0;

/**
 * Create a toolbar
 *
 * @param  string tbid ID of the element where to insert the toolbar
 * @param  string edid ID of the editor textarea
 * @param  array  tb   Associative array defining the buttons
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function initToolbar(tbid,edid,tb){
    var toolbar = $(tbid);
    if(!toolbar) return;
    var edit = $(edid);
    if(!edit) return;
    if(edit.readOnly) return;

    //empty the toolbar area:
    toolbar.innerHTML='';

    var cnt = tb.length;
    for(var i=0; i<cnt; i++){
        var actionFunc;

        // create new button
        var btn = createToolButton(tb[i]['icon'],
                                   tb[i]['title'],
                                   tb[i]['key']);


        // type is a tb function -> assign it as onclick
        actionFunc = 'tb_'+tb[i]['type'];
        if( isFunction(window[actionFunc]) ){
            addEvent(btn,'click', bind(window[actionFunc],btn,tb[i],edid));
            toolbar.appendChild(btn);
            continue;
        }

        // type is a init function -> execute it
        actionFunc = 'addBtnAction'+tb[i]['type'].charAt(0).toUpperCase()+tb[i]['type'].substring(1);
        if( isFunction(window[actionFunc]) ){
            if(window[actionFunc](btn, tb[i], edid)){
                toolbar.appendChild(btn);
            }
            continue;
        }

        alert('unknown toolbar type: '+tb[i]['type']+'  '+actionFunc);
    } // end for

}

/**
 * Button action for format buttons
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @author Gabriel Birke <birke@d-scribe.de>
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function tb_format(btn, props, edid) {
    var sample = props['title'];
    if(props['sample']){
        sample = props['sample'];
    }
    insertTags(edid,
               fixtxt(props['open']),
               fixtxt(props['close']),
               fixtxt(sample));
    pickerClose();
    return false;
}

/**
 * Button action for format buttons
 *
 * This works exactly as tb_format() except that, if multiple lines
 * are selected, each line will be formatted seperately
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @author Gabriel Birke <birke@d-scribe.de>
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function tb_formatln(btn, props, edid) {
    var sample = props['title'];
    if(props['sample']){
        sample = props['sample'];
    }
    sample = fixtxt(sample);

    props['open']  = fixtxt(props['open']);
    props['close'] = fixtxt(props['close']);

    // is something selected?
    var opts;
    var selection = getSelection($(edid));
    if(selection.getLength()){
        sample = selection.getText();
        opts = {nosel: true};
    }else{
        opts = {
            startofs: props['open'].length,
            endofs: props['close'].length
        };
    }

    sample = sample.split("\n").join(props['close']+"\n"+props['open']);
    sample = props['open']+sample+props['close'];

    pasteText(selection,sample,opts);

    pickerClose();
    return false;
}

/**
 * Button action for insert buttons
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @author Gabriel Birke <birke@d-scribe.de>
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function tb_insert(btn, props, edid) {
    insertAtCarret(edid,fixtxt(props['insert']));
    pickerClose();
    return false;
}

/**
 * Button action for the media popup
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function tb_mediapopup(btn, props, edid) {
    window.open(
        DOKU_BASE+props['url']+encodeURIComponent(NS),
        props['name'],
        props['options']);
    return false;
}

/**
 * Button action for automatic headlines
 *
 * Insert a new headline based on the current section level
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function tb_autohead(btn, props, edid){
    var lvl = currentHeadlineLevel(edid);

    // determine new level
    lvl += props['mod'];
    if(lvl < 1) lvl = 1;
    if(lvl > 5) lvl = 5;

    var tags = '=';
    for(var i=0; i<=5-lvl; i++) tags += '=';
    insertTags(edid, tags+' ', ' '+tags+"\n", props['text']);
    pickerClose();
    return false;
}


/**
 * Add button action for picker buttons and create picker element
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @return boolean    If button should be appended
 * @author Gabriel Birke <birke@d-scribe.de>
 */
function addBtnActionPicker(btn, props, edid) {
    var pickerid = 'picker'+(pickercounter++);
    createPicker(pickerid, props, edid);
    addEvent(btn,'click',function(){
        pickerToggle(pickerid,btn);
        return false;
    });
    return true;
}

/**
 * Add button action for the link wizard button
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @return boolean    If button should be appended
 * @author Andreas Gohr <gohr@cosmocode.de>
 */
function addBtnActionLinkwiz(btn, props, edid) {
    linkwiz.init($(edid));
    addEvent(btn,'click',function(){
        linkwiz.toggle();
        return false;
    });
    return true;
}

/**
 * Show/Hide a previosly created picker window
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function pickerToggle(pickerid,btn){
    var picker = $(pickerid);
    if(picker.style.marginLeft == '-10000px'){
        var x = findPosX(btn);
        var y = findPosY(btn);
        picker.style.left = (x+3)+'px';
        picker.style.top = (y+btn.offsetHeight+3)+'px';
        picker.style.marginLeft = '0px';
    }else{
        picker.style.marginLeft = '-10000px';
    }
}

/**
 * Close all open pickers
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function pickerClose(){
    var pobjs = getElementsByClass('picker');
    for(var i=0; i<pobjs.length; i++){
        pobjs[i].style.marginLeft = '-10000px';
    }
}


/**
 * Replaces \n with linebreaks
 */
function fixtxt(str){
    return str.replace(/\\n/g,"\n");
}



/* XXXXXXXXXX end of lib/scripts/toolbar.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/edit.js XXXXXXXXXX */

/**
 * Functions for text editing (toolbar stuff)
 *
 * @todo most of the stuff in here should be revamped and then moved to toolbar.js
 * @author Andreas Gohr <andi@splitbrain.org>
 */

/**
 * Creates a toolbar button through the DOM
 *
 * Style the buttons through the toolbutton class
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function createToolButton(icon,label,key,id){
    var btn = document.createElement('button');
    var ico = document.createElement('img');

    // preapare the basic button stuff
    btn.className = 'toolbutton';
    btn.title = label;
    if(key){
        btn.title += ' ['+key.toUpperCase()+']';
        btn.accessKey = key;
    }

    // set IDs if given
    if(id){
        btn.id = id;
        ico.id = id+'_ico';
    }

    // create the icon and add it to the button
    if(icon.substr(0,1) == '/'){
        ico.src = icon;
    }else{
        ico.src = DOKU_BASE+'lib/images/toolbar/'+icon;
    }
    btn.appendChild(ico);

    return btn;
}

/**
 * Creates a picker window for inserting text
 *
 * The given list can be an associative array with text,icon pairs
 * or a simple list of text. Style the picker window through the picker
 * class or the picker buttons with the pickerbutton class. Picker
 * windows are appended to the body and created invisible.
 *
 * @param  string id    the ID to assign to the picker
 * @param  array  props the properties for the picker
 * @param  string edid  the ID of the textarea
 * @rteurn DOMobject    the created picker
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function createPicker(id,props,edid){
    var icobase = props['icobase'];
    var list    = props['list'];

    // create the wrapping div
    var picker            = document.createElement('div');
    picker.className      = 'picker';
    if(props['class']){
        picker.className += ' '+props['class'];
    }
    picker.id               = id;
    picker.style.position   = 'absolute';
    picker.style.marginLeft = '-10000px'; // no display:none, to keep access keys working

    for(var key in list){
        if (!list.hasOwnProperty(key)) continue;

        if(isNaN(key)){
            // associative array -> treat as image/value pairs
            var btn = document.createElement('button');
            btn.className = 'pickerbutton';
            var ico = document.createElement('img');
            if(list[key].substr(0,1) == '/'){
                ico.src = list[key];
            }else{
                ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key];
            }
            btn.title     = key;
            btn.appendChild(ico);
            addEvent(btn,'click',bind(pickerInsert,key,edid));
            picker.appendChild(btn);
        }else if(isString(list[key])){
            // a list of text -> treat as text picker
            var btn = document.createElement('button');
            btn.className = 'pickerbutton';
            var txt = document.createTextNode(list[key]);
            btn.title     = list[key];
            btn.appendChild(txt);
            addEvent(btn,'click',bind(pickerInsert,list[key],edid));
            picker.appendChild(btn);
        }else{
            // a list of lists -> treat it as subtoolbar
            initToolbar(picker,edid,list);
            break; // all buttons handled already
        }

    }
    var body = document.getElementsByTagName('body')[0];
    body.appendChild(picker);
    return picker;
}

/**
 * Called by picker buttons to insert Text and close the picker again
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function pickerInsert(text,edid){
    insertAtCarret(edid,text);
    pickerClose();
}

/**
 * Add button action for signature button
 *
 * @param  DOMElement btn   Button element to add the action to
 * @param  array      props Associative array of button properties
 * @param  string     edid  ID of the editor textarea
 * @return boolean    If button should be appended
 * @author Gabriel Birke <birke@d-scribe.de>
 */
function addBtnActionSignature(btn, props, edid) {
    if(typeof(SIG) != 'undefined' && SIG != ''){
        addEvent(btn,'click',bind(insertAtCarret,edid,SIG));
        return true;
    }
    return false;
}

/**
 * Make intended formattings easier to handle
 *
 * Listens to all key inputs and handle indentions
 * of lists and code blocks
 *
 * Currently handles space, backspce and enter presses
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @fixme handle tabs
 */
function keyHandler(e){
    if(e.keyCode != 13 &&
       e.keyCode != 8  &&
       e.keyCode != 32) return;
    var field     = e.target;
    var selection = getSelection(field);
    var search    = "\n"+field.value.substr(0,selection.start);
    var linestart = Math.max(search.lastIndexOf("\n"),
                             search.lastIndexOf("\r")); //IE workaround
    search = search.substr(linestart);


    if(e.keyCode == 13){ // Enter
        // keep current indention for lists and code
        var match = search.match(/(\n  +([\*-] ?)?)/);
        if(match){
            var scroll = field.scrollHeight;
            insertAtCarret(field.id,match[1]);
            field.scrollTop += (field.scrollHeight - scroll);
            e.preventDefault(); // prevent enter key
            return false;
        }
    }else if(e.keyCode == 8){ // Backspace
        // unindent lists
        var match = search.match(/(\n  +)([*-] ?)$/);
        if(match){
            var spaces = match[1].length-1;

            if(spaces > 3){ // unindent one level
                field.value = field.value.substr(0,linestart)+
                              field.value.substr(linestart+2);
                selection.start = selection.start - 2;
                selection.end   = selection.start;
            }else{ // delete list point
                field.value = field.value.substr(0,linestart)+
                              field.value.substr(selection.start);
                selection.start = linestart;
                selection.end   = linestart;
            }
            setSelection(selection);
            e.preventDefault(); // prevent backspace
            return false;
        }
    }else if(e.keyCode == 32){ // Space
        // intend list item
        var match = search.match(/(\n  +)([*-] )$/);
        if(match){
            field.value = field.value.substr(0,linestart)+'  '+
                          field.value.substr(linestart);
            selection.start = selection.start + 2;
            selection.end   = selection.start;
            setSelection(selection);
            e.preventDefault(); // prevent space
            return false;
        }
    }
}

//FIXME consolidate somewhere else
addInitEvent(function(){
    var field = $('wiki__text');
    if(!field) return;
    addEvent(field,'keydown',keyHandler);
});

/**
 * Determine the current section level while editing
 *
 * @author Andreas Gohr <gohr@cosmocode.de>
 */
function currentHeadlineLevel(textboxId){
    var field     = $(textboxId);
    var selection = getSelection(field);
    var search    = "\n"+field.value.substr(0,selection.start);
    var lasthl    = search.lastIndexOf("\n==");
    if(lasthl == -1 && field.form.prefix){
        // we need to look in prefix context
        search = field.form.prefix.value;
        lasthl    = search.lastIndexOf("\n==");
    }
    search    = search.substr(lasthl+1,6);

    if(search == '======') return 1;
    if(search.substr(0,5) == '=====') return 2;
    if(search.substr(0,4) == '====') return 3;
    if(search.substr(0,3) == '===') return 4;
    if(search.substr(0,2) == '==') return 5;

    return 0;
}


/**
 * global var used for not saved yet warning
 */
var textChanged = false;

/**
 * Check for changes before leaving the page
 */
function changeCheck(msg){
  if(textChanged){
    var ok = confirm(msg);
    if(ok){
        // remove a possibly saved draft using ajax
        var dwform = $('dw__editform');
        if(dwform){
            var params = 'call=draftdel';
            params += '&id='+encodeURIComponent(dwform.elements.id.value);

            var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php');
            sackobj.AjaxFailedAlert = '';
            sackobj.encodeURIString = false;
            sackobj.runAJAX(params);
            // we send this request blind without waiting for
            // and handling the returned data
        }
    }
    return ok;
  }else{
    return true;
  }
}

/**
 * Add changeCheck to all Links and Forms (except those with a
 * JSnocheck class), add handlers to monitor changes
 *
 * Sets focus to the editbox as well
 *
 * @fixme this is old and crappy code. needs to be redone
 */
function initChangeCheck(msg){
    var edit_text   = document.getElementById('wiki__text');
    if(!edit_text) return;
    if(edit_text.readOnly) return;
    if(!$('dw__editform')) return;

    // add change check for links
    var links = document.getElementsByTagName('a');
    for(var i=0; i < links.length; i++){
        if(links[i].className.indexOf('JSnocheck') == -1){
            links[i].onclick = function(){
                                    var rc = changeCheck(msg);
                                    if(window.event) window.event.returnValue = rc;
                                    return rc;
                               };
        }
    }
    // add change check for forms
    var forms = document.forms;
    for(i=0; i < forms.length; i++){
        if(forms[i].className.indexOf('JSnocheck') == -1){
            forms[i].onsubmit = function(){
                                    var rc = changeCheck(msg);
                                    if(window.event) window.event.returnValue = rc;
                                    return rc;
                               };
        }
    }

    // reset change memory var on submit
    var btn_save        = document.getElementById('edbtn__save');
    btn_save.onclick    = function(){ textChanged = false; };
    var btn_prev        = document.getElementById('edbtn__preview');
    btn_prev.onclick    = function(){ textChanged = false; };

    // add change memory setter
    edit_text.onchange = function(){
        textChanged = true; //global var
        summaryCheck();
    };
    var summary = document.getElementById('edit__summary');
    addEvent(summary, 'change', summaryCheck);
    addEvent(summary, 'keyup', summaryCheck);
    if (textChanged) summaryCheck();

    // set focus
    edit_text.focus();
}

/**
 * Checks if a summary was entered - if not the style is changed
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
function summaryCheck(){
    var sum = document.getElementById('edit__summary');
    if(sum.value === ''){
        sum.className='missing';
    }else{
        sum.className='edit';
    }
}


/**
 * Class managing the timer to display a warning on a expiring lock
 */
function locktimer_class(){
        this.sack     = null;
        this.timeout  = 0;
        this.timerID  = null;
        this.lasttime = null;
        this.msg      = '';
        this.pageid   = '';
};
var locktimer = new locktimer_class();
    locktimer.init = function(timeout,msg,draft){
        // init values
        locktimer.timeout  = timeout*1000;
        locktimer.msg      = msg;
        locktimer.draft    = draft;
        locktimer.lasttime = new Date();

        if(!$('dw__editform')) return;
        locktimer.pageid = $('dw__editform').elements.id.value;
        if(!locktimer.pageid) return;

        // init ajax component
        locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        locktimer.sack.AjaxFailedAlert = '';
        locktimer.sack.encodeURIString = false;
        locktimer.sack.onCompletion = locktimer.refreshed;

        // register refresh event
        addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();});

        // start timer
        locktimer.reset();
    };

    /**
     * (Re)start the warning timer
     */
    locktimer.reset = function(){
        locktimer.clear();
        locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout);
    };

    /**
     * Display the warning about the expiring lock
     */
    locktimer.warning = function(){
        locktimer.clear();
        alert(locktimer.msg);
    };

    /**
     * Remove the current warning timer
     */
    locktimer.clear = function(){
        if(locktimer.timerID !== null){
            window.clearTimeout(locktimer.timerID);
            locktimer.timerID = null;
        }
    };

    /**
     * Refresh the lock via AJAX
     *
     * Called on keypresses in the edit area
     */
    locktimer.refresh = function(){
        var now = new Date();
        // refresh every minute only
        if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time
            var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid);
            if(locktimer.draft){
                var dwform = $('dw__editform');
                params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value);
                params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);
                params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value);
                params += '&date='+encodeURIComponent(dwform.elements.date.value);
            }
            locktimer.sack.runAJAX(params);
            locktimer.lasttime = now;
        }
    };


    /**
     * Callback. Resets the warning timer
     */
    locktimer.refreshed = function(){
        var data  = this.response;
        var error = data.charAt(0);
            data  = data.substring(1);

        $('draft__status').innerHTML=data;
        if(error != '1') return; // locking failed
        locktimer.reset();
    };
// end of locktimer class functions



/* XXXXXXXXXX end of lib/scripts/edit.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/linkwiz.js XXXXXXXXXX */

/**
 * The Link Wizard
 *
 * @author Andreas Gohr <gohr@cosmocode.de>
 */
var linkwiz = {
    wiz:    null,
    entry:  null,
    result: null,
    timer:  null,
    sack:   null,
    textArea: null,
    selected: -1,
    selection: null,

    /**
     * Initialize the linkwizard by creating the needed HTML
     * and attaching the eventhandlers
     */
    init: function(textArea){
        // prepare AJAX object
        linkwiz.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        linkwiz.sack.AjaxFailedAlert = '';
        linkwiz.sack.encodeURIString = false;

        // create HTML Structure
        linkwiz.wiz = document.createElement('div');
        linkwiz.wiz.id = 'link__wiz';
        linkwiz.wiz.className     = 'picker';
        linkwiz.wiz.style.top  = (findPosY(textArea)+20)+'px';
        linkwiz.wiz.style.left = (findPosX(textArea)+80)+'px';
        linkwiz.wiz.style.marginLeft = '-10000px';

        linkwiz.wiz.innerHTML =
             '<div id="link__wiz_header">'+
             '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+
             LANG['linkwiz']+'</div>'+
             '<div>'+LANG['linkto']+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
             '<div id="link__wiz_result"></div>';
        textArea.form.parentNode.appendChild(linkwiz.wiz);
        linkwiz.textArea = textArea;
        linkwiz.result = $('link__wiz_result');
        linkwiz.entry = $('link__wiz_entry');

        // attach event handlers
        var obj;
        obj = $('link__wiz_close');
        obj.onclick = linkwiz.hide;

        linkwiz.sack.elementObj = linkwiz.result;
        addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);
        addEvent(linkwiz.result,'click',linkwiz.onResultClick);
        drag.attach(linkwiz.wiz,$('link__wiz_header'));
    },

    /**
     * handle all keyup events in the entry field
     */
    onEntry: function(e){
        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
            return true; //ignore
        }
        if(e.keyCode == 27){
            linkwiz.hide();
            e.preventDefault();
            e.stopPropagation();
            return false;
        }
        if(e.keyCode == 38){ //Up
            linkwiz.select(linkwiz.selected -1);
            e.preventDefault();
            e.stopPropagation();
            return false;
        }
        if(e.keyCode == 40){ //Down
            linkwiz.select(linkwiz.selected +1);
            e.preventDefault();
            e.stopPropagation();
            return false;
        }
        if(e.keyCode == 13){ //Enter
            if(linkwiz.selected > -1){
                var obj = linkwiz.getResult(linkwiz.selected);
                if(obj){
                    var a = obj.getElementsByTagName('A')[0];
                    linkwiz.resultClick(a);
                }
            }else if(linkwiz.entry.value){
                linkwiz.insertLink(linkwiz.entry.value);
            }

            e.preventDefault();
            e.stopPropagation();
            return false;
        }
        linkwiz.autocomplete();
    },

    /**
     * Get one of the result by index
     *
     * @param int result div to return
     * @returns DOMObject or null
     */
    getResult: function(num){
        var obj;
        var childs = linkwiz.result.getElementsByTagName('DIV');
        obj = childs[num];
        if(obj){
            return obj;
        }else{
            return null;
        }
    },

    /**
     * Select the given result
     */
    select: function(num){
        if(num < 0){
            linkwiz.deselect();
            return;
        }

        var obj = linkwiz.getResult(num);
        if(obj){
            linkwiz.deselect();
            obj.className += ' selected';

            // make sure the item is viewable in the scroll view
            // FIXME check IE compatibility
            if(obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight){
                linkwiz.result.scrollTop += obj.clientHeight;
            }else if(obj.offsetTop - linkwiz.result.clientHeight < linkwiz.result.scrollTop){ // this works but isn't quite right, fixes welcome
                linkwiz.result.scrollTop -= obj.clientHeight;
            }
            // now recheck - if still not in view, the user used the mouse to scroll
            if( (obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight) ||
                (obj.offsetTop < linkwiz.result.scrollTop) ){
                obj.scrollIntoView();
            }

            linkwiz.selected = num;
        }
    },

    /**
     * deselect a result if any is selected
     */
    deselect: function(){
        if(linkwiz.selected > -1){
            var obj = linkwiz.getResult(linkwiz.selected);
            if(obj){
                obj.className = obj.className.replace(/ ?selected/,'');
            }
        }
        linkwiz.selected = -1;
    },

    /**
     * Handle clicks in the result set an dispatch them to
     * resultClick()
     */
    onResultClick: function(e){
        if(e.target.tagName != 'A') return;
        e.stopPropagation();
        e.preventDefault();
        linkwiz.resultClick(e.target);
        return false;
    },

    /**
     * Handles the "click" on a given result anchor
     */
    resultClick: function(a){
        var id = a.title;
        if(id == '' || id.substr(id.length-1) == ':'){
            linkwiz.entry.value = id;
            linkwiz.autocomplete_exec();
        }else{
            linkwiz.entry.value = id;
            if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){
                linkwiz.insertLink(a.nextSibling.innerHTML);
            }else{
                linkwiz.insertLink('');
            }
        }
    },

    /**
     * Insert the id currently in the entry box to the textarea,
     * replacing the current selection or at the curso postion.
     * When no selection is available the given title will be used
     * as link title instead
     */
    insertLink: function(title){
        if(!linkwiz.entry.value) return;

        var sel = getSelection(linkwiz.textArea);
        if(sel.start == 0 && sel.end == 0) sel = linkwiz.selection;

        var stxt = sel.getText();
        if(!stxt && !DOKU_UHC) stxt=title;

        // prepend colon inside namespaces for non namespace pages
        if(linkwiz.textArea.form['id'].value.indexOf(':') != -1 &&
           linkwiz.entry.value.indexOf(':') == -1){
            linkwiz.entry.value = ':'+linkwiz.entry.value;
        }

        var link = '[['+linkwiz.entry.value+'|';
        if(stxt) link += stxt;
        link += ']]';

        var so = linkwiz.entry.value.length+3;
        var eo = 2;

        pasteText(sel,link,{startofs: so, endofs: eo});
        linkwiz.hide();
    },

    /**
     * Start the page/namespace lookup timer
     *
     * Calls autocomplete_exec when the timer runs out
     */
    autocomplete: function(){
        if(linkwiz.timer !== null){
            window.clearTimeout(linkwiz.timer);
            linkwiz.timer = null;
        }

        linkwiz.timer = window.setTimeout(linkwiz.autocomplete_exec,350);
    },

    /**
     * Executes the AJAX call for the page/namespace lookup
     */
    autocomplete_exec: function(){
        linkwiz.deselect();
        linkwiz.result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />';
        linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));
    },

    /**
     * Clears the result area
     */
    clear: function(){
        linkwiz.result.innerHTML = 'Search for a matching page name above, or browse through the pages on the right';
        linkwiz.entry.value = '';
    },

    /**
     * Show the linkwizard
     */
    show: function(){
        linkwiz.selection  = getSelection(linkwiz.textArea);
        linkwiz.wiz.style.marginLeft = '0px';
        linkwiz.entry.focus();
        linkwiz.autocomplete();
    },

    /**
     * Hide the link wizard
     */
    hide: function(){
        linkwiz.wiz.style.marginLeft = '-10000px';
        linkwiz.textArea.focus();
    },

    /**
     * Toggle the link wizard
     */
    toggle: function(){
        if(linkwiz.wiz.style.marginLeft == '-10000px'){
            linkwiz.show();
        }else{
            linkwiz.hide();
        }
    }
};



/* XXXXXXXXXX end of lib/scripts/linkwiz.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/scripts/media.js XXXXXXXXXX */

/**
 * JavaScript functionalitiy for the media management popup
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
var media_manager = {
    keepopen: false,
    hide: false,

    /**
     * Attach event handlers to all "folders" below the given element
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    treeattach: function(obj){
        if(!obj) return;

        var items = obj.getElementsByTagName('li');
        for(var i=0; i<items.length; i++){
            var elem = items[i];

            // attach action to make the +/- clickable
            var clicky = elem.getElementsByTagName('img')[0];
            clicky.style.cursor = 'pointer';
            addEvent(clicky,'click',function(event){ return media_manager.toggle(event,this); });

            // attach action load folder list via AJAX
            var link = elem.getElementsByTagName('a')[0];
            link.style.cursor = 'pointer';
            addEvent(link,'click',function(event){ return media_manager.list(event,this); });
        }
    },

    /**
     * Attach the image selector action to all links below the given element
     * also add the action to autofill the "upload as" field
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    selectorattach: function(obj){
        if(!obj) return;

        var items = getElementsByClass('select',obj,'a');
        for(var i=0; i<items.length; i++){
            var elem = items[i];
            elem.style.cursor = 'pointer';
            addEvent(elem,'click',function(event){ return media_manager.select(event,this); });
        }

        // hide syntax example
        items = getElementsByClass('example',obj,'div');
        for(var i=0; i<items.length; i++){
            elem = items[i];
            elem.style.display = 'none';
        }

        var file = $('upload__file');
        if(!file) return;
        addEvent(file,'change',media_manager.suggest);
    },

    /**
     * Attach deletion confirmation dialog to the delete buttons.
     *
     * Michael Klier <chi@chimeric.de>
     */
    confirmattach: function(obj){
        if(!obj) return;

        items = getElementsByClass('btn_media_delete',obj,'a');
        for(var i=0; i<items.length; i++){
            var elem = items[i];
            addEvent(elem,'click',function(e){
                if(e.target.tagName == 'IMG'){
                    var name = e.target.parentNode.title;
                }else{
                    var name = e.target.title;
                }
                if(!confirm(LANG['del_confirm'] + "\n" + name)) {
                    e.preventDefault();
                    return false;
                } else {
                    return true;
                }
            });
        }
    },

    /**
     * Creates checkboxes for additional options
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    attachoptions: function(obj){
        if(!obj) return;

        // keep open
        if(opener){
            var kobox  = document.createElement('input');
            kobox.type = 'checkbox';
            kobox.id   = 'media__keepopen';
            if(DokuCookie.getValue('keepopen')){
                kobox.checked  = true;
                kobox.defaultChecked = true; //IE wants this
                media_manager.keepopen = true;
            }
            addEvent(kobox,'click',function(event){ return media_manager.togglekeepopen(event,this); });

            var kolbl  = document.createElement('label');
            kolbl.htmlFor   = 'media__keepopen';
            kolbl.innerHTML = LANG['keepopen'];

            var kobr = document.createElement('br');

            obj.appendChild(kobox);
            obj.appendChild(kolbl);
            obj.appendChild(kobr);
        }

        // hide details
        var hdbox  = document.createElement('input');
        hdbox.type = 'checkbox';
        hdbox.id   = 'media__hide';
        if(DokuCookie.getValue('hide')){
            hdbox.checked = true;
            hdbox.defaultChecked = true; //IE wants this
            media_manager.hide    = true;
        }
        addEvent(hdbox,'click',function(event){ return media_manager.togglehide(event,this); });

        var hdlbl  = document.createElement('label');
        hdlbl.htmlFor   = 'media__hide';
        hdlbl.innerHTML = LANG['hidedetails'];

        var hdbr = document.createElement('br');

        obj.appendChild(hdbox);
        obj.appendChild(hdlbl);
        obj.appendChild(hdbr);
        media_manager.updatehide();
    },

    /**
     * Opens the searchfield
     *
     * @author Tobias Sarnowski <sarnowski@cosmocode.de>
     */
    showsearchfield: function(event,link){
        // prepare an AJAX call to fetch the search
        var ajax = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        cleanMsgArea();

        var content = $('media__content');
        content.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" alt="..." class="load" />';

        ajax.elementObj = content;
        ajax.afterCompletion = function(){
            media_manager.selectorattach(content);
            media_manager.confirmattach(content);
            media_manager.updatehide();
        };
        ajax.runAJAX(link.search.substr(1)+'&call=mediasearchlist');
        return false;
    },

    /**
     * Toggles the keep open state
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    togglekeepopen: function(event,cb){
        if(cb.checked){
            DokuCookie.setValue('keepopen',1);
            media_manager.keepopen = true;
        }else{
            DokuCookie.setValue('keepopen','');
            media_manager.keepopen = false;
        }
    },

    /**
     * Toggles the hide details state
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    togglehide: function(event,cb){
        if(cb.checked){
            DokuCookie.setValue('hide',1);
            media_manager.hide = true;
        }else{
            DokuCookie.setValue('hide','');
            media_manager.hide = false;
        }
        media_manager.updatehide();
    },

    /**
     * Sets the visibility of the image details accordingly to the
     * chosen hide state
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    updatehide: function(){
        var obj = $('media__content');
        if(!obj) return;
        var details = getElementsByClass('detail',obj,'div');
        for(var i=0; i<details.length; i++){
            if(media_manager.hide){
                details[i].style.display = 'none';
            }else{
                details[i].style.display = '';
            }
        }
    },

    /**
     * Insert the clicked image into the opener's textarea
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    select: function(event,link){
        var id = link.name.substr(2);

        if(!opener){
            // if we don't run in popup display example
            var ex = $('ex_'+id.replace(/:/g,'_'));
            if(ex.style.display == ''){
                ex.style.display = 'none';
            }else{
                ex.style.display = '';
            }
            return false;
        }
        opener.insertTags('wiki__text','{{'+id+'|','}}','');

        if(!media_manager.keepopen) window.close();
        opener.focus();
        return false;
    },

    /**
     * list the content of a namespace using AJAX
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    list: function(event,link){
        // prepare an AJAX call to fetch the subtree
        var ajax = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        cleanMsgArea();

        var content = $('media__content');
        content.innerHTML = '<img src="'+DOKU_BASE+'lib/images/loading.gif" alt="..." class="load" />';

        ajax.elementObj = content;
        ajax.afterCompletion = function(){
            media_manager.selectorattach(content);
            media_manager.confirmattach(content);
            media_manager.updatehide();
            media_manager.initFlashUpload();
        };
        ajax.runAJAX(link.search.substr(1)+'&call=medialist');
        return false;
    },


    /**
     * Open or close a subtree using AJAX
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    toggle: function(event,clicky){
        var listitem = clicky.parentNode;

        // if already open, close by removing the sublist
        var sublists = listitem.getElementsByTagName('ul');
        if(sublists.length){
            listitem.removeChild(sublists[0]);
            clicky.src = DOKU_BASE+'lib/images/plus.gif';
            return false;
        }

        // get the enclosed link (is always the first one)
        var link = listitem.getElementsByTagName('a')[0];

        // prepare an AJAX call to fetch the subtree
        var ajax = new sack(DOKU_BASE + 'lib/exe/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        //prepare the new ul
        var ul = document.createElement('ul');
        //fixme add classname here
        listitem.appendChild(ul);
        ajax.elementObj = ul;
        ajax.afterCompletion = function(){ media_manager.treeattach(ul); };
        ajax.runAJAX(link.search.substr(1)+'&call=medians');
        clicky.src = DOKU_BASE+'lib/images/minus.gif';
        return false;
    },

    /**
     * Prefills the wikiname.
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    suggest: function(){
        var file = $('upload__file');
        var name = $('upload__name');
        if(!file || !name) return;

        var text = file.value;
        text = text.substr(text.lastIndexOf('/')+1);
        text = text.substr(text.lastIndexOf('\\')+1);
        name.value = text;
    },


    initFlashUpload: function(){
        if(!hasFlash(8)) return;
        var oform  = $('dw__upload');
        var oflash = $('dw__flashupload');
        if(!oform || !oflash) return;

        var clicky = document.createElement('img');
        clicky.src     = DOKU_BASE+'lib/images/multiupload.png';
        clicky.title   = LANG['mu_btn'];
        clicky.alt     = LANG['mu_btn'];
        clicky.style.cursor = 'pointer';
        clicky.onclick = function(){
                            oform.style.display  = 'none';
                            oflash.style.display = '';
                         };
        oform.appendChild(clicky);
    }
};

addInitEvent(function(){
    media_manager.treeattach($('media__tree'));
    media_manager.selectorattach($('media__content'));
    media_manager.confirmattach($('media__content'));
    media_manager.attachoptions($('media__opts'));
    media_manager.initFlashUpload();
});


/* XXXXXXXXXX end of lib/scripts/media.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/tpl/unpriviliged/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/tpl/unpriviliged/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/acl/script.js XXXXXXXXXX */

acl = {
    init: function(){
        this.ctl = $('acl_manager');
        if(!this.ctl) return;

        var sel = $('acl__user').getElementsByTagName('select')[0];

        addEvent(sel,'change',acl.userselhandler);
        addEvent($('acl__tree'),'click',acl.treehandler);
        addEvent($('acl__user').getElementsByTagName('input')[1],'click',acl.loadinfo);
    },


    /**
     * Handle user dropdown
     */
    userselhandler: function(e){
        // make entry field visible/invisible
        if(this.value == '__g__' || this.value == '__u__'){
            $('acl__user').getElementsByTagName('input')[0].style.display = ''; //acl_w
            $('acl__user').getElementsByTagName('input')[1].style.display = ''; //submit
        }else{
            $('acl__user').getElementsByTagName('input')[0].style.display = 'none';
            $('acl__user').getElementsByTagName('input')[1].style.display = 'none';
        }

        acl.loadinfo();
    },

    /**
     * Load the current permission info and edit form
     *
     * @param frm - Form element with needed data
     */
    loadinfo: function(){
        // get form
        var frm = $('acl__detail').getElementsByTagName('form')[0];

        // prepare an AJAX call
        var ajax = new sack(DOKU_BASE + 'lib/plugins/acl/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        // prepare data
        var data = Array();
        data[0] = ajax.encVar('ns',frm.elements['ns'].value);
        data[1] = ajax.encVar('id',frm.elements['id'].value);
        data[2] = ajax.encVar('acl_t',frm.elements['acl_t'].value);
        data[3] = ajax.encVar('acl_w',frm.elements['acl_w'].value);
        data[4] = ajax.encVar('sectok',frm.elements['sectok'].value);
        data[5] = ajax.encVar('ajax','info');

        ajax.elementObj = $('acl__info');

        ajax.runAJAX(data.join('&'));
        return false;
    },

    /**
     * parse URL attributes into a associative array
     *
     * @todo put into global script lib?
     */
    parseatt: function(str){
        if(str[0] == '?') str = str.substr(1);
        var attributes = {};
        var all = str.split('&');
        for(var i=0; i<all.length; i++){
            var att = all[i].split('=');
            attributes[att[0]] = decodeURIComponent(att[1]);
        }
        return attributes;
    },

    /**
     * htmlspecialchars equivalent
     *
     * @todo put in gloabl scripts lib?
     */
    hsc: function(str) {
        str = str.replace(/&/g,"&amp;");
        str = str.replace(/\"/g,"&quot;");
        str = str.replace(/\'/g,"&#039;");
        str = str.replace(/</g,"&lt;");
        str = str.replace(/>/g,"&gt;");
        return str;
    },


    /**
     * Open or close a subtree using AJAX
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    treetoggle: function(clicky){
        var listitem = clicky.parentNode.parentNode;

        // if already open, close by removing the sublist
        var sublists = listitem.getElementsByTagName('ul');
        if(sublists.length){
            listitem.removeChild(sublists[0]);
            clicky.src = DOKU_BASE+'lib/images/plus.gif';
            clicky.alt = '+';
            return false;
        }

        // get the enclosed link (is always the first one)
        var link = listitem.getElementsByTagName('a')[0];

        // prepare an AJAX call to fetch the subtree
        var ajax = new sack(DOKU_BASE + 'lib/plugins/acl/ajax.php');
        ajax.AjaxFailedAlert = '';
        ajax.encodeURIString = false;
        if(ajax.failed) return true;

        //prepare the new ul
        var ul = document.createElement('ul');
        listitem.appendChild(ul);
        ajax.elementObj = ul;
        ajax.runAJAX(link.search.substr(1)+'&ajax=tree');
        clicky.src = DOKU_BASE+'lib/images/minus.gif';
        return false;
    },

    /**
     * Handles all clicks in the tree, dispatching the right action based on the
     * clicked element
     */
    treehandler: function(e){
        if(e.target.src){ // is it an image?
            acl.treetoggle(e.target);
        } else if(e.target.href){ // is it a link?
            // remove highlighting
            var obj = getElementsByClass('cur',$('acl__tree'),'a');
            for(var i=0; i<obj.length; i++){
                obj[i].className = obj[i].className.replace(/ cur/,'');
            }

            // add new highlighting
            e.target.className += ' cur';

            // set new page to detail form
            var frm = $('acl__detail').getElementsByTagName('form')[0];
            if(e.target.className.search(/wikilink1/) > -1){
                frm.elements['ns'].value = '';
                frm.elements['id'].value = acl.hsc(acl.parseatt(e.target.search)['id']);
            }else if(e.target.className.search(/idx_dir/) > -1){
                frm.elements['ns'].value = acl.hsc(acl.parseatt(e.target.search)['ns']);
                frm.elements['id'].value = '';
            }

            acl.loadinfo();
        }

        e.stopPropagation();
        e.preventDefault();
        return false;
    }

};

addInitEvent(acl.init);


/* XXXXXXXXXX end of lib/plugins/acl/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/config/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/config/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/info/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/info/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/plugin/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/plugin/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/popularity/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/popularity/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/revert/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/revert/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/usermanager/script.js XXXXXXXXXX */

/**
 * Add JavaScript confirmation to the User Delete button
 */
function usrmgr_delconfirm(){
    if($('usrmgr__del')){
        addEvent( $('usrmgr__del'),'click',function(){ return confirm(reallyDel); } );
    }
};
addInitEvent(usrmgr_delconfirm);


/* XXXXXXXXXX end of lib/plugins/usermanager/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/blog/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/blog/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/countdown/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/countdown/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/crypt/script.js XXXXXXXXXX */

var crypt_keys=[];
var tag_enc="ENCRYPTED";
var tag_pt="SECRET";
var encryptForSubmitInUse=false;

addInitEvent(function() { return(decryptEditSetup()); });

// the function here is borrowed from an anonymous function in
// lib/scripts/edit.js (initChangeCheck()).  
// This should be replaced with some way that a plugin can request
// onSubmit handlers for a given form element
function editFormOnSubmit() {
  // begin plugin modified code
  // need the following to avoid 'msg is not defined' error. I'mnot sure why
  var msg="Unsaved changes will be lost [edt].\nReally continue?"; 
  if(encryptForSubmit()===false) { return(false); }
  // end plugin modified code
  var rc = changeCheck(msg);
  if(window.event) { window.event.returnValue = rc; }
  return rc;
}

function decryptEditSetup(msg) {
  var editform=null, wikitext=null, hiddentext=null, preview=null;
  if(!(editform=document.getElementById('dw__editform'))) { 
    // alert("no form dw__editform\n");
    return(true); 
  }
  if(!(wikitext=document.getElementById('wiki__text'))) {
   // alert("no wiki__text");
   return(false);
  }
  // if there is no preview button, then assume this is a
  // "Recover draft" page, dont do anything.
  if(!(preview=document.getElementById('edbtn__preview'))) {
    return(false);
  }

  // create a hidden element with id 'wiki__text_submit' and
  // name wikitext_edit (same as the wiki__text.  move the real
  // wikI__text element out of the form (so it is not submitted and
  // any <SECRET> text left unencrypted
  if(!(hiddentext=document.createElement('input'))) {
   return(false);
  }
  hiddentext.setAttribute('id', 'wiki__text_submit');
  hiddentext.setAttribute('name', 'wikitext');
  hiddentext.setAttribute('type','hidden');
  editform.insertBefore(hiddentext,null);
  editform.parentNode.insertBefore(wikitext,editform);

  if(!(decryptButton=document.createElement('input'))) {
   return(false);
  }
  decryptButton.setAttribute('id', 'decryptButton');
  decryptButton.setAttribute('name', 'decryptButton');
  decryptButton.setAttribute('type','Button');
  decryptButton.setAttribute('value','DecryptSecret');
  // decryptButton.setAttribute('onclick',decryptEditForm);
  decryptButton.onclick=decryptEditForm;
  decryptButton.setAttribute('class','button');
  decryptButton.setAttribute('className','button'); // required for IE
  preview.parentNode.insertBefore(decryptButton,preview);

  editform.onsubmit = function() {return editFormOnSubmit();};

  // the following is taken from lib/scripts/edit.js to make drafts work
  locktimer.refresh = function(){
      var now = new Date();
      // refresh every minute only
      if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time
          var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid);
          if(locktimer.draft){
              var dwform = $('dw__editform');
              // begin plugin modified code
              if(encryptForSubmit()===false) { return(false); }
              // end plugin modified code
              params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value);
              params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);
              params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value);
              params += '&date='+encodeURIComponent(dwform.elements.date.value);
          }
          locktimer.sack.runAJAX(params);
          locktimer.lasttime = now;
      }
  };
}

function encryptForSubmit() {
   var wikitext=null, hiddentext=null;
   // bad semaphore like protection to avoid multiple calls to this code
   // at once (user pushing 'Submit' and draft save running, or multiple draft
   // save.  its not really safe
   while(encryptForSubmitInUse!==false) {
      // wish I had sleep here
   }
   encryptForSubmitInUse=true;

   if(!(wikitext=document.getElementById('wiki__text'))) { 
      alert("failed to get wiki__text");
      encryptForSubmitInUse=false; return(false); 
   }
   if(!(hiddentext=document.getElementById('wiki__text_submit'))) {
      alert("failed to get wiki__text_submit");
      encryptForSubmitInUse=false; return(false); 
   }
   var tosubmit=encryptMixedText(wikitext.value);
   if(tosubmit===false) { encryptForSubmitInUse=false; return(false); }
   hiddentext.value=tosubmit;
   encryptForSubmitInUse=false; return(true); 
}

function decryptEditForm() {
  var elem=null, newtext="";
  if(!(elem=document.getElementById('wiki__text'))) { 
    // alert("no form wiki__text\n");
    return(true); 
  }
  if((newtext=decryptMixedText(elem.value))===false) { 
    alert("failed to decrypt wiki__text");
    return(false);
  }
  elem.value=newtext;
  return(true);
}

function setKeyFromAscii(pass) {
  var s = encode_utf8(pass);
  var i, kmd5e, kmd5o;

  if (s.length == 1) {
    s += s;
  }

  md5_init();
  for (i = 0; i < s.length; i += 2) {
    md5_update(s.charCodeAt(i));
  }
  md5_finish();
  kmd5e = byteArrayToHex(digestBits);

  md5_init();
  for (i = 1; i < s.length; i += 2) {
    md5_update(s.charCodeAt(i));
  }
  md5_finish();
  kmd5o = byteArrayToHex(digestBits);

  var hs = kmd5e + kmd5o;
  key =  hexToByteArray(hs);
  hs = byteArrayToHex(key);
  return(key);
}

/*
  this is called from <A HREF=> links to decrypt the  inline html
*/
function toggleCryptDiv(elemid,lock,ctext) {
   var elem=null, atab=null, key="", ptext="";
   var ctStr="Decrypt Encrypted Text", ptStr="Hide Plaintext";
   elem=document.getElementById(elemid);
   atag=document.getElementById(elemid + "_atag");
   if(elem===null || atag===null) { 
      alert("failed to find element id " + elemid);
   }
   if(atag.innerHTML==ptStr) {
      // encrypt text (set back to ctext, and forget key)
      elem.innerHTML=ctext;
      atag.innerHTML=ctStr;
      crypt_keys[lock]=undefined;
   } else if (atag.innerHTML==ctStr) {
      // decrypt text
      if((ptext=verifyDecrypt(ctext,lock,false))===false) {
         alert("unable to find key for lock " + lock);
         return;
      }
      elem.innerHTML=ptext;
      atag.innerHTML=ptStr;
   } else { alert("Broken"); return; }
}

function getEncryptionKeyForLock(lock) {
  // alert("crypt_keys[" + lock + "]=" + crypt_keys[lock] + "\n");
  if(undefined===crypt_keys[lock]) {
    var x,y;
    x=prompt("Enter passphrase key for lock " + lock);
    if(x===null) { return(false); }
    y=prompt("Verify passphrase key for lock " + lock);
    if(y===null) { return(false); }
    if(x!=y) { crypt_debug("passwords do not match\n"); return(false); }
    crypt_debug("x=" + x + " y=" + y);
    crypt_keys[lock]=x;
    return(x);
  } else {
    return(crypt_keys[lock]);  
  }
}
var debugval="";
function crypt_debug(str) {
  // document.getElementById("debug_field").value+=str + "\n";
  debugval+=str;
}
/* decrypt the text between <CRYPT> and </CRYPT> */
function decryptMixedText(x) {
  var tag=tag_enc;
  var ret="", key="", ctext="";
  var tagend=0, opentag=0, blockend=0, pos=0;
  while((cur=x.indexOf("<" + tag,pos))!=-1) {
    if((opentag_end=x.indexOf(">",cur))==-1) {
      alert("unable to close to open tag"); return(false);
    }
    if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
      alert("unable to find close of " + tag + " tag"); return(false);
    }
    if(!(ctext=decryptBlock(x.substring(cur,closetag+tag.length+3),false))) {
      return(false);
    }
    ret+=x.substring(pos,cur) + ctext;
    pos=closetag+tag.length+3;
  }
  ret+=x.substring(pos);
  return(ret);
}

function encryptMixedText(x) {
  var tag=tag_pt;
  var ret="", key="", ctext="";
  var tagend=0, opentag=0, blockend=0, pos=0;
  while((cur=x.indexOf("<" + tag,pos))!=-1) {
    if((opentag_end=x.indexOf(">",cur))==-1) {
      alert("unable to close to open tag"); return(false);
    }
    if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
      x=x+"</" + tag + ">";
      // if there is no close tag, add one to the end.
      closetag=x.indexOf("</" + tag + ">",opentag_end);
      // alert("unable to find close of " + tag + " tag"); return(false);
    }
    if(!(ctext=encryptBlock(x.substring(cur,closetag+tag.length+3),false))) {
      alert("failed to encrypt text");
      return(false);
    }
    ret+=x.substring(pos,cur) + ctext;
    pos=closetag+tag.length+3;
  }
  ret+=x.substring(pos);
  return(ret);
}

function verifyDecrypt(ctext,lock,key) {
  var ptext=null;
  if(undefined!==crypt_keys[lock]) { key=crypt_keys[lock]; }
  if(key===false && (undefined===crypt_keys[lock])) {
    var key=prompt("Enter passphrase for lock " + lock);
    if(key===null) { return(false); } // user hit cancel
    if(!(ptext=decryptTextString(ctext,key))) {
      var pstr="Try again: Enter passphrase for lock " + lock;
      while(null!==(key=prompt(pstr))) {
        ptext=decryptTextString(ctext,key);
        if(ptext) { 
          break;
        }
      }
      if(key==null) { return(false); } // user hit cancel
    }
    crypt_keys[lock]=key; 
  } else {
    var xkey=key;
    if(key===false) { xkey=crypt_keys[lock]; }
    if(!(ptext=decryptTextString(ctext,xkey))) {
      if(key!==false) { alert("failed to decrypt with provided key"); }
      return(false);
    }
  }
  return(ptext);
}
function decryptBlock(data,key) {
  var tagend=0, ptend=0, lock=null, ptext;
  if((tagend=data.indexOf(">"))==-1) { 
    crypt_debug("no > in " + data);
    return(false); 
  }
  if((ptend=data.lastIndexOf("</"))==-1) {
    crypt_debug(" no </ in " + data);
    return(false);
  }
  lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
  if(lock===null) { lock="default"; }

  if(!(ptext=verifyDecrypt(data.substring(tagend+1,ptend),lock,key))) {
    return(false);
  }
  return("<" + tag_pt + " LOCK=" + lock + ">" + ptext + "</" + tag_pt + ">");
}

// for getTagAttr("<FOO ATTR=val>","ATTR"), return "val"
function getTagAttr(opentag,attr) {
  var loff=0;
  if((loff=opentag.indexOf(attr + "=" ))!=-1) {
    if((t=opentag.indexOf(" ",loff+attr.length+1))!=-1) {
      return(opentag.substring(loff+attr.length+1,t));
    } else {
      return(opentag.substring(loff+attr.length+1,opentag.length-1));
    }
  }
  return(null);
}

function encryptBlock(data,key) {
  var tagend=0, ptend=0, lock=null, ctext;
  if((tagend=data.indexOf(">"))==-1) { 
    crypt_debug("no > in " + data);
    return(false); 
  }
  if((ptend=data.lastIndexOf("</"))==-1) {
    crypt_debug(" no </ in " + data);
    return(false);
  }
  lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
  if(lock===null) { lock="default"; }

  if(key===false) {
    key=getEncryptionKeyForLock(lock);
    if(key===false) { return(false); }
  }
  if(!(ctext=encryptTextString(data.substring(tagend+1,ptend),key))) {
    return(false); 
  }
  return("<ENCRYPTED LOCK=" + lock + ">" + ctext + "</ENCRYPTED>");
}


/* encrypt the string in text with ascii key in akey 
  modified from Encrypt_Text to expect ascii key and take input params
  and to return base64 encoded
*/
function encryptTextString(ptext,akey) {
  var v, i, ret, key;
  var prefix = "#####  Encrypted: decrypt with ";
  prefix+="http://www.fourmilab.ch/javascrypt/\n";
  suffix = "#####  End encrypted message\n";

  if (akey.length === 0) {
    alert("Please specify a key with which to encrypt the message.");
    return;
  }
  if (ptext.length === 0) {
    alert("No plain text to encrypt!");
    return;
  }
  ret="";
  key=setKeyFromAscii(akey);

  // addEntroptyTime eventually results in setting of global entropyData
  // which is used by keyFromEntropy
  addEntropyTime();
  prng = new AESprng(keyFromEntropy());
  var plaintext = encode_utf8(ptext);

  //  Compute MD5 sum of message text and add to header

  md5_init();
  for (i = 0; i < plaintext.length; i++) {
    md5_update(plaintext.charCodeAt(i));
  }
  md5_finish();
  var header = "";
  for (i = 0; i < digestBits.length; i++) {
    header += String.fromCharCode(digestBits[i]);
  }

  //  Add message length in bytes to header

  i = plaintext.length;
  header += String.fromCharCode(i >>> 24);
  header += String.fromCharCode(i >>> 16);
  header += String.fromCharCode(i >>> 8);
  header += String.fromCharCode(i & 0xFF);

  /*  The format of the actual message passed to rijndaelEncrypt
  is:
     Bytes  Content
     0-15   MD5 signature of plaintext
     16-19  Length of plaintext, big-endian order
     20-end Plaintext

  Note that this message will be padded with zero bytes
  to an integral number of AES blocks (blockSizeInBits / 8).
  This does not include the initial vector for CBC
  encryption, which is added internally by rijndaelEncrypt.
  */

  var ct = rijndaelEncrypt(header + plaintext, key, "CBC");
  delete prng;
  return(prefix + armour_base64(ct) + suffix);
}

function decryptTextString(ctext,akey) {
  key=setKeyFromAscii(akey);
  var ct=[];

  // remove line breaks
  ct=disarm_base64(ctext);
  var result=rijndaelDecrypt(ct,key,"CBC");
  var header=result.slice(0,20);
  result=result.slice(20);
  var dl=(header[16]<<24)|(header[17]<<16)|(header[18]<<8)|header[19];
  
  if((dl<0)||(dl>result.length)) {
   // alert("Message (length "+result.length+") != expected (" + dl + ")");
   dl=result.length;
  }
  
  var i,plaintext="";
  md5_init();
  
  for(i=0;i<dl;i++) {
    plaintext+=String.fromCharCode(result[i]);
    md5_update(result[i]);
  }
  
  md5_finish();

  successful = true;
  
  for(i=0;i<digestBits.length;i++) {
    if(digestBits[i]!=header[i]) {
      crypt_debug("Invalid decryption key.");
      return(false);
    }
  }
  return(decode_utf8(plaintext));
}

// BEGIN: javascript/aes.js
// Rijndael parameters --  Valid values are 128, 192, or 256

var keySizeInBits = 256;
var blockSizeInBits = 128;

//
// Note: in the following code the two dimensional arrays are indexed as
//       you would probably expect, as array[row][column]. The state arrays
//       are 2d arrays of the form state[4][Nb].


// The number of rounds for the cipher, indexed by [Nk][Nb]
var roundsArray = [ undefined, undefined, undefined, undefined,[ undefined, undefined, undefined, undefined,10, undefined, 12, undefined, 14], undefined, 
                        [ undefined, undefined, undefined, undefined, 12, undefined, 12, undefined, 14], undefined, 
                        [ undefined, undefined, undefined, undefined, 14, undefined, 14, undefined, 14] ];

// The number of bytes to shift by in shiftRow, indexed by [Nb][row]
var shiftOffsets = [ undefined, undefined, undefined, undefined,[ undefined,1, 2, 3], undefined,[ undefined,1, 2, 3], undefined,[ undefined,1, 3, 4] ];

// The round constants used in subkey expansion
var Rcon = [ 
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 
0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 
0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ];

// Precomputed lookup table for the SBox
var SBox = [
 99, 124, 119, 123, 242, 107, 111, 197,  48,   1, 103,  43, 254, 215, 171, 
118, 202, 130, 201, 125, 250,  89,  71, 240, 173, 212, 162, 175, 156, 164, 
114, 192, 183, 253, 147,  38,  54,  63, 247, 204,  52, 165, 229, 241, 113, 
216,  49,  21,   4, 199,  35, 195,  24, 150,   5, 154,   7,  18, 128, 226, 
235,  39, 178, 117,   9, 131,  44,  26,  27, 110,  90, 160,  82,  59, 214, 
179,  41, 227,  47, 132,  83, 209,   0, 237,  32, 252, 177,  91, 106, 203, 
190,  57,  74,  76,  88, 207, 208, 239, 170, 251,  67,  77,  51, 133,  69, 
249,   2, 127,  80,  60, 159, 168,  81, 163,  64, 143, 146, 157,  56, 245, 
188, 182, 218,  33,  16, 255, 243, 210, 205,  12,  19, 236,  95, 151,  68,  
23,  196, 167, 126,  61, 100,  93,  25, 115,  96, 129,  79, 220,  34,  42, 
144, 136,  70, 238, 184,  20, 222,  94,  11, 219, 224,  50,  58,  10,  73,
  6,  36,  92, 194, 211, 172,  98, 145, 149, 228, 121, 231, 200,  55, 109, 
141, 213,  78, 169, 108,  86, 244, 234, 101, 122, 174,   8, 186, 120,  37,  
 46,  28, 166, 180, 198, 232, 221, 116,  31,  75, 189, 139, 138, 112,  62, 
181, 102,  72,   3, 246,  14,  97,  53,  87, 185, 134, 193,  29, 158, 225,
248, 152,  17, 105, 217, 142, 148, 155,  30, 135, 233, 206,  85,  40, 223,
140, 161, 137,  13, 191, 230,  66, 104,  65, 153,  45,  15, 176,  84, 187,  
 22 ];

// Precomputed lookup table for the inverse SBox
var SBoxInverse = [
 82,   9, 106, 213,  48,  54, 165,  56, 191,  64, 163, 158, 129, 243, 215, 
251, 124, 227,  57, 130, 155,  47, 255, 135,  52, 142,  67,  68, 196, 222, 
233, 203,  84, 123, 148,  50, 166, 194,  35,  61, 238,  76, 149,  11,  66, 
250, 195,  78,   8,  46, 161, 102,  40, 217,  36, 178, 118,  91, 162,  73, 
109, 139, 209,  37, 114, 248, 246, 100, 134, 104, 152,  22, 212, 164,  92, 
204,  93, 101, 182, 146, 108, 112,  72,  80, 253, 237, 185, 218,  94,  21,  
 70,  87, 167, 141, 157, 132, 144, 216, 171,   0, 140, 188, 211,  10, 247, 
228,  88,   5, 184, 179,  69,   6, 208,  44,  30, 143, 202,  63,  15,   2, 
193, 175, 189,   3,   1,  19, 138, 107,  58, 145,  17,  65,  79, 103, 220, 
234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116,  34, 231, 173,
 53, 133, 226, 249,  55, 232,  28, 117, 223, 110,  71, 241,  26, 113,  29, 
 41, 197, 137, 111, 183,  98,  14, 170,  24, 190,  27, 252,  86,  62,  75, 
198, 210, 121,  32, 154, 219, 192, 254, 120, 205,  90, 244,  31, 221, 168,
 51, 136,   7, 199,  49, 177,  18,  16,  89,  39, 128, 236,  95,  96,  81,
127, 169,  25, 181,  74,  13,  45, 229, 122, 159, 147, 201, 156, 239, 160,
224,  59,  77, 174,  42, 245, 176, 200, 235, 187,  60, 131,  83, 153,  97, 
 23,  43,   4, 126, 186, 119, 214,  38, 225, 105,  20,  99,  85,  33,  12,
125 ];

// This method circularly shifts the array left by the number of elements
// given in its parameter. It returns the resulting array and is used for 
// the ShiftRow step. Note that shift() and push() could be used for a more 
// elegant solution, but they require IE5.5+, so I chose to do it manually. 

function cyclicShiftLeft(theArray, positions) {
  var temp = theArray.slice(0, positions);
  theArray = theArray.slice(positions).concat(temp);
  return theArray;
}

// Cipher parameters ... do not change these
var Nk = keySizeInBits / 32;                   
var Nb = blockSizeInBits / 32;
var Nr = roundsArray[Nk][Nb];

// Multiplies the element "poly" of GF(2^8) by x. See the Rijndael spec.

function xtime(poly) {
  poly <<= 1;
  return ((poly & 0x100) ? (poly ^ 0x11B) : (poly));
}

// Multiplies the two elements of GF(2^8) together and returns the result.
// See the Rijndael spec, but should be straightforward: for each power of
// the indeterminant that has a 1 coefficient in x, add y times that power
// to the result. x and y should be bytes representing elements of GF(2^8)

function mult_GF256(x, y) {
  var bit, result = 0;
  
  for (bit = 1; bit < 256; bit *= 2, y = xtime(y)) {
    if (x & bit) { result ^= y; }
  }
  return result;
}

// Performs the substitution step of the cipher.  State is the 2d array of
// state information (see spec) and direction is string indicating whether
// we are performing the forward substitution ("encrypt") or inverse 
// substitution (anything else)

function byteSub(state, direction) {
  var S;
  if (direction == "encrypt") { S = SBox; } // Point S to the SBox we're using
  else { S = SBoxInverse; }
  for (var i = 0; i < 4; i++) { // Substitute for every byte in state
    for (var j = 0; j < Nb; j++) { state[i][j] = S[state[i][j]]; }
  }
}

// Performs the row shifting step of the cipher.

function shiftRow(state, direction) {
  for (var i=1; i<4; i++) {             // Row 0 never shifts
    if (direction == "encrypt") {
       state[i] = cyclicShiftLeft(state[i], shiftOffsets[Nb][i]);
    } else {
       state[i] = cyclicShiftLeft(state[i], Nb - shiftOffsets[Nb][i]);
    }
  }

}

// Performs the column mixing step of the cipher. Most of these steps can
// be combined into table lookups on 32bit values (at least for encryption)
// to greatly increase the speed. 

function mixColumn(state, direction) {
  var b = [];                            // Result of matrix multiplications
  var i = 0;
  for (var j = 0; j < Nb; j++) {         // Go through each column...
    for (i = 0; i < 4; i++) {        // and for each row in the column...
      if (direction == "encrypt") {
        b[i] = mult_GF256(state[i][j], 2) ^          // perform mixing
               mult_GF256(state[(i+1)%4][j], 3) ^ 
               state[(i+2)%4][j] ^ 
               state[(i+3)%4][j];
      } else {
        b[i] = mult_GF256(state[i][j], 0xE) ^ 
               mult_GF256(state[(i+1)%4][j], 0xB) ^
               mult_GF256(state[(i+2)%4][j], 0xD) ^
               mult_GF256(state[(i+3)%4][j], 9);
      }
    }
    for (i = 0; i < 4; i++) {        // Place result back into column
      state[i][j] = b[i];
    }
  }
}

// Adds the current round key to the state information. Straightforward.

function addRoundKey(state, roundKey) {
  for (var j = 0; j < Nb; j++) {                 // Step through columns...
    state[0][j] ^= (roundKey[j] & 0xFF);         // and XOR
    state[1][j] ^= ((roundKey[j]>>8) & 0xFF);
    state[2][j] ^= ((roundKey[j]>>16) & 0xFF);
    state[3][j] ^= ((roundKey[j]>>24) & 0xFF);
  }
}

// This function creates the expanded key from the input (128/192/256-bit)
// key. The parameter key is an array of bytes holding the value of the key.
// The returned value is an array whose elements are the 32-bit words that 
// make up the expanded key.

function keyExpansion(key) {
  var expandedKey = [];
  var temp;

  // in case the key size or parameters were changed...
  Nk = keySizeInBits / 32;                   
  Nb = blockSizeInBits / 32;
  Nr = roundsArray[Nk][Nb];

  for (var j=0; j < Nk; j++) {   // Fill in input key first
    expandedKey[j] = 
      (key[4*j]) | (key[4*j+1]<<8) | (key[4*j+2]<<16) | (key[4*j+3]<<24);
  }

  // Now walk down the rest of the array filling in expanded key bytes as
  // per Rijndael's spec
  for (j = Nk; j < Nb * (Nr + 1); j++) {    // For each word of expanded key
    temp = expandedKey[j - 1];
    if (j % Nk === 0) {
      temp = ( (SBox[(temp>>8) & 0xFF]) |
               (SBox[(temp>>16) & 0xFF]<<8) |
               (SBox[(temp>>24) & 0xFF]<<16) |
               (SBox[temp & 0xFF]<<24) ) ^ Rcon[Math.floor(j / Nk) - 1];
    } else if (Nk > 6 && j % Nk == 4) {
      temp = (SBox[(temp>>24) & 0xFF]<<24) |
             (SBox[(temp>>16) & 0xFF]<<16) |
             (SBox[(temp>>8) & 0xFF]<<8) |
             (SBox[temp & 0xFF]);
    }
    expandedKey[j] = expandedKey[j-Nk] ^ temp;
  }
  return expandedKey;
}

// Rijndael's round functions... 

function jcRound(state, roundKey) {
  byteSub(state, "encrypt");
  shiftRow(state, "encrypt");
  mixColumn(state, "encrypt");
  addRoundKey(state, roundKey);
}

function inverseRound(state, roundKey) {
  addRoundKey(state, roundKey);
  mixColumn(state, "decrypt");
  shiftRow(state, "decrypt");
  byteSub(state, "decrypt");
}

function finalRound(state, roundKey) {
  byteSub(state, "encrypt");
  shiftRow(state, "encrypt");
  addRoundKey(state, roundKey);
}

function inverseFinalRound(state, roundKey){
  addRoundKey(state, roundKey);
  shiftRow(state, "decrypt");
  byteSub(state, "decrypt");  
}

// encrypt is the basic encryption function. It takes parameters
// block, an array of bytes representing a plaintext block, and expandedKey,
// an array of words representing the expanded key previously returned by
// keyExpansion(). The ciphertext block is returned as an array of bytes.

function encrypt(block, expandedKey) {
  var i;  
  if (!block || block.length*8 != blockSizeInBits) { return; }
  if (!expandedKey) { return; }

  block = packBytes(block);
  addRoundKey(block, expandedKey);
  for (i=1; i<Nr; i++) { jcRound(block, expandedKey.slice(Nb*i, Nb*(i+1))); }
  finalRound(block, expandedKey.slice(Nb*Nr)); 
  return unpackBytes(block);
}

// decrypt is the basic decryption function. It takes parameters
// block, an array of bytes representing a ciphertext block, and expandedKey,
// an array of words representing the expanded key previously returned by
// keyExpansion(). The decrypted block is returned as an array of bytes.

function decrypt(block, expandedKey) {
  var i;
  if (!block || block.length*8 != blockSizeInBits) { return; }
  if (!expandedKey) { return; }

  block = packBytes(block);
  inverseFinalRound(block, expandedKey.slice(Nb*Nr)); 
  for (i = Nr - 1; i>0; i--) {
    inverseRound(block, expandedKey.slice(Nb*i, Nb*(i+1)));
  }
  addRoundKey(block, expandedKey);
  return unpackBytes(block);
}

/* !NEEDED
// This method takes a byte array (byteArray) and converts it to a string by
// applying String.fromCharCode() to each value and concatenating the result.
// The resulting string is returned. Note that this function SKIPS zero bytes
// under the assumption that they are padding added in formatPlaintext().
// Obviously, do not invoke this method on raw data that can contain zero
// bytes. It is really only appropriate for printable ASCII/Latin-1 
// values. Roll your own function for more robust functionality :)

function byteArrayToString(byteArray) {
  var result = "";
  for(var i=0; i<byteArray.length; i++)
    if (byteArray[i] != 0) 
      result += String.fromCharCode(byteArray[i]);
  return result;
}
*/

// This function takes an array of bytes (byteArray) and converts them
// to a hexadecimal string. Array element 0 is found at the beginning of 
// the resulting string, high nibble first. Consecutive elements follow
// similarly, for example [16, 255] --> "10ff". The function returns a 
// string.

function byteArrayToHex(byteArray) {
  var result = "";
  if (!byteArray) { return; }
  for (var i=0; i<byteArray.length; i++) {
    result += ((byteArray[i]<16) ? "0" : "") + byteArray[i].toString(16);
  }

  return result;
}

// This function converts a string containing hexadecimal digits to an 
// array of bytes. The resulting byte array is filled in the order the
// values occur in the string, for example "10FF" --> [16, 255]. This
// function returns an array. 

function hexToByteArray(hexString) {
  var byteArray = [];
  if (hexString.length % 2) { return; } // must have even length
  if (hexString.indexOf("0x") === 0 || hexString.indexOf("0X") === 0) {
    hexString = hexString.substring(2);
  }
  for (var i = 0; i<hexString.length; i += 2) {
    byteArray[Math.floor(i/2)] = parseInt(hexString.slice(i, i+2), 16);
  }
  return byteArray;
}

// This function packs an array of bytes into the four row form defined by
// Rijndael. It assumes the length of the array of bytes is divisible by
// four. Bytes are filled in according to the Rijndael spec (starting with
// column 0, row 0 to 3). This function returns a 2d array.

function packBytes(octets) {
  var state = [];
  if (!octets || octets.length % 4) { return; }

  state[0] = []; state[1] = [];
  state[2] = []; state[3] = [];
  for (var j=0; j<octets.length; j+= 4) {
    state[0][j/4] = octets[j];
    state[1][j/4] = octets[j+1];
    state[2][j/4] = octets[j+2];
    state[3][j/4] = octets[j+3];
  }
  return state;  
}

// This function unpacks an array of bytes from the four row format preferred
// by Rijndael into a single 1d array of bytes. It assumes the input "packed"
// is a packed array. Bytes are filled in according to the Rijndael spec. 
// This function returns a 1d array of bytes.

function unpackBytes(packed) {
  var result = [];
  for (var j=0; j<packed[0].length; j++) {
    result[result.length] = packed[0][j];
    result[result.length] = packed[1][j];
    result[result.length] = packed[2][j];
    result[result.length] = packed[3][j];
  }
  return result;
}

// This function takes a prospective plaintext (string or array of bytes)
// and pads it with pseudorandom bytes if its length is not a multiple of the block 
// size. If plaintext is a string, it is converted to an array of bytes
// in the process. The type checking can be made much nicer using the 
// instanceof operator, but this operator is not available until IE5.0 so I 
// chose to use the heuristic below. 

function formatPlaintext(plaintext) {
  var bpb = blockSizeInBits / 8;               // bytes per block
  var i;

  // if primitive string or String instance
  if ((!((typeof plaintext == "object") &&
        ((typeof (plaintext[0])) == "number"))) &&
      ((typeof plaintext == "string") || plaintext.indexOf)) {
    plaintext = plaintext.split("");
    // Unicode issues here (ignoring high byte)
    for (i=0; i<plaintext.length; i++) {
      plaintext[i] = plaintext[i].charCodeAt(0) & 0xFF;
    }
  } 

  i = plaintext.length % bpb;
  if (i > 0) {
    plaintext = plaintext.concat(getRandomBytes(bpb - i));
  }
  
  return plaintext;
}

// Returns an array containing "howMany" random bytes.

function getRandomBytes(howMany) {
  var i, bytes = [];
    
  for (i = 0; i < howMany; i++) {
    bytes[i] = prng.nextInt(255);
  }
  return bytes;
}

// rijndaelEncrypt(plaintext, key, mode)
// Encrypts the plaintext using the given key and in the given mode. 
// The parameter "plaintext" can either be a string or an array of bytes. 
// The parameter "key" must be an array of key bytes. If you have a hex 
// string representing the key, invoke hexToByteArray() on it to convert it 
// to an array of bytes. The third parameter "mode" is a string indicating
// the encryption mode to use, either "ECB" or "CBC". If the parameter is
// omitted, ECB is assumed.
// 
// An array of bytes representing the cihpertext is returned. To convert 
// this array to hex, invoke byteArrayToHex() on it.

function rijndaelEncrypt(plaintext, key, mode) {
  var expandedKey, i, aBlock;
  var bpb = blockSizeInBits / 8;          // bytes per block
  var ct;                                 // ciphertext

  if (!plaintext || !key) { return; }
  if (key.length*8 != keySizeInBits) { return; }
  if (mode == "CBC") {
    ct = getRandomBytes(bpb);             // get IV
//dump("IV", byteArrayToHex(ct));
  } else {
    mode = "ECB";
    ct = [];
  }

  // convert plaintext to byte array and pad with zeros if necessary. 
  plaintext = formatPlaintext(plaintext);

  expandedKey = keyExpansion(key);
  
  for (var block = 0; block < plaintext.length / bpb; block++) {
    aBlock = plaintext.slice(block * bpb, (block + 1) * bpb);
    if (mode == "CBC") {
      for (i = 0; i < bpb; i++) {
        aBlock[i] ^= ct[(block * bpb) + i];
      }
    }
    ct = ct.concat(encrypt(aBlock, expandedKey));
  }

  return ct;
}

// rijndaelDecrypt(ciphertext, key, mode)
// Decrypts the using the given key and mode. The parameter "ciphertext" 
// must be an array of bytes. The parameter "key" must be an array of key 
// bytes. If you have a hex string representing the ciphertext or key, 
// invoke hexToByteArray() on it to convert it to an array of bytes. The
// parameter "mode" is a string, either "CBC" or "ECB".
// 
// An array of bytes representing the plaintext is returned. To convert 
// this array to a hex string, invoke byteArrayToHex() on it. To convert it 
// to a string of characters, you can use byteArrayToString().

function rijndaelDecrypt(ciphertext, key, mode) {
  var expandedKey;
  var bpb = blockSizeInBits / 8;          // bytes per block
  var pt = [];                   // plaintext array
  var aBlock;                             // a decrypted block
  var block;                              // current block number

  if (!ciphertext || !key || typeof ciphertext == "string") { return; }
  if (key.length*8 != keySizeInBits) { return; }
  if (!mode) { mode = "ECB"; } // assume ECB if mode omitted

  expandedKey = keyExpansion(key);
 
  // work backwards to accomodate CBC mode 
  for (block=(ciphertext.length / bpb)-1; block>0; block--) {
    aBlock = 
     decrypt(ciphertext.slice(block*bpb,(block+1)*bpb), expandedKey);
    if (mode == "CBC") {
      for (var i=0; i<bpb; i++) {
        pt[(block-1)*bpb + i] = aBlock[i] ^ ciphertext[(block-1)*bpb + i];
      }
    } else {
      pt = aBlock.concat(pt);
    }
  }

  // do last block if ECB (skips the IV in CBC)
  if (mode == "ECB") {
    pt = decrypt(ciphertext.slice(0, bpb), expandedKey).concat(pt);
  }

  return pt;
}

// END: javascrypt/aes.js 
// BEGIN: javascrypt/entropy.js

//  Entropy collection utilities

/* Start by declaring static storage and initialise
   the entropy vector from the time we come through
   here. */
  
var entropyData = []; // Collected entropy data
var edlen = 0;        // Keyboard array data length
 
addEntropyTime();     // Start entropy collection with page load time
ce();                 // Roll milliseconds into initial entropy

//  Add a byte to the entropy vector
    
function addEntropyByte(b) {
  entropyData[edlen++] = b;
}
            
/*  Capture entropy.  When the user presses a key or performs
  various other events for which we can request
  notification, add the time in 255ths of a second to the
  entropyData array.  The name of the function is short
  so it doesn't bloat the form object declarations in
  which it appears in various "onXXX" events.  */
    
function ce() {
  addEntropyByte(Math.floor((((new Date()).getMilliseconds()) * 255) / 999));
}
    
//  Add a 32 bit quantity to the entropy vector
    
function addEntropy32(w) {
  var i;
  
  for (i = 0; i < 4; i++) {
    addEntropyByte(w & 0xFF);
    w >>= 8;
  }
}
    
/*  Add the current time and date (milliseconds since the epoch,
    truncated to 32 bits) to the entropy vector.  */
  
function addEntropyTime() {
  addEntropy32((new Date()).getTime());
}
/*  Start collection of entropy from mouse movements. The
  argument specifies the  number of entropy items to be
  obtained from mouse motion, after which mouse motion
  will be ignored.  Note that you can re-enable mouse
  motion collection at any time if not already underway.  */
  
var mouseMotionCollect = 0;
var oldMoveHandler;    // For saving and restoring mouse move handler in IE4
  
function mouseMotionEntropy(maxsamp) {
  if (mouseMotionCollect <= 0) {
    mouseMotionCollect = maxsamp;
    if ((document.implementation.hasFeature("Events", "2.0")) &&
        document.addEventListener) {
      //  Browser supports Document Object Model (DOM) 2 events
      document.addEventListener("mousemove", mouseMoveEntropy, false);
    } else {
      if (document.attachEvent) {
        //  Internet Explorer 5 and above event model
        document.attachEvent("onmousemove", mouseMoveEntropy);
      } else {
        //  Internet Explorer 4 event model
        oldMoveHandler = document.onmousemove;
        document.onmousemove = mouseMoveEntropy;
      }
    }
    //dump("Mouse enable", mouseMotionCollect);
  }
}
    
/*  Collect entropy from mouse motion events.  Note that
  this is craftily coded to work with either DOM2 or Internet
  Explorer style events.  Note that we don't use every successive
  mouse movement event.  Instead, we XOR the three bytes collected
  from the mouse and use that to determine how many subsequent
  mouse movements we ignore before capturing the next one.  */
  
var mouseEntropyTime = 0;      // Delay counter for mouse entropy collection
  
function mouseMoveEntropy(e) {
  if (!e) {
    e = window.event;      // Internet Explorer event model
  }
  if (mouseMotionCollect > 0) {
    if (mouseEntropyTime-- <= 0) {
      addEntropyByte(e.screenX & 0xFF);
      addEntropyByte(e.screenY & 0xFF);
      ce();
      mouseMotionCollect--;
      mouseEntropyTime = (entropyData[edlen - 3] ^ entropyData[edlen - 2] ^
                          entropyData[edlen - 1]) % 19;
      //dump("Mouse Move", byteArrayToHex(entropyData.slice(-3)));
    }
    if (mouseMotionCollect <= 0) {
      if (document.removeEventListener) {
        document.removeEventListener("mousemove", mouseMoveEntropy, false);
      } else if (document.detachEvent) {
        document.detachEvent("onmousemove", mouseMoveEntropy);
      } else {
        document.onmousemove = oldMoveHandler;
      }
      //dump("Spung!", 0);
    }
  }
}
    
/*  Compute a 32 byte key value from the entropy vector.
  We compute the value by taking the MD5 sum of the even
  and odd bytes respectively of the entropy vector, then
  concatenating the two MD5 sums.  */
    
function keyFromEntropy() {
  var i, k = [];
  
  if (edlen === 0) {
    alert("Blooie!  Entropy vector void at call to keyFromEntropy.");
  }
  //dump("Entropy bytes", edlen);

  md5_init();
  for (i = 0; i < edlen; i += 2) {
    md5_update(entropyData[i]);
  }
  md5_finish();
  for (i = 0; i < 16; i++) {
    k[i] = digestBits[i];
  }

  md5_init();
  for (i = 1; i < edlen; i += 2) {
    md5_update(entropyData[i]);
  }
  md5_finish();
  for (i = 0; i < 16; i++) {
    k[i + 16] = digestBits[i];
  }
  
  //dump("keyFromEntropy", byteArrayToHex(k));
  return k;
}
// END: javascrypt/entropy.js
// BEGIN: javascrypt/aesprng.js
//  AES based pseudorandom number generator

/*  Constructor.  Called with an array of 32 byte (0-255) values
  containing the initial seed.  */

function AESprng(seed) {
  this.key = [];
  this.key = seed;
  this.itext = hexToByteArray("9F489613248148F9C27945C6AE62EECA3E3367BB14064E4E6DC67A9F28AB3BD1");
  this.nbytes = 0;          // Bytes left in buffer
  
  this.next = AESprng_next;
  this.nextbits = AESprng_nextbits;
  this.nextInt = AESprng_nextInt;
  this.round = AESprng_round;
  
  /*  Encrypt the initial text with the seed key
      three times, feeding the output of the encryption
      back into the key for the next round.  */
  
  bsb = blockSizeInBits;
  blockSizeInBits = 256;    
  var i, ct;
  for (i = 0; i < 3; i++) {
    this.key = rijndaelEncrypt(this.itext, this.key, "ECB");
  }
  
  /*  Now make between one and four additional
      key-feedback rounds, with the number determined
      by bits from the result of the first three
      rounds.  */
  
  var n = 1 + (this.key[3] & 2) + (this.key[9] & 1);    
  for (i = 0; i < n; i++) {
    this.key = rijndaelEncrypt(this.itext, this.key, "ECB");
  }
  blockSizeInBits = bsb;
}
    
function AESprng_round() {
  bsb = blockSizeInBits;
  blockSizeInBits = 256;    
  this.key = rijndaelEncrypt(this.itext, this.key, "ECB");
  this.nbytes = 32;
  blockSizeInBits = bsb;
}
    
//  Return next byte from the generator
function AESprng_next() {
  if (this.nbytes <= 0) {
    this.round();
  }
  return(this.key[--this.nbytes]);
}
    
//  Return n bit integer value (up to maximum integer size)
function AESprng_nextbits(n) {
  var i, w = 0, nbytes = Math.floor((n + 7) / 8);

  for (i = 0; i < nbytes; i++) {
    w = (w << 8) | this.next();
  }
  return w & ((1 << n) - 1);
}

//  Return integer between 0 and n inclusive
function AESprng_nextInt(n) {
  var p = 1, nb = 0;
  
  //  Determine smallest p,  2^p > n
  //  nb = log_2 p
  
  while (n >= p) {
    p <<= 1;
    nb++;
  }
  p--;
  
  /*  Generate values from 0 through n by first generating
      values v from 0 to (2^p)-1, then discarding any results v > n.
      For the rationale behind this (and why taking
      values mod (n + 1) is biased toward smaller values, see
      Ferguson and Schneier, "Practical Cryptography",
      ISBN 0-471-22357-3, section 10.8).  */

  while (true) {
    var v = this.nextbits(nb) & p;
      
    if (v <= n) {
      return v;
    }
  }
}
// END: javascrypt/aesprng.js
// BEGIN: javascrypt/lecuyer.js
/*
   L'Ecuyer's two-sequence generator with a Bays-Durham shuffle
  on the back-end.  Schrage's algorithm is used to perform
  64-bit modular arithmetic within the 32-bit constraints of
  JavaScript.

  Bays, C. and S. D. Durham.  ACM Trans. Math. Software: 2 (1976)
    59-64.

  L'Ecuyer, P.  Communications of the ACM: 31 (1968) 742-774.

  Schrage, L.  ACM Trans. Math. Software: 5 (1979) 132-138.

*/

// Schrage's modular multiplication algorithm
function uGen(old, a, q, r, m) {      
  var t;

  t = Math.floor(old / q);
  t = a * (old - (t * q)) - (t * r);
  return Math.round((t < 0) ? (t + m) : t);
}

// Return next raw value
function LEnext() {
  var i;

  this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
  this.gen2 = uGen(this.gen2, 40692, 52774, 3791, 2147483399);

  /* Extract shuffle table index from most significant part
     of the previous result. */

  i = Math.floor(this.state / 67108862);

  // New state is sum of generators modulo one of their moduli

  this.state = Math.round((this.shuffle[i] + this.gen2) % 2147483563);

  // Replace value in shuffle table with generator 1 result

  this.shuffle[i] = this.gen1;

  return this.state;
}

//  Return next random integer between 0 and n inclusive

function LEnint(n) {
  var p = 1;

  //  Determine smallest p,  2^p > n

  while (n >= p) {
    p <<= 1;
  }
  p--;

  /*  Generate values from 0 through n by first masking
    values v from 0 to (2^p)-1, then discarding any results v > n.
  For the rationale behind this (and why taking
  values mod (n + 1) is biased toward smaller values, see
  Ferguson and Schneier, "Practical Cryptography",
  ISBN 0-471-22357-3, section 10.8).  */

    while (true) {
      var v = this.next() & p;

      if (v <= n) {
      return v;
    }
  }
}

//  Constructor.  Called with seed value
function LEcuyer(s) {
  var i;

  this.shuffle = [];
  this.gen1 = this.gen2 = (s & 0x7FFFFFFF);
  for (i = 0; i < 19; i++) {
    this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
  }

  // Fill the shuffle table with values

  for (i = 0; i < 32; i++) {
    this.gen1 = uGen(this.gen1, 40014, 53668, 12211, 2147483563);
    this.shuffle[31 - i] = this.gen1;
  }
  this.state = this.shuffle[0];
  this.next = LEnext;
  this.nextInt = LEnint;
}
// END:  javascrypt/lecuyer.js
// BEGIN: javascrypt/md5.js
function array(n) {
    for (i = 0; i < n; i++) {
        this[i] = 0;
    }
    this.length = n;
}

/* Some basic logical functions had to be rewritten because of a bug in
 * Javascript.. Just try to compute 0xffffffff >> 4 with it..
 * Of course, these functions are slower than the original would be, but
 * at least, they work!
 */

function integer(n) {
    return n % (0xffffffff + 1);
}

function shr(a, b) {
    a = integer(a);
    b = integer(b);
    if (a - 0x80000000 >= 0) {
        a = a % 0x80000000;
        a >>= b;
        a += 0x40000000 >> (b - 1);
    } else {
        a >>= b;
    }
    return a;
}

function shl1(a) {
    a = a % 0x80000000;
    if (a & 0x40000000 == 0x40000000) {
        a -= 0x40000000;  
        a *= 2;
        a += 0x80000000;
    } else {
        a *= 2;
    }
    return a;
}

function shl(a, b) {
    a = integer(a);
    b = integer(b);
    for (var i = 0; i < b; i++) {
        a = shl1(a);
    }
    return a;
}

function and(a, b) {
    a = integer(a);
    b = integer(b);
    var t1 = a - 0x80000000;
    var t2 = b - 0x80000000;
    if (t1 >= 0) {
        if (t2 >= 0) {
            return ((t1 & t2) + 0x80000000);
        } else {
            return (t1 & b);
        }
    } else {
        if (t2 >= 0) {
            return (a & t2);
        } else {
            return (a & b);  
        }
    }
}

function or(a, b) {
    a = integer(a);
    b = integer(b);
    var t1 = a - 0x80000000;
    var t2 = b - 0x80000000;
    if (t1 >= 0) {
        if (t2 >= 0) {
            return ((t1 | t2) + 0x80000000);
        } else {
            return ((t1 | b) + 0x80000000);
        }
    } else {
        if (t2 >= 0) {
            return ((a | t2) + 0x80000000);
        } else {
            return (a | b);  
        }
    }
}

function xor(a, b) {
  a = integer(a);
  b = integer(b);
  var t1 = a - 0x80000000;
  var t2 = b - 0x80000000;
  if (t1 >= 0) {
    if (t2 >= 0) {
      return (t1 ^ t2);
    } else {
      return ((t1 ^ b) + 0x80000000);
    }
  } else {
    if (t2 >= 0) {
      return ((a ^ t2) + 0x80000000);
    } else {
      return (a ^ b);  
    }
  }
}

function not(a) {
  a = integer(a);
  return 0xffffffff - a;
}

/* Here begin the real algorithm */

var state = [];
var count = [];
    count[0] = 0;
    count[1] = 0;                     
var buffer = [];
var transformBuffer = [];
var digestBits = [];

var S11 = 7;
var S12 = 12;
var S13 = 17;
var S14 = 22;
var S21 = 5;
var S22 = 9;
var S23 = 14;
var S24 = 20;
var S31 = 4;
var S32 = 11;
var S33 = 16;
var S34 = 23;
var S41 = 6;
var S42 = 10;
var S43 = 15;
var S44 = 21;

function jcF(x, y, z) {
  return or(and(x, y), and(not(x), z));
}

function jcG(x, y, z) {
  return or(and(x, z), and(y, not(z)));
}

function jcH(x, y, z) {
  return xor(xor(x, y), z);
}

function jcI(x, y, z) {
  return xor(y ,or(x , not(z)));
}

function rotateLeft(a, n) {
  return or(shl(a, n), (shr(a, (32 - n))));
}

function jcFF(a, b, c, d, x, s, ac) {
  a = a + jcF(b, c, d) + x + ac;
  a = rotateLeft(a, s);
  a = a + b;
  return a;
}

function jcGG(a, b, c, d, x, s, ac) {
  a = a + jcG(b, c, d) + x + ac;
  a = rotateLeft(a, s);
  a = a + b;
  return a;
}

function jcHH(a, b, c, d, x, s, ac) {
  a = a + jcH(b, c, d) + x + ac;
  a = rotateLeft(a, s);
  a = a + b;
  return a;
}

function jcII(a, b, c, d, x, s, ac) {
  a = a + jcI(b, c, d) + x + ac;
  a = rotateLeft(a, s);
  a = a + b;
  return a;
}

function transform(buf, offset) { 
  var a = 0, b = 0, c = 0, d = 0; 
  var x = transformBuffer;
  
  a = state[0];
  b = state[1];
  c = state[2];
  d = state[3];
  
  for (i = 0; i < 16; i++) {
    x[i] = and(buf[i * 4 + offset], 0xFF);
    for (j = 1; j < 4; j++) {
      x[i] += shl(and(buf[i * 4 + j + offset] ,0xFF), j * 8);
    }
  }

  /* Round 1 */
  a = jcFF( a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
  d = jcFF( d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
  c = jcFF( c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
  b = jcFF( b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
  a = jcFF( a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
  d = jcFF( d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
  c = jcFF( c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
  b = jcFF( b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
  a = jcFF( a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
  d = jcFF( d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
  c = jcFF( c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
  b = jcFF( b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
  a = jcFF( a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
  d = jcFF( d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
  c = jcFF( c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
  b = jcFF( b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

  /* Round 2 */
  a = jcGG( a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
  d = jcGG( d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
  c = jcGG( c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
  b = jcGG( b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
  a = jcGG( a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
  d = jcGG( d, a, b, c, x[10], S22,  0x2441453); /* 22 */
  c = jcGG( c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
  b = jcGG( b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
  a = jcGG( a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
  d = jcGG( d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
  c = jcGG( c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
  b = jcGG( b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
  a = jcGG( a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
  d = jcGG( d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
  c = jcGG( c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
  b = jcGG( b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

  /* Round 3 */
  a = jcHH( a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
  d = jcHH( d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
  c = jcHH( c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
  b = jcHH( b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
  a = jcHH( a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
  d = jcHH( d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
  c = jcHH( c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
  b = jcHH( b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
  a = jcHH( a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
  d = jcHH( d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
  c = jcHH( c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
  b = jcHH( b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
  a = jcHH( a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
  d = jcHH( d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
  c = jcHH( c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
  b = jcHH( b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */

  /* Round 4 */
  a = jcII( a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
  d = jcII( d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
  c = jcII( c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
  b = jcII( b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
  a = jcII( a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
  d = jcII( d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
  c = jcII( c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
  b = jcII( b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
  a = jcII( a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
  d = jcII( d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
  c = jcII( c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
  b = jcII( b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
  a = jcII( a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
  d = jcII( d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
  c = jcII( c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
  b = jcII( b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */

  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;

}

function md5_init() {
  count[0] = count[1] = 0;
  state[0] = 0x67452301;
  state[1] = 0xefcdab89;
  state[2] = 0x98badcfe;
  state[3] = 0x10325476;
  for (i = 0; i < digestBits.length; i++) {
    digestBits[i] = 0;
  }
}

function md5_update(b) { 
  var index, i;
    
  index = and(shr(count[0],3) , 0x3F);
  if (count[0] < 0xFFFFFFFF - 7) {
    count[0] += 8;
  } else {
    count[1]++;
    count[0] -= 0xFFFFFFFF + 1;
    count[0] += 8;
  }
  buffer[index] = and(b, 0xff);
  if (index  >= 63) {
    transform(buffer, 0);
  }
}

function md5_finish() {
  var bits = [];
  var padding; 
  var i = 0, index = 0, padLen = 0;

  for (i = 0; i < 4; i++) {
    bits[i] = and(shr(count[0], (i * 8)), 0xFF);
  }
  for (i = 0; i < 4; i++) {
    bits[i + 4] = and(shr(count[1], (i * 8)), 0xFF);
  }
  index = and(shr(count[0], 3), 0x3F);
  padLen = (index < 56) ? (56 - index) : (120 - index);
  padding = [];
  padding[0] = 0x80;
  for (i = 0; i < padLen; i++) {
    md5_update(padding[i]);
  }
  for (i = 0; i < 8; i++) {
  md5_update(bits[i]);
  }

  for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++) {
      digestBits[i * 4 + j] = and(shr(state[i], (j * 8)) , 0xFF);
    }
  } 
}

/* End of the MD5 algorithm */
// END: javascyprt/md5.js
// BEGIN: javscrypt/armour.js

//  Varieties of ASCII armour for binary data

var maxLineLength = 64; // Maximum line length for armoured text
    
/* Hexadecimal Armour
    
   A message is encoded in Hexadecimal armour by expressing its
   bytes as a hexadecimal string which is prefixed by a sentinel
   of "?HX?" and suffixed by "?H", then broken into lines no
   longer than maxLineLength.  Armoured messages use lower case
   letters for digits with decimal values of 0 through 15, but
   either upper or lower case letters are accepted when decoding
   a message.  The hexadecimal to byte array interconversion
   routines in aes.js do most of the heavy lifting here.  */
    
var hexSentinel = "?HX?", hexEndSentinel = "?H";
    
//  Encode byte array in hexadecimal armour
    
function armour_hex(b) {
  var h = hexSentinel + byteArrayToHex(b) + hexEndSentinel;
  var t = "";
  while (h.length > maxLineLength) {
    //dump("h.length", h.length);
    t += h.substring(0, maxLineLength) + "\n";
    h = h.substring(maxLineLength, h.length);
  }
  //dump("h.final_length", h.length);
  t += h + "\n";
  return t;
}
    
/* Decode string in hexadecimal armour to byte array.  If the
   string supplied contains a start and/or end sentinel,
   only characters within the sentinels will be decoded.
   Non-hexadecimal digits are silently ignored, which
   automatically handles line breaks.  We might want to
   diagnose invalid characters as opposed to ignoring them.  */
    
function disarm_hex(s) {
  var hexDigits = "0123456789abcdefABCDEF";
  var hs = "", i;

  //  Extract hexadecimal data between sentinels, if present
  if ((i = s.indexOf(hexSentinel)) >= 0) {
    s = s.substring(i + hexSentinel.length, s.length);
  }
  if ((i = s.indexOf(hexEndSentinel)) >= 0) {
    s = s.substring(0, i);
  }

  //  Assemble string of valid hexadecimal digits

  for (i = 0; i < s.length; i++) {
    var c = s.charAt(i);
    if (hexDigits.indexOf(c) >= 0) {
      hs += c;
    }
  }
//dump("hs", hs);
  return hexToByteArray(hs);
}

  /*  Codegroup Armour
      Codegroup armour encodes a byte string into a sequence of five
  letter code groups like spies used in the good old days.  The
  first group of a message is always "ZZZZZ" and the last "YYYYY";
  the decoding process ignores any text outside these start and
  end sentinels.  Bytes are encoded as two letters in the range
  "A" to "X", each encoding four bits of the byte.  Encoding uses
  a pseudorandomly generated base letter and wraps around modulo
  24 to spread encoded letters evenly through the alphabet.  (This
  refinement is purely aesthetic; the base letter sequence is
  identical for all messages and adds no security.  If the message
  does not fill an even number of five letter groups, the last
  group is padded to five letters with "Z" characters, which are
  ignored when decoding.  */
    
var acgcl, acgt, acgg;
    
// Output next codegroup, flushing current line if it's full
    
function armour_cg_outgroup() {
  if (acgcl.length > maxLineLength) {
    acgt += acgcl + "\n";
    acgcl = "";
  }
  if (acgcl.length > 0) {
    acgcl += " ";
  }
  acgcl += acgg;
  acgg = "";
}
    
/* Add a letter to the current codegroup, emitting it when
   it reaches five letters.  */
    
function armour_cg_outletter(l) {
  if (acgg.length >= 5) {
    armour_cg_outgroup();
  }
  acgg += l;
}
    
var codegroupSentinel = "ZZZZZ";
    
function armour_codegroup(b) {
  var charBase = ("A").charCodeAt(0);

  acgcl = codegroupSentinel;
  acgt = "";
  acgg = "";

  var cgrng = new LEcuyer(0xbadf00d);
  for (i = 0; i < b.length; i++) {
    var r = cgrng.nextInt(23);
    armour_cg_outletter(String.fromCharCode(charBase + ((((b[i] >> 4) & 0xF)) + r) % 24));
    r = cgrng.nextInt(23);
    armour_cg_outletter(String.fromCharCode(charBase + ((((b[i] & 0xF)) + r) % 24)));
  }
  delete cgrng;

  //  Generate nulls to fill final codegroup if required

  while (acgg.length < 5) {
    armour_cg_outletter("Z");
  }
  armour_cg_outgroup();

  //  Append terminator group

  acgg = "YYYYY";
  armour_cg_outgroup();

  //  Flush last line

  acgt += acgcl + "\n";

  return acgt;
}
    
var dcgs, dcgi;
    
  /*  Obtain next "significant" character from message.  Characters
    other than letters are silently ignored; both lower and upper
    case letters are accepted.  */
    
function disarm_cg_insig() {
  while (dcgi < dcgs.length) {
    var c = dcgs.charAt(dcgi++).toUpperCase();
    if ((c >= "A") && (c <= "Z")) {
      //dump("c", c);
      return c;
    }
  }
  return "";
}
    
// Decode a message in codegroup armour
    
function disarm_codegroup(s) {
  var b = [];
  var nz = 0, ba, bal = 0, c;

  dcgs = s;
  dcgi = 0;

  //  Search for initial group of "ZZZZZ"

  while (nz < 5) {
    c = disarm_cg_insig();
      
    if (c == "Z") {
      nz++;
    } else if (c === "") {
      nz = 0;
      break;
    } else {
      nz = 0;
    }
  }
  
  if (nz === 0) {
      alert("No codegroup starting symbol found in message.");
      return "";
  }
  
  /*  Decode letter pairs from successive groups
      and assemble into bytes.  */
  
  var charBase = ("A").charCodeAt(0);    
  var cgrng = new LEcuyer(0xbadf00d);
  for (nz = 0; nz < 2; ) {
    c = disarm_cg_insig();
    //dump("c", c);
   
    if ((c == "Y") || (c === "")) {
      break;
    } else if (c != "Z") {
      var r = cgrng.nextInt(23);
      var n = c.charCodeAt(0) - charBase;
      n = (n + (24 - r)) % 24;
      //dump("n", n);
      if (nz === 0) {
        ba = (n << 4);
        nz++;
      } else {
        ba |= n;
        b[bal++] = ba;
        nz = 0;
      }
    }
  }
  delete cgrng;

  /*  Ponder how we escaped from the decoder loop and
      issue any requisite warnings.  */

  var kbo = "  Attempting decoding with data received.";
  if (nz !== 0) {
    alert("Codegroup data truncated." + kbo);
  } else {
    if (c == "Y") {
      nz = 1;
      while (nz < 5) {
        c = disarm_cg_insig();
        if (c != "Y") {
          break;
        }
        nz++;
      }
      if (nz != 5) {
        alert("Codegroup end group incomplete." + kbo);
      }
    } else {
      alert("Codegroup end group missing." + kbo);
    }
  }
  
  return b;
}
    
    /*  Base64 Armour
    
  Base64 armour encodes a byte array as described in RFC 1341.  Sequences
  of three bytes are encoded into groups of four characters from a set
  of 64 consisting of the upper and lower case letters, decimal digits,
  and the special characters "+" and "/".  If the input is not a multiple
  of three characters, the end of the message is padded with one or two
  "=" characters to indicate its actual length.  We prefix the armoured
  message with "?b64" and append "?64b" to the end; if one or both
  of these sentinels are present, text outside them is ignored.  You can
  suppress the generation of sentinels in armour by setting base64addsent
  false before calling armour_base64.  */
    
    
var base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  base64sent = "?b64", base64esent = "?64b", base64addsent = true;
    
function armour_base64(b) {
  var b64t = "";
  var b64l = base64addsent ? base64sent : "";

  var i;
  for (i = 0; i <= b.length - 3; i += 3) {
    if ((b64l.length + 4) > maxLineLength) {
      b64t += b64l + "\n";
      b64l = "";
    }
    b64l += base64code.charAt(b[i] >> 2);
    b64l += base64code.charAt(((b[i] & 3) << 4) | (b[i + 1] >> 4));
    b64l += base64code.charAt(((b[i + 1] & 0xF) << 2) | (b[i + 2] >> 6));
    b64l += base64code.charAt(b[i + 2] & 0x3F);
  }
  
  //dump("b.length", b.length);  dump("i", i); dump("(b.length - i)", (b.length - i));
  if ((b.length - i) == 1) {
    b64l += base64code.charAt(b[i] >> 2);
    b64l += base64code.charAt(((b[i] & 3) << 4));
    b64l += "==";
  } else if ((b.length - i) == 2) {
    b64l += base64code.charAt(b[i] >> 2);
    b64l += base64code.charAt(((b[i] & 3) << 4) | (b[i + 1] >> 4));
    b64l += base64code.charAt(((b[i + 1] & 0xF) << 2));
    b64l += "=";
  }

  if ((b64l.length + 4) > maxLineLength) {
    b64t += b64l + "\n";
    b64l = "";
  }
  if (base64addsent) {
    b64l += base64esent;
  }
  b64t += b64l + "\n";
  return b64t;
}
    
function disarm_base64(s) {
  var b = [];
  var i = 0, j, c, shortgroup = 0, n = 0;
  var d = [];
  
  if ((j = s.indexOf(base64sent)) >= 0) {
    s = s.substring(j + base64sent.length, s.length);
  }
  if ((j = s.indexOf(base64esent)) >= 0) {
    s = s.substring(0, j);
  }
  
  /*  Ignore any non-base64 characters before the encoded
      data stream and skip the type sentinel if present.  */

  while (i < s.length) {
    if (base64code.indexOf(s.charAt(i)) != -1) {
      break;
    }
    i++;
  }
  
  /*  Decode the base64 data stream.  The decoder is
      terminated by the end of the input string or
      the occurrence of the explicit end sentinel.  */
  
  while (i < s.length) {
    for (j = 0; j < 4; ) {
      if (i >= s.length) {
        if (j > 0) {
          alert("Base64 cipher text truncated.");
          return b;
        }
        break;
      }
      c = base64code.indexOf(s.charAt(i));
      if (c >= 0) {
        d[j++] = c;
      } else if (s.charAt(i) == "=") {
        d[j++] = 0;
        shortgroup++;
      } else if (s.substring(i, i + base64esent.length) == base64esent) {
        //dump("s.substring(i, i + base64esent.length)", s.substring(i, i + base64esent.length));
        //dump("esent", i);
        i = s.length;
        continue;
      } else {
        //dump("s.substring(i, i + base64esent.length)", s.substring(i, i + base64esent.length));
        //dump("usent", i);
        // Might improve diagnosis of improper character in else clause here
      }
      i++;
    }
    //dump("d0", d[0]); dump("d1", d[1]); dump("d2", d[2]); dump("d3", d[3]); 
    //dump("shortgroup", shortgroup);
    //dump("n", n);
    if (j == 4) {
      b[n++] = ((d[0] << 2) | (d[1] >> 4)) & 0xFF;
      if (shortgroup < 2) {
        b[n++] = ((d[1] << 4) | (d[2] >> 2)) & 0xFF;
        //dump("(d[1] << 4) | (d[2] >> 2)", (d[1] << 4) | (d[2] >> 2));
        if (shortgroup < 1) {
          b[n++] = ((d[2] << 6) | d[3]) & 0xFF;
        }
      }
    }
  }
  return b;
}
// END: javascrypt/armour.js
// BEGIN: javscrypt/utf-8.js

/*  Encoding and decoding of Unicode character strings as
    UTF-8 byte streams.  */
  
//  UNICODE_TO_UTF8  --  Encode Unicode argument string as UTF-8 return value

function unicode_to_utf8(s) {
  var utf8 = "";
  for (var n = 0; n < s.length; n++) {
    var c = s.charCodeAt(n);

    if (c <= 0x7F) {
      //  0x00 - 0x7F:  Emit as single byte, unchanged
      utf8 += String.fromCharCode(c);
    } else if ((c >= 0x80) && (c <= 0x7FF)) {
      //  0x80 - 0x7FF:  Output as two byte code, 0xC0 in first byte
      //  0x80 in second byte
      utf8 += String.fromCharCode((c >> 6) | 0xC0);
      utf8 += String.fromCharCode((c & 0x3F) | 0x80);
    } else {
      // 0x800 - 0xFFFF:  Output as three bytes, 0xE0 in first byte
      // 0x80 in second byte
      // 0x80 in third byte
      utf8 += String.fromCharCode((c >> 12) | 0xE0);
      utf8 += String.fromCharCode(((c >> 6) & 0x3F) | 0x80);
      utf8 += String.fromCharCode((c & 0x3F) | 0x80);
    }
  }
  return utf8;
}

    //  UTF8_TO_UNICODE  --  Decode UTF-8 argument into Unicode string return value

function utf8_to_unicode(utf8) {
  var s = "", i = 0, b1, b2;

  while (i < utf8.length) {
    b1 = utf8.charCodeAt(i);
    if (b1 < 0x80) {      // One byte code: 0x00 0x7F
      s += String.fromCharCode(b1);
      i++;
    } else if((b1 >= 0xC0) && (b1 < 0xE0)) {  // Two byte code: 0x80 - 0x7FF
      b2 = utf8.charCodeAt(i + 1);
      s += String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
      i += 2;
    } else {            // Three byte code: 0x800 - 0xFFFF
      b2 = utf8.charCodeAt(i + 1);
      b3 = utf8.charCodeAt(i + 2);
      s += String.fromCharCode(((b1 & 0xF) << 12) |
              ((b2 & 0x3F) << 6) | (b3 & 0x3F));
      i += 3;
    }
  }
  return s;
}

    /*  ENCODE_UTF8  --  Encode string as UTF8 only if it contains
       a character of 0x9D (Unicode OPERATING
       SYSTEM COMMAND) or a character greater
       than 0xFF.  This permits all strings
       consisting exclusively of 8 bit
       graphic characters to be encoded as
       themselves.  We choose 0x9D as the sentinel
       character as opposed to one of the more
       logical PRIVATE USE characters because 0x9D
       is not overloaded by the regrettable
       "Windows-1252" character set.  Now such characters
       don't belong in JavaScript strings, but you never
       know what somebody is going to paste into a
       text box, so this choice keeps Windows-encoded
       strings from bloating to UTF-8 encoding.  */
       
function encode_utf8(s) {
  var i, necessary = false;
  
  for (i = 0; i < s.length; i++) {
    if ((s.charCodeAt(i) == 0x9D) || (s.charCodeAt(i) > 0xFF)) {
      necessary = true;
      break;
    }
  }
  if (!necessary) {
    return s;
  }
  return String.fromCharCode(0x9D) + unicode_to_utf8(s);
}
    
/*  DECODE_UTF8  --  Decode a string encoded with encode_utf8
    above.  If the string begins with the
    sentinel character 0x9D (OPERATING
    SYSTEM COMMAND), then we decode the
    balance as a UTF-8 stream.  Otherwise,
    the string is output unchanged, as
    it's guaranteed to contain only 8 bit
    characters excluding 0x9D.  */
       
function decode_utf8(s) {
  if ((s.length > 0) && (s.charCodeAt(0) == 0x9D)) {
    return utf8_to_unicode(s.substring(1));
  }
  return s;
}
// END: javscrypt/utf-8.js


/* XXXXXXXXXX end of lib/plugins/crypt/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/include/script.js XXXXXXXXXX */

/**
 * Javascript functionality for the include plugin
 */

/**
 * Highlight the included section when hovering over the appropriate include edit button
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @author Michael Klier <chi@chimeric.de>
 */
addInitEvent(function(){
    var btns = getElementsByClass('btn_incledit',document,'form');
    for(var i=0; i<btns.length; i++){
        addEvent(btns[i],'mouseover',function(e){
            var tgt = e.target;
            if(tgt.form) tgt = tgt.form;
            id = 'plugin_include__' + tgt.id.value;
            var divs = getElementsByClass('plugin_include_content');
            for(var j=0; j<divs.length; j++) {
                if(divs[j].id == id) {
                    divs[j].className += ' section_highlight';
                }
            }
        });

        addEvent(btns[i],'mouseout',function(e){
            var secs = getElementsByClass('section_highlight',document,'div');
            for(var j=0; j<secs.length; j++){
                secs[j].className = secs[j].className.replace(/ section_highlight/,'');
            }
        });
    }
});

// vim:ts:4:sw:4:et:enc=utf-8:


/* XXXXXXXXXX end of lib/plugins/include/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/osm/script.js XXXXXXXXXX */

addInitEvent(function () {
  this.init_osm = function (div) {
    var params = this.getParams(div);
    var points = this.readPoints(div);
    while (div.firstChild) {
      div.removeChild(div.firstChild);
    }
    var map = new OpenLayers.Map (div.id, {
        controls:[
          new OpenLayers.Control.Navigation(),
          new OpenLayers.Control.PanZoomBar(),
          new OpenLayers.Control.LayerSwitcher(),
          new OpenLayers.Control.Attribution()],
        maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
        maxResolution: 156543.0399,
        numZoomLevels: 19,
        units: 'm',
        projection: new OpenLayers.Projection("EPSG:900913"),
        displayProjection: new OpenLayers.Projection("EPSG:4326")
    } );

    var layers = new Object();
    layers['osmarender'] = new OpenLayers.Layer.OSM.Osmarender("Osmarender");
    layers['maplint'] = new OpenLayers.Layer.OSM.Maplint("Maplint");
    layers['mapnik'] = new OpenLayers.Layer.OSM.Mapnik("Mapnik");

    if (!layers[params['layer']]) {
      params['layer'] = 'mapnik';
    }
    map.addLayer(layers[params['layer']]);

    for (var layer in layers) {
      if (layer != params['layer']) {
        map.addLayer(layers[layer]);
      }
    }

    var layerMarkers = new OpenLayers.Layer.Markers("Markers");
    map.addLayer(layerMarkers);

    var lonLat = new OpenLayers.LonLat(params['lon'], params['lat']).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());

    map.setCenter(lonLat, params['zoom']);

    if (isArray(points)) {
      var pLonLat;
      var size = new OpenLayers.Size(21,25);
      var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
      var icon;
      for(var p = 0; p < points.length; p++) {
        var point = points[p];
        var pLonLat = new OpenLayers.LonLat(point.lon, point.lat).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
        icon = new OpenLayers.Icon('http://www.openstreetmap.org/openlayers/img/marker.png',size,offset);
        layerMarkers.addMarker(new OpenLayers.Marker(pLonLat,icon));
      }
    }
  };
  this.getParams = function (div) {
    var url = div.getElementsByTagName('a')[0].href;
    url = url.split('?')[1];
    var params = url.split('&');
    var param;
    for (var j = 0; j < params.length; j++) {
      param = params[j].split('=');
      delete params[j];
      if (param[0] != 'layer') {
        param[1] = parseFloat(param[1]);
      }
      params[param[0]] = param[1];
    }
    return params;
  };
  this.readPoints = function (div) {
    var comment = div.firstChild;
    while (comment.nodeType != 8) {
      comment = comment.nextSibling;
    }
    return eval(comment.data);
  };
  var osm_divs = getElementsByClass('openstreetmap', document, 'div');
  for (var i=0; i < osm_divs.length; i++) {
    var osm_div = osm_divs[i];
    osm_div.id = 'osm_'+i;
    this.init_osm(osm_div);
  }
});


/* XXXXXXXXXX end of lib/plugins/osm/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/templater/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/templater/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/youtube/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/youtube/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/s5/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/s5/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/recommend/script.js XXXXXXXXXX */

/* Lib */

var recommend_ajax_call = 'plugin_recommend';

function sack_form(form, fnc) {
    var ajax = new sack(DOKU_BASE + 'lib/exe/ajax.php');
    ajax.setVar('call', recommend_ajax_call);
    function serializeByTag(tag) {
        var inps = form.getElementsByTagName(tag);
        for (var inp in inps) {
            if (inps[inp].name) {
                ajax.setVar(inps[inp].name, inps[inp].value);
            }
        }
    }
    serializeByTag('input');
    serializeByTag('textarea');
    ajax.onCompletion = fnc;
    ajax.runAJAX();
    return false;
}

function bind(fnc, val) {
    return function () {
        return fnc(val);
    };
}

function change_form_handler(forms, handler) {
    if (!forms) return;
    for (var formid in forms) {
        var form = forms[formid];
        form.onsubmit = bind(handler, form);
    }
}

/* Recommend */

function recommend_box(content) {
    var div = $('recommend_box');
    if (!div) {
        div = document.createElement('div');
        div.id = 'recommend_box';
    } else if (content === '') {
        div.parentNode.removeChild(div);
        return;
    }
    div.innerHTML = content;
    document.body.appendChild(div);
    return div;
}

function recommend_handle() {
    if (this.response === "AJAX call '" + recommend_ajax_call + "' unknown!\n") {
        /* No user logged in. */
        return;
    }
    if (this.responseStatus[0] === 204) {
        var box = recommend_box('<form id="recommend_plugin" accept-charset="utf-8" method="post" action="?do=recommend"><div class="no"><fieldset><legend>Finished</legend<p>Thanks for recommending our site.</p><input type="submit" class="button" value="Cancel" name="do[cancel]"/></fieldset></div></form>');
    } else {

        var box = recommend_box(this.response);
        box.getElementsByTagName('label')[0].focus();
        change_form_handler(box.getElementsByTagName('form'),
                            function (form) {return sack_form(form, recommend_handle); });
    }
    var inputs = box.getElementsByTagName('input');
    inputs[inputs.length - 1].onclick = function() {recommend_box(''); return false;};
}

addInitEvent(function () {
                change_form_handler(getElementsByClass('btn_recommend', document, 'form'),
                                    function (form) {return sack_form(form, recommend_handle); });
             });


/* XXXXXXXXXX end of lib/plugins/recommend/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of lib/plugins/feed/script.js XXXXXXXXXX */



/* XXXXXXXXXX end of lib/plugins/feed/script.js XXXXXXXXXX */



/* XXXXXXXXXX begin of conf/userscript.js XXXXXXXXXX */



/* XXXXXXXXXX end of conf/userscript.js XXXXXXXXXX */

addInitEvent(function(){ ajax_qsearch.init('qsearch__in','qsearch__out'); });
addInitEvent(function(){ addEvent(document,'click',closePopups); });
addInitEvent(function(){ addTocToggle(); });
addInitEvent(function(){ initSizeCtl('size__ctl','wiki__text'); });
addInitEvent(function(){ initToolbar('tool__bar','wiki__text',toolbar); });
addInitEvent(function(){ initChangeCheck('Nicht gespeicherte Änderungen gehen verloren!\nWeitermachen?'); });
addInitEvent(function(){ locktimer.init(840,'Die Sperre zur Bearbeitung dieser Seite läuft in einer Minute ab.\nUm Bearbeitungskonflikte zu vermeiden, sollten Sie sie durch einen Klick auf den Vorschau-Knopf verlängern.',1); });
addInitEvent(function(){ scrollToMarker(); });
addInitEvent(function(){ focusMarker(); });

