User:Adrian~enwiki/combined.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/****************************************************************************
 * Built from the following scripts:
 *    module.js
 *    datetime.js
 *    msg.js
 *    util.js
 *    download.js
 *    wikiwidget.js
 *    wikins.js
 *    wikipage.js
 *    wikipageXfd.js
 *    wikiedit.js
 *    wikistate.js
 *    autoreplace.js
 *    wikiparse.js
 *    diff.js
 *    kookie.js
 *    md5.js
 *    token.js
 *    shortcuts.js
 *    diffsince.js
 *    wikiwatch.js
 *    autoedit.js
 *    nav_custom.js
 *    iframedl.js
 *    wikiimg.js
 *    drag.js
 *    overlib/overlib.js
 *    instaview.js
 *    instaviewtiny.js
 *    notes.js
 *    linkedit.js
 *    sig.js
 *    rollback.js
 *    autotag.js
 *    copyvio.js
 *    edittop.js
 *    watchlist.js
 *    watchbutton.js
 *    autofocus.js
 *    directredirect.js
 *    locz.js
 *    datez.js
 *    coorz.js
 *    imdbz.js
 *    alexafy.js
 *    userscript.js
 *    autosummary.js
 *    smartsubmit.js
 *    newmessages.js
 *    tabdiff.js
 *    tabsince.js
 *    purge.js
 *    editcount.js
 *    nav_logs.js
 *    nav_afd.js
 *    hideown.js
 *    google.js
 *    debugnote.js
 *    querysave.js
 *    extedit.js
 *    autoafd.js
 *    afd_vote.js
 *    xfdclose.js
 *    obsolete/autowarn.js
 *    redirector.js
 *    popups.js
 *    picturepopups.js
 *    instapreview.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN module.js
 ****************************************************************************/

// $Id: module.js 1179 2006-02-23 22:10:31Z quarl $

// module.js - dependency tracking

// quarl 2006-02-07 initial version

var $module = new Object();

// can be a URL prefix or a [[User:Wikipedia prefix]]
$module.options = {
    // auto_load: "http://wikipedia.quarl.org/scripts/"
    auto_load: null
};

var Module = $module.Module = function(name) {
    if (!(this instanceof $module.Module)) {
        return new $module.Module(name, params);
    }

    if (typeof(name) != 'string') {
        alert("$module.Module: need name (error 7e05c98d-b55c-4e1f-a48a-170d7bcb63a8)");
        return void(0);
    }

    this.module = { name: name };
    $module.provide(this.module.name, this);
    return this;
}

$module.Module.prototype.depend = function() {
    var l;
    for (var i = 0; i < arguments.length; ++i) {
        if (!(l=$module.depend1(arguments[i], this))) return null;
    }
    return l;
}

$module.Module.prototype.inherit = function(parent) {
    // allow string name of parent, because the module might not yet be loaded
    // at this point
    var parent0 = parent;
    if (typeof(parent) == 'string') {
        parent = window[parent];
    }
    if (!parent) {
        alert($module.msgpfx(child) + "Can't inherit from " + parent0);
        return;
    }
    for (var v in parent) {
        if (v == 'module') continue;
        if (typeof(this[v]) == 'undefined') child[v] = parent[v];
    }
}

$module.Module.prototype.alert = function(msg) {
    window.alert(this.module.name + ': ' + msg);
}

// requires msg.js
$module.Module.prototype.error = function(msg) {
    return $msg.error('(' + this.module.name + ') ' + msg);
}

// requires msg.js
$module.Module.prototype.warning = function(msg) {
    return msg.warning('(' + this.module.name + ') ' + msg);
}

// requires msg.js
$module.Module.prototype.debug = function(msg) {
    return $msg.debug('(' + this.module.name + ') ' + msg);
}

$module.modules = {};

$module.provide = function(s, t) {
    $module.modules[s] = (t || 1);
}

$module.msgpfx = function(t) {
    return (t && t.module && t.module.name) ? ("$module (" + t.module.name + "): ") : "$module: ";
}

$module.depend = function() {
    var l;
    for (var i = 0; i < arguments.length; ++i) {
        if (!(l=$module.depend1(arguments[i], null))) return null;
    }
    return l;
}

$module.depend1 = function(s, t) {
    var m = $module.modules[s];
    if (!m) {
        if ($module.options.auto_load) {
            $module.loadScript($module._makeScriptUrl(s));

            m = $module.modules[s];
            if (!m) {
                alert($module.msgpfx(t) + "Error loading module '"+s+"'");
                return false;
            }
        } else {
            if (!$module.options.auto_load && s.match(/[.]css$/)) {
                // XXX TODO: check document.styleSheets
                return;
            }
            alert($module.msgpfx(t) + "Error, depends on '"+s+"', but not yet loaded (error 05ceb474-86ec-49ac-833f-89a5d77130bc)");
        }
    }
    return m;
}

$module.loadScript = function(s) {
    if (s.match(/[.]js$/)) {
        document.write('<scr'+'ipt type="text/javascript" src="' +s+ '</scr'+'ipt>');
    } else if (s.match(/[.]css$/)) {
        document.write('<sty'+'le type="text/css">' + s + '";</st'+'yle>');
        // TODO: use document.styleSheets; test with .href
        $module.modules[s] = 1;
    } else {
        alert("$module.loadScript: unknown type (error 32326eb9-095b-4f21-ba23-1bda4d6091fe)");
    }
}

$module._makeScriptUrl = function(t, ns) {
    ns = ns || $module.options.auto_load;
    if (ns.match(/^(http|ftp):/)) {
        return ns + '/' + t;
    } else if (ns.match(/^\[\[(.*)\]\]$/)) {
        var page = RegExp.$1 + '/' + t;
        if (ns.match(/[.]js$/)) {
            return ('/w/index.php?title=' + page +
                    '&action=raw&ctype=text/javascript&dontcountme=s');
        } else if (ns.match(/[.]css$/)) {
            return ('/w/index.php?title=' + page +
                    '&action=raw&ctype=text/css&dontcountme=s');
        } else {
            return ('/w/index.php?title=' + page);
        }
    } else {
        alert("Unknown value for ns (error 01b7cf1a-f746-4c93-8992-b0542a2f54c3)");
        return null;
    }
}

/****************************************************************************
 * END module.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN datetime.js
 ****************************************************************************/

// $Id: datetime.js 1236 2006-02-24 11:35:23Z quarl $

// datetime.js - date/time utility functions

var $datetime = new Module('datetime.js');

// return N days ago (default today)
$datetime.previousDay = function(days) {
    days = days || 0;
    var d = new Date();
    d.setDate(d.getDate() - days); // automatically wraps as necessary
    return d;
}

$datetime.L2 = function(x) {
    if (x < 0) return ""+x;
    if (x < 10) return "0"+x;
    return ""+x;
}

$datetime.L3 = function(x) {
    if (x < 0) return ""+x;
    if (x < 10) return "00"+x;
    if (x < 100) return "0"+x;
    return ""+x;
}

$datetime.datestampUTCISO = function(d) {
    d = d || new Date();
    return ("" +
            d.getUTCFullYear() + '-' +
            $datetime.L2(d.getUTCMonth()+1) + '-' +
            $datetime.L2(d.getUTCDate()));
}

$datetime.timestampUTCISO = function(d) {
    d = d || new Date();
    return ($datetime.L2(d.getUTCHours()) + ':' +
            $datetime.L2(d.getUTCMinutes()));
}

$datetime.logtimestamp = function(d) {
    d = d || new Date();

    return ($datetime.L2(d.getHours()) + ':' +
            $datetime.L2(d.getMinutes()) + ':' +
            $datetime.L2(d.getSeconds()) + '.' +
            $datetime.L3(d.getTime() % 100));
}

$datetime.monthnames = [
    "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December" ];

$datetime.datestampYYYYMonthD = function(d) {
    d = d || new Date();
    return ("" +
            d.getUTCFullYear() + ' ' +
            $datetime.monthnames[d.getUTCMonth()] + ' ' +
            d.getUTCDate());
}

$datetime.datestampMonthYYYY = function(d) {
    d = d || new Date();
    return ("" +
            $datetime.monthnames[d.getUTCMonth()] + ' ' +
            d.getUTCFullYear());
}

// from Lupin's popups
$datetime.formatAge = function(age) {
    var addunit = function(num,str) {
        return '' + num + ' ' + str + ((num!=1) ? 's' : '') ;
    }

    // coerce into a number
    age = 0 + age;
    var a = age;

    var seclen   = 1000;
    var minlen   = 60*seclen;
    var hourlen  = 60*minlen;
    var daylen   = 24*hourlen;
    var weeklen  = 7*daylen;

    var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week');
    var numdays  = (a-a%daylen)/daylen;   a = a-numdays*daylen;   var sdays  = addunit(numdays,  'day');
    var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours, 'hour');
    var nummins  = (a-a%minlen)/minlen;   a = a-nummins*minlen;   var smins  = addunit(nummins,  'minute');
    var numsecs  = (a-a%seclen)/seclen;   a = a-numsecs*seclen;   var ssecs  = addunit(numsecs,  'second');

    if (age > 4*weeklen) { return sweeks; }
    if (age > weeklen)   { return sweeks + ' ' + sdays; }
    if (age > daylen)    { return sdays  + ' ' + shours; }
    if (age > 6*hourlen) { return shours; }
    if (age > hourlen)   { return shours + ' ' + smins; }
    if (age > 10*minlen) { return smins; }
    if (age > minlen)    { return smins  + ' ' + ssecs; }
    return ssecs;
}

/****************************************************************************
 * END datetime.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN msg.js
 ****************************************************************************/

// $Id: msg.js 1188 2006-02-23 22:23:09Z quarl $

// error, warning, debug functions

// quarl 2006-02-20 initial version

var $msg = new Module('msg.js');
$msg.depend('msg.css');
$msg.depend('datetime.js');

$msg.options = {
    debuglogsize: 500
};

$msg.error = function(msg) {
    alert(msg);
    // dummy try/catch to automatically interrupt if running under debugger
    // (kind of like a built-in breakpoint)
    try { throw 0; } catch(e) {};
    return void(0);
}

$msg.warning = function(msg) {
    alert(msg);
    return void(0);
}

$msg.debuglog = [];
$msg.debug = function(msg) {
    msg = '[' + $datetime.logtimestamp() + '] ' + msg;

    $msg.debuglog.push(msg);

    // if it's way over budget, delete until we're at budget
    if ($msg.debuglog.length > $msg.options.debuglogsize * 1.5) {
        $msg.debuglog.splice(0, $msg.debuglog.length-$msg.options.debuglogsize);
    }
}

$msg.debug("-- Initializing Wikipedia Power Toolkit --");

/****************************************************************************
 * END msg.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN util.js
 ****************************************************************************/

// $Id: util.js 1224 2006-02-24 10:40:00Z quarl $

// util.js - miscellaneous utility functions for Wikipedia user scripts

// quarl 2006-01-09 initial version

// NON-NAMESPACED FUNCTION NAMES ARE DEPRECATED

var $util = new Module('util.js');
$util.depend('msg.js');

/////////////////////////////////////////////////////////////
// MISCELLANEOUS FUNCTIONS

// $util.defineClass = function(constructor, functions) {
//     var c = constructor;
//     c.prototype = functions;
//     return c;
// }

$util.merge = function(o, params) {
    if (!params) return o;
    for (var p in params) {
        o[p] = params[p];
    }
    return o;
}

$util.copyArray = function(a, i) {
    var r = [];
    i = i || 0;
    for (; i < a.length; i++) {
        r.push(a[i]);
    }
    return r;
}

$util.assocArray = function(x) {
    for (var i in x) {
        x[ x[i] ] = 1;
    }
    return x;
}

// flatten an array
$util.flatten = function(list, start) {
    var ret=[];
    if (typeof start == 'undefined') start=0;
    for (var i=start; i<list.length; ++i) {
        if (typeof list[i] == typeof []) {
            return ret.concat($util.flatten(list[i])).concat($util.flatten(list, i+1));
        }
        else ret.push(list[i]);
    }
    return ret;
};

$util.bindThis = function(this_, method) {
    return function() { return method.apply(this_, arguments); };
}

$util.callHooks = function(callbacks) {
    var args = $util.copyArray(arguments, 1);
    for (var i in callbacks) {
        var callback = callbacks[i];
        callback.apply(callback, args);
    }
}

$util.hookEventObj = function(obj, hookName, hookFunct) {
    if (!obj) return;

    if (obj.addEventListener)
        obj.addEventListener(hookName, hookFunct, false);
    else if (obj.attachEvent)
        obj.attachEvent("on" + hookName, hookFunct);
}

// wikibits.js:addOnloadHook does almost exactly what we want.  Its primary
// feature is it runs at the end of loading rather than after loading, so the
// user does not see the page before running the hooks.
//
// The disadvantage is its buggy behavior if any of the hooks thrown an
// exception: the entire list of hooks is re-run, producing poor results like
// multiple tabs.

$util._onloadFunctions = [];

$util._runOnloadHooks = function() {
    if ($util._onloadFunctions == null) return;
    // Using a while loop in this manner ensures that if we queue up more
    // hooks during _runOnloadHooks, they get queued at the end, and also if
    // we raise an exception for some reason, we don't re-run the entire list.
    while ($util._onloadFunctions.length) {
        try {
            $util._onloadFunctions[0] ();
        } catch(e) {
            var s = $util._onloadFunctions[0].toString();
            $util.error("_runOnloadHooks: Error in onload hook '"+$util.strAbbrev(s,297)+"': " + e);
        }
        $util._onloadFunctions.shift();
    }
    $util._onloadFunctions = null;
}

// This is more robust than wikibits.js's addOnloadHook.  That version uses an
// array of fuctions; if there is an error, the hook functions sometimes get
// re-run.
$util.addOnloadHook = function(f) {
    if (!f) {
        alert("$util.addOnloadHook: no function given! (error 0a0fb885-97fb-4089-b7c5-90f0be7d5abc)");
        return;
    }

    if ($util._onloadFunctions == null) {
        // we've already finished _runOnloadHooks, so just run f now.
        // TODO: any advantage to doing setTimeout(f, 0) here?
        f();
    } else {
        $util._onloadFunctions.push(f);
        // $util.hookEventObj(window, "load", f);
    }

    // setTimeout is NOT a good alternative to addEventListener.  The reason
    // is that timeout functions are called too soon: since the <script> tag
    // is before the main body content, we don't yet see that content at time
    // of setTimeout.

    // setTimeout(f, 0);
};

// use wikibits.js's onload hook system to invoke ours.
$($util._runOnloadHooks);

$util.addOnunloadHook = function(f) {
    $util.hookEventObj(window, 'unload', f);
}

// conditional func eval
$util.funkyval = function(x) {
    if (typeof(x) == 'function') {
        x = x();
    }
    return x;
}

/////////////////////////////////////////////////////////////
// STRING UTILITY FUNCTIONS

$util.trimSpaces = function(s) {
    if (!s) return s;
    s = s.replace(/^\s+/,'');
    s = s.replace(/\s+$/,'');
    return s;
}

$util.trimLines = function(s) {
    return s.replace(/^\n+/, '').replace(/\n+$/, '');
}

$util.removeAnchor = function(s) {
    return s.replace(/(?:#|%23).*/, '');
}

$util.reverseString = function(s) {
    var ret = '';
    for (var i = s.length-1; i >= 0; --i) {
        ret += s[i];
    }
    return ret;
}

$util.strAbbrev = function(s, n) {
    if (!s) return s;
    if (s.length <= n) return s;
    return s.substr(0, n-3) + '...';
}

$util.wordCount = function(t) {
    return t.split(/\s+/).length;
}

$util.stringQuoteEscape = function(str) {
    if (!str) return str;
    return "'" + str.replace(/\'/g, '\\\'').replace(/\%27/g, '\\\'') + "'";
}

$util.reEscape = function(str) {
    return str.replace(RegExp('([-.()\\+?*^${}\\[\\]])', 'g'), '\\$1');
};

// wiki article name escaping
$util.wpaEscape = function(s) {
    // encodeURIComponent is better than 'escape' for unicode chars;
    // it also escapes '+'.
    // Don't escape ':'
    return encodeURIComponent(s.replace(/ /g,'_')).replace(/%3A/g,':').replace(/%2F/g,'/');
}

$util.wpaDecode = function(s) {
    return decodeURIComponent(s).replace(/_/g,' ');
}

// from Scriptaculous
$util.escapeHTML = function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
};

// from Scriptaculous
$util.unescapeHTML = function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
};

$util.urlGetPath = function(s) {
    return s.replace(/^http:\/\/[^\/]+/, '');
}

// from Lupin's popups:
// String.prototype.parenSplit should do what ECMAscript says
// String.prototype.split does, interspersing paren matches between
// the split elements

if (String('abc'.split(/(b)/))!='a,b,c') {
    // broken String.split, e.g. konq, IE
    String.prototype.parenSplit=function (re) {
        var m=re.exec(this);
        if (!m) return [this];
        // without the following loop, we have
        // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
        for(var i=0; i<m.length; ++i) {
            if (typeof m[i]=='undefined') m[i]='';
        }
        return [this.substring(0,m.index)]
        .concat(m.slice(1))
        .concat(this.substring(m.index+m[0].length).parenSplit(re));
    };
} else {
    String.prototype.parenSplit=function (re) {return this.split(re);};
}

// e.g. $util.printf("%s, my name is %s", "Hello", "Quarl")
//                => "Hello, my name is Quarl"
$util.printf = function(fmt) {
    var r = '';
    var s = fmt.split('%s');
    for (var i = 1; i < s.length; ++i) {
        r += s[i-1];
        r += (i < arguments.length) ? arguments[i] : '%s';
    }
    r += s[s.length-1];
    return r;
};

// e.g. $util.printf("$1, my name is $2", "Hello", "Quarl")
//                => "Hello, my name is Quarl"
$util.pprintf = function(fmt) {
    // start at last argument in case we have more than 9 arguments
    for (var i = arguments.length; i >= 1; --i) {
        fmt = fmt.replace(new RegExp('\\$' + i,'g'), arguments[i]);
    }
    return fmt;
};

$util.capitalizeFirstChar = function(s) {
    if (!s) return s;
    return s[0].toUpperCase() + s.substr(1);
};

$util.describeCharCount = function(s) {
    if (s == null) return '(null)';
    return "[" + s.length + " chars]";
}

// replaces all occurrences of RE with REPL, returning {str:str, count:count}
$util.reReplaceAll = function(str, re, repl)
{
    re = new RegExp(re);
    var m;
    var result = '';
    var count = 0;
    while ( (m=str.match(re)) ) {
        str = RegExp.rightContext;
        result += RegExp.leftContext;
        result += m[0].replace(re, repl);
        ++count;
    }
    result += str;
    return { str: result, count: count };
};

////////////////////////////////////////////////////////////
// DOM UTILITY FUNCTIONS
$util.getElementsByClass = function(searchClass, node, tag) {
    var classElements = [];
    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; i < elsLen; i++) {
        if (pattern.test(els[i].className) ) {
            classElements.push(els[i]);
        }
    }
    return classElements;
}

$util.findDescendantById = function(node, id) {
    if (node.id == id) { return node; }
    for (var i = node.firstChild; i != null; i=i.nextSibling) {
        var c = $util.findDescendantById(i,id);
        if (c != null)
            return c;
    }
    return null;
}

$util.isDescendent = function(node, ancestorNode, cachePropertyName) {
    if (typeof node[cachePropertyName] != 'undefined') {
        return node[cachePropertyName];
    }

    if (node == ancestorNode) {
        return node[cachePropertyName] = true;
    }

    if (!node.parentNode) {
        return node[cachePropertyName] = false;
    }

    return node[cachePropertyName] = $util.isDescendent(
        node.parentNode, ancestorNode, cachePropertyName);
};


$util.addClass = function(node, cls) {
    node.className += ' '+cls;
}

$util.removeClass = function(node, cls) {
    node.className = node.className.replace(
        new RegExp("(^|\\s)"+cls+"(\\s|$)",'g'), ' ');
}

$util.addNodeBefore = function(node, newnode) {
    node.parentNode.insertBefore(newnode, node);
    return newnode;
}

$util.addNodeAfter = function(node, newnode) {
    if (node.nextSibling) {
        node.parentNode.insertBefore(newnode, node.nextSibling);
    } else {
        node.parentNode.appendChild(newnode);
    }
    return newnode;
}

// return nodes in [node_start, node_end)
$util.getNodesInRange = function(node_start, node_end) {
    var nodes = [];
    while (node_start != node_end) {
        nodes.push(node_start);
        node_start = node_start.nextSibling;
        if (!node_start) return null; // didn't reach node_end!
    }
    return nodes;
}

$util.removeNodesInRange = function(node_start, node_end) {
    if (!node_end) {
        alert("## removeNodesInRange: node_end==null");
        return null;
    }
    if (!$util.getNodesInRange(node_start, node_end)) {
        alert("## removeNodesInRange: range does not terminate");
        return null;
    }
    var parent = node_start.parentNode;
    var count = 0;
    while (node_start != node_end) {
        ++count;
        var n = node_start.nextSibling; // save before it gets clobbered
        parent.removeChild(node_start);
        node_start = n;
        if (!node_start) return null;
    }
    return count;
}

$util.createHref = function(href, title, inner) {
    var a = document.createElement('a');
    a.href = href;
    a.title = title;
    a.innerHTML = inner;
    return a;
}

$util.findHref = function(href) {
    href = $util.wpaEscape($util.wpaDecode(href));
    var links=document.links;
    for(i=0;i<links.length;++i) {
        // unescape and reescape to ensure canonical escaping
        if ($util.wpaEscape($util.wpaDecode(links[i].href)) == href) return links[i];
    }
    return null;
}

// insert a new node as parent of node
$util.insertNode = function(node, newNode) {
    if (!node) return null;

    node.parentNode.replaceChild(newNode, node);
    newNode.appendChild(node);
    return newNode;
}

$util.appendChildren = function(node, newNodes) {
    for (var i in newNodes) {
        node.appendChild(newNodes[i]);
    }
}

// add a span around a node if there isn't one.
$util.ensureSpan = function(node) {
    if (node.parentNode.nodeName == 'SPAN') {
        return node.parentNode;
    }
    return $util.insertNode(node, document.createElement('span'));
}

////////////////////////////////////////////////////////////
// STYLESHEET FUNCTIONS
$util.addStylesheetRule = function(tag, style) {
    var ss = document.styleSheets[0];
    if (ss.insertRule) {
        ss.insertRule(tag + '{' + style + '}', ss.cssRules.length);
    } else if (ss.addRule) {
        ss.addRule(tag, style);
    }
}

////////////////////////////////////////////////////////////
// AJAX FUNCTIONS

// cross-platform
$util.HTTPClient = function() {
    var http;
    if(window.XMLHttpRequest) {
        http = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        try {
            http = new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            try {
                http = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (E) {
                http = false;
            }
        }
    }
    return http;
}

$util.asyncDownloadXML = function(url, callback, props) {
    var req = $util.HTTPClient();
    if (!req) return null;
    // add optional arguments
    if (props) {
        for (var k in props) {
            req[k] = props[k];
        }
    }

    var args = $util.copyArray(arguments, 2);

    req.open("GET", url, true);
    // TODO: another way to parse XML is to create a div and then set its
    // innerHTML to the responseText -- this may be more reliable than
    // overrideMimeType.
    req.overrideMimeType('text/xml');
    // using onload instead of onreadystatechange allows multiple asynchronous requests
    // TODO: since we now have access to 'req' as a variable, we could change back.
    // Is there any advantage to using onreadystatechange?
    req.onload = function(event) {
        var req = event.target;
        args[0] = req;
        if (req.readyState == 4) callback.apply(event, args);
    };
    req.send(null);
    return req;
}

// doesn't try to parse as XML
$util.asyncDownloadText = function(url, callback, props) {
    var req = $util.HTTPClient();
    if (!req) return null;
    // add optional arguments
    if (props) {
        for (var k in props) {
            req[k] = props[k];
        }
    }

    var args = $util.copyArray(arguments, 2);

    req.open("GET", url, true);
    req.overrideMimeType('text/plain');
    req.onload = function(event) {
        var req = event.target;
        args[0] = req;
        if (req.readyState == 4) callback.apply(event, args);
    };
    req.send(null);
    return req;
}

$util.buildParams = function(paramArray) {
    var params = '';
    for (k in paramArray) {
        v = paramArray[k];
        // if v is a Boolean then the form was a checkbox.
        // unchecked checkboxes should not add any input fields.
        if (v == false) continue;
        if (v == true) v = 'on';
        params += '&' + k + '=' + encodeURIComponent(v);
    }
    params = params.replace(/^&/,'');
    return params;
}

$util.addFormHiddenParams = function(newform, d) {
    for (var k in d) {
        v = d[k];
        // if v is a Boolean then the form was a checkbox.
        // unchecked checkboxes should not add any input fields.
        if (v == false) continue;
        if (v == true) v = 'on';
        var t = document.createElement('input');
        t.type = 'hidden';
        t.name = k;
        t.value = d[k];
        newform.appendChild(t);
    }
    return newform;
}

$util.asyncPostXML = function(url, parameters, callback, props) {
    var req = $util.HTTPClient();
    if (!req) return null;
    if (typeof parameters != 'string') parameters = $util.buildParams(parameters);
    // add optional arguments
    if (props) {
        for (var k in props) {
            req[k] = props[k];
        }
    }

    var args = $util.copyArray(arguments, 2);

    req.open("POST", url, true);
    req.overrideMimeType('text/xml');
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.setRequestHeader("Content-length", parameters.length);
    req.setRequestHeader("Connection", "close");
    req.onload = function(event) {
        var req = event.target;
        args[0] = req;
        if (req.readyState == 4) callback.apply(event, args);
    };
    req.send(parameters);
    return req;
}

// Temporarily replace the content of statusNode with a non-clicakble, bolded string
// that shows we're doing something.  statusNode should be the node that completely wraps
// an <a> element that was just clicked (e.g. a <span>)
$util.buttonShowStatus = function(statusNode, statusText) {
    if (!statusNode) return;

    if (!statusText) {
        // use <a> tag to keep padding/margin/color/etc.
        statusText = '<a><b>'+statusNode.textContent+'...</b></a>';
    }

    // Note: saving innerHTML doesn't work if we've messed with the document
    // tree.

    // statusNode.savedContent = statusNode.innerHTML;
    // statusNode.innerHTML = statusText;

    // save content (but don't clobber it if we're called again before buttonRestoreStatus)
    if (!statusNode.savedContent) {
        statusNode.savedContent = $util.copyArray(statusNode.childNodes);
    }
    statusNode.innerHTML = statusText;
}

$util.buttonRestoreStatus = function(statusNode, tempStatusText) {
    if (tempStatusText) {
        // temporarily show a string indicating success, and after a while, reset.
        $util.buttonShowStatus(statusNode, tempStatusText);
        setTimeout(function() { $util.buttonRestoreStatus(statusNode); }, 10000);
        return;
    }

    if (statusNode && statusNode.savedContent) {
        // statusNode.innerHTML = statusNode.savedContent;
        statusNode.innerHTML = '';
        $util.appendChildren(statusNode, statusNode.savedContent);
    }
}

////////////////////////////////////////////////////////////
// UI FUNCTIONS

$util.eventLeftButtonP = function(e) {
    if (typeof(e.which) != 'undefined') return e.which == 1;    // Mozilla
    if (typeof(e.button) != 'undefined') return (e.button | 1); // IE
    return null;
}

/****************************************************************************
 * END util.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN download.js
 ****************************************************************************/

// $Id: download.js 1142 2006-02-23 09:25:47Z quarl $

var $download = new Module('download.js');
$download.depend('util.js');

$download._completed = {};
$download._inProgress = {};

// calls callback({ url: url, data: data, lastModified: lastModified})
// on error, data==null
$download.downloadUrl = function(url, callback)
{
    if ($download._completed[url]) {
        $download.debug("downloadUrl (cached) " + url);
        callback($download._completed[url]);
        return;
    } else if ($download._inProgress[url]) {
        $download.debug("downloadUrl (already in progress) " + url);
        $download._inProgress[url].callbacks.push(callback);
        return;
    } else {
        $download.debug("downloadUrl (initiating async) " + url);
        var dl = $download._inProgress[url] = { url: url, callbacks: [callback] };
        var cb = function(req) {
            if (typeof window.$download == 'undefined') return; // unloaded

            if (req.status == 200) {
                $download.debug("downloadUrl: successful download of " + url);
                dl.data = req.responseText;
                var lm = req.getResponseHeader('Last-Modified');
                dl.lastModified = lm && new Date(lm);
            } else {
                $download.debug("downloadUrl: failed to download " + url);
                dl.data = null;
                dl.lastModified = null;
            }
            callbacks = dl.callbacks;
            delete dl.callbacks;
            delete dl.req;
            delete $download._inProgress[url];
            $download._completed[url] = dl;
            $util.callHooks(callbacks, dl);
        };

        dl.req = $util.asyncDownloadText(url, cb);
    }
}

$download.abortAllDownloads = function() {
    for (var url in $download._inProgress)
    {
        var dl = $download._inProgress[url];
        dl.req.abort();
    }
    $download._inProgress = {};
};

$util.addOnunloadHook($download.abortAllDownloads);

/****************************************************************************
 * END download.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikiwidget.js
 ****************************************************************************/

// $Id: wikiwidget.js 1193 2006-02-24 07:21:46Z quarl $

// originally based on
// http://en.wikipedia.org/wiki/Wikipedia:WikiProject_User_scripts/Scripts/Add_LI_link

var $wikiwidget = new Module('wikiwidget.js');
$wikiwidget.depend('wikiwidget.css');
$wikiwidget.depend('util.js');

$wikiwidget.getPortlet0 = function(n) {
    return document.getElementById(n).getElementsByTagName('ul')[0];
}

// this is monobook-specific
$wikiwidget.portlet_names = {
    'Personal':'p-personal',
    'Actions': 'p-cactions',
    'Navigation': 'p-navigation',
    'Toolbox': 'p-tb'
    // 'Search': 'p-search'
};

$wikiwidget._load = function() {
    $wikiwidget.portlets = {};
    for (var n in $wikiwidget.portlet_names) {
        $wikiwidget.portlets[n] = $wikiwidget.getPortlet0($wikiwidget.portlet_names[n]);
    }
}

$wikiwidget.getPortlet = function(n) {
    if (!$wikiwidget.portlets) {
        alert("## $wikiwidget.getPortlet: not yet initialized?! (error 0b8ab92b-8f67-425b-b1eb-2485aa8265af)");
        return null;
    }
    return $wikiwidget.portlets[n];
}

// add a node at a location.  If it's a regular node, then append to location
// as parent.  If it's {after:node}, then insert after node.
$wikiwidget._fancyAdd = function(location, newNode) {
    if (typeof(location) == 'function') {
        location = location();
    }
    if (location['portlet']) {
        var portlet = $wikiwidget.getPortlet(location['portlet']);
        portlet.appendChild(newNode);
    } else if (location['after']) {
        var after = location['after'];
        if (typeof(after) == 'string') {
            after = document.getElementById(after);
        }
        if (!after) {
            alert("$wikiwidget: couldn't get location to add after (error 6e8df341-9ee6-4ada-9d44-42a8e600d87c)");
            return;
        }
        if (after.nextSibling) {
            after.parentNode.insertBefore(newNode, after.nextSibling);
        } else {
            after.parentNode.appendChild(newNode);
        }
    } else if (location && location.appendChild) {
        location.appendChild(newNode);
    } else {
        alert("$wikiwidget: must specify location to add (error bb4c83cf-8ab6-4cc3-a2c0-f9fc53f66dbb)");
    }
}

var WikiWidget = $wikiwidget.WikiWidget = function(params) {
    $util.merge(this, params);
    this.add = $util.bindThis(this, this.add_);
}

$wikiwidget.WikiWidget.prototype.add_ = function(location) {
    $wikiwidget.debug("WikiWidget.add(): id='"+this.id+"', entry='"+this.entry+"', name='"+this.name+"', url='"+this.url+"', title='"+this.title+"'");
    location = location || this.default_location;
    var li = this.li = document.createElement('li');
    if (this.id) li.id = this.id;

    if (typeof(this.entry) == 'string') {
        li.innerHTML = this.entry;
    } else if (this.entry) {
        li.appendChild(this.entry);
    } else if (this.name) {
        var na = document.createElement('a');
        if (this.url) {
            na.href = this.url;
        }
        if (this.onclick) {
            na.onclick = this.onclick;
            if (!na.href) {
                na.href = "javascript:void 0";
            }
        }
        na.appendChild(document.createTextNode(this.name));
        li.appendChild(na);
    } else {
        $wikiwidget.error("WikiWidget.add: invalid WikiWidget (error aed67c57-3bba-47e0-9b7f-e8d420c7eeea)");
    }

    $wikiwidget._fancyAdd(location, li);

    if (this.id && (this.key || this.title) && window.ta) {
        ta[this.id] = [(this.key||''), (this.title||'')];
        $wikiwidget._scheduleAkeytt();
    }
    return li;
}

$wikiwidget.WikiWidget.prototype.showStatus = function(statusText) {
    $util.buttonShowStatus(this.li, statusText);
}

$wikiwidget.WikiWidget.prototype.hideStatus = function(statusText) {
    $util.buttonHideStatus(this.li, statusText);
}

// Re-render the title and access keys.  Since we add many tabs onLoad, don't
// call akeytt() a unnecessarily -- just call it once after we're done loading.
$wikiwidget._scheduled_akeytt = false;
$wikiwidget._scheduleAkeytt = function() {
    if (!$wikiwidget._scheduled_akeytt) {
        $wikiwidget._scheduled_akeytt = true;
        setTimeout(function() { akeytt(); $wikiwidget._scheduled_akeytt = false; }, 0);
    }
}

$wikiwidget._toggleMenu = function() {
    var mn = this.nextSibling;
    if (!mn || typeof(mn.displayState) != 'boolean') {
        alert("## invalid target for $wikiwidget._toggleMenu (error f66282ae-0762-4c90-8b5d-f095909cb786)");
        return;
    }

    if ( (mn.displayState = !mn.displayState) ) {
        // new state: display
        mn.className += ' sticky';
    } else {
        // new state: hide
        mn.className = mn.className.replace(/(^| )sticky/, '');
    }
}

$wikiwidget.addTabMenu = function(name, id)
{
    var parent = $wikiwidget.getPortletTabActions();

    var na = document.createElement('a');
    na.href = 'javascript:void(0)';
    na.appendChild(document.createTextNode(name));
    na.onclick = $wikiwidget._toggleMenu;

    var mn = document.createElement('ul');
    mn.displayState = false;

    var li = document.createElement('li');
    li.id = id;
    li.className = 'tabmenu';
    li.appendChild(na);
    li.appendChild(mn);
    parent.appendChild(li);
    return mn;
}

// deprecated:
$wikiwidget.addLiLinkX = function(location, entry, id, title, key){
    new $wikiwidget.WikiWidget({entry: entry, id: id, title: title, key: key}).add(location);
}

// deprecated:
$wikiwidget.addLiLink = function(location, url, name, id, title, key) {
    new $wikiwidget.WikiWidget({url: url, name: name, id: id, title: title, key: key}).add(location);
}

// deprecated:
$wikiwidget.getPortletPersonal = function() { return $wikiwidget.getPortlet('Personal'); }
// deprecated:
$wikiwidget.getPortletTabActions = function() { return $wikiwidget.getPortlet('Actions'); }
// deprecated:
$wikiwidget.getPortletNavigation = function() { return $wikiwidget.getPortlet('Navigation'); }
// deprecated:
$wikiwidget.getPortletToolbox = function() { return $wikiwidget.getPortlet('Toolbox'); }

// deprecated:
$wikiwidget.addTab = function(url, name, id, title, key) {
    return new $wikiwidget.WikiWidget({url: url, name: name, id: id, title: title, key: key}).add({portlet:'Actions'});
}

// deprecated:
$wikiwidget.addToolboxLink = function(url, name, id, title, key) {
    return new $wikiwidget.WikiWidget({url: url, name: name, id: id, title: title, key: key}).add({portlet:'Toolbox'});
}

// deprecated:
$wikiwidget.addNavigationLink = function(url, name, id, title, key) {
    return new $wikiwidget.WikiWidget({url: url, name: name, id: id, title: title, key: key}).add({portlet:'Navigation'});
}

$util.addOnloadHook($wikiwidget._load);

/****************************************************************************
 * END wikiwidget.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikins.js
 ****************************************************************************/

// -*- coding:utf-8 -*-
// $Id: wikins.js 1241 2006-02-24 11:57:26Z quarl $

// define internationalized namespace strings

// $wikins.namespaceTalk maps English non-Talk namespace to Talk namespace
// $wikins.namespaceNoTalk maps English Talk namespace to non-Talk namespace


// $wikins.namespaces maps local names to English
// $wikins.namespaceNames maps English to local names

// $wikins.User_talk == $wikins.namespaceNames['User talk']

// e.g. on de.wikipedia.org:

//  $wikins.User_talk == 'Benutzerseite Diskussion'
//  $wikins.namespaceNames['User talk'] == 'Benutzerseite Diskussion'
//  $wikins.namespaces['Benutzerseite Diskussion'] == 'User talk'

// originally from Lupin's popups

var $wikins = new Module('wikins.js');

$wikins.namespacesEnglish = ["Media", "Special",
                             "Talk",
                             "User", "User talk",
                             "Wikipedia", "Wikipedia talk",
                             "Image", "Image talk",
                             "MediaWiki", "MediaWiki talk",
                             "Template", "Template talk",
                             "Help", "Help talk",
                             "Category", "Category talk",
                             "Portal", "Portal talk"];

$wikins.namespaceTalk = {
    ""          : "Talk",
    "User"      : "User talk",
    "Wikipedia" : "Wikipedia talk",
    "Image"     : "Image talk",
    "MediaWiki" : "MediaWiki talk",
    "Template"  : "Template talk",
    "Help"      : "Help talk",
    "Category"  : "Category talk",
    "Portal"    : "Portal talk"
};

$wikins.namespaceNoTalk = {
    "Talk"           : "",
    "User talk"      : "User",
    "Wikipedia talk" : "Wikipedia",
    "Image talk"     : "Image",
    "MediaWiki talk" : "MediaWiki",
    "Template talk"  : "Template",
    "Help talk"      : "Help",
    "Category talk"  : "Category",
    "Portal talk"    : "Portal"
};

$wikins.getNS = function(lang) {
    switch(lang) {
        case "en": return $wikins.namespacesEnglish;
        case "af": return ["Media", "Spesiaal", "Bespreking", "Gebruiker", "Gebruikerbespreking", "Wikipedia", "Wikipediabespreking", "Beeld", "Beeldbespreking", "MediaWiki", "MediaWikibespreking", "Sjabloon", "Sjabloonbespreking", "Hulp", "Hulpbespreking", "Kategorie", "Kategoriebespreking"];
        case "als": return ["Media", "Spezial", "Diskussion", "Benutzer", "Benutzer Diskussion", "Wikipedia", "Wikipedia Diskussion", "Bild", "Bild Diskussion", "MediaWiki", "MediaWiki Diskussion", "Vorlage", "Vorlage Diskussion", "Hilfe", "Hilfe Diskussion", "Kategorie", "Kategorie Diskussion"];
        case "ar": return ["ملف", "خاص", "نقاش", "مستخدم", "نقاش المستخدم", "ويكيبيديا", "نقاش ويكيبيديا", "صورة", "نقاش الصورة", "ميدياويكي", "نقاش ميدياويكي", "Template", "نقاش Template", "مساعدة", "نقاش المساعدة", "تصنيف", "نقاش التصنيف"];
        case "ast": return ["Media", "Especial", "Discusión", "Usuariu", "Usuariu discusión", "Uiquipedia", "Uiquipedia discusión", "Imaxen", "Imaxen discusión", "MediaWiki", "MediaWiki discusión", "Plantilla", "Plantilla discusión", "Ayuda", "Ayuda discusión", "Categoría", "Categoría discusión"];
        case "be": return ["Мэдыя", "Спэцыяльныя", "Абмеркаваньне", "Удзельнік", "Гутаркі ўдзельніка", "Вікіпэдыя", "Абмеркаваньне Вікіпэдыя", "Выява", "Абмеркаваньне выявы", "MediaWiki", "Абмеркаваньне MediaWiki", "Шаблён", "Абмеркаваньне шаблёну", "Дапамога", "Абмеркаваньне дапамогі", "Катэгорыя", "Абмеркаваньне катэгорыі"];
        case "bg": return ["Медия", "Специални", "Беседа", "Потребител", "Потребител беседа", "Уикипедия", "Уикипедия беседа", "Картинка", "Картинка беседа", "МедияУики", "МедияУики беседа", "Шаблон", "Шаблон беседа", "Помощ", "Помощ беседа", "Категория", "Категория беседа"];
        case "bm": return ["Media", "Special", "Discuter", "Utilisateur", "Discussion Utilisateur", "Wikipedia", "Discussion Wikipedia", "Image", "Discussion Image", "MediaWiki", "Discussion MediaWiki", "Modèle", "Discussion Modèle", "Aide", "Discussion Aide", "Catégorie", "Discussion Catégorie"];
        case "bn": return ["বিশেষ", "আলাপ", "ব্যবহারকারী", "ব্যবহারকারী আলাপ", "উইকিপেডিয়া", "উইকিপেডিয়া আলাপ", "চিত্র", "চিত্র আলাপ", "MediaWik i আলাপ", "Media", "MediaWiki", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "br": return ["Media", "Dibar", "Kaozeal", "Implijer", "Kaozeadenn Implijer", "Wikipedia", "Kaozeadenn Wikipedia", "Skeudenn", "Kaozeadenn Skeudenn", "MediaWiki", "Kaozeadenn MediaWiki", "Patrom", "Kaozeadenn Patrom", "Skoazell", "Kaozeadenn Skoazell", "Rummad", "Kaozeadenn Rummad"];
        case "ca": return ["Media", "Especial", "Discussió", "Usuari", "Usuari Discussió", "Viquipèdia", "Viquipèdia Discussió", "Imatge", "Imatge Discussió", "MediaWiki", "MediaWiki Discussió", "Template", "Template Discussió", "Ajuda", "Ajuda Discussió", "Categoria", "Categoria Discussió"];
        case "cs": return ["Média", "Speciální", "Diskuse", "Wikipedista", "Wikipedista diskuse", "Wikipedie", "Wikipedie diskuse", "Soubor", "Soubor diskuse", "MediaWiki", "MediaWiki diskuse", "Šablona", "Šablona diskuse", "Nápověda", "Nápověda diskuse", "Kategorie", "Kategorie diskuse"];
        case "csb": return ["Media", "Specjalnô", "Diskùsëjô", "Brëkòwnik", "Diskùsëjô brëkòwnika", "Wiki", "Diskùsëjô Wiki", "Òbrôzk", "Diskùsëjô òbrôzków", "MediaWiki", "Diskùsëjô MediaWiki", "Szablóna", "Diskùsëjô Szablónë", "Pòmòc", "Diskùsëjô Pòmòcë", "Kategòrëjô", "Diskùsëjô Kategòrëji"];
        case "cv": return ["Медиа", "Ятарлă", "Сӳтсе явасси", "Хутшăнакан", "Хутшăнаканăн канашлу страници", "Wikipedia", "0", "Ӳкерчĕк", "Ӳкерчĕке сӳтсе явмалли", "MediaWiki", "MediaWiki сӳтсе явмалли", "Шаблон", "Шаблона сӳтсе явмалли", "Пулăшу", "Пулăшăва сӳтсе явмалли", "Категори", "Категорине сӳтсе явмалли"];
        case "cy": return ["Media", "Arbennig", "Sgwrs", "Defnyddiwr", "Sgwrs Defnyddiwr", "Wicipedia", "Sgwrs Wicipedia", "Delwedd", "Sgwrs Delwedd", "MediaWiki", "Sgwrs MediaWiki", "Nodyn", "Sgwrs Nodyn", "Help", "Help talk", "Category", "Category talk"];
        case "da": return ["Media", "Speciel", "Diskussion", "Bruger", "Bruger diskussion", "Wikipedia", "Wikipedia diskussion", "Billede", "Billede diskussion", "MediaWiki", "MediaWiki diskussion", "Skabelon", "Skabelon diskussion", "Hjælp", "Hjælp diskussion", "Kategori", "Kategori diskussion"];
        case "de": return ["Media", "Spezial", "Diskussion", "Benutzer", "Benutzer Diskussion", "Wikipedia", "Wikipedia Diskussion", "Bild", "Bild Diskussion", "MediaWiki", "MediaWiki Diskussion", "Vorlage", "Vorlage Diskussion", "Hilfe", "Hilfe Diskussion", "Kategorie", "Kategorie Diskussion", "Portal", "Portal Diskussion"];
        case "el": return ["Μέσον", "Ειδικό", "Συζήτηση", "Χρήστης", "Συζήτηση χρήστη", "Βικιπαίδεια", "Βικιπαίδεια συζήτηση", "Εικόνα", "Συζήτηση εικόνας", "MediaWiki", "MediaWiki talk", "Πρότυπο", "Συζήτηση προτύπου", "Βοήθεια", "Συζήτηση βοήθειας", "Κατηγορία", "Συζήτηση κατηγορίας"];
        case "eo": return ["Media", "Speciala", "Diskuto", "Vikipediisto", "Vikipediista diskuto", "Vikipedio", "Vikipedio diskuto", "Dosiero", "Dosiera diskuto", "MediaWiki", "MediaWiki diskuto", "Åœablono", "Åœablona diskuto", "Helpo", "Helpa diskuto", "Kategorio", "Kategoria diskuto"];
        case "es": return ["Media", "Especial", "Discusión", "Usuario", "Usuario Discusión", "Wikipedia", "Wikipedia Discusión", "Imagen", "Imagen Discusión", "MediaWiki", "MediaWiki Discusión", "Plantilla", "Plantilla Discusión", "Ayuda", "Ayuda Discusión", "Categoría", "Categoría Discusión"];
        case "et": return ["Meedia", "Eri", "Arutelu", "Kasutaja", "Kasutaja arutelu", "Vikipeedia", "Vikipeedia arutelu", "Pilt", "Pildi arutelu", "MediaWiki", "MediaWiki arutelu", "Mall", "Malli arutelu", "Juhend", "Juhendi arutelu", "Kategooria", "Kategooria arutelu"];
        case "eu": return ["Media", "Aparteko", "Eztabaida", "Lankide", "Lankide eztabaida", "Wikipedia", "Wikipedia eztabaida", "Irudi", "Irudi eztabaida", "MediaWiki", "MediaWiki eztabaida", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "fa": return ["مدیا", "ویژه", "بحث", "کاربر", "بحث کاربر", "ویکی‌پدیا", "بحث ویکی‌پدیا", "تصویر", "بحث تصویر", "مدیاویکی", "بحث مدیاویکی", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "fi": return ["Media", "Toiminnot", "Keskustelu", "Käyttäjä", "Keskustelu käyttäjästä", "Wikipedia", "Keskustelu Wikipediasta", "Kuva", "Keskustelu kuvasta", "MediaWiki", "MediaWiki talk", "Malline", "Keskustelu mallineesta", "Ohje", "Keskustelu ohjeesta", "Luokka", "Keskustelu luokasta"];
        case "fo": return ["Miðil", "Serstakur", "Kjak", "Brúkari", "Brúkari kjak", "Wikipedia", "Wikipedia kjak", "Mynd", "Mynd kjak", "MidiaWiki", "MidiaWiki kjak", "Fyrimynd", "Fyrimynd kjak", "Hjálp", "Hjálp kjak", "Bólkur", "Bólkur kjak"];
        case "fr": return ["Media", "Special", "Discuter", "Utilisateur", "Discussion Utilisateur", "Wikipédia", "Discussion Wikipédia", "Image", "Discussion Image", "MediaWiki", "Discussion MediaWiki", "Modèle", "Discussion Modèle", "Aide", "Discussion Aide", "Catégorie", "Discussion Catégorie", "Portail", "Discussion Portail"];
        case "fur": return ["Media", "Speciâl", "Discussion", "Utent", "Discussion utent", "Vichipedie", "Discussion Vichipedie", "Figure", "Discussion figure", "MediaWiki", "Discussion MediaWiki", "Model", "Discussion model", "Jutori", "Discussion jutori", "Categorie", "Discussion categorie"];
        case "fy": return ["Media", "Wiki", "Oerlis", "Meidogger", "Meidogger oerlis", "Wikipedy", "Wikipedy oerlis", "Ofbyld", "Ofbyld oerlis", "MediaWiki", "MediaWiki oerlis", "Berjocht", "Berjocht oerlis", "Hulp", "Hulp oerlis", "Kategory", "Kategory oerlis"];
        case "ga": return ["Meán", "Speisialta", "Plé", "Úsáideoir", "Plé úsáideora", "Vicipéid", "Plé Vicipéide", "Íomhá", "Plé íomhá", "MediaWiki", "Plé MediaWiki", "Teimpléad", "Plé teimpléid", "Cabhair", "Plé cabhrach", "Catagóir", "Plé catagóire"];
        case "gu": return ["Media", "Special", "Talk", "User", "User talk", "વિકિપીડિયા", "વિકિપીડિયા talk", "Image", "Image talk", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "he": return ["Media", "מיוחד", "שיחה", "משתמש", "שיחת משתמש", "ויקיפדיה", "שיחת ויקיפדיה", "×ª×ž×•× ×”", "שיחת ×ª×ž×•× ×”", "MediaWiki", "שיחת MediaWiki", "×ª×‘× ×™×ª", "שיחת ×ª×‘× ×™×ª", "עזרה", "שיחת עזרה", "קטגוריה", "שיחת קטגוריה"];
        case "hi": return ["Media", "विशेष", "वार्ता", "सदस्य", "सदस्य वार्ता", "विकिपीडिया", "विकिपीडिया वार्ता", "चित्र", "चित्र वार्ता", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "श्रेणी", "श्रेणी वार्ता", "Help", "Help talk"];
        case "hr": return ["Mediji", "Posebno", "Razgovor", "Suradnik", "Razgovor sa suradnikom", "Wikipedia", "Razgovor Wikipedia", "Slika", "Razgovor o slici", "MediaWiki", "MediaWiki razgovor", "Predložak", "Razgovor o predlošku", "Pomoć", "Razgovor o pomoći", "Kategorija", "Razgovor o kategoriji"];
        case "hu": return ["Média", "Speciális", "Vita", "User", "User vita", "Wikipédia", "Wikipédia vita", "Kép", "Kép vita", "MediaWiki", "MediaWiki vita", "Sablon", "Sablon vita", "Segítség", "Segítség vita", "Kategória", "Kategória vita"];
        case "ia": return ["Media", "Special", "Discussion", "Usator", "Discussion Usator", "Wikipedia", "Discussion Wikipedia", "Imagine", "Discussion Imagine", "MediaWiki", "Discussion MediaWiki", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "id": return ["Media", "Istimewa", "Bicara", "Pengguna", "Bicara Pengguna", "Wikipedia", "Pembicaraan Wikipedia", "Gambar", "Pembicaraan Gambar", "MediaWiki", "Pembicaraan MediaWiki", "Templat", "Pembicaraan Templat", "Bantuan", "Pembicaraan Bantuan", "Kategori", "Pembicaraan Kategori"];
        case "is": return ["Miðill", "Kerfissíða", "Spjall", "Notandi", "Notandaspjall", "Wikipedia", "Wikipediaspjall", "Mynd", "Myndaspjall", "Melding", "Meldingarspjall", "Snið", "Sniðaspjall", "Hjálp", "Hjálparspjall", "Flokkur", "Flokkaspjall"];
        case "it": return ["Media", "Speciale", "Discussione", "Utente", "Discussioni utente", "Wikipedia", "Discussioni Wikipedia", "Immagine", "Discussioni immagine", "MediaWiki", "Discussioni MediaWiki", "Template", "Discussioni template", "Aiuto", "Discussioni aiuto", "Categoria", "Discussioni categoria"];
        case "ja": return ["Media", "特別", "ノート", "利用者", "利用者‐会話", "Wikipedia", "Wikipedia‐ノート", "画像", "画像‐ノート", "MediaWiki", "MediaWiki‐ノート", "Template", "Template‐ノート", "Help", "Help‐ノート", "Category", "Category‐ノート"];
        case "ka": return ["მედია", "სპეციალური", "განხილვა", "მომხმარებელი", "მომხმარებელი განხილვა", "ვიკიპედია", "ვიკიპედია განხილვა", "სურათი", "სურათი განხილვა", "მედიავიკი", "მედიავიკი განხილვა", "თარგი", "თარგი განხილვა", "დახმარება", "დახმარება განხილვა", "კატეგორია", "კატეგორია განხილვა"];
        case "ko": return ["Media", "특수기능", "í† ë¡ ", "사용자", "ì‚¬ìš©ìží† ë¡ ", "위키백과", "ìœ„í‚¤ë°±ê³¼í† ë¡ ", "그림", "ê·¸ë¦¼í† ë¡ ", "분류", "ë¶„ë¥˜í† ë¡ ", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Help", "Help talk"];
        case "ku": return ["Medya", "Taybet", "Nîqaş", "Bikarhêner", "Bikarhêner nîqaş", "Wîkîpediya", "Wîkîpediya nîqaş", "Wêne", "Wêne nîqaş", "MediaWiki", "MediaWiki nîqaş", "Şablon", "Şablon nîqaş", "Alîkarî", "Alîkarî nîqaş", "Kategorî", "Kategorî nîqaş"];
        case "la": return ["Specialis", "Disputatio", "Usor", "Disputatio Usoris", "Vicipaedia", "Disputatio Vicipaediae", "Imago", "Disputatio Imaginis", "MediaWiki", "Disputatio MediaWiki", "Formula", "Disputatio Formulae", "Auxilium", "Disputatio Auxilii", "Categoria", "Disputatio Categoriae", "Media"];
        case "li": return ["Media", "Speciaal", "Euverlik", "Gebroeker", "Euverlik gebroeker", "Wikipedia", "Euverlik Wikipedia", "Aafbeilding", "Euverlik afbeelding", "MediaWiki", "Euverlik MediaWiki", "Sjabloon", "Euverlik sjabloon", "Help", "Euverlik help", "Kategorie", "Euverlik kategorie"];
        case "lt": return ["Medija", "Specialus", "Aptarimas", "Naudotojas", "Naudotojo aptarimas", "Wikipedia", "Wikipedia aptarimas", "Vaizdas", "Vaizdo aptarimas", "MediaWiki", "MediaWiki aptarimas", "Å ablonas", "Å ablono aptarimas", "Pagalba", "Pagalbos aptarimas", "Kategorija", "Kategorijos aptarimas"];
        case "mk": return ["Медија", "Специјални", "Разговор", "Корисник", "Корисник разговор", "Wikipedia", "Wikipedia разговор", "Слика", "Слика разговор", "МедијаВики", "МедијаВики разговор", "Шаблон", "Шаблон разговор", "Помош", "Помош разговор", "Категорија", "Категорија разговор"];
        case "ms": return ["Media", "Istimewa", "Perbualan", "Pengguna", "Perbualan Pengguna", "Wikipedia", "Perbualan Wikipedia", "Imej", "Imej Perbualan", "MediaWiki", "MediaWiki Perbualan", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "mt": return ["Media", "Special", "Talk", "User", "User talk", "Wikipedija", "Wikipedija talk", "Image", "Image talk", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "nap": return ["Media", "Speciale", "Discussione", "Utente", "Discussioni utente", "Wikipedia", "Discussioni Wikipedia", "Immagine", "Discussioni immagine", "MediaWiki", "Discussioni MediaWiki", "Template", "Discussioni template", "Aiuto", "Discussioni aiuto", "Categoria", "Discussioni categoria"];
        case "nds": return ["Media", "Spezial", "Diskuschoon", "Bruker", "Bruker Diskuschoon", "Wikipedia", "Wikipedia Diskuschoon", "Bild", "Bild Diskuschoon", "MediaWiki", "MediaWiki Diskuschoon", "Vörlaag", "Vörlaag Diskuschoon", "Hülp", "Hülp Diskuschoon", "Kategorie", "Kategorie Diskuschoon"];
        case "nl": return ["Media", "Speciaal", "Overleg", "Gebruiker", "Overleg gebruiker", "Wikipedia", "Overleg Wikipedia", "Afbeelding", "Overleg afbeelding", "MediaWiki", "Overleg MediaWiki", "Sjabloon", "Overleg sjabloon", "Help", "Overleg help", "Categorie", "Overleg categorie"];
        case "nn": return ["Filpeikar", "Spesial", "Diskusjon", "Brukar", "Brukardiskusjon", "Wikipedia", "Wikipedia-diskusjon", "Fil", "Fildiskusjon", "MediaWiki", "MediaWiki-diskusjon", "Mal", "Maldiskusjon", "Hjelp", "Hjelpdiskusjon", "Kategori", "Kategoridiskusjon"];
        case "no": return ["Medium", "Spesial", "Diskusjon", "Bruker", "Brukerdiskusjon", "Wikipedia", "Wikipedia-diskusjon", "Bilde", "Bildediskusjon", "MediaWiki", "MediaWiki-diskusjon", "Mal", "Maldiskusjon", "Hjelp", "Hjelpdiskusjon", "Kategori", "Kategoridiskusjon"];
        case "nv": return ["Media", "Special", "Naaltsoos baa yinísht'į́", "Choinish'įįhí", "Choinish'įįhí baa yinísht'į́", "Wikiibíídiiya", "Wikiibíídiiya baa yinísht'į́", "E'elyaaígíí", "E'elyaaígíí baa yinísht'į́", "MediaWiki", "MediaWiki baa yinísht'į́", "Template", "Template talk", "Aná'álwo'", "Aná'álwo' baa yinísht'į́", "T'ááłáhági át'éego", "T'ááłáhági át'éego baa yinísht'į́"];
        case "oc": return ["Especial", "Discutir", "Utilisator", "Discutida Utilisator", "Oiquipedià ", "Discutida Oiquipedià ", "Image", "Discutida Image", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Media", "Help", "Help talk", "Category", "Category talk"];
        case "os": return ["Media", "Сæрмагонд", "Дискусси", "Архайæг", "Архайæджы дискусси", "Wikipedia", "0", "Ныв", "Нывы тыххæй дискусси", "MediaWiki", "Дискусси MediaWiki", "Шаблон", "Шаблоны тыххæй дискусси", "Æххуыс", "Æххуысы тыххæй дискусси", "Категори", "Категорийы тыххæй дискусси"];
        case "pa": return ["ਮੀਡੀਆ", "ਖਾਸ", "ਚਰਚਾ", "ਮੈਂਬਰ", "ਮੈਂਬਰ ਚਰਚਾ", "Wikipedia", "Wikipedia ਚਰਚਾ", "ਤਸਵੀਰ", "ਤਸਵੀਰ ਚਰਚਾ", "ਮੀਡੀਆਵਿਕਿ", "ਮੀਡੀਆਵਿਕਿ ਚਰਚਾ", "ਨਮੂਨਾ", "ਨਮੂਨਾ ਚਰਚਾ", "ਮਦਦ", "ਮਦਦ ਚਰਚਾ", "ਸ਼੍ਰੇਣੀ", "ਸ਼੍ਰੇਣੀ ਚਰਚਾ"];
        case "pl": return ["Media", "Specjalna", "Dyskusja", "Wikipedysta", "Dyskusja Wikipedysty", "Wikipedia", "Dyskusja Wikipedii", "Grafika", "Dyskusja grafiki", "MediaWiki", "Dyskusja MediaWiki", "Szablon", "Dyskusja szablonu", "Pomoc", "Dyskusja pomocy", "Kategoria", "Dyskusja kategorii", "Portal", "Dyskusja portalu"];
        case "pt": return ["Media", "Especial", "Discussão", "Usuário", "Usuário Discussão", "Wikipedia", "Wikipedia Discussão", "Imagem", "Imagem Discussão", "MediaWiki", "MediaWiki Discussão", "Predefinição", "Predefinição Discussão", "Ajuda", "Ajuda Discussão", "Categoria", "Categoria Discussão"];
        case "ro": return ["Media", "Special", "Discuţie", "Utilizator", "Discuţie Utilizator", "Wikipedia", "Discuţie Wikipedia", "Imagine", "Discuţie Imagine", "MediaWiki", "Discuţie MediaWiki", "Format", "Discuţie Format", "Ajutor", "Discuţie Ajutor", "Categorie", "Discuţie Categorie"];
        case "ru": return ["Медиа", "Служебная", "Обсуждение", "Участник", "Обсуждение участника", "Википедия", "Обсуждение Википедии", "Изображение", "Обсуждение изображения", "MediaWiki", "Обсуждение MediaWiki", "Шаблон", "Обсуждение шаблона", "Справка", "Обсуждение справки", "Категория", "Обсуждение категории"];
        case "sc": return ["Speciale", "Contièndha", "Utente", "Utente discussioni", "Wikipedia", "Wikipedia discussioni", "Immà gini", "Immà gini contièndha", "Media", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "sk": return ["Médiá", "Špeciálne", "Diskusia", "Redaktor", "Diskusia s redaktorom", "Wikipédia", "Diskusia k Wikipédii", "Obrázok", "Diskusia k obrázku", "MediaWiki", "Diskusia k MediaWiki", "Šablóna", "Diskusia k šablóne", "Pomoc", "Diskusia k pomoci", "Kategória", "Diskusia ku kategórii"];
        case "sl": return ["Media", "Posebno", "Pogovor", "Uporabnik", "Uporabniški pogovor", "Wikipedija", "Pogovor k Wikipediji", "Slika", "Pogovor k sliki", "MediaWiki", "MediaWiki talk", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "sq": return ["Media", "Speciale", "Diskutim", "Përdoruesi", "Përdoruesi diskutim", "Wikipedia", "Wikipedia diskutim", "Figura", "Figura diskutim", "MediaWiki", "MediaWiki diskutim", "Stampa", "Stampa diskutim", "Ndihmë", "Ndihmë diskutim", "Category", "Category talk"];
        case "sr": return ["Media", "Посебно", "Разговор", "Корисник", "Разговор са корисником", "Википедија", "Разговор о Википедији", "Слика", "Разговор о слици", "МедијаВики", "Разговор о МедијаВикију", "Шаблон", "Разговор о шаблону", "Помоћ", "Разговор о помоћи", "Категорија", "Разговор о категорији", "Портал", "Разговор о порталу"];
        case "sv": return ["Media", "Special", "Diskussion", "Användare", "Användardiskussion", "Wikipedia", "Wikipediadiskussion", "Bild", "Bilddiskussion", "MediaWiki", "MediaWiki diskussion", "Mall", "Malldiskussion", "Hjälp", "Hjälp diskussion", "Kategori", "Kategoridiskussion"];
        case "ta": return ["ஊடகம்", "சிறப்பு", "பேச்சு", "பயனர்", "பயனர் பேச்சு", "Wikipedia", "Wikipedia பேச்சு", "படிமம்", "படிமப் பேச்சு", "மீடியாவிக்கி", "மீடியாவிக்கி பேச்சு", "வார்ப்புரு", "வார்ப்புரு பேச்சு", "உதவி", "உதவி பேச்சு", "பகுப்பு", "பகுப்பு பேச்சு"];
        case "th": return ["Media", "พิเศษ", "พูดคุย", "ผู้ใช้", "คุยเกี่ยวกับผู้ใช้", "Wikipedia", "Wikipedia talk", "ภาพ", "คุยเกี่ยวกับภาพ", "MediaWiki", "คุยเกี่ยวกับ MediaWiki", "Template", "Template talk", "Help", "Help talk", "Category", "Category talk"];
        case "tlh": return ["Doch", "le'", "ja'chuq", "lo'wI'", "lo'wI' ja'chuq", "wIqIpe'DIya", "wIqIpe'DIya ja'chuq", "nagh beQ", "nagh beQ ja'chuq", "MediaWiki", "MediaWiki ja'chuq", "chen'ay'", "chen'ay' ja'chuq", "QaH", "QaH ja'chuq", "Segh", "Segh ja'chuq"];
        case "tr": return ["Media", "Özel", "Tartışma", "Kullanıcı", "Kullanıcı mesaj", "Vikipedi", "Vikipedi tartışma", "Resim", "Resim tartışma", "MedyaViki", "MedyaViki tartışma", "Şablon", "Şablon tartışma", "Yardım", "Yardım tartışma", "Kategori", "Kategori tartışma"];
        case "tt": return ["Media", "Maxsus", "Bäxäs", "Äğzä", "Äğzä bäxäse", "Wikipedia", "Wikipedia bäxäse", "Räsem", "Räsem bäxäse", "MediaWiki", "MediaWiki bäxäse", "Ürnäk", "Ürnäk bäxäse", "Yärdäm", "Yärdäm bäxäse", "Törkem", "Törkem bäxäse"];
        case "uk": return ["Медіа", "Спеціальні", "Обговорення", "Користувач", "Обговорення користувача", "Wikipedia", "Обговорення Wikipedia", "Зображення", "Обговорення зображення", "MediaWiki", "Обговорення MediaWiki", "Шаблон", "Обговорення шаблону", "Довідка", "Обговорення довідки", "Категорія", "Обговорення категорії"];
        case "vi": return ["Phương tiện", "Đặc biệt", "Thảo luận", "Thà nh viên", "Thảo luận Thà nh viên", "Wikipedia", "Thảo luận Wikipedia", "Hình", "Thảo luận Hình", "MediaWiki", "Thảo luận MediaWiki", "Tiêu bản", "Thảo luận Tiêu bản", "Trợ giúp", "Thảo luận Trợ giúp", "Thể loại", "Thảo luận Thể loại"];
        case "wa": return ["Media", "Sipeciås", "Copene", "Uzeu", "Uzeu copene", "Wikipedia", "Wikipedia copene", "Imådje", "Imådje copene", "MediaWiki", "MediaWiki copene", "Modele", "Modele copene", "Aidance", "Aidance copene", "Categoreye", "Categoreye copene"];
    }
    return null;
}

$wikins.createMap = function(k,v) {
    var r = {"" : ""};
    for (var i = 0; i < k.length; ++i) {
        r[k[i]] = v[i];
    }
    return r;
}

$wikins.init = function() {
    // This works only on Wikimedia-style URLs.  Failsafe is English.
    var lang = window.location.host.split('.')[0];
    var ns = $wikins.getNS(lang) || $wikins.namespacesEnglish;

    $wikins.namespaces = $wikins.createMap(
        ns, $wikins.namespacesEnglish);
    $wikins.namespaceNames = $wikins.createMap(
        $wikins.namespacesEnglish, ns);

    for (var k in $wikins.namespaceNames) {
        $wikins[k.replace(/ /g,'_')] = $wikins.namespaceNames[k];
    }
}

$wikins.nsjoin = function(ns, article) {
    if (ns == "") return article;
    else return ns + ':' + article;
}

// TODO: this only applies for Wikimedia servers
$wikins.interwiki = 'ab|aa|af|ak|sq|als|am|ang|ar|an|arc|hy|roa-rup|as|ast|av|ay|az|bm|ba|eu|be|bn|bh|bi|bs|br|bg|my|ca|ch|ce|chr|chy|ny|zh|zh-tw|zh-cn|cho|cv|kw|co|cr|hr|cs|da|dv|nl|dz|en|eo|et|ee|fo|fj|fi|fr|fy|ff|gl|ka|de|got|el|kl|gn|gu|ht|ha|haw|he|hz|hi|ho|hu|is|io|ig|id|ia|ie|iu|ik|ga|it|ja|jv|kn|kr|csb|ks|kk|km|ki|rw|rn|tlh|kv|kg|ko|kj|ku|ky|lo|la|lv|li|ln|lt|jbo|nds|lg|lb|mk|mg|ms|ml|mt|gv|mi|minnan|mr|mh|zh-min-nan|mo|mn|mus|nah|na|nv|ne|se|no|nn|oc|or|om|pi|fa|pl|pt|pa|ps|qu|ro|rm|ru|sm|sg|sa|sc|gd|sr|sh|st|tn|sn|scn|simple|sd|si|sk|sl|so|st|es|su|sw|ss|sv|tl|ty|tg|ta|tt|te|th|bo|ti|tpi|to|tokipona|ts|tum|tr|tk|tw|uk|ur|ug|uz|ve|vi|vo|wa|cy|wo|xh|ii|yi|yo|za|zu';

$wikins.init();

/****************************************************************************
 * END wikins.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikipage.js
 ****************************************************************************/

// $Id: wikipage.js 1243 2006-02-24 13:05:03Z quarl $

// wikipage.js - "WikiPage" class for page name, etc. functionality
//   Has i18n support.

// Suppose we are editing [[Template talk:Foo bar 'blah']].
//   wikiDoc.editingP:             true;
//   ...

// wikiPage is a pre-initialized global variable using the current page's canonical URL.
// It uses the "Retrieved from" page hyperlink which is robust against redirections, editing, special characters, special pages.
//
//   wikiPage.url:         "http://en.wikipedia.org/wiki/Template_talk:Foo_bar_%27blah%27"
//   wikiPage.qurl:        "http://en.wikipedia.org/w/index.php?title=Template_talk:Foo_bar_%27blah%27"
//   wikiPage.page:        "Template talk:Foo bar 'blah'"
//   wikiPage.article:     "Foo bar 'blah'"
//   wikiPage.namespace:   "Template talk" // always the English version
//   wikiPage.namespaceNT: "Template"
//   wikiPage.talkP:       true
//   wikiPage.nsTemplateP: true
//   wikiPage.nsMainP:     false
//   wikiPage.nsUserP:     false
//   wikiPage.nsCategoryP: false
//   wikiPage.nsSpecialP:  false
//   wikiPage.nsProjectP:  false    // (namespace "Wikipedia")

// To create new WikiPage object from a URL:
//   var wp = new WikiPage("http://en.wikipedia.org/wiki/Article_Name");
// To create a new WikiPage object from a page name:
//   var wp = new WikiPage(null, 'Article Name');

// TODO: generalize code to other languages

var $wikipage = new Module('wikipage.js');
$wikipage.depend('util.js', 'wikins.js');

var WikiPage = $wikipage.WikiPage = function(url, page, doc, usafe) {
    if (!(this instanceof WikiPage)) {
        var wp= new WikiPage(url, page, doc, usafe);
        if (wp.failed) return null;
        return wp;
    }
    this.doc = doc;
    var extraParams;
    if (url) {
        url = "" + url;
        if (url.match( '^(?:http://'+WikiPage.server+')?/wiki/')) {
            this.pageQuoted = RegExp.rightContext;
        } else if (url.match( '^(?:http://'+WikiPage.server+')?/w/index\\.php\\?title=([^&=+]+)(.*)')) {
            this.pageQuoted = RegExp.$1;
            extraParams = RegExp.$2;
        } else {
            if (usafe) { this.failed=1; return null; }
            return $wikipage.error("Couldn't parse page name from url '"+url+"' (error 3a9483df-fc14-419f-8b0b-6e00bb4a6d12)");
        }
        this.pageQuoted = $util.removeAnchor(this.pageQuoted);
        this.page = $util.wpaDecode(this.pageQuoted);
    } else if (page) {
        this.page = $util.removeAnchor(page.replace(/_/g, ' '));
        this.pageQuoted = $util.wpaEscape(page);
    } else {
        return $wikipage.error("must specify url or page (12af0703-eeba-4d70-87dd-06e10c26dbf8)");
    }

    // this is the requested oldid for the page, if present
    this.oldid = extraParams && extraParams.match(/&oldid=([0-9]+)/) && RegExp.$1;
// XXX TODO this doesn't work right now for window.wikiPage because we use
// canonical URL, not window.location
// maybe we should create another class that wraps WikiPage

    this.url = 'http://'+WikiPage.server+'/wiki/' + this.pageQuoted;
    this.qurl = 'http://'+WikiPage.server+'/w/index.php?title=' + this.pageQuoted;

    if (this.oldid) {
        this.qurl += '&oldid=' + this.oldid;
    }

    // Get term on the left of ":".  Not any string is a namespace though, only certain hardcoded ones!
    if (this.page.match(/:/) &&
        (this.namespace = $wikins.namespaces[RegExp.leftContext]))
    {
        // this.namespace contains the ENGLISH version of the namespace
        this.article = RegExp.rightContext;
    } else {
        this.namespace = ''; // (main)
        this.article = this.page;
    }

    if ($wikins.namespaceNoTalk[this.namespace]) {
        this.talkP = true;
        this.editableP = true;
        this.namespaceNT = $wikins.namespaceNoTalk[this.namespace];
        this.namespaceT = this.namespace;
    } else if ($wikins.namespaceTalk[this.namespace]) {
        this.talkP = false;
        this.editableP = true;
        this.namespaceNT = this.namespace;
        this.namespaceT = $wikins.namespaceTalk[this.namespace];
    } else {
        // this should be a namespace with no corresponding Talk namespace,
        // i.e. Media or Special
        this.talkP = false;
        this.editableP = false;
        this.namespaceNT = this.namespace;
        this.namespaceT = null;
    }

    if (this.article.match(/\//)) {
        this.superarticle = RegExp.leftContext;
        this.subarticle = RegExp.rightContext;
    } else {
        this.superarticle = this.article;
        this.subarticle = '';
    }

    this.nsMainP = (this.namespaceNT == '');
    this.nsMediaP = (this.namespaceNT == 'Media');
    this.nsSpecialP = (this.namespaceNT == 'Special');
    this.nsUserP = (this.namespaceNT == 'User');
    this.nsPortalP = (this.namespaceNT == 'Portal');
    this.nsImageP = (this.namespaceNT == 'Image');
    this.nsMediaWikiP = (this.namespaceNT == 'MediaWiki');
    this.nsTemplateP = (this.namespaceNT == 'Template');
    this.nsHelpP = (this.namespaceNT == 'Help');
    this.nsCategoryP = (this.namespaceNT == 'Category');
    this.nsProjectP = (this.namespaceNT == 'Wikipedia');

    // remember, this.namespace is in English, but WikiPage argument is the
    // localized wikimarkup name
    this.talkPage = function() {
        if (this.talkP) { return this; }
        if (this.namespaceT == null) return null;
        var ns = $wikins.namespaceNames[this.namespaceT];
        return new WikiPage(null, $wikins.nsjoin(ns, this.article));
    }

    this.notalkPage = function() {
        if (!this.talkP) { return this; }
        var ns = $wikins.namespaceNames[this.namespaceNT];
        return new WikiPage(null, $wikins.nsjoin(ns, this.article));
    }

    this.sandboxP     = Boolean(this.page.match(/sandbox$/i));

    this.setDoc = function(doc) {
        var wd = new WikiDocument(doc, this);

        if (wd.editingP) {
            this.editDoc = wd;
        } else {
            this.viewDoc = wd;
        }

        this.wd = wd;
    }

    if (doc) {
        // Note that a WikiPage may have more than one associated WikiDocument,
        // e.g. one from viewing and one from editing
        this.setDoc(doc);
    }

    this.relevantUser = $wikipage.getRelevantUser0(this, extraParams);
    // TODO: if relevantUser was from extraParams, we need to fixup
    // page/pageQuoted

    return this;
}

WikiPage.prototype.toString = function() {
    return 'WikiPage(null,' + $util.stringQuoteEscape(this.page) + ')';
}


/*
<div class="printfooter">
Retrieved from "<a href="http://en.wikipedia.org/wiki/Albert_Einstein">http://en.wikipedia.org/wiki/Albert_Einstein</a>"</div>
*/


$wikipage.getCanonPageURL0 = function() {
    // for 'Special' pages, use location.href, because the "retrieved from"
    // note doesn't include the sub-page or target
    if (window.location.pathname.match('^/wiki/' + $wikins.Special + ':') ||
        window.location.href.match('/w/index\\.php\\?title=' + $wikins.Special + ':'))
    {
        return window.location.href;
    } else {
        return  $wikipage.getCanonPageURL1();
    }
}

// the "retrieved from" text contains the canonical article URL (even if we're
// looking at an edit or history page)
$wikipage.getCanonPageURL1 = function() {
    return $util.getElementsByClass("printfooter", null, 'div')[0].getElementsByTagName('a')[0].href;
}

var WikiDocument = $wikipage.WikiDocument = function(doc, wp) {
    this.doc = doc;
    this.wp = wp;

    this.username     = $wikipage.getUsername0(doc);

    // Note: can't use "doc.editform" or "doc.forms.editform", because 'doc'
    // might actually be an XMLDocument (not HTMLDocument), if this is the
    // result of an XMLHTTPRequest.
    this.editForm     = doc.getElementById('editform');
    this.editingP     = Boolean(this.editForm);
    // obsolete method: document.title.match(/^Editing /)
    this.protectedP   = Boolean(doc.getElementById("ca-viewsource"));
    this.newSectionP  = this.editForm && (this.editForm.wpSection.value == "new");
    this.movePageP    = Boolean(doc.getElementById("movepage"));
    this.previewP     = Boolean(doc.getElementById("wikiPreview"));
    this.historyP     = Boolean(doc.getElementById("pagehistory"));
    this.permalinkP   = Boolean(doc.getElementById("t-ispermalink"));

    // this is the revision id for this document, which always exists for
    // saved pages, even if we're looking at a 'latest version' URL.
    this.oldid        = $wikipage.getOldid0(doc, this.permalinkP);
}

$wikipage.getUsername0 = function(doc) {
    // read username from pt-userpage link.
    // <li id="pt-userpage"><a href="/wiki/User:Quarl">Quarl</a></li>
    return doc.getElementById('pt-userpage').getElementsByTagName('a')[0].text;
}

$wikipage.getUsernameFromLink = function(link) {
    return link && (new WikiPage(link)).relevantUser;
}

$wikipage.getRelevantUser0 = function(wp, extraParams) {
    if (wp.nsUserP) return wp.superarticle;
    if (wp.nsSpecialP) {
        if (wp.superarticle == 'Contributions' ||
            wp.superarticle == 'Emailuser' ||
            wp.superarticle == 'Blockip'
            )
        {
            // e.g. /w/index.php?title=Special:Contributions&target=foo
            if (extraParams && extraParams.match(/&target=([^&=+]+)/)) {
                var u = RegExp.$1;
            } else {
                var u = wp.subarticle;
            }

            // Special:Contributions/newbies is a magic username
            if (u == 'newbies') return null;
            return u;
        }
        // TODO: return current user if Watchlist?
        // TODO: /w/index.php?title=Special:Log&user=Quarl
    }

    return null;

    // if (doc && wp.page == 'Special:Contributions') {
    //     var cdiv = doc.getElementById('contentSub');
    //     if (cdiv.textContent == "For newbies") return null;
    //     return $wikipage.getUsernameFromLink(cdiv.getElementsByTagName('a')[0]);
    // }
}

// Get the oldid ("permalink") for the current page.
// Note that we can't get oldid for editing pages, special pages, etc.
$wikipage.getOldid0 = function(doc, perm) {
    var tagid = perm ? 'ca-edit' : 't-permalink';
    var tag = doc.getElementById(tagid);
    if (!tag) return null;
    var href = tag.getElementsByTagName("a")[0].href;
    if (!href) return null;
    href.match(/&oldid=([0-9]+)/);
    return RegExp.$1;
}

$wikipage.getQueryVars0 = function(){
    var res = [];
    var queryStr = window.location.search.substring(1);
    if (queryStr) {
        var pairs = queryStr.split("&");
        for(var i=0; i < pairs.length; i++){
            var pair = pairs[i].split("=");
            res[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
        }
    }
    // default nulls to avoid 'undefined property' warnings for commonly
    // checked properties
    if (!res.fakeaction) res.fakeaction = null;
    return res;
}

$wikipage.getDbName0 = function(server) {
    switch(server) {
        case 'commons.wikimedia.org': return 'commonswiki';
        case 'en.wiktionary.org': return 'enwiktionary';
    }
    if (server.match(/^([a-z][a-z])[.]wikipedia[.]org$/)) {
        return RegExp.$1 + 'wiki';
    }
    if (server.match(/^([a-z][a-z])[.]wikisource[.]org$/)) {
        return RegExp.$1 + 'wikisource';
    }
    $wikipage.error("unknown server, couldn't figure out dbname (error 3b448857-7545-4187-8959-d80b1cd8149b)");
    return 'enwiki';
}

$wikipage.load = function() {
    WikiPage.server = window.location.host || 'en.wikipedia.org';
    WikiPage.wikimediaWikiP = Boolean(WikiPage.server.match(/wiki([pm]edia|source|books)\.org|wiktionary\.org/));

    WikiPage.dbname = $wikipage.getDbName0(WikiPage.server);

    window.wikiPage = new WikiPage($wikipage.getCanonPageURL0(), null, document);
    window.wikiDoc = wikiPage.wd;

    WikiDocument.queryVars = $wikipage.getQueryVars0();

    $wikipage.debug("load(): wikiPage=" + wikiPage);
}

$util.addOnloadHook($wikipage.load);

// obsolete method 1:
// function getPname0() {
//     var z=document.getElementById("content").childNodes;
//     for (var n=0;n<z.length;n++) {
//         if (z[n].className=="firstHeading") return z[n].textContent;
//     }
// }

// function getPname1() {
//     var t = getPname0();
//     t = t.replace(/^Editing /,'');
//     t = t.replace(/ \(section\)$/,'');

//     return t;
// }

// obsolete method 2:
//    document.title.substr(0, document.title.lastIndexOf(' - Wikipedia, the free'));


/****************************************************************************
 * END wikipage.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikipageXfd.js
 ****************************************************************************/

// $Id: wikipageXfd.js 1195 2006-02-24 07:38:24Z quarl $

// wikipageXfd.js - extra afd/rfd/tfd/cfd/mfd-related wikipage
// functionality

var $wikipageXfd = new Module('wikipageXfd.js');
$wikipageXfd.depend('wikipage.js', 'datetime.js');

$wikipageXfd.ddNames = { 'afd' : 'Wikipedia:Articles for deletion',
                        'tfd' : 'Wikipedia:Templates for deletion',
                        'ifd' : 'Wikipedia:Images and media for deletion',
                        'cfd' : 'Wikipedia:Categories for deletion',
                        'sfd' : 'Wikipedia:Stub types for deletion',
                        'rfd' : 'Wikipedia:Redirects for deletion',
                        'mfd' : 'Wikipedia:Miscellany for deletion' };

$wikipageXfd._docIsRedirectP = function(doc) {
    var d = doc.getElementById('contentSub');
    return Boolean(d && d.textContent == 'Redirect page');
}

$wikipageXfd._textIsRedirectP = function(doc) {
    var d = doc.getElementById('wpTextbox1');
    return Boolean(d && $wikiparse.isRedirect(d.value));
}

// look at content to see whether this is a RFD
WikiPage.prototype._isRedirectP = function() {
    if (this.viewDoc) {
        return $wikipageXfd._docIsRedirectP(this.viewDoc.doc);
    } else if (this.editDoc) {
        return $wikipageXfd._textIsRedirectP(this.editDoc.doc);
    } else {
        // $wikipageXfd.error("WikiPage._isRedirectP(): error a2ac8317-fbf3-4995-8041-92a638099adc");
        // return null;
        return false;
    }
}

// Return deletion debate type depending on page type.
//
// Valid types:
//   afd (Articles)
//   tfd (Templates)
//   ifd (Images and media)
//   cfd (Categories)
//   sfd (Stub types)
//   rfd (Redirects)
//   mfd (Miscellaneous)
//
// Note: technically, if this is a Talk page, would have to use MFD; in
// practice if I'm on the talk page, I really mean to do action on non-Talk
// page.
WikiPage.prototype.xfdType = function() {
    if (this._xfd_type) {
    } else if (this.nsMainP) {
        if (this._isRedirectP()) {
            this._xfd_type = 'rfd';
        } else {
            this._xfd_type = 'afd';
        }
    } else if (this.nsCategoryP) {
        this._xfd_type = 'cfd';
    } else if (this.nsTemplateP) {
        if (this.article.match(/-stub$/)) {
            this._xfd_type = 'sfd';
        } else {
            this._xfd_type = 'tfd';
        }
    } else if (this.nsImageP) {
        this._xfd_type = 'ifd';
    } else {
        this._xfd_type = 'mfd';
    }
    return this._xfd_type;
}

// return log page for given date (default today)
function afdLogPage(d) {
    d = d || new Date();
    return new WikiPage(null,'Wikipedia:Articles for deletion/Log/' + $datetime.datestampYYYYMonthD(d));
}

WikiPage.prototype.xfdDebateName = function() {
    var n = $wikipageXfd.ddNames[this.xfdType()];
    if (!n) {
        alert("## WikiPage.xfdDebateName error 46d7d9a3-e63e-4749-a393-6de9ed6c87fa");
        return null;
    }

    return n;
}

WikiPage.prototype.afdPage = function() {
    if (this.xfdType() != 'afd') return null;
    return new WikiPage(null, 'Wikipedia:Articles for deletion/' + this.article);
}

WikiPage.prototype.afdPageX = function() {
    // already an AFD page?
    if (this.afdTargetPage()) return this;
    return this.afdPage();
}

WikiPage.prototype.afdTargetPage = function() {
    if (!this.page.match(/^Wikipedia:Articles for deletion\/(.*?)(?: \([0-9]+|2nd nomination|3rd nomination|4th nomination\))?$/)) return null;

    var p = RegExp.$1;
    if (p.match(/^Log\//)) return null;

    return new WikiPage(null, p);
}

WikiPage.prototype.afdLogDate = function() {
    if (!this.page.match(/^Wikipedia:Articles for deletion\/Log\/(.*?)?$/)) return null;
    var d = RegExp.$1;
    return new Date(Date.parse(d));
}

$wikipageXfd._load = function() {
    window.wpAfdTarget = wikiPage.afdTargetPage();
    window.wpAfd = wikiPage.afdPageX();
    window.wpAfdLogDate = wikiPage.afdLogDate();

    window.afdP = Boolean(wpAfdTarget);
    window.afdLogP = Boolean(wpAfdLogDate);
}

$util.addOnloadHook($wikipageXfd._load);


/****************************************************************************
 * END wikipageXfd.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikiedit.js
 ****************************************************************************/

// $Id: wikiedit.js 1156 2006-02-23 13:26:11Z quarl $

// wikiedit.js - functions for automatically editing pages

// synposis:
//     function beginEdit() {
//         wikiPage.getEditorAsync(myPage_edit, data1, data2);
//     }
//     function myPage_edit(editor, data1, data2) {
//         editor.wpTextbox1 += data1;
//         editor.wpSummary += data2;
//         editor.submit();
//

// WikiEditor is a class that facilitates editing and submitting Wikipedia edit forms.
//
// use wp.getEditorAsync() to use the current edit form if available, else download one.
//     - inside the callback, "this" is equivalent to "editor"
//
// available properties:
//     wpTextbox1
//     wpSummary
//     wpMinoredit
//     wpWatchthis
//
// available functions:
//     submitDirect: submit form directly via document.form.submit
//     submitHidden: create a new hidden form, attach to document, and submit
//     submit: submitDirect if possible, else submitHidden
//     submitAsync: asynchronously submit a form via XMLHTTPRequest, and callback result
//     updateForm: update what the user sees and prepare for submitting
//     refuseCreate: if wpTextbox1 is empty, alert and return 'True'
//

// WikiEditor.addSubmitHook adds a hook that's called from all form
// submissions (including asynchronous ones).  For example usage see
// autoreplace.js.
//     WikiEditor.addSubmitHook(function(editor, button) { ... });

// quarl 2006-01-23 initial version

var $wikiedit = new Module('wikiedit.js');
$wikiedit.depend('util.js', 'wikipage.js');
// recommends: smartsubmit.js

// WikiEditor class
//
// A WikiEditor doubles as an associative array of the edit properties -
// i.e. editor.wpTextbox1 contains the edit text.
var WikiEditor = $wikiedit.WikiEditor = function (wd) {
    if (!(this instanceof WikiEditor)) return new WikiEditor(wd);
    window.wikiEditor = this;

    if (!(wd instanceof WikiDocument)) { return $wikiedit.error("WikiEditor: need a WikiDocument (error 22a8bb3a-80e9-4df4-87c1-8c100b6aebbf)"); }
    this.wd = wd;
    this.wp = wd.wp;
    this.form = WikiEditor.getEditForm(wd.doc);
    if (!this.form) { return $wikiedit.error("WikiEditor: no form! (error d49dfc77-752c-4064-a5c8-503b635fdc7b)"); }

    // The HTML default maxlength is 200, but the MediaWiki server actually
    // accepts up to 250 chars!
    this.form.wpSummary.setAttribute('maxlength', 250);

    this.refuseCreate = function() {
        if (!this.wpTextbox1) {
            alert("Error!  Page is empty; refusing to create.");
            return true;
        } else {
            return false;
        }
    }

    this.getFormParams = function(button) {
        button = WikiEditor._checkButton(button);
        d = {};
        WikiEditor.updateFields(d, this, WikiEditor.wpFormFields);
        d[button] = this.form[button];
        return d;
    }

    this.updateThis = function() {
        WikiEditor.updateFields(this, this.form, WikiEditor.wpFormFields);
    }

    this.updateForm = function() {
        WikiEditor.updateFields(this.form, this, WikiEditor.wpFormFields);
    }

    // Direct submission, should only be used when the form is part of the
    // currently-viewed HTML page.  Navigates to result page.
    this.submitDirect = function(button) {
        button = WikiEditor._checkButton(button);
        this.updateForm();
        // Click the appropriate button.
        // Note that this generates an onClick event, which in turn calls the
        // runPreSubmitHooks function.
        this.form[button].click();
    }

    // Adds a hidden form to the current page and submits it, navigating to
    // the result page.
    this.submitHidden = function(button) {
        button = WikiEditor._checkButton(button);
        this.runPreSubmitHooks(this, button);
        var newform = document.createElement('form');
        $util.addFormHiddenParams(newform, this.getFormParams(button));
        newform.name = this.form.name;
        newform.method = this.form.method;
        newform.id = this.form.id;
        newform.action = this.form.action;
        document.getElementById('bodyContent').appendChild(newform);
        newform.submit();
    }

    // Asynchronously submit the form and call CALLBACK.
    this.submitAsync = function(button, callback) {
        button = WikiEditor._checkButton(button);
        var cb;
        if (callback) {
            var thisE = this;
            var args = $util.copyArray(arguments);
            args.shift(); args[0] = null;
            cb = function(req) { args[0] = req; callback.apply(thisE, args); };
        } else {
            cb = function(req) { /* dummy */ };
        }
        var data = this.getFormParams(button);
        this.runPreSubmitHooks(data, button);
        $util.asyncPostXML(this.form.action, data, cb);
    }

    // copy input fields from form into this object for easy access (we'll copy back later)
    this.updateThis();
    this.wpTextbox1_orig = this.form.wpTextbox1_orig;

    // If this form is the current document's form, we can submit directly.
    // Else we must use the hidden submit method.
    if (this.form == document.editform) {
        this.submit = this.submitDirect;
    } else {
        this.submit = this.submitHidden;
    }

    return this;
}

WikiEditor.getEditForm = function(doc) {
    if (!doc) doc = document;
    // Note: can't use "doc.editform", because 'doc' might actually be an XMLDocument (not HTMLDocument), if this is the result of an XMLHTTPRequest.
    return doc.getElementById('editform');
}

WikiEditor.wpFormFields = $util.assocArray( [
    'wpSection', 'wpStarttime', 'wpEdittime', 'wpScrolltop',
    'wpTextbox1', 'wpSummary', 'wpMinoredit', 'wpWatchthis',
    'wpEditToken' ] );
WikiEditor.wpButtons = $util.assocArray( [ 'wpSave', 'wpPreview', 'wpDiff' ] );

WikiEditor._checkButton = function(button) {
    if (!button) return 'wpSave';                   // default
    if (typeof button != 'string' || WikiEditor.wpButtons[button] != 1) {
        alert("## WikiEditor._checkButton: invalid button '"+button+"' (error 1a0655e7-ac83-4f15-8447-694b16a834ed)");
        return 'wpPreview';
    }
    return button;
}

WikiEditor.updateFields = function(target, source, fields) {
    var targetFormP = Boolean(target.nodeName);
    var sourceFormP = Boolean(source.nodeName);
    for (var i in fields) {
        var f = fields[i];
        var v;
        if (sourceFormP && source[f]) {
            if (source[f].type == "checkbox") {
                v = source[f].checked;
            } else {
                v = source[f].value;
            }
        } else {
            v = source[f];
        }
        if (targetFormP) {
            if (target[f].type == "checkbox") {
                target[f].checked = v;
            } else {
                // don't set it if unchanged, to avoid focus/selection change
                if (target[f].value != v) target[f].value = v;
            }
        } else {
            target[f] = v;
        }
    }
}

// Get an editor for this WikiPage -- it needs to have an editDoc already (as
// an editing window.wikiPage would have); else need to use getEditorAsync().
// Usually it's easier to just always use getEditorAsync.
WikiPage.prototype.getEditor = function() {
    if (!this.editor) {
        if (!this.editDoc) {
            return $wikiedit.error("WikiPage.getEditor: no editDoc - use getEditorAsync (error 9f4a2c95-b3e0-437e-972d-88290a5bd7ee)");
        }
        this.editor = WikiEditor(this.editDoc);
    }
    return this.editor;
}

// If already editing the target page, return a WikiEditor now.
// Else, download the edit form first.
// Call-back with new WikiEditor instance.
WikiPage.prototype.getEditorAsync = function(callback) {
    var wp = this;
    var args = $util.copyArray(arguments); // copy arguments because we need it in 'cb' below

    // already cached
    if (wp.editor) {
        args[0] = wp.editor;
        callback.apply(wp.editor, args); return;
    }

    // do we already have an edit document?  (window.wikiPage.editDoc would be
    // initialized to 'WikiDocument(document)' as appropriate).
    if (wp.editDoc) {
        wp.editor = WikiEditor(wp.editDoc);
        args[0] = wp.editor;
        callback.apply(wp.editor, args); return;
    }

    // need to download a new edit document.
    var cb = function(req) {
        if (req.status != 200) {
            alert("getEditorAsync: Error downloading edit page!");
            return;
        }

        wp.setDoc(req.responseXML);
        wp.editor = WikiEditor(wp.editDoc);
        args[0] = wp.editor;
        callback.apply(wp.editor, args); return;
    };
    $util.asyncDownloadXML(wp.qurl + '&action=edit', cb);
}

WikiEditor.pre_submit_hooks = [];

// add a submit hook to all forms (including asynchronous ones).
// Submit hooks are called with arguments (editor, form, button)
//   Note that the form argument may not be the same as editor.form or
//   document.form, if the submit is via submitHidden or submitAsync!
WikiEditor.addPreSubmitHook = function(func) {
    WikiEditor.pre_submit_hooks.push(func);
}

WikiEditor.prototype.runPreSubmitHooks = function(data, button) {
    // 'data' should be a hash array and could be either a WikiEditor
    // instance, or a separate object
    for (var i in WikiEditor.pre_submit_hooks) {
        WikiEditor.pre_submit_hooks[i](this, data, button);
    }
}

WikiEditor.onClickSubmitHook = function(button) {
    var editor = wikiPage.getEditor();
    if (editor.form[button].preventPreSumitHook) return;
    editor.updateThis();
    editor.runPreSubmitHooks(editor, button);
    // submit hooks may have changed data.
    editor.updateForm();
}

document.write('<form style="display: none;" id="wikiedit_sessionform">'+
               '<textarea id="wikiedit_wpTextbox1_orig"></textarea></form>');

WikiEditor.load = function() {
    if (document.forms.editform) {
        // save original version of edit box.
        // write this to a hidden field (in a separate form) so that we
        // don't lose this data on forward/back.
        var w = document.getElementById('wikiedit_wpTextbox1_orig');
        if (w.value) {
            document.forms.editform.wpTextbox1_orig = w.value;
        } else {
            document.forms.editform.wpTextbox1_orig = w.value = document.forms.editform.wpTextbox1.value;
        }

        // $util.hookEventObj(document.forms.editform, 'submit', WikiEditor.onClickSubmitHook);

        // add submit hooks
        $util.hookEventObj(document.editform.wpSave, 'click', function() { WikiEditor.onClickSubmitHook('wpSave'); });
        $util.hookEventObj(document.editform.wpDiff, 'click', function() { WikiEditor.onClickSubmitHook('wpDiff'); });
        $util.hookEventObj(document.editform.wpPreview, 'click', function() { WikiEditor.onClickSubmitHook('wpPreview'); });
    }
}

$util.addOnloadHook(WikiEditor.load);


/****************************************************************************
 * END wikiedit.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikistate.js
 ****************************************************************************/

// $Id: wikistate.js 371 2006-02-14 08:40:57Z quarl $

// wikistate.js - utilities to keep track of session state

// Session state persists THROUGHOUT THIS SESSION, on THIS PAGE ONLY

// * Currently works with back/forward/refresh, but not with navigating anew.

// Usage:
//     wikistate.set('foo', 123);
//     alert("foo = " + wikistate.get('foo'));

// quarl 2006-02-05 initial version

var $wikistate = new Module('wikistate.js');

document.write('<form style="display: none;" id="wikiState_sessionform">'+
               '<textarea id="wikiState_data"></textarea></form>');

$wikistate._load = function() {
    $wikistate._dataNode = document.getElementById('wikiState_data');
    $wikistate._dataString = $wikistate._dataNode.value || '';
    $wikistate._data = $wikistate._deserialize($wikistate._dataString) || {};
}

$wikistate.save = function() {
    $wikistate._dataString = $wikistate._serialize($wikistate._data);
    $wikistate._dataNode.value = $wikistate._dataString;
}

$wikistate.get = function(varname) {
    return $wikistate._data[varname];
}

$wikistate.set = function(varname, value, nosave) {
    // if (!(value instanceof String)) {
    //     alert("wikistate error: must be a string (error 5dc01aeb-2fc1-44ca-a0bd-359ec9210f46)");
    //     return;
    // }
    $wikistate._data[varname] = value;
    if (!nosave) $wikistate.save();
}

$wikistate._serialize = function(dict) {
    return dict.toSource();
    // var r = '';
    // for (var v in dict) {
    //     var value = dict[r];
    //     if (!(value instanceof String)) continue;
    //     r += value+':'+$util.stringQuoteEscape(value);
    // }
}

$wikistate._deserialize = function(s) {
    // since only we can write to the form data, should be safe to evaluate
    // it.
    return eval(s);
}

$util.addOnloadHook($wikistate._load);


/****************************************************************************
 * END wikistate.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autoreplace.js
 ****************************************************************************/

// $Id: autoreplace.js 305 2006-02-14 06:35:23Z quarl $

// autoreplace.js - provides capability to replace strings (regular
// expressions) in edit boxes on submission.
//    Strings between <nowiki> ... </nowiki> and comments are not replaced.

// external entry points:
//    $autoreplace.addReplacement("string", "replacement" || replacement_function);

// quarl 2006-01-04 initial version

var $autoreplace = new Module('autoreplace.js');
$autoreplace.depend('wikipage.js', 'wikiedit.js', 'util.js');
// recommends: smartsubmit.js

$autoreplace.enabled = true;
$autoreplace.replacements = Array();

// add a replacement
$autoreplace.addReplacement = function(str, replacement) {
    $autoreplace.replacements.push([str,replacement]);
}

$autoreplace.fToStr = function(t) {
    if (typeof t == 'function') t = t();
    return ""+t;
}

$autoreplace.replaceString = function(text, str, replacement) {
    return text.replace(new RegExp(str,'g'), replacement);
}

$autoreplace.replaceStringNoNowiki = function(text, str, replacement) {
    var rtext = '';
    while ( text.match(/<nowiki>(?:.|\n)*?<\/nowiki>|<!--(?:.|\n)*?-->/) ) {
        // save these before they get clobbered!
        var left = RegExp.leftContext;
        var match = RegExp.lastMatch;
        var right = RegExp.rightContext;

        rtext += $autoreplace.replaceString(left, str, replacement) + match;
        text = right;
    }
    rtext += $autoreplace.replaceString(text, str, replacement)
    return rtext;
}

$autoreplace.replaceStrings = function(text) {
    if (!text) return text;
    for (i in $autoreplace.replacements) {
        r = $autoreplace.replacements[i];
        text = $autoreplace.replaceStringNoNowiki(text, r[0], $autoreplace.fToStr(r[1]));
    }
    return text;
}

$autoreplace.preSubmitHook = function(editor, data, button) {
    if (!$autoreplace.enabled) return;

    if (!data.wpTextbox1) return;
    data.wpTextbox1 = $autoreplace.replaceStrings(data.wpTextbox1);
}

$autoreplace.load = function() {
    WikiEditor.addPreSubmitHook($autoreplace.preSubmitHook);
}

$util.addOnloadHook($autoreplace.load);


/****************************************************************************
 * END autoreplace.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikiparse.js
 ****************************************************************************/

// $Id: wikiparse.js 1221 2006-02-24 10:30:51Z quarl $

// functions for parsing wikitext

// quarl 2006-02-22 initial version - refactoring from popups.js

var $wikiparse = new Module('wikiparse.js');
$wikiparse.depend('wikipage.js', 'wikins.js');

// this stuff should be customized for different languages

$wikiparse.stubRegex= RegExp('stub[}][}]|This .*-related article is a .*stub', 'im') ;
$wikiparse.disambigRegex=RegExp('([{][{]\\s*disambig|disambig\\s*[}][}]|dab\\s*[}][}])' +
                                '|[{][{]\\s*(geodis)\\s*[}][}]' + // explicit, whole template names on this line
                                '|is a .*disambiguation.*page', 'im') ;

// end of language-specific things

//                      (^|\[\[)image: *([^|\]]*[^|\] ]) *
//                      (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
//                                                        $4 = 120 as in 120px

$wikiparse.imageRegex= RegExp('(^|\\[\\[)'+
                       $wikins.Image +
                       ': *([^|\\]]*[^|\\] ])([^0-9\\]]*([0-9]+) *px)?',
                       'img') ;
$wikiparse.imageRegexBracketCount = 4;

$wikiparse.categoryRegex= RegExp('\\[\\['+$wikins.Category+
                          ': *([^|\\]]*[^|\\] ]) *', 'i') ;
$wikiparse.categoryRegexBracketCount = 1;

// this could be improved!
$wikiparse.countLinks = function(wikiText) {
    return wikiText.split('[[').length - 1;
};

// if N = # matches, n = # brackets, then
// String.parenSplit(regex) intersperses the N+1 split elements
// with Nn other elements. So total length is
// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).

$wikiparse.countImages = function(wikiText) {
    return (wikiText.parenSplit($wikiparse.imageRegex).length - 1) / ($wikiparse.imageRegexBracketCount + 1);
};

$wikiparse.countCategories = function(wikiText) {
    return (wikiText.parenSplit($wikiparse.categoryRegex).length - 1) / ($wikiparse.categoryRegexBracketCount + 1);
};

$wikiparse.isStub = function(data) {
    return $wikiparse.stubRegex.test(data);
};

$wikiparse.isDisambig = function(wp, data) {
    return wp.editableP && $wikiparse.disambigRegex.test(data);
};

// returns a WikiPage for the first image found in wikitext (with width at
// least minWidth)
$wikiparse.getValidImageFromWikiText = function(wikiText, minWidth) {
    // In imageRegex, we're interested in the second bracketed expression
    // this may change if the regex changes :-(
    $wikiparse.imageRegex.lastIndex=0;
    var match;
    while ( (match = $wikiparse.imageRegex.exec(wikiText)) ) {
        /* now find a sane image name - exclude templates by seeking { */
        var m = match[2];
        var pxWidth=match[4];
        if ( $wikiimg.isValidImageName(m) && (!pxWidth || parseInt(pxWidth) >= minWidth) ) {
            return WikiPage(null, $wikins.Image+':'+$util.capitalizeFirstChar(m) );
        }
    }
    return null;
};

// returns target of redirect (as string), or null
$wikiparse.isRedirect = function(wikiText)
{
    var m = wikiText.match(/^[ \n]*[#]redirect[:\s]*\[\[([^\|\]]*)(|[^\]]*)?\]\]\s*(.*)/i);
    return m && m[1];
};

// returns a sorted array of links which aren't special or interwiki
$wikiparse.getLinks = function(wikitext)
{
    var reg = RegExp('\\[\\[([^|]*?)(\\||\\]\\])', 'gi');
    var ret = [];
    var splitted = wikitext.parenSplit(reg);
    var seen = {};
    for (var i=1; i<splitted.length; i=i+3) {
        var link = splitted[i];
        if (!link) continue;
        if (seen[link]) continue;
        seen[link] = 1;
        ret.push(link);
    }

    ret.sort();
    return ret;
}

/****************************************************************************
 * END wikiparse.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN diff.js
 ****************************************************************************/

// $Id: diff.js 937 2006-02-22 06:30:01Z quarl $

// diff.js - utility functions for doing diffs

// quarl 2006-01-29 initial version

/*
 * $diff() is based on http://ejohn.org/projects/javascript-diff-algorithm/
 *   Copyright John Resig
 */

var $diff = new Module('diff.js');
$diff.depend('util.js');

$diff.diff = function(o, n) {
    var ns = {};
    var os = {};

    for ( var i = 0; i < n.length; i++ ) {
        // note we have to check that it is in fact an object with "rows", in
        // case ns[i] happens to match a javascript member function of class
        // Array, e.g. "some"!
        if ( ns[ n[i] ] == null || !ns[n[i]].rows )
            ns[ n[i] ] = { rows: new Array(), o: null };
        ns[ n[i] ].rows.push( i );
    }

    for ( var i = 0; i < o.length; i++ ) {
        if ( os[ o[i] ] == null || !os[o[i]].rows )
            os[ o[i] ] = { rows: new Array(), n: null };
        os[ o[i] ].rows.push( i );
    }

    for ( var i in ns ) {
        if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {
            n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
            o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
        }
    }

    for ( var i = 0; i < n.length - 1; i++ ) {
        if ( n[i].text != null && n[i+1].text == null &&
             0 <= n[i].row+1 && n[i].row+1 < o.length &&
             o[ n[i].row + 1 ].text == null &&
             n[i+1] == o[ n[i].row + 1 ] )
        {
            n[i+1] = { text: n[i+1], row: n[i].row + 1 };
            o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
        }
    }

    for ( var i = n.length - 1; i > 0; i-- ) {
        if ( n[i].text != null && n[i-1].text == null &&
             0 <= n[i].row-1 && n[i].row-1 < o.length &&
             o[ n[i].row - 1 ].text == null &&
             n[i-1] == o[ n[i].row - 1 ] )
        {
            n[i-1] = { text: n[i-1], row: n[i].row - 1 };
            o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
        }
    }

    return { o: o, n: n };
}

// if more than this many words of changes, use overflow string
$diff.wikisummary_maxwords = 30;
$diff.wikisummary_overflow = "$1 words changed";

$diff._split = function(s) {
    //return $util.trimSpaces(s).split(/(?:\s|[.,;\'\"`])+/);
    return $util.trimSpaces(s).split(/\s+/);
}

$diff.aggregate = function(words) {
    var phrases = new Array();
    var cur = null;
    var wordcount = 0;

    // start at virtual index -1 to check for removed words at beginning of
    // text
    for ( var i = -1; i < words.n.length; i++ ) {
        if ( i!=-1 && words.n[i].text == null ) {
            if (!cur) {
                cur = { o: "", n: "" };
                phrases.push(cur);
            }
            cur.n += " " + words.n[i];
            wordcount ++;
        } else {
            var pre = "";
            var j = i==-1 ? 0 : words.n[i].row + 1;
            while ( j < words.o.length && words.o[j].text == null ) {
                pre += " " + words.o[j];
                j++;
                wordcount ++;
            }
            if (pre) {
                if (!cur) {
                    cur = { o: "", n: "" };
                    phrases.push(cur);
                }
                cur.o += pre;
            }
            if (pre && words.n[i+1] && words.n[i+1].text == null) {
                // If there's an addition following, treat this as part of the
                // same change.
            } else {
                cur = null;
            }
        }
    }

    for (var i in phrases) {
        phrases[i].n = $util.trimSpaces(phrases[i].n);
        phrases[i].o = $util.trimSpaces(phrases[i].o);
    }

    return { phrases: phrases, wordcount: wordcount };
}

$diff.wikiQuote = function(s) {
    if (!s) return s;
    if (s.match(/^\{\{.*\}\}$/)) return s;
    s = s.replace(/\"/g, "'");
    return '"'+s+'"';
}

// trim the equal chars from the front and back of o,n at a word boundary
$diff.stringTrim = function(o, n) {
    var r = $diff.stringTrim0($util.reverseString(o), $util.reverseString(n));
    return $diff.stringTrim0($util.reverseString(r.o), $util.reverseString(r.n));
}

$diff.stringTrim0 = function(o, n) {
    var i = 0;
    while (i < o.length && i < n.length && o[i] == n[i]) {
        ++i;
    }

    // find index of last non-word character
    var prefix = o.substr(0, i);

    // if prefix ends with word characters and suffix starts with non-word,
    // then erase entire prefix
    if (prefix.match(/\w$/) &&
        !o.substr(i, 1).match(/^\w/) && !n.substr(i, 1).match(/^\w/))
    {
        o = o.substr(i);
        n = n.substr(i);
    } else if (prefix.match(/.*\W/)) {
        i = RegExp.lastMatch.length;
        o = o.substr(i);
        n = n.substr(i);
    } else {
        // keep entire prefix
    }
    return { o: o, n: n };
}

$diff.diffSummary = function(o, n) {
    if (o == n) return "";
    if (!o) {
        return "new (" + $util.wordCount(n) + " words)";
    }
    if (!n) {
        return "blank (" + $util.wordCount(o) + " words)";
    }
    var words = $diff.diff( $diff._split(o), $diff._split(n) );
    var r = $diff.aggregate(words);
    if (!r.wordcount) return "";
    if (r.wordcount > $diff.wikisummary_maxwords) {
        return $diff.wikisummary_overflow.replace('$1', r.wordcount);
    }

    var phrases = r.phrases;
    var str = [];
    for (var i in phrases) {
        var r = $diff.stringTrim(phrases[i].o, phrases[i].n);
        var ro = $diff.wikiQuote(r.o), rn = $diff.wikiQuote(r.n);
        if (ro && rn) {
            str.push(ro + ' → ' + rn);
        } else if (ro) {
            str.push('-' + ro);
        } else if (rn) {
            str.push('+' + rn);
        } else {
            // alert("## internal error 15e1b13f-bae3-4399-86c5-721786822fa2");
        }
    }

    return str.join(", ");
}


/****************************************************************************
 * END diff.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN kookie.js
 ****************************************************************************/

// $Id: kookie.js 1184 2006-02-23 22:15:03Z quarl $

// kookie.js - cookie functions

// use a kooky name to avoid name clashes since "cookie" is so common
var $kookie = new Module('kookie.js');

$kookie.get = function(name)
{
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++)
    {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

$kookie.set = function(name, value, days, path)
{
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";

    // use path="" for no path; path=null defaults to root
    if (path == null) path = "/";
    if (path) path = "; path="+path;
    else path = "";

    var newcookie = name + "=" + value + expires + path;
    $kookie.debug("set(): " + newcookie);
    document.cookie = newcookie;
}

$kookie.erase = function(name)
{
    $kookie.set(name,"",-1);
}


/****************************************************************************
 * END kookie.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN md5.js
 ****************************************************************************/

// $Id: md5.js 1186 2006-02-23 22:22:33Z quarl $

// md5.js - md5 digest code

// SYNOPSIS: alert($md5.hex_md5("foo bar"))

// A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
// Digest Algorithm, as defined in RFC 1321.
// Version 2.2-alpha Copyright (C) Paul Johnston 1999 - 2005
// Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
// Distributed under the BSD License
// See http://pajhome.org.uk/crypt/md5 for more info.

// quarl 2006-02-12 refactored OO
// quarl 2006-02-21 padding fixes; b64u functions

var $md5 = new Module('md5.js');

// Configurable variables. You may need to tweak these to be compatible
// with the server-side, but the defaults work in most cases.
$md5.options = {
    hexcase: 0,             // hex output format. 0 - lowercase; 1 - uppercase
    b64pad : "="       // base-64 pad character. "=" for strict RFC compliance
};

// These are the functions you'll usually want to call.  They take string
// arguments and return either hex or base-64 encoded strings

$md5.hex_md5 = function (s)
{ return $md5.rstr2hex($md5.rstr_md5($md5.str2rstr_utf8(s))); };

$md5.b64_md5  = function (s)
{ return $md5.rstr2b64($md5.rstr_md5($md5.str2rstr_utf8(s))); };

$md5.b64u_md5  = function (s)
{ return $md5.rstr2b64u($md5.rstr_md5($md5.str2rstr_utf8(s))); };

$md5.any_md5  = function (s, e)
{ return $md5.rstr2any($md5.rstr_md5($md5.str2rstr_utf8(s)), e); };

$md5.hex_hmac_md5  = function(k, d)
{ return $md5.rstr2hex($md5.rstr_hmac_md5($md5.str2rstr_utf8(k), $md5.str2rstr_utf8(d))); };

$md5.b64_hmac_md5  = function(k, d)
{ return $md5.rstr2b64($md5.rstr_hmac_md5($md5.str2rstr_utf8(k), $md5.str2rstr_utf8(d))); };

$md5.b64u_hmac_md5  = function(k, d)
{ return $md5.rstr2b64u($md5.rstr_hmac_md5($md5.str2rstr_utf8(k), $md5.str2rstr_utf8(d))); };

$md5.any_hmac_md5  = function(k, d, e)
{ return $md5.rstr2any($md5.rstr_hmac_md5($md5.str2rstr_utf8(k), $md5.str2rstr_utf8(d)), e); };

// Perform a simple self-test to see if the VM is working
$md5.selftest  = function()
{ return $md5.hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; };

// Calculate the MD5 of a raw string
$md5.rstr_md5  = function(s)
{
    return $md5.binl2rstr($md5.binl_md5($md5.rstr2binl(s), s.length * 8));
};

// Calculate the HMAC-MD5, of a key and some data (raw strings)
$md5.rstr_hmac_md5  = function(key, data)
{
    var bkey = $md5.rstr2binl(key);
    if (bkey.length > 16) {
        bkey = $md5.binl_md5(bkey, key.length * 8);
    }

    // pad zeros
    for (var i = bkey.length; i < 16; ++i) {
        bkey[i] = 0;
    }

    var ipad = Array(16), opad = Array(16);
    for(var i = 0; i < 16; i++)
    {
        ipad[i] = bkey[i] ^ 0x36363636;
        opad[i] = bkey[i] ^ 0x5C5C5C5C;
    }

    var hash = $md5.binl_md5(ipad.concat($md5.rstr2binl(data)), 512 + data.length * 8);
    return $md5.binl2rstr($md5.binl_md5(opad.concat(hash), 512 + 128));
};

// Convert a raw string to a hex string
$md5.rstr2hex  = function(input)
{
    var hex_tab = $md5.options.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
    var output = "";
    var x;
    for(var i = 0; i < input.length; i++)
    {
        x = input.charCodeAt(i);
        output += (   hex_tab.charAt((x >>> 4) & 0x0F)
                      +  hex_tab.charAt( x        & 0x0F));
    }
    return output;
};

// Convert a raw string to a base-64 string
$md5.rstr2b64  = function(input)
{
    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var output = "";
    var len = input.length;
    for(var i = 0; i < len; i += 3)
    {
        var triplet = (input.charCodeAt(i) << 16)
            | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
            | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
        for(var j = 0; j < 4; j++)
        {
            if(i * 8 + j * 6 > input.length * 8) output += $md5.options.b64pad;
            else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
        }
    }
    return output;
};

// Convert a raw string to a URL-friendly base-64 string
//
// Uses '*' and '-' as the 63rd and 64th characters, since '/' and '+' have
// special meaning in URLs and would need ugly encoding/decoding.  Does not
// pad with '='.
$md5.rstr2b64u  = function(input)
{
    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-";
    var output = "";
    var len = input.length;
    for(var i = 0; i < len; i += 3)
    {
        var triplet = (input.charCodeAt(i) << 16)
            | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
            | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
        for(var j = 0; j < 4; j++)
        {
            if(i * 8 + j * 6 > input.length * 8) break;
            output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
        }
    }
    return output;
};

// Convert a raw string to an arbitrary string encoding
$md5.rstr2any  = function(input, encoding)
{
    var divisor = encoding.length;
    var remainders = Array();
    var i, q, x, quotient;

    /* Convert to an array of 16-bit big-endian values, forming the dividend */
    var dividend = Array(input.length / 2);
    for(i = 0; i < dividend.length; i++)
    {
        dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
    }

    /*
     * Repeatedly perform a long division. The binary array forms the dividend,
     * the length of the encoding is the divisor. Once computed, the quotient
     * forms the dividend for the next step. We stop when the dividend is zero.
     * All remainders are stored for later use.
     */
    while(dividend.length > 0)
    {
        quotient = Array();
        x = 0;
        for(i = 0; i < dividend.length; i++)
        {
            x = (x << 16) + dividend[i];
            q = Math.floor(x / divisor);
            x -= q * divisor;
            if(quotient.length > 0 || q > 0)
                quotient[quotient.length] = q;
        }
        remainders[remainders.length] = x;
        dividend = quotient;
    }

    /* Convert the remainders to the output string */
    var output = "";
    for(i = remainders.length - 1; i >= 0; i--)
        output += encoding.charAt(remainders[i]);

    return output;
};

// Encode a string as utf-8.
// For efficiency, this assumes the input is valid utf-16.
$md5.str2rstr_utf8  = function(input)
{
    var output = "";
    var i = -1;
    var x, y;

    while(++i < input.length)
    {
        /* Decode utf-16 surrogate pairs */
        x = input.charCodeAt(i);
        y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
        if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
        {
            x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
            i++;
        }

        /* Encode output as utf-8 */
        if(x <= 0x7F)
            output += String.fromCharCode(x);
        else if(x <= 0x7FF)
            output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
                                          0x80 | ( x         & 0x3F));
        else if(x <= 0xFFFF)
            output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
                                          0x80 | ((x >>> 6 ) & 0x3F),
                                          0x80 | ( x         & 0x3F));
        else if(x <= 0x1FFFFF)
            output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
                                          0x80 | ((x >>> 12) & 0x3F),
                                          0x80 | ((x >>> 6 ) & 0x3F),
                                          0x80 | ( x         & 0x3F));
    }
    return output;
};

// Encode a string as utf-16
$md5.str2rstr_utf16le  = function(input)
{
    var output = "";
    for(var i = 0; i < input.length; i++)
        output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
                                       (input.charCodeAt(i) >>> 8) & 0xFF);
    return output;
};

$md5.str2rstr_utf16be  = function(input)
{
    var output = "";
    for(var i = 0; i < input.length; i++)
        output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
                                      input.charCodeAt(i)        & 0xFF);
    return output;
};

// Convert a raw string to an array of little-endian words
// Characters >255 have their high-byte silently ignored.
$md5.rstr2binl  = function(input)
{
    var output = Array((input.length+3) >> 2);
    for(var i = 0; i < output.length; i++)
        output[i] = 0;
    for(var i = 0; i < input.length * 8; i += 8)
        output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
    return output;
};

// Convert an array of little-endian words to a string
$md5.binl2rstr  = function(input)
{
    var output = "";
    for(var i = 0; i < input.length * 32; i += 8)
        output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
    return output;
};

// Calculate the MD5 of an array of little-endian words, and a bit length.
$md5.binl_md5  = function(x, len)
{
    /* append padding */
    var padupto = (((len + 64) >>> 9) << 4) + 14;   // 448 bits
    // write zeros so that we don't get 'undefined' later (otherwise we get a
    // bajillion javascript warnings!)
    for (var i = x.length; i < padupto; ++i) {
        x[i] = 0;
    }
    // append the bit "1" to end of message
    x[len >> 5] |= 0x80 << ((len) % 32);
    // add length as a 64-bit little-endian integer
    x[padupto] = len;
    x[padupto+1] = 0;

    var a =  1732584193;
    var b = -271733879;
    var c = -1732584194;
    var d =  271733878;

    for(var i = 0; i < x.length; i += 16)
    {
        var olda = a;
        var oldb = b;
        var oldc = c;
        var oldd = d;

        a = $md5._ff(a, b, c, d, x[i+ 0], 7 , -680876936);
        d = $md5._ff(d, a, b, c, x[i+ 1], 12, -389564586);
        c = $md5._ff(c, d, a, b, x[i+ 2], 17,  606105819);
        b = $md5._ff(b, c, d, a, x[i+ 3], 22, -1044525330);
        a = $md5._ff(a, b, c, d, x[i+ 4], 7 , -176418897);
        d = $md5._ff(d, a, b, c, x[i+ 5], 12,  1200080426);
        c = $md5._ff(c, d, a, b, x[i+ 6], 17, -1473231341);
        b = $md5._ff(b, c, d, a, x[i+ 7], 22, -45705983);
        a = $md5._ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
        d = $md5._ff(d, a, b, c, x[i+ 9], 12, -1958414417);
        c = $md5._ff(c, d, a, b, x[i+10], 17, -42063);
        b = $md5._ff(b, c, d, a, x[i+11], 22, -1990404162);
        a = $md5._ff(a, b, c, d, x[i+12], 7 ,  1804603682);
        d = $md5._ff(d, a, b, c, x[i+13], 12, -40341101);
        c = $md5._ff(c, d, a, b, x[i+14], 17, -1502002290);
        b = $md5._ff(b, c, d, a, x[i+15], 22,  1236535329);

        a = $md5._gg(a, b, c, d, x[i+ 1], 5 , -165796510);
        d = $md5._gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
        c = $md5._gg(c, d, a, b, x[i+11], 14,  643717713);
        b = $md5._gg(b, c, d, a, x[i+ 0], 20, -373897302);
        a = $md5._gg(a, b, c, d, x[i+ 5], 5 , -701558691);
        d = $md5._gg(d, a, b, c, x[i+10], 9 ,  38016083);
        c = $md5._gg(c, d, a, b, x[i+15], 14, -660478335);
        b = $md5._gg(b, c, d, a, x[i+ 4], 20, -405537848);
        a = $md5._gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
        d = $md5._gg(d, a, b, c, x[i+14], 9 , -1019803690);
        c = $md5._gg(c, d, a, b, x[i+ 3], 14, -187363961);
        b = $md5._gg(b, c, d, a, x[i+ 8], 20,  1163531501);
        a = $md5._gg(a, b, c, d, x[i+13], 5 , -1444681467);
        d = $md5._gg(d, a, b, c, x[i+ 2], 9 , -51403784);
        c = $md5._gg(c, d, a, b, x[i+ 7], 14,  1735328473);
        b = $md5._gg(b, c, d, a, x[i+12], 20, -1926607734);

        a = $md5._hh(a, b, c, d, x[i+ 5], 4 , -378558);
        d = $md5._hh(d, a, b, c, x[i+ 8], 11, -2022574463);
        c = $md5._hh(c, d, a, b, x[i+11], 16,  1839030562);
        b = $md5._hh(b, c, d, a, x[i+14], 23, -35309556);
        a = $md5._hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
        d = $md5._hh(d, a, b, c, x[i+ 4], 11,  1272893353);
        c = $md5._hh(c, d, a, b, x[i+ 7], 16, -155497632);
        b = $md5._hh(b, c, d, a, x[i+10], 23, -1094730640);
        a = $md5._hh(a, b, c, d, x[i+13], 4 ,  681279174);
        d = $md5._hh(d, a, b, c, x[i+ 0], 11, -358537222);
        c = $md5._hh(c, d, a, b, x[i+ 3], 16, -722521979);
        b = $md5._hh(b, c, d, a, x[i+ 6], 23,  76029189);
        a = $md5._hh(a, b, c, d, x[i+ 9], 4 , -640364487);
        d = $md5._hh(d, a, b, c, x[i+12], 11, -421815835);
        c = $md5._hh(c, d, a, b, x[i+15], 16,  530742520);
        b = $md5._hh(b, c, d, a, x[i+ 2], 23, -995338651);

        a = $md5._ii(a, b, c, d, x[i+ 0], 6 , -198630844);
        d = $md5._ii(d, a, b, c, x[i+ 7], 10,  1126891415);
        c = $md5._ii(c, d, a, b, x[i+14], 15, -1416354905);
        b = $md5._ii(b, c, d, a, x[i+ 5], 21, -57434055);
        a = $md5._ii(a, b, c, d, x[i+12], 6 ,  1700485571);
        d = $md5._ii(d, a, b, c, x[i+ 3], 10, -1894986606);
        c = $md5._ii(c, d, a, b, x[i+10], 15, -1051523);
        b = $md5._ii(b, c, d, a, x[i+ 1], 21, -2054922799);
        a = $md5._ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
        d = $md5._ii(d, a, b, c, x[i+15], 10, -30611744);
        c = $md5._ii(c, d, a, b, x[i+ 6], 15, -1560198380);
        b = $md5._ii(b, c, d, a, x[i+13], 21,  1309151649);
        a = $md5._ii(a, b, c, d, x[i+ 4], 6 , -145523070);
        d = $md5._ii(d, a, b, c, x[i+11], 10, -1120210379);
        c = $md5._ii(c, d, a, b, x[i+ 2], 15,  718787259);
        b = $md5._ii(b, c, d, a, x[i+ 9], 21, -343485551);

        a = $md5._safe_add(a, olda);
        b = $md5._safe_add(b, oldb);
        c = $md5._safe_add(c, oldc);
        d = $md5._safe_add(d, oldd);
    }
    return Array(a, b, c, d);
};

// These functions implement the four basic operations the algorithm uses.
$md5._cmn  = function(q, a, b, x, s, t)
{
    return $md5._safe_add($md5._bit_rol($md5._safe_add($md5._safe_add(a, q), $md5._safe_add(x, t)), s),b);
};
$md5._ff  = function(a, b, c, d, x, s, t)
{
    return $md5._cmn((b & c) | ((~b) & d), a, b, x, s, t);
};
$md5._gg  = function(a, b, c, d, x, s, t)
{
    return $md5._cmn((b & d) | (c & (~d)), a, b, x, s, t);
};
$md5._hh  = function(a, b, c, d, x, s, t)
{
    return $md5._cmn(b ^ c ^ d, a, b, x, s, t);
};
$md5._ii  = function(a, b, c, d, x, s, t)
{
    return $md5._cmn(c ^ (b | (~d)), a, b, x, s, t);
};

// Add integers, wrapping at 2^32. This uses 16-bit operations internally
// to work around bugs in some JS interpreters.
$md5._safe_add  = function(x, y)
{
    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xFFFF);
};

// Bitwise rotate a 32-bit number to the left.
$md5._bit_rol  = function(num, cnt)
{
    return (num << cnt) | (num >>> (32 - cnt));
};

/****************************************************************************
 * END md5.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN token.js
 ****************************************************************************/

// $Id: token.js 969 2006-02-22 09:03:19Z quarl $

// $token.makeAuthToken - create an authentication token using HMAC

var $token = new Module('token.js');
$token.depend('md5.js', 'wikipage.js', 'kookie.js');

$token.getKey0 = function() {
    var l = [];
    if (WikiPage.dbname && document.cookie) {
        var v1 = $kookie.get(WikiPage.dbname+'Token');
        var v2 = $kookie.get(WikiPage.dbname+'_session');
        if (v1 && v2) return (v1 + '%' + v2 + '%' + wikiDoc.username);
    }

    // fail-safe - this is still secure; it's just more likely to mis-match a
    // valid token (if the cookie somehow changes by the next transaction)
    if (l.length == 0 && document.cookie) {
        return document.cookie + '%' + wikiDoc.username;
    }

    if (wikiDoc.username) {
        return wikiDoc.username;
    }

    return '';
}

$token.load = function() {
    $token.key = $token.getKey0();
}

$util.addOnloadHook($token.load);

$token.getKey = function() {
    return $token.key;
}

// Return an authentication token useful to pass through URL for automatic
// edits, to prevent XSS attacks.  Accepts arbitrary string arguments.
$token.makeAuthToken = function() {
    return $md5.b64u_hmac_md5(
        $token.getKey(), 'auth_token:'+Array.join(arguments, '%') + '%%%%%xxx');
}

/****************************************************************************
 * END token.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN shortcuts.js
 ****************************************************************************/

// $Id: shortcuts.js 938 2006-02-22 06:30:21Z quarl $

// shortcuts.js - shortcut utilities

// Defines the Shortcuts class.
//   Input is an associative array like this:
//
/*
   s = Shortcuts({
                    'x1'   : 'str 1',
                    'x2 '  : 'str 2', // the space after x2 prevents msg() from displaying this entry
                    'x3,x4': 'str 34' // x3 and x4 are aliases; msg() only displays x3
                });
*/

var $shortcuts = new Module('shortcuts.js');

var Shortcuts = $shortcuts.Shortcuts = function(input) {
    if (!(this instanceof Shortcuts)) return new Shortcuts(input);

    this.shortcuts = {};
    for (k in input) {
        var keys = k.toUpperCase().split(',');
        this.shortcuts[keys[0]] = input[k];
        for (var i=1; i < keys.length; ++i) {
            this.shortcuts[keys[i]] = input[k] + ' ';
        }
    }

    return this;
}

Shortcuts.prototype.msg = function() {
    var msg = 'Shortcuts available:\n';
    for (var key in this.shortcuts) {
        if (this.shortcuts[key].match(/ $/)) continue;
        msg += key + ': ' + this.shortcuts[key] + '\n';
    }
    return msg;
}

Shortcuts.prototype.subst = function(word) {
    return $util.trimSpaces(this.shortcuts[word.toUpperCase()]) || word;
}

Shortcuts.prototype.substP = function(word) {
    return Boolean(this.shortcuts[word.toUpperCase()]);
}

// replace the first word (doesn't require uppercase)
Shortcuts.prototype.substFirstWord = function(msg) {
    if (!msg) return msg;
    if (msg.match(/^([a-zA-Z]+)(.*)$/)) {
        return this.subst(RegExp.$1) + RegExp.$2;
    }
    return msg;
}

// replace all UPPERCASE words
Shortcuts.prototype.substUppercaseWords = function(msg) {
    if (!msg) return msg;
    var ret = '';
    var m;
    while (msg && (m = msg.match(/^(.*?)\b([A-Z]+)\b(.*)$/)) ) {
        ret += m[1] + this.subst(m[2]);
        msg = m[3];
    }

    ret += msg;
    return ret;
}

// replace all words (ignoring case)
Shortcuts.prototype.substWords = function(msg) {
    if (!msg) return msg;
    var ret = '';
    var m;
    while (msg && (m = msg.match(/^(.*?)\b([A-Za-z]+)\b(.*)$/)) ) {
        ret += m[1] + this.subst(m[2]);
        msg = m[3];
    }

    ret += msg;
    return ret;
}


/****************************************************************************
 * END shortcuts.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN diffsince.js
 ****************************************************************************/

// $Id: diffsince.js 1249 2006-02-25 06:25:32Z quarl $

// diffsince.js - utility functions for doing a "diff since I last edited"

// Synopsis:
//    url = diffsince.makeUrl(wikiPage);
//    href = '<a onclick="javascript:diffsince.diffThis()" href="'+url+'">diff since</a>';

// Or:
//    url = diffsince.makeUrl(wp);
//    href = '<a onclick="javascript:diffsince.diffPageAsync(wp)" href="'+url+'">diff since</a>';


// Navigating to the value of makeUrl will open a history page, parse it, and
// jump to the appropriate diff.

// The diffPageAsync() function asynchronously downloads the history page and
// then navigates to the diff.  It is appropriate as an onClick handler.

// The diffThis() function does the diff directly if already looking at a
// history page, else calls diffPageAsync(wikiPage).

// diffPageAsync and diffThis take status_node and status_text parameters for
// showing progress.

// quarl 2006-01-16 (asynchronous code originally written in show_diff_since.js)
// quarl 2006-02-03 factored as library

var $diffsince = new Module('diffsince.js');
$diffsince.depend('wikipage.js', 'util.js');

$diffsince.options = { history_limit: [200] };

// return a URL that points to a "fakeaction" page for doing a diff.
$diffsince.makeUrl = function(wp) {
    return wp.qurl + '&fakeaction=diffsince&action=history&limit='+$diffsince.options.history_limit[0];
}

$diffsince._load = function() {
    if (WikiDocument.queryVars.fakeaction == 'diffsince') {
        $diffsince._parseHistory(document);
    }
}

$diffsince.diffPageAsync = function(wp, statusNode, statusText) {
    var url = wp.qurl + '&action=history&limit='+$diffsince.options.history_limit[0];
    var req = new XMLHttpRequest();

    // change the button to show the user we're doing something and
    // prevent another click
    $util.buttonShowStatus(statusNode, statusText); // (statusNode, statusText are optional)

    $util.asyncDownloadXML(url, $diffsince._handleHistoryPage,
                           { statusNode: statusNode });
    return false;
}

$diffsince.diffThis = function(statusNode, statusText) {
    if (wikiDoc.historyP) {
        // already looking at a history page
        $diffsince._parseHistory(document);
    } else {
        // not yet a history page -- asynchronously download it first
        $diffsince.diffPageAsync(wikiPage, statusNode, statusText);
    }
    return false;
}

$diffsince._handleHistoryPage = function(req) {
    // restore button in case this fails, so user can click again
    $util.buttonRestoreStatus(req.statusNode);

    if (req.status == 200) {
        $diffsince._parseHistory(req.responseXML);
    } else {
        alert("Error downloading history page!");
    }
}

$diffsince._parseHistory = function(doc) {
    if (!doc) { return $diffsince.error("no doc?! (error 771d4660-0109-4e56-9cd7-70fc583fd4b5)"); }
    var hists = doc.getElementById("pagehistory").childNodes;
    if (!hists) { return $diffsince.error("Couldn't parse history from page (error db3ebe3e-cc2f-48ab-9c18-d348f92b0d91)"); }
    for (n=0;n<hists.length;n++) {
        if (hists[n].getElementsByTagName &&
            hists[n].getElementsByTagName("span")[0].textContent==wikiDoc.username)
        {
            document.location = hists[n].childNodes[1].href;
            return;
        }
    }

    alert("diffsince: Couldn't find your entry in the history page; you haven't edited recently!");
    // TODO: retry with a larger limit
}

$util.addOnloadHook($diffsince._load);


/****************************************************************************
 * END diffsince.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikiwatch.js
 ****************************************************************************/

// $Id: wikiwatch.js 633 2006-02-17 08:03:52Z quarl $

// wikiwatch.js - utility functions for manipulating watchlist

// quarl 2006-01-09 (initial asynchronous implementation at unwatch.js)
// quarl 2006-02-03 factored out to utility library

var $wikiwatch = new Module('wikiwatch.js');
$wikiwatch.depend('wikipage.js', 'util.js');

$wikiwatch.unwatchAsync = function(wp, callback, statusNode, statusText) {
    $wikiwatch.wuwatchAsync(false, wp, callback, statusNode, statusText);
}

$wikiwatch.watchAsync = function(wp, callback, statusNode, statusText) {
    $wikiwatch.wuwatchAsync(true, wp, callback, statusNode, statusText);
}

$wikiwatch.makeUrl = function(wp, wuwp) {
    if (wuwp) {
        return wp.qurl + '&action=watch';
    } else {
        return wp.qurl + '&action=unwatch';
    }
}

// wuwp true: watch
// wuwp false: unwatch
$wikiwatch.wuwatchAsync = function(wuwp, wp, callback, statusNode, statusText) {
    var h1expect = wuwp ? 'Added to watchlist' : 'Removed from watchlist';

    wp = wp.notalkPage();
    $util.buttonShowStatus(statusNode, statusText);
    var cb = function(req) {
        $util.buttonRestoreStatus(statusNode);
        var m;
        if ((req.status == 200) &&
            (req.responseXML.getElementById('content').getElementsByTagName('h1')[0].textContent == h1expect))
        {
            if (callback) {
                callback(wp, wuwp);
            }
        } else {
            alert("$wikiwatch.wuwatchAsync "+wuwp+" '"+wp.page+"' failed!");
        }
    };
    $util.asyncDownloadXML($wikiwatch.makeUrl(wp, wuwp), cb);
}

/****************************************************************************
 * END wikiwatch.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autoedit.js
 ****************************************************************************/

// $Id: autoedit.js 1208 2006-02-24 09:22:41Z quarl $

// autoedit.js - tab menu & framework for automatically editing
// wiki pages

// SYNOPSIS:
//    var myeditor = new $autoedit('myeditor', 'MyEdit', 'ca-myedit', 'Do my edits', 'Edit summary prefix');
//    myeditor.initData = function() { this.foo=5; } /* optional */
//    myeditor.splitText = function(s) { return s.split('\n'); } /* optional */
//    myeditor.buildRegExp = function() { return /foo/ }
//    myeditor.replaceRegExp = function(s) { return + "("+s+")" }

// USER USAGE:
//    myeditor.widgetLoad();

// quarl 2006-02-08 initial version, refactoring *_canonicalize.js scripts

var $autoedit = new Module('autoedit.js');
$autoedit.depend('wikipage.js','wikiedit.js', 'util.js',
                 'wikiwidget.js', 'token.js');

$autoedit.editors = {};

$autoedit.options = { submitButton : 'wpDiff' };

// auto edit if query string asks us to (more commonly this is not necessary;
// user will invoke through tab)
$autoedit.load = function() {
    var v = WikiDocument.queryVars['autoedit'];
    if (!v) return;

    var autoeditor = $autoedit.editors[v];
    if (!autoeditor || !autoeditor.varname) {
        alert("$autoedit: invalid autoeditor! (error ae2a3784-7daf-49fb-9c6c-b68a7f28dc63)");
        return;
    }

    if (WikiDocument.queryVars['auth'] != autoeditor._makeAuthToken(wikiPage)) {
        alert("$autoedit: invalid auth token! (error a1908c50-620b-4104-bcfa-881ecf094442)");
        return;
    }

    setTimeout(function(){autoeditor.run();}, 50);
}

$autoedit._getMenu = function() {
    if (!$autoedit._menuAutoEdit) {
        $autoedit._menuAutoEdit = $wikiwidget.addTabMenu('AutoEdit', 'mn-autoedit');
    }
    return $autoedit._menuAutoEdit;
}

$autoedit.AutoEditor = function(varname, name, id, title, summaryPrefix) {
    this.varname = varname;
    this.summaryPrefix = summaryPrefix;
    this.name = name;
    this.id = id;
    this.title = title;

    this.widgetLoad = $util.bindThis(this, this.widgetLoad_);

    $autoedit.editors[varname] = this;
};

$autoedit.AutoEditor.prototype = {
    widgetLoad_ : function(location) {
        var me = this;
        if (!(me instanceof $autoedit.AutoEditor)) {
            alert("## $autoedit.widgetLoad: need $autoedit instance (error 87bd9bfd-7ff6-4577-a59c-488936fa536c)");
            return;
        }
        $util.addOnloadHook(function() {
                if (wikiPage.nsSpecialP) return;
                if (wikiDoc.protectedP) return;

                this.widget = new WikiWidget({default_location: $autoedit._getMenu,
                                              onclick: $util.bindThis(me, me.run),
                                              href: me.makeEditUrl(wikiPage),
                                              name: me.name, id: me.id, title: me.title});
                this.widget.add(location);
                });
    },

    _makeAuthToken: function(wp) {
        return $token.makeAuthToken('autoedit', this.varname, wp.page);
    },

    makeEditUrl : function(wp) {
        return (wp.qurl + '&action=edit&autoedit=' + this.varname + '&auth=' +
                this._makeAuthToken(wp));
    },

    run : function() {
        this.initData();
        $util.buttonShowStatus(this.tab);
        wikiPage.getEditorAsync(this._edit0, this);
        return false;
    },

    _edit0 : function(editor, this_) {
        this_._edit1(editor);
    },

    _edit1 : function(editor) {
        var result = '';
        var input = editor.wpTextbox1;
        var changes = [];

        var inputs = this.splitText(input);

        var result = this.editStrings(inputs, changes);

        $util.buttonRestoreStatus(this.tab);
        if (changes.length) {
            editor.wpTextbox1 = result;
            editor.wpSummary = this.summaryPrefix + ': ' + changes.join('; ');
            editor.wpMinoredit = true;
            editor.submit($autoedit.options.submitButton);
        } else {
            alert("No changes to make!");
        }
    },

    initData : function() {
        /* TO BE OVERRIDDEN */
        // default: nothing to initialize
    },

    splitText : function(s) {
        /* TO BE OVERRIDDEN */
        // default: don't split at all
        return [s];
    },

    editStrings : function(inputs, changes) {
        for (var i in inputs) {
            inputs[i] = this.editString(inputs[i], changes);
        }
        return inputs.join('');
    },

    editString : function(input, changes) {
        /* TO BE OVERRIDDEN */
        return this.regexpEditString(input, changes);
    },

    regexpEditString : function(input, changes) {
        var result = '';

        var regexp = this.buildRegExp();
        var m;
        while ((m=input.match(regexp))) {
            result += RegExp.leftContext;

            // This descriptor is passed in for input as well as taken as output.
            //
            // LEFT is the text on the left of the match; TEXT is the text itself;
            // RIGHT is the text on the right of the match.  All 3 can be modified.
            //
            // Return value of replaceRegExp overrides d.text if non-null --
            // it's simply a convenience feature.

            var d = { left: result, text: m[0], right: RegExp.rightContext };

            var dresult = this.replaceRegExp(d, m);
            if (dresult != null) {
                d.text = dresult;
            }

            if (d.text != m[0]) {
                changes.push('"'+m[0]+'"' + ' → ' +'"'+d.text+'"');
            }
            result = d.left + d.text;
            input = d.right;
        }
        result += input;
        return result;
    },

    buildRegExp : function() {
        alert("## $autoedit.AutoEditor.buildRegExp() not overridden! (38c826f0-9f24-4ce2-9708-567d8e55a7c5)");
    },

    replaceRegExp : function(d, match) {
        // d.left
        // d.text
        // d.right
        alert("## $autoedit.AutoEditor.replaceRegExp() not overridden! (1a791fee-8020-453f-adb1-df3c69dc6828)");
    },

    // $autoedit._findCategoryContentTable = function() {
    //     var tables = document.getElementsByTagName('table');
    //     for (var i = 0; i < tables.length; ++i) {
    //         var table = tables[i];
    //         if (table.previousSibling.textContent.match(/^There are/)) {
    //             return table;
    //         }
    //     }
    //     return false;
    // }

    // prefix [varname] buttons before all wikilinks.  Good for category pages.
    addAutoEditButtons : function() {
        // if (!wikiPage.nsCategoryP) return;

        // var table = $autoedit._findCategoryContentTable();
        // if (!table) return;

        var d = document.getElementById('bodyContent');

        var links = $util.copyArray(d.getElementsByTagName('a'));
        for (var i = 0; i < links.length; ++i) {
            var link = links[i];
            if (!link.href || !link.href.match(/\/wiki\//)) continue;
            if (link.href.match(/#/)) continue;
            if (link.className.match(/external/)) continue;
            var wp = new WikiPage(link);
            var url = this.makeEditUrl(wp);

            var span = document.createElement('span');
            span.innerHTML = '[<a href="'+url+'">'+this.varname+'</a>] ';
            $util.addNodeBefore(link, span);
        }
    }
};

$util.addOnloadHook($autoedit.load);

/****************************************************************************
 * END autoedit.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN nav_custom.js
 ****************************************************************************/

// $Id: nav_custom.js 894 2006-02-22 05:54:10Z quarl $

// nav_custom.js - customizations for navigation box

//  - adds links:
//     [[User:Quarl/todo]]
//     [[User:Quarl/sandbox]]
//     [[User:Quarl/monobook]]

//  - removes links:
//     [[Help:Contents]]
//     [[Wikipedia:Contact us]]
//     [http://wikimediafoundation.org/wiki/Fundraising#Donation_methods]

var $nav_custom = new Module('nav_custom.js');
$nav_custom.depend('wikipage.js', 'wikiwidget.js');

$nav_custom.addNavLink = function(params) {
    var wp = WikiPage(null, params.pagename);
    var wpe = params.editpagename ? WikiPage(null, params.editpagename) : wp;
    var urlN = wp.url, urlE = wpe.qurl+'&action=edit';
    var name = params.name || wp.article;
    var e = "<a style='display:inline' href='" + urlN + "'>" + name + "</a> (<a style='display:inline' href='"+urlE+"'>edit</a>)";

    var widget = new WikiWidget({default_location: {portlet:'Navigation'},
                                 entry: e,
                                 id: params.id,
                                 key: params.key});
    widget.add(params.location||null);
}

$nav_custom.delNavLink = function(id) {
    var node = document.getElementById(id);
    if (!node) return;
    //node.style.display = 'none';
    node.className += ' hiddenStructure';
}

$nav_custom.alterLinkText = function(id, newText) {
    var node = document.getElementById(id);
    if (!node) return;
    node.firstChild.innerHTML = newText;
}

/****************************************************************************
 * END nav_custom.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN iframedl.js
 ****************************************************************************/

// $Id: iframedl.js 632 2006-02-17 05:27:33Z quarl $

// iframedl.js - download content in a hidden <iframe>
//
// $iframedl.refreshUrl: refreshes a URL
//
// $iframedl.loadUrl: loads a URL and return the HTMLDocument
//   advantage over XMLHttpRequest: browser automatically caches the contents
//   as a regular document.
//
//  Note that as with XMLHttpRequest, you cannot retrieve pages from other
//  domains.

// quarl 2006-01-10 initial version

var $iframedl = new Module('iframedl.js');

$iframedl.refreshUrl = function(url, callback) {
    // We can't do frm.location.reload() right now because frm.location will
    // still be "about:blank", but if we complete this event and start a new
    // one, it works.

    //frm.location.replace(url);
    //frm.location.href = url;

    $iframedl.node.src = url;

    var cb = function() {
        $iframedl.frm.location.reload();
        if (callback) callback();
    };

    setTimeout(cb, 0);
}

$iframedl._ifrmDoc = function(frm) {
    if (frm.contentDocument) {
        // For NS6
        return frm.contentDocument;
    } else if (frm.contentWindow) {
        // For IE5.5 and IE6
        return frm.contentWindow.document;
    } else if (frm.document) {
        // For IE5
        return frm.document;
    } else {
        return null;
    }
}

$iframedl.loadUrl = function(url, callback) {
    var doc = $iframedl._ifrmDoc($iframedl.node);
    // doc.location.replace(url);
    $iframedl.node.src = url;

    var cb = function() {
        // $iframedl.frm.location.reload();
        if (callback) callback(doc);
    };

    setTimeout(cb, 1);
}

// write the hidden iframe.  Note: don't use display:none !
document.write('<div style="position:absolute;left:0px;top:0px;visibility:hidden;" id="iframedl_hidden_div">' +
               '<iframe src="about:blank" height="0" width="0" name="iframedl_iframe" id="iframedl_iframe">' +
               '</iframe></div>');

$iframedl.node = document.getElementById('iframedl_iframe');
$iframedl.frm = window.frames['iframedl_iframe'];

/****************************************************************************
 * END iframedl.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN wikiimg.js
 ****************************************************************************/

// $Id: wikiimg.js 1246 2006-02-25 04:51:53Z quarl $

// wikiimg.js -- utilities for downloading images

// quarl 2006-02-20 initial version, based on image code in Lupin's popups

var $wikiimg = new Module('wikiimg.js');
$wikiimg.depend('util.js', 'wikipage.js');

$wikiimg.options = {
    same_wiki_only : false,
    no_thumbs : false,
    max_concurrent_downloads : 5,

    // this sets the MINIMUM latency between calls to $wikiimage._checkImages
    // -- that doesn't mean it gets called every 50 ms, just that it's not
    // called faster than 50 ms !
    update_latency : 50                             // in milliseconds
};

$wikiimg.load = function() {
    var commonsWiki='commons.wikimedia.org';
    var thisWiki = WikiPage.server;

    var imageSources = $wikiimg.imageSources = [];

    // frequently seen thumbs
    imageSources.push(
        {wiki: thisWiki, thumb: true,  width: 180}, // default
        {wiki: thisWiki, thumb: true,  width: 120} // gallery
        );

    // frequently seen thumbs on commons
    if (WikiPage.wikimediaWikiP && thisWiki!=commonsWiki) {
        imageSources.push(
            {wiki: commonsWiki, thumb: true,  width: 180},
            {wiki: commonsWiki, thumb: true,  width: 120},
            {wiki: commonsWiki, thumb: true,  width: 96}
            );
    }

    // unusual thumb sizes and full-size
    imageSources.push(
        {wiki: thisWiki, thumb: true,  width: 200}, // common?
        {wiki: thisWiki, thumb: true,  width: 250}, // common?
        {wiki: thisWiki, thumb: true,  width: 300},
        {wiki: thisWiki, thumb: true,  width: 210},
        {wiki: thisWiki, thumb: true,  width: 230},
        {wiki: thisWiki, thumb: false, width: 0} // no comma
        );

    // full-size on commons
    if (WikiPage.wikimediaWikiP && thisWiki!=commonsWiki) {
        imageSources.push({wiki: commonsWiki, thumb: false, width: 0});
    }
}
$util.addOnloadHook($wikiimg.load);

// this returns a trailing slash
$wikiimg.getUrlBase = function(wiki) {
    switch (wiki) {
        case 'en.wikipedia.org':      return 'http://upload.wikimedia.org/wikipedia/en/';
        case 'commons.wikimedia.org': return 'http://upload.wikimedia.org/wikipedia/commons/';
        case 'en.wiktionary.org':     return 'http://en.wiktionary.org/upload/en/';
        default: // unsupported - take a guess
        if (WikiPage.wikimediaWikiP) {
            var lang=wiki.split('.')[0];
            return 'http://upload.wikimedia.org/wikipedia/' + lang +'/';
        }
        else /* this should work for wikicities */
            return 'http://' + wiki + '/images/';
    }
};

// those a/a5/ bits of image urls
$wikiimg.imagePathComponent = function(filename) {
    var hash = $md5.hex_md5($util.wpaEscape(filename));
    return hash.substring(0,1) + '/' + hash.substring(0,2) + '/';
};

$wikiimg.imageURL = function(filename, wiki) {
    if (!wiki) return $wikiimg.error("imageURL: need 'wiki'");
    if ($wikiimg.options.same_wiki_only && wiki != WikiPage.server) return null;
    return ($wikiimg.getUrlBase(wiki) +
            $wikiimg.imagePathComponent(filename) +
            filename);
};

$wikiimg.imageThumbURL = function(filename, wiki, width) {
    //
    // eg http://upload.wikimedia.org/wikipedia/en/thumb/6/61/
    //           Rubiks_cube_solved.jpg/120px-Rubiks_cube_solved.jpg
    //           ^^^^^^^^^^^^^^^^^^^^^^^
    //          wikicities omits this bit
    //  AND wikicities needs a new pathcpt for each filename including thumbs
    if ($wikiimg.options.same_wiki_only && wiki != WikiPage.server) return null;
    if ($wikiimg.options.no_thumbs) return null;

    var thumbname = width + "px-" + filename;

    var imgurl = $wikiimg.getUrlBase(wiki) +  "thumb/";

    if (WikiPage.wikimediaWikiP) {
        imgurl += $wikiimg.imagePathComponent(filename);
        imgurl += filename + '/';
    } else {
        imgurl += $wikiimg.imagePathComponent(thumbname);
    }

    imgurl += thumbname;
    return imgurl;
};

// returns a list of URLs, which have an attached 'wiki' property.
$wikiimg.getUrls = function(filename) {
    var urls = [];
    for (var i in $wikiimg.imageSources) {
        var imgsrc = $wikiimg.imageSources[i];
        var url = null;
        if (imgsrc.thumb)
            url=$wikiimg.imageThumbURL(filename, imgsrc.wiki, imgsrc.width);
        else
            url=$wikiimg.imageURL(filename, imgsrc.wiki);
        if (url) {
            url = new String(url);
            url.wiki = imgsrc.wiki;
            urls.push(url);
        }
    }
    return urls;
};

$wikiimg.isValidImageName = function(str)
{
    return str.match(/\{/) == null;
};

$wikiimg._cache = {};
$wikiimg._inProgress = {}; // maps filename to map from url to Image
$wikiimg._concurrent_downloads = 0;

// asynchronously starts downloading possible URL(s) for the given Image wp.
// Calls callback(wp, img) on success and callback(wp, null) on failure.  Memoized.
//
// img is an Image instance with the extra property img.wiki being the
// hostname of the wiki it's from.
$wikiimg.downloadImage = function(filename, callback) {
    if (!$wikiimg.isValidImageName(filename)) {
        $wikiimg.error("downloadImage: '" + filename + "' is not a valid image link (error e341bf13-3a16-475d-94bc-574233981e07)");
        return;
    }
    if ($wikiimg._cache[filename]) {
        callback(filename, $wikiimg._cache[filename]);
        return;
    }
    var urls = $wikiimg.getUrls(filename);
    $wikiimg._downloadImagesParallel(filename, urls, callback);
}

$wikiimg._downloadImagesParallel = function(filename, urls, callback)
{
    var descriptor = $wikiimg._inProgress[filename];
    if (descriptor) {
        // download already in progress.
        descriptor.callbacks.push(callback);
        return;
    }
    descriptor = $wikiimg._inProgress[filename] = {
        filename: filename, callbacks: [callback], imgs: [] };

    for (var i in urls) {
        var img = new Image();
        img.bLoaded = false;
        img.bErrored = false;
        img.bAborted = false;
        img.rsrc = urls[i];
        img.wiki = urls[i].wiki;
        // we don't actually initiate the download yet; that is the job of
        // _checkImages
        descriptor.imgs.push(img);
    }
    $wikiimg._checkImages();           // call it now to start downloading
};

$wikiimg._checkImages = function() {
    if (typeof window.$wikiimg == 'undefined') return; // unloaded?

    var findOk = function(imgs) {
        for (var i in imgs) {
            var img = imgs[i];
            if (img.bLoaded) return img;
        }
        return null;
    }

    var allFailedP = function(imgs) {
        for (var i in imgs) {
            if (!imgs[i].bErrored) return false;
        }
        return true;
    }

    var checkImagesScheduled = false;
    var imgCompletedSchedCheckImages = function(img) {
        if (typeof window.$wikiimg == 'undefined') return; // unloaded
        $wikiimg.debug("imgCompletedSchedCheckImages " + img.rsrc);
        $wikiimg._concurrent_downloads --;
        if (!checkImagesScheduled) {
            checkImagesScheduled = true;
            setTimeout($wikiimg._checkImages, $wikiimg.options.update_latency);
        }
    }

    var startImgDownload = function(img) {
        $wikiimg.debug("startImgDownload " + img.rsrc);
        img.onload = function() { img.bLoaded = true; imgCompletedSchedCheckImages(img); };
        img.onerror = function() { img.bErrored = true; imgCompletedSchedCheckImages(img); };
        // img.onabort = function() { img.bAborted = true; imgCompletedSchedCheckImages(); };
        img.onabort = $wikiimg.abortAllDownloads;
        img.src = img.rsrc;
    }

    var startDownloads = function(imgs) {
        for (var i in imgs) {
            if ($wikiimg._concurrent_downloads >= $wikiimg.options.max_concurrent_downloads)
                return;
            var img = imgs[i];
            if (! img.src) {
                startImgDownload(img);
                $wikiimg._concurrent_downloads ++;
            }
        }
    }

    var stopDownloads = function(imgs) {
        while (imgs.length) {
            var img = imgs[0];
            delete img.onload;
            delete img.onerror;
            delete img.onabort;
            if (img.src) {
                $wikiimg._concurrent_downloads --;
            }
            imgs.shift();
        }
    }

    // have any of the concurrent download completed?
    for (var filename in $wikiimg._inProgress) {
        var descriptor = $wikiimg._inProgress[filename];
        if (allFailedP(descriptor.imgs)) {
            // failed :(
            $wikiimg.debug("Failed all "+descriptor.imgs.length+" attempts to download "+filename);
            $util.callHooks(descriptor.callbacks, descriptor.filename, null);
            stopDownloads(descriptor.imgs);
            delete $wikiimg._inProgress[filename];
            continue;
        }
        var img = findOk(descriptor.imgs);
        if (img) {
            // success! now call the callbacks and clear the data
            // structure
            $wikiimg.debug("Successfully downloaded "+filename+ " at " + img.src);
            $wikiimg._cache[descriptor.filename] = img;
            $util.callHooks(descriptor.callbacks, descriptor.filename, img);
            stopDownloads(descriptor.imgs);
            delete $wikiimg._inProgress[filename];
            continue;
        }
        // Still looking.  Start new downloads?
        startDownloads(descriptor.imgs);
    }
};

$wikiimg.imageIdCounter = 0;
$wikiimg.htmlDlImages = [];
$wikiimg.makeAutoDlHtml = function(filename) {
    var id = $wikiimg.imageIdCounter++;
    $wikiimg.htmlDlImages[id] = filename;
    // use CSS background-url javascript hack so that as soon as this HTML
    // fragment is inserted into the document, we start downloading the
    // image.  This is useful when we don't know when the resulting HTML will
    // be inserted into the document.
    var jsurl = $util.pprintf("javascript:$wikiimg._htmlDownloadImage($1)", id);
    return $util.pprintf('<img id="wikiimg-$1" style="background:url(\'$2\')" />',
                        id, jsurl);
};


$wikiimg._htmlDownloadImage = function(id)
{
    $wikiimg.debug("htmlDownloadImage("+id+")");
    var filename = $wikiimg.htmlDlImages[id];
    if (!filename) {
        $wikiimg.debug("htmlDownloadImage("+id+"): no filename");
        return;
    }
    var img = document.getElementById('wikiimg-'+id);
    if (!img) {
        $wikiimg.debug("htmlDownloadImage("+id+"): no img");
        return;
    }

    var cb = function(filename, dlImg) {
        $wikiimg.debug('htmlDownloadImage ' + filename + ' ' + dlImg&&dlImg.src);
        if (dlImg) {
            img.src = dlImg.src;
        }
    };

    $wikiimg.downloadImage(filename, cb);
};


$wikiimg.abortAllDownloads = function() {
    $wikiimg._downloads = [];
    $wikiimg._concurrent_downloads = 0;
};

$util.addOnunloadHook($wikiimg.abortAllDownloads);

/****************************************************************************
 * END wikiimg.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN drag.js
 ****************************************************************************/

// $Id: drag.js 1119 2006-02-23 07:38:50Z quarl $

var $drag = new Module('drag.js');
$drag.depend('util.js');

$drag.makeDraggable = function(obj, root, args)
{
    if (obj.dragger) return false;

    obj.dragger = new $drag.Dragger(obj, root, args);
    return true;
}

$drag.Dragger = function(obj, root, args)
{
    // args: pickupCondition, onPickup

    var dragger = this;
    this.obj = obj;
    this.root = root || obj;
    this.dragging = false;
    $util.merge(this, args);

    $util.hookEventObj(this.obj, 'mousedown', function(e){dragger.pickup(e)});
    $util.hookEventObj(this.obj, 'click', function(e){dragger.click(e)});
};

$drag.Dragger.prototype.pickup = function(e)
{
    if (!$util.eventLeftButtonP(e)) return;
    if (this.pickupCondition && !this.pickupCondition(e)) return;
    if (this.onPickup) this.onPickup(e);

    if (this.dragging) {
        // we could get here due to a Mozilla bug(?) where if you drag and
        // release off-window, no mouseUp event is delivered
        e.preventDefault();
        this.dragging = false;
        return;
    }

    this.mouse_x = e.clientX;
    this.mouse_y = e.clientY;

    this.drag_top = parseInt(this.obj.style.top);
    this.drag_left = parseInt(this.obj.style.left);

    this.dragging = false;

    var dragger = this;
    $util.hookEventObj(document, 'mousemove',
                       (this.hookMouseMove = function(e){dragger.drag(e)}));
    $util.hookEventObj(document, 'mouseup',
                       (this.hookMouseUp = function(e){dragger.drop(e)}));

    e.preventDefault();
};

$drag.Dragger.prototype.drag = function(e)
{
    this.dragging = true;

    var x = e.clientX;
    var y = e.clientY;

    this.obj.style.top = (y - this.mouse_y + this.drag_top) + "px";
    this.obj.style.left = (x - this.mouse_x + this.drag_left) + "px";
};

$drag.Dragger.prototype.click = function(e) {
    if (this.dragging) e.preventDefault();
}

$drag.Dragger.prototype.drop = function(e)
{
    document.removeEventListener("mousemove", this.hookMouseMove, false);
    document.removeEventListener("mouseup", this.hookMouseUp, false);
    this.dragging = false;
};

/****************************************************************************
 * END drag.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN overlib/overlib.js
 ****************************************************************************/

var $overlib = new Module('overlib/overlib.js');

//\/////
//\  overLIB 4.21 - You may not remove or change this notice.
//\  Copyright Erik Bosrup 1998-2004. All rights reserved.
//\
//\  Contributors are listed on the homepage.
//\  This file might be old, always check for the latest version at:
//\  http://www.bosrup.com/web/overlib/
//\
//\  Please read the license agreement (available through the link above)
//\  before using overLIB. Direct any licensing questions to [email protected].
//\
//\  Do not sell this as your own work or remove this copyright notice.
//\  For full details on copying or changing this script please read the
//\  license agreement at the link above. Please give credit on sites that
//\  use overLIB and submit changes of the script so other people can use
//\  them as well.
//   $Revision: 1.119 $                $Date: 2005/07/02 23:41:44 $
//\/////
//\mini

////////
// PRE-INIT
// Ignore these lines, configuration is below.
////////
var olLoaded = 0;var pmStart = 10000000; var pmUpper = 10001000; var pmCount = pmStart+1; var pmt=''; var pms = new Array(); var olInfo = new Info('4.21', 1);
var FREPLACE = 0; var FBEFORE = 1; var FAFTER = 2; var FALTERNATE = 3; var FCHAIN=4;
var olHideForm=0;  // parameter for hiding SELECT and ActiveX elements in IE5.5+
var olHautoFlag = 0;  // flags for over-riding VAUTO and HAUTO if corresponding
var olVautoFlag = 0;  // positioning commands are used on the command line
var hookPts = new Array(), postParse = new Array(), cmdLine = new Array(), runTime = new Array();
// for plugins
registerCommands('donothing,inarray,caparray,sticky,background,noclose,caption,left,right,center,offsetx,offsety,fgcolor,bgcolor,textcolor,capcolor,closecolor,width,border,cellpad,status,autostatus,autostatuscap,height,closetext,snapx,snapy,fixx,fixy,relx,rely,fgbackground,bgbackground,padx,pady,fullhtml,above,below,capicon,textfont,captionfont,closefont,textsize,captionsize,closesize,timeout,function,delay,hauto,vauto,closeclick,wrap,followmouse,mouseoff,closetitle,cssoff,compatmode,cssclass,fgclass,bgclass,textfontclass,captionfontclass,closefontclass');

////////
// DEFAULT CONFIGURATION
// Settings you want everywhere are set here. All of this can also be
// changed on your html page or through an overLIB call.
////////
if (typeof ol_fgcolor=='undefined') var ol_fgcolor="#CCCCFF";
if (typeof ol_bgcolor=='undefined') var ol_bgcolor="#333399";
if (typeof ol_textcolor=='undefined') var ol_textcolor="#000000";
if (typeof ol_capcolor=='undefined') var ol_capcolor="#FFFFFF";
if (typeof ol_closecolor=='undefined') var ol_closecolor="#9999FF";
if (typeof ol_textfont=='undefined') var ol_textfont="Verdana,Arial,Helvetica";
if (typeof ol_captionfont=='undefined') var ol_captionfont="Verdana,Arial,Helvetica";
if (typeof ol_closefont=='undefined') var ol_closefont="Verdana,Arial,Helvetica";
if (typeof ol_textsize=='undefined') var ol_textsize="1";
if (typeof ol_captionsize=='undefined') var ol_captionsize="1";
if (typeof ol_closesize=='undefined') var ol_closesize="1";
if (typeof ol_width=='undefined') var ol_width="200";
if (typeof ol_border=='undefined') var ol_border="1";
if (typeof ol_cellpad=='undefined') var ol_cellpad=2;
if (typeof ol_offsetx=='undefined') var ol_offsetx=10;
if (typeof ol_offsety=='undefined') var ol_offsety=10;
if (typeof ol_text=='undefined') var ol_text="Default Text";
if (typeof ol_cap=='undefined') var ol_cap="";
if (typeof ol_sticky=='undefined') var ol_sticky=0;
if (typeof ol_background=='undefined') var ol_background="";
if (typeof ol_close=='undefined') var ol_close="Close";
if (typeof ol_hpos=='undefined') var ol_hpos=RIGHT;
if (typeof ol_status=='undefined') var ol_status="";
if (typeof ol_autostatus=='undefined') var ol_autostatus=0;
if (typeof ol_height=='undefined') var ol_height=-1;
if (typeof ol_snapx=='undefined') var ol_snapx=0;
if (typeof ol_snapy=='undefined') var ol_snapy=0;
if (typeof ol_fixx=='undefined') var ol_fixx=-1;
if (typeof ol_fixy=='undefined') var ol_fixy=-1;
if (typeof ol_relx=='undefined') var ol_relx=null;
if (typeof ol_rely=='undefined') var ol_rely=null;
if (typeof ol_fgbackground=='undefined') var ol_fgbackground="";
if (typeof ol_bgbackground=='undefined') var ol_bgbackground="";
if (typeof ol_padxl=='undefined') var ol_padxl=1;
if (typeof ol_padxr=='undefined') var ol_padxr=1;
if (typeof ol_padyt=='undefined') var ol_padyt=1;
if (typeof ol_padyb=='undefined') var ol_padyb=1;
if (typeof ol_fullhtml=='undefined') var ol_fullhtml=0;
if (typeof ol_vpos=='undefined') var ol_vpos=BELOW;
if (typeof ol_aboveheight=='undefined') var ol_aboveheight=0;
if (typeof ol_capicon=='undefined') var ol_capicon="";
if (typeof ol_frame=='undefined') var ol_frame=self;
if (typeof ol_timeout=='undefined') var ol_timeout=0;
if (typeof ol_function=='undefined') var ol_function=null;
if (typeof ol_delay=='undefined') var ol_delay=0;
if (typeof ol_hauto=='undefined') var ol_hauto=0;
if (typeof ol_vauto=='undefined') var ol_vauto=0;
if (typeof ol_closeclick=='undefined') var ol_closeclick=0;
if (typeof ol_wrap=='undefined') var ol_wrap=0;
if (typeof ol_followmouse=='undefined') var ol_followmouse=1;
if (typeof ol_mouseoff=='undefined') var ol_mouseoff=0;
if (typeof ol_closetitle=='undefined') var ol_closetitle='Close';
if (typeof ol_compatmode=='undefined') var ol_compatmode=0;
if (typeof ol_css=='undefined') var ol_css=CSSOFF;
if (typeof ol_fgclass=='undefined') var ol_fgclass="";
if (typeof ol_bgclass=='undefined') var ol_bgclass="";
if (typeof ol_textfontclass=='undefined') var ol_textfontclass="";
if (typeof ol_captionfontclass=='undefined') var ol_captionfontclass="";
if (typeof ol_closefontclass=='undefined') var ol_closefontclass="";

////////
// ARRAY CONFIGURATION
////////

// You can use these arrays to store popup text here instead of in the html.
if (typeof ol_texts=='undefined') var ol_texts = new Array("Text 0", "Text 1");
if (typeof ol_caps=='undefined') var ol_caps = new Array("Caption 0", "Caption 1");

////////
// END OF CONFIGURATION
// Don't change anything below this line, all configuration is above.
////////





////////
// INIT
////////
// Runtime variables init. Don't change for config!
var o3_text="";
var o3_cap="";
var o3_sticky=0;
var o3_background="";
var o3_close="Close";
var o3_hpos=RIGHT;
var o3_offsetx=2;
var o3_offsety=2;
var o3_fgcolor="";
var o3_bgcolor="";
var o3_textcolor="";
var o3_capcolor="";
var o3_closecolor="";
var o3_width=100;
var o3_border=1;
var o3_cellpad=2;
var o3_status="";
var o3_autostatus=0;
var o3_height=-1;
var o3_snapx=0;
var o3_snapy=0;
var o3_fixx=-1;
var o3_fixy=-1;
var o3_relx=null;
var o3_rely=null;
var o3_fgbackground="";
var o3_bgbackground="";
var o3_padxl=0;
var o3_padxr=0;
var o3_padyt=0;
var o3_padyb=0;
var o3_fullhtml=0;
var o3_vpos=BELOW;
var o3_aboveheight=0;
var o3_capicon="";
var o3_textfont="Verdana,Arial,Helvetica";
var o3_captionfont="Verdana,Arial,Helvetica";
var o3_closefont="Verdana,Arial,Helvetica";
var o3_textsize="1";
var o3_captionsize="1";
var o3_closesize="1";
var o3_frame=self;
var o3_timeout=0;
var o3_timerid=0;
var o3_allowmove=0;
var o3_function=null;
var o3_delay=0;
var o3_delayid=0;
var o3_hauto=0;
var o3_vauto=0;
var o3_closeclick=0;
var o3_wrap=0;
var o3_followmouse=1;
var o3_mouseoff=0;
var o3_closetitle='';
var o3_compatmode=0;
var o3_css=CSSOFF;
var o3_fgclass="";
var o3_bgclass="";
var o3_textfontclass="";
var o3_captionfontclass="";
var o3_closefontclass="";

// Display state variables
var o3_x = 0;
var o3_y = 0;
var o3_showingsticky = 0;
var o3_removecounter = 0;

// Our layer
var over = null;
var fnRef, hoveringSwitch = false;
var olHideDelay;

// Decide browser version
var isMac = (navigator.userAgent.indexOf("Mac") != -1);
var olOp = (navigator.userAgent.toLowerCase().indexOf('opera') > -1 && document.createTextNode);  // Opera 7
var olNs4 = (navigator.appName=='Netscape' && parseInt(navigator.appVersion) == 4);
var olNs6 = (document.getElementById) ? true : false;
var olKq = (olNs6 && /konqueror/i.test(navigator.userAgent));
var olIe4 = (document.all) ? true : false;
var olIe5 = false;
var olIe55 = false; // Added additional variable to identify IE5.5+
var docRoot = 'document.body';

// Resize fix for NS4.x to keep track of layer
if (olNs4) {
	var oW = window.innerWidth;
	var oH = window.innerHeight;
	window.onresize = function() { if (oW != window.innerWidth || oH != window.innerHeight) location.reload(); }
}

// Microsoft Stupidity Check(tm).
if (olIe4) {
	var agent = navigator.userAgent;
	if (/MSIE/.test(agent)) {
		var versNum = parseFloat(agent.match(/MSIE[ ](\d\.\d+)\.*/i)[1]);
		if (versNum >= 5){
			olIe5=true;
			olIe55=(versNum>=5.5&&!olOp) ? true : false;
			if (olNs6) olNs6=false;
		}
	}
	if (olNs6) olIe4 = false;
}

// Check for compatability mode.
if (document.compatMode && document.compatMode == 'CSS1Compat') {
	docRoot= ((olIe4 && !olOp) ? 'document.documentElement' : docRoot);
}

// Add window onload handlers to indicate when all modules have been loaded
// For Netscape 6+ and Mozilla, uses addEventListener method on the window object
// For IE it uses the attachEvent method of the window object and for Netscape 4.x
// it sets the window.onload handler to the OLonload_handler function for Bubbling
if(window.addEventListener) window.addEventListener("load",OLonLoad_handler,false);
else if (window.attachEvent) window.attachEvent("onload",OLonLoad_handler);

var capExtent;

////////
// PUBLIC FUNCTIONS
////////

// overlib(arg0,...,argN)
// Loads parameters into global runtime variables.
overlib = $overlib.overlib = function() {
	if (!olLoaded || isExclusive(arguments)) return true;
	if (olCheckMouseCapture) olMouseCapture();
	if (over) {
		over = (typeof over.id != 'string') ? o3_frame.document.all['overDiv'] : over;
		cClick();
	}

	// Load defaults to runtime.
  olHideDelay=0;
	o3_text=ol_text;
	o3_cap=ol_cap;
	o3_sticky=ol_sticky;
	o3_background=ol_background;
	o3_close=ol_close;
	o3_hpos=ol_hpos;
	o3_offsetx=ol_offsetx;
	o3_offsety=ol_offsety;
	o3_fgcolor=ol_fgcolor;
	o3_bgcolor=ol_bgcolor;
	o3_textcolor=ol_textcolor;
	o3_capcolor=ol_capcolor;
	o3_closecolor=ol_closecolor;
	o3_width=ol_width;
	o3_border=ol_border;
	o3_cellpad=ol_cellpad;
	o3_status=ol_status;
	o3_autostatus=ol_autostatus;
	o3_height=ol_height;
	o3_snapx=ol_snapx;
	o3_snapy=ol_snapy;
	o3_fixx=ol_fixx;
	o3_fixy=ol_fixy;
	o3_relx=ol_relx;
	o3_rely=ol_rely;
	o3_fgbackground=ol_fgbackground;
	o3_bgbackground=ol_bgbackground;
	o3_padxl=ol_padxl;
	o3_padxr=ol_padxr;
	o3_padyt=ol_padyt;
	o3_padyb=ol_padyb;
	o3_fullhtml=ol_fullhtml;
	o3_vpos=ol_vpos;
	o3_aboveheight=ol_aboveheight;
	o3_capicon=ol_capicon;
	o3_textfont=ol_textfont;
	o3_captionfont=ol_captionfont;
	o3_closefont=ol_closefont;
	o3_textsize=ol_textsize;
	o3_captionsize=ol_captionsize;
	o3_closesize=ol_closesize;
	o3_timeout=ol_timeout;
	o3_function=ol_function;
	o3_delay=ol_delay;
	o3_hauto=ol_hauto;
	o3_vauto=ol_vauto;
	o3_closeclick=ol_closeclick;
	o3_wrap=ol_wrap;
	o3_followmouse=ol_followmouse;
	o3_mouseoff=ol_mouseoff;
	o3_closetitle=ol_closetitle;
	o3_css=ol_css;
	o3_compatmode=ol_compatmode;
	o3_fgclass=ol_fgclass;
	o3_bgclass=ol_bgclass;
	o3_textfontclass=ol_textfontclass;
	o3_captionfontclass=ol_captionfontclass;
	o3_closefontclass=ol_closefontclass;

	setRunTimeVariables();

	fnRef = '';

	// Special for frame support, over must be reset...
	o3_frame = ol_frame;

	if(!(over=createDivContainer())) return false;

	parseTokens('o3_', arguments);
	if (!postParseChecks()) return false;

	if (o3_delay == 0) {
		return runHook("olMain", FREPLACE);
 	} else {
		o3_delayid = setTimeout("runHook('olMain', FREPLACE)", o3_delay);
		return false;
	}
}

// Clears popups if appropriate
function nd(time) {
	if (olLoaded && !isExclusive()) {
		hideDelay(time);  // delay popup close if time specified

		if (o3_removecounter >= 1) { o3_showingsticky = 0 };

		if (o3_showingsticky == 0) {
			o3_allowmove = 0;
			if (over != null && o3_timerid == 0) runHook("hideObject", FREPLACE, over);
		} else {
			o3_removecounter++;
		}
	}

	return true;
}

// The Close onMouseOver function for stickies
function cClick() {
	if (olLoaded) {
		runHook("hideObject", FREPLACE, over);
		o3_showingsticky = 0;
	}
	return false;
}

// Method for setting page specific defaults.
function overlib_pagedefaults() {
	parseTokens('ol_', arguments);
}


////////
// OVERLIB MAIN FUNCTION
////////

// This function decides what it is we want to display and how we want it done.
function olMain() {
	var layerhtml, styleType;
 	runHook("olMain", FBEFORE);

	if (o3_background!="" || o3_fullhtml) {
		// Use background instead of box.
		layerhtml = runHook('ol_content_background', FALTERNATE, o3_css, o3_text, o3_background, o3_fullhtml);
	} else {
		// They want a popup box.
		styleType = (pms[o3_css-1-pmStart] == "cssoff" || pms[o3_css-1-pmStart] == "cssclass");

		// Prepare popup background
		if (o3_fgbackground != "") o3_fgbackground = "background=\""+o3_fgbackground+"\"";
		if (o3_bgbackground != "") o3_bgbackground = (styleType ? "background=\""+o3_bgbackground+"\"" : o3_bgbackground);

		// Prepare popup colors
		if (o3_fgcolor != "") o3_fgcolor = (styleType ? "bgcolor=\""+o3_fgcolor+"\"" : o3_fgcolor);
		if (o3_bgcolor != "") o3_bgcolor = (styleType ? "bgcolor=\""+o3_bgcolor+"\"" : o3_bgcolor);

		// Prepare popup height
		if (o3_height > 0) o3_height = (styleType ? "height=\""+o3_height+"\"" : o3_height);
		else o3_height = "";

		// Decide which kinda box.
		if (o3_cap=="") {
			// Plain
			layerhtml = runHook('ol_content_simple', FALTERNATE, o3_css, o3_text);
		} else {
			// With caption
			if (o3_sticky) {
				// Show close text
				layerhtml = runHook('ol_content_caption', FALTERNATE, o3_css, o3_text, o3_cap, o3_close);
			} else {
				// No close text
				layerhtml = runHook('ol_content_caption', FALTERNATE, o3_css, o3_text, o3_cap, "");
			}
		}
	}

	// We want it to stick!
	if (o3_sticky) {
		if (o3_timerid > 0) {
			clearTimeout(o3_timerid);
			o3_timerid = 0;
		}
		o3_showingsticky = 1;
		o3_removecounter = 0;
	}

	// Created a separate routine to generate the popup to make it easier
	// to implement a plugin capability
	if (!runHook("createPopup", FREPLACE, layerhtml)) return false;

	// Prepare status bar
	if (o3_autostatus > 0) {
		o3_status = o3_text;
		if (o3_autostatus > 1) o3_status = o3_cap;
	}

	// When placing the layer the first time, even stickies may be moved.
	o3_allowmove = 0;

	// Initiate a timer for timeout
	if (o3_timeout > 0) {
		if (o3_timerid > 0) clearTimeout(o3_timerid);
		o3_timerid = setTimeout("cClick()", o3_timeout);
	}

	// Show layer
	runHook("disp", FREPLACE, o3_status);
	runHook("olMain", FAFTER);

	return (olOp && event && event.type == 'mouseover' && !o3_status) ? '' : (o3_status != '');
}

////////
// LAYER GENERATION FUNCTIONS
////////
// These functions just handle popup content with tags that should adhere to the W3C standards specification.

// Makes simple table without caption
function ol_content_simple(text) {
	var cpIsMultiple = /,/.test(o3_cellpad);
	var txt = '<table width="'+o3_width+ '" border="0" cellpadding="'+o3_border+'" cellspacing="0" '+(o3_bgclass ? 'class="'+o3_bgclass+'"' : o3_bgcolor+' '+o3_height)+'><tr><td><table width="100%" border="0" '+((olNs4||!cpIsMultiple) ? 'cellpadding="'+o3_cellpad+'" ' : '')+'cellspacing="0" '+(o3_fgclass ? 'class="'+o3_fgclass+'"' : o3_fgcolor+' '+o3_fgbackground+' '+o3_height)+'><tr><td valign="TOP"'+(o3_textfontclass ? ' class="'+o3_textfontclass+'">' : ((!olNs4&&cpIsMultiple) ? ' style="'+setCellPadStr(o3_cellpad)+'">' : '>'))+(o3_textfontclass ? '' : wrapStr(0,o3_textsize,'text'))+text+(o3_textfontclass ? '' : wrapStr(1,o3_textsize))+'</td></tr></table></td></tr></table>';

	set_background("");
	return txt;
}

// Makes table with caption and optional close link
function ol_content_caption(text,title,close) {
	var nameId, txt, cpIsMultiple = /,/.test(o3_cellpad);
	var closing, closeevent;

	closing = "";
	closeevent = "onmouseover";
	if (o3_closeclick == 1) closeevent = (o3_closetitle ? "title='" + o3_closetitle +"'" : "") + " onclick";
	if (o3_capicon != "") {
	  nameId = ' hspace = \"5\"'+' align = \"middle\" alt = \"\"';
	  if (typeof o3_dragimg != 'undefined' && o3_dragimg) nameId =' hspace=\"5\"'+' name=\"'+o3_dragimg+'\" id=\"'+o3_dragimg+'\" align=\"middle\" alt=\"Drag Enabled\" title=\"Drag Enabled\"';
	  o3_capicon = '<img src=\"'+o3_capicon+'\"'+nameId+' />';
	}

	if (close != "")
		closing = '<td '+(!o3_compatmode && o3_closefontclass ? 'class="'+o3_closefontclass : 'align="RIGHT')+'"><a href="javascript:return '+fnRef+'cClick();"'+((o3_compatmode && o3_closefontclass) ? ' class="' + o3_closefontclass + '" ' : ' ')+closeevent+'="return '+fnRef+'cClick();">'+(o3_closefontclass ? '' : wrapStr(0,o3_closesize,'close'))+close+(o3_closefontclass ? '' : wrapStr(1,o3_closesize,'close'))+'</a></td>';
	txt = '<table width="'+o3_width+ '" border="0" cellpadding="'+o3_border+'" cellspacing="0" '+(o3_bgclass ? 'class="'+o3_bgclass+'"' : o3_bgcolor+' '+o3_bgbackground+' '+o3_height)+'><tr><td><table width="100%" border="0" cellpadding="2" cellspacing="0"><tr><td'+(o3_captionfontclass ? ' class="'+o3_captionfontclass+'">' : '>')+(o3_captionfontclass ? '' : '<b>'+wrapStr(0,o3_captionsize,'caption'))+o3_capicon+title+(o3_captionfontclass ? '' : wrapStr(1,o3_captionsize)+'</b>')+'</td>'+closing+'</tr></table><table width="100%" border="0" '+((olNs4||!cpIsMultiple) ? 'cellpadding="'+o3_cellpad+'" ' : '')+'cellspacing="0" '+(o3_fgclass ? 'class="'+o3_fgclass+'"' : o3_fgcolor+' '+o3_fgbackground+' '+o3_height)+'><tr><td valign="TOP"'+(o3_textfontclass ? ' class="'+o3_textfontclass+'">' :((!olNs4&&cpIsMultiple) ? ' style="'+setCellPadStr(o3_cellpad)+'">' : '>'))+(o3_textfontclass ? '' : wrapStr(0,o3_textsize,'text'))+text+(o3_textfontclass ? '' : wrapStr(1,o3_textsize)) + '</td></tr></table></td></tr></table>';

	set_background("");
	return txt;
}

// Sets the background picture,padding and lots more. :)
function ol_content_background(text,picture,hasfullhtml) {
	if (hasfullhtml) {
		txt=text;
	} else {
		txt='<table width="'+o3_width+'" border="0" cellpadding="0" cellspacing="0" height="'+o3_height+'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'+o3_padxl+'"></td><td valign="TOP" width="'+(o3_width-o3_padxl-o3_padxr)+(o3_textfontclass ? '" class="'+o3_textfontclass : '')+'">'+(o3_textfontclass ? '' : wrapStr(0,o3_textsize,'text'))+text+(o3_textfontclass ? '' : wrapStr(1,o3_textsize))+'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'+o3_padyb+'"></td></tr></table>';
	}

	set_background(picture);
	return txt;
}

// Loads a picture into the div.
function set_background(pic) {
	if (pic == "") {
		if (olNs4) {
			over.background.src = null;
		} else if (over.style) {
			over.style.backgroundImage = "none";
		}
	} else {
		if (olNs4) {
			over.background.src = pic;
		} else if (over.style) {
			over.style.width=o3_width + 'px';
			over.style.backgroundImage = "url("+pic+")";
		}
	}
}

////////
// HANDLING FUNCTIONS
////////
var olShowId=-1;

// Displays the popup
function disp(statustext) {
	runHook("disp", FBEFORE);

	if (o3_allowmove == 0) {
		runHook("placeLayer", FREPLACE);
		(olNs6&&olShowId<0) ? olShowId=setTimeout("runHook('showObject', FREPLACE, over)", 1) : runHook("showObject", FREPLACE, over);
		o3_allowmove = (o3_sticky || o3_followmouse==0) ? 0 : 1;
	}

	runHook("disp", FAFTER);

	if (statustext != "") self.status = statustext;
}

// Creates the actual popup structure
function createPopup(lyrContent){
	runHook("createPopup", FBEFORE);

	if (o3_wrap) {
		var wd,ww,theObj = (olNs4 ? over : over.style);
		theObj.top = theObj.left = ((olIe4&&!olOp) ? 0 : -10000) + (!olNs4 ? 'px' : 0);
		layerWrite(lyrContent);
		wd = (olNs4 ? over.clip.width : over.offsetWidth);
		if (wd > (ww=windowWidth())) {
			lyrContent=lyrContent.replace(/\&nbsp;/g, ' ');
			o3_width=ww;
			o3_wrap=0;
		}
	}

	layerWrite(lyrContent);

	// Have to set o3_width for placeLayer() routine if o3_wrap is turned on
	if (o3_wrap) o3_width=(olNs4 ? over.clip.width : over.offsetWidth);

	runHook("createPopup", FAFTER, lyrContent);

	return true;
}

// Decides where we want the popup.
function placeLayer() {
	var placeX, placeY, widthFix = 0;

	// HORIZONTAL PLACEMENT, re-arranged to work in Safari
	if (o3_frame.innerWidth) widthFix=18;
	iwidth = windowWidth();

	// Horizontal scroll offset
	winoffset=(olIe4) ? eval('o3_frame.'+docRoot+'.scrollLeft') : o3_frame.pageXOffset;

	placeX = runHook('horizontalPlacement',FCHAIN,iwidth,winoffset,widthFix);

	// VERTICAL PLACEMENT, re-arranged to work in Safari
	if (o3_frame.innerHeight) {
		iheight=o3_frame.innerHeight;
	} else if (eval('o3_frame.'+docRoot)&&eval("typeof o3_frame."+docRoot+".clientHeight=='number'")&&eval('o3_frame.'+docRoot+'.clientHeight')) {
		iheight=eval('o3_frame.'+docRoot+'.clientHeight');
	}

	// Vertical scroll offset
	scrolloffset=(olIe4) ? eval('o3_frame.'+docRoot+'.scrollTop') : o3_frame.pageYOffset;
	placeY = runHook('verticalPlacement',FCHAIN,iheight,scrolloffset);

	// Actually move the object.
	repositionTo(over, placeX, placeY);
}

// Moves the layer
olMouseMove = $overlib.olMouseMove = function(e) {
	var e = (e) ? e : event;

	if (e.pageX) {
		o3_x = e.pageX;
		o3_y = e.pageY;
	} else if (e.clientX) {
		o3_x = eval('e.clientX+o3_frame.'+docRoot+'.scrollLeft');
		o3_y = eval('e.clientY+o3_frame.'+docRoot+'.scrollTop');
	}

	if (o3_allowmove == 1) runHook("placeLayer", FREPLACE);

	// MouseOut handler
	if (hoveringSwitch && !olNs4 && runHook("cursorOff", FREPLACE)) {
		(olHideDelay ? hideDelay(olHideDelay) : cClick());
		hoveringSwitch = !hoveringSwitch;
	}
}

// Fake function for 3.0 users.
function no_overlib() { return ver3fix; }

// Capture the mouse and chain other scripts.
function olMouseCapture() {
	capExtent = document;
	var fN, str = '', l, k, f, wMv, sS, mseHandler = olMouseMove;
	var re = /function[ ]*(\w*)\(/;

	wMv = (!olIe4 && window.onmousemove);
	if (document.onmousemove || wMv) {
		if (wMv) capExtent = window;
		f = capExtent.onmousemove.toString();
		fN = f.match(re);
		if (fN == null) {
			str = f+'(e); ';
		} else if (fN[1] == 'anonymous' || fN[1] == 'olMouseMove' || (wMv && fN[1] == 'onmousemove')) {
			if (!olOp && wMv) {
				l = f.indexOf('{')+1;
				k = f.lastIndexOf('}');
				sS = f.substring(l,k);
				if ((l = sS.indexOf('(')) != -1) {
					sS = sS.substring(0,l).replace(/^\s+/,'').replace(/\s+$/,'');
					if (eval("typeof " + sS + " == 'undefined'")) window.onmousemove = null;
					else str = sS + '(e);';
				}
			}
			if (!str) {
				olCheckMouseCapture = false;
				return;
			}
		} else {
			if (fN[1]) str = fN[1]+'(e); ';
			else {
				l = f.indexOf('{')+1;
				k = f.lastIndexOf('}');
				str = f.substring(l,k) + '\n';
			}
		}
		str += 'olMouseMove(e); ';
		mseHandler = new Function('e', str);
	}

	capExtent.onmousemove = mseHandler;
	if (olNs4) capExtent.captureEvents(Event.MOUSEMOVE);
}

////////
// PARSING FUNCTIONS
////////

// Does the actual command parsing.
function parseTokens(pf, ar) {
	// What the next argument is expected to be.
	var v, i, mode=-1, par = (pf != 'ol_');
	var fnMark = (par && !ar.length ? 1 : 0);

	for (i = 0; i < ar.length; i++) {
		if (mode < 0) {
			// Arg is maintext,unless its a number between pmStart and pmUpper
			// then its a command.
			if (typeof ar[i] == 'number' && ar[i] > pmStart && ar[i] < pmUpper) {
				fnMark = (par ? 1 : 0);
				i--;   // backup one so that the next block can parse it
			} else {
				switch(pf) {
					case 'ol_':
						ol_text = ar[i].toString();
						break;
					default:
						o3_text=ar[i].toString();
				}
			}
			mode = 0;
		} else {
			// Note: NS4 doesn't like switch cases with vars.
			if (ar[i] >= pmCount || ar[i]==DONOTHING) { continue; }
			if (ar[i]==INARRAY) { fnMark = 0; eval(pf+'text=ol_texts['+ar[++i]+'].toString()'); continue; }
			if (ar[i]==CAPARRAY) { eval(pf+'cap=ol_caps['+ar[++i]+'].toString()'); continue; }
			if (ar[i]==STICKY) { if (pf!='ol_') eval(pf+'sticky=1'); continue; }
			if (ar[i]==BACKGROUND) { eval(pf+'background="'+ar[++i]+'"'); continue; }
			if (ar[i]==NOCLOSE) { if (pf!='ol_') opt_NOCLOSE(); continue; }
			if (ar[i]==CAPTION) { eval(pf+"cap='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==CENTER || ar[i]==LEFT || ar[i]==RIGHT) { eval(pf+'hpos='+ar[i]); if(pf!='ol_') olHautoFlag=1; continue; }
			if (ar[i]==OFFSETX) { eval(pf+'offsetx='+ar[++i]); continue; }
			if (ar[i]==OFFSETY) { eval(pf+'offsety='+ar[++i]); continue; }
			if (ar[i]==FGCOLOR) { eval(pf+'fgcolor="'+ar[++i]+'"'); continue; }
			if (ar[i]==BGCOLOR) { eval(pf+'bgcolor="'+ar[++i]+'"'); continue; }
			if (ar[i]==TEXTCOLOR) { eval(pf+'textcolor="'+ar[++i]+'"'); continue; }
			if (ar[i]==CAPCOLOR) { eval(pf+'capcolor="'+ar[++i]+'"'); continue; }
			if (ar[i]==CLOSECOLOR) { eval(pf+'closecolor="'+ar[++i]+'"'); continue; }
			if (ar[i]==WIDTH) { eval(pf+'width='+ar[++i]); continue; }
			if (ar[i]==BORDER) { eval(pf+'border='+ar[++i]); continue; }
			if (ar[i]==CELLPAD) { i=opt_MULTIPLEARGS(++i,ar,(pf+'cellpad')); continue; }
			if (ar[i]==STATUS) { eval(pf+"status='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==AUTOSTATUS) { eval(pf +'autostatus=('+pf+'autostatus == 1) ? 0 : 1'); continue; }
			if (ar[i]==AUTOSTATUSCAP) { eval(pf +'autostatus=('+pf+'autostatus == 2) ? 0 : 2'); continue; }
			if (ar[i]==HEIGHT) { eval(pf+'height='+pf+'aboveheight='+ar[++i]); continue; } // Same param again.
			if (ar[i]==CLOSETEXT) { eval(pf+"close='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==SNAPX) { eval(pf+'snapx='+ar[++i]); continue; }
			if (ar[i]==SNAPY) { eval(pf+'snapy='+ar[++i]); continue; }
			if (ar[i]==FIXX) { eval(pf+'fixx='+ar[++i]); continue; }
			if (ar[i]==FIXY) { eval(pf+'fixy='+ar[++i]); continue; }
			if (ar[i]==RELX) { eval(pf+'relx='+ar[++i]); continue; }
			if (ar[i]==RELY) { eval(pf+'rely='+ar[++i]); continue; }
			if (ar[i]==FGBACKGROUND) { eval(pf+'fgbackground="'+ar[++i]+'"'); continue; }
			if (ar[i]==BGBACKGROUND) { eval(pf+'bgbackground="'+ar[++i]+'"'); continue; }
			if (ar[i]==PADX) { eval(pf+'padxl='+ar[++i]); eval(pf+'padxr='+ar[++i]); continue; }
			if (ar[i]==PADY) { eval(pf+'padyt='+ar[++i]); eval(pf+'padyb='+ar[++i]); continue; }
			if (ar[i]==FULLHTML) { if (pf!='ol_') eval(pf+'fullhtml=1'); continue; }
			if (ar[i]==BELOW || ar[i]==ABOVE) { eval(pf+'vpos='+ar[i]); if (pf!='ol_') olVautoFlag=1; continue; }
			if (ar[i]==CAPICON) { eval(pf+'capicon="'+ar[++i]+'"'); continue; }
			if (ar[i]==TEXTFONT) { eval(pf+"textfont='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==CAPTIONFONT) { eval(pf+"captionfont='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==CLOSEFONT) { eval(pf+"closefont='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==TEXTSIZE) { eval(pf+'textsize="'+ar[++i]+'"'); continue; }
			if (ar[i]==CAPTIONSIZE) { eval(pf+'captionsize="'+ar[++i]+'"'); continue; }
			if (ar[i]==CLOSESIZE) { eval(pf+'closesize="'+ar[++i]+'"'); continue; }
			if (ar[i]==TIMEOUT) { eval(pf+'timeout='+ar[++i]); continue; }
			if (ar[i]==FUNCTION) { if (pf=='ol_') { if (typeof ar[i+1]!='number') { v=ar[++i]; ol_function=(typeof v=='function' ? v : null); }} else {fnMark = 0; v = null; if (typeof ar[i+1]!='number') v = ar[++i];  opt_FUNCTION(v); } continue; }
			if (ar[i]==DELAY) { eval(pf+'delay='+ar[++i]); continue; }
			if (ar[i]==HAUTO) { eval(pf+'hauto=('+pf+'hauto == 0) ? 1 : 0'); continue; }
			if (ar[i]==VAUTO) { eval(pf+'vauto=('+pf+'vauto == 0) ? 1 : 0'); continue; }
			if (ar[i]==CLOSECLICK) { eval(pf +'closeclick=('+pf+'closeclick == 0) ? 1 : 0'); continue; }
			if (ar[i]==WRAP) { eval(pf +'wrap=('+pf+'wrap == 0) ? 1 : 0'); continue; }
			if (ar[i]==FOLLOWMOUSE) { eval(pf +'followmouse=('+pf+'followmouse == 1) ? 0 : 1'); continue; }
			if (ar[i]==MOUSEOFF) { eval(pf +'mouseoff=('+pf+'mouseoff==0) ? 1 : 0'); v=ar[i+1]; if (pf != 'ol_' && eval(pf+'mouseoff') && typeof v == 'number' && (v < pmStart || v > pmUpper)) olHideDelay=ar[++i]; continue; }
			if (ar[i]==CLOSETITLE) { eval(pf+"closetitle='"+escSglQuote(ar[++i])+"'"); continue; }
			if (ar[i]==CSSOFF||ar[i]==CSSCLASS) { eval(pf+'css='+ar[i]); continue; }
			if (ar[i]==COMPATMODE) { eval(pf+'compatmode=('+pf+'compatmode==0) ? 1 : 0'); continue; }
			if (ar[i]==FGCLASS) { eval(pf+'fgclass="'+ar[++i]+'"'); continue; }
			if (ar[i]==BGCLASS) { eval(pf+'bgclass="'+ar[++i]+'"'); continue; }
			if (ar[i]==TEXTFONTCLASS) { eval(pf+'textfontclass="'+ar[++i]+'"'); continue; }
			if (ar[i]==CAPTIONFONTCLASS) { eval(pf+'captionfontclass="'+ar[++i]+'"'); continue; }
			if (ar[i]==CLOSEFONTCLASS) { eval(pf+'closefontclass="'+ar[++i]+'"'); continue; }
			i = parseCmdLine(pf, i, ar);
		}
	}

	if (fnMark && o3_function) o3_text = o3_function();

	if ((pf == 'o3_') && o3_wrap) {
		o3_width = 0;

		var tReg=/<.*\n*>/ig;
		if (!tReg.test(o3_text)) o3_text = o3_text.replace(/[ ]+/g, '&nbsp;');
		if (!tReg.test(o3_cap))o3_cap = o3_cap.replace(/[ ]+/g, '&nbsp;');
	}
	if ((pf == 'o3_') && o3_sticky) {
		if (!o3_close && (o3_frame != ol_frame)) o3_close = ol_close;
		if (o3_mouseoff && (o3_frame == ol_frame)) opt_NOCLOSE(' ');
	}
}


////////
// LAYER FUNCTIONS
////////

// Writes to a layer
function layerWrite(txt) {
	txt += "\n";
	if (olNs4) {
		var lyr = o3_frame.document.layers['overDiv'].document
		lyr.write(txt)
		lyr.close()
	} else if (typeof over.innerHTML != 'undefined') {
		if (olIe5 && isMac) over.innerHTML = '';
		over.innerHTML = txt;
	} else {
		range = o3_frame.document.createRange();
		range.setStartAfter(over);
		domfrag = range.createContextualFragment(txt);

		while (over.hasChildNodes()) {
			over.removeChild(over.lastChild);
		}

		over.appendChild(domfrag);
	}
}

// Make an object visible
function showObject(obj) {
	runHook("showObject", FBEFORE);

	var theObj=(olNs4 ? obj : obj.style);
	theObj.visibility = 'visible';

	runHook("showObject", FAFTER);
}

// Hides an object
function hideObject(obj) {
        if (!obj) {
            try { throw 0; } catch(e) {};
            return;
        }

	runHook("hideObject", FBEFORE);

	var theObj=(olNs4 ? obj : obj.style);
	if (olNs6 && olShowId>0) { clearTimeout(olShowId); olShowId=0; }
	theObj.visibility = 'hidden';
	theObj.top = theObj.left = ((olIe4&&!olOp) ? 0 : -10000) + (!olNs4 ? 'px' : 0);

	if (o3_timerid > 0) clearTimeout(o3_timerid);
	if (o3_delayid > 0) clearTimeout(o3_delayid);

	o3_timerid = 0;
	o3_delayid = 0;
	self.status = "";

	if (obj.onmouseout||obj.onmouseover) {
		if (olNs4) obj.releaseEvents(Event.MOUSEOUT || Event.MOUSEOVER);
		obj.onmouseout = obj.onmouseover = null;
	}

	runHook("hideObject", FAFTER);
}

// Move a layer
function repositionTo(obj, xL, yL) {
	var theObj=(olNs4 ? obj : obj.style);
	theObj.left = xL + (!olNs4 ? 'px' : 0);
	theObj.top = yL + (!olNs4 ? 'px' : 0);
}

// Check position of cursor relative to overDiv DIVision; mouseOut function
function cursorOff() {
	var left = parseInt(over.style.left);
	var top = parseInt(over.style.top);
	var right = left + (over.offsetWidth >= parseInt(o3_width) ? over.offsetWidth : parseInt(o3_width));
	var bottom = top + (over.offsetHeight >= o3_aboveheight ? over.offsetHeight : o3_aboveheight);

	if (o3_x < left || o3_x > right || o3_y < top || o3_y > bottom) return true;

	return false;
}


////////
// COMMAND FUNCTIONS
////////

// Calls callme or the default function.
function opt_FUNCTION(callme) {
	o3_text = (callme ? (typeof callme=='string' ? (/.+\(.*\)/.test(callme) ? eval(callme) : callme) : callme()) : (o3_function ? o3_function() : 'No Function'));

	return 0;
}

// Handle hovering
function opt_NOCLOSE(unused) {
	if (!unused) o3_close = "";

	if (olNs4) {
		over.captureEvents(Event.MOUSEOUT || Event.MOUSEOVER);
		over.onmouseover = function () { if (o3_timerid > 0) { clearTimeout(o3_timerid); o3_timerid = 0; } }
		over.onmouseout = function (e) { if (olHideDelay) hideDelay(olHideDelay); else cClick(e); }
	} else {
		over.onmouseover = function () {hoveringSwitch = true; if (o3_timerid > 0) { clearTimeout(o3_timerid); o3_timerid =0; } }
	}

	return 0;
}

// Function to scan command line arguments for multiples
function opt_MULTIPLEARGS(i, args, parameter) {
  var k=i, re, pV, str='';

  for(k=i; k<args.length; k++) {
		if(typeof args[k] == 'number' && args[k]>pmStart) break;
		str += args[k] + ',';
	}
	if (str) str = str.substring(0,--str.length);

	k--;  // reduce by one so the for loop this is in works correctly
	pV=(olNs4 && /cellpad/i.test(parameter)) ? str.split(',')[0] : str;
	eval(parameter + '="' + pV + '"');

	return k;
}

// Remove &nbsp; in texts when done.
function nbspCleanup() {
	if (o3_wrap) {
		o3_text = o3_text.replace(/\&nbsp;/g, ' ');
		o3_cap = o3_cap.replace(/\&nbsp;/g, ' ');
	}
}

// Escape embedded single quotes in text strings
function escSglQuote(str) {
  return str.toString().replace(/\'/g,"\\'");
}

// Onload handler for window onload event
function OLonLoad_handler(e) {
	var re = /\w+\(.*\)[;\s]+/g, olre = /overlib\(|nd\(|cClick\(/, fn, l, i;

	if(!olLoaded) olLoaded=1;

  // Remove it for Gecko based browsers
	if(window.removeEventListener && e.eventPhase == 3) window.removeEventListener("load",OLonLoad_handler,false);
	else if(window.detachEvent) { // and for IE and Opera 4.x but execute calls to overlib, nd, or cClick()
		window.detachEvent("onload",OLonLoad_handler);
		var fN = document.body.getAttribute('onload');
		if (fN) {
			fN=fN.toString().match(re);
			if (fN && fN.length) {
				for (i=0; i<fN.length; i++) {
					if (/anonymous/.test(fN[i])) continue;
					while((l=fN[i].search(/\)[;\s]+/)) != -1) {
						fn=fN[i].substring(0,l+1);
						fN[i] = fN[i].substring(l+2);
						if (olre.test(fn)) eval(fn);
					}
				}
			}
		}
	}
}

// Wraps strings in Layer Generation Functions with the correct tags
//    endWrap true(if end tag) or false if start tag
//    fontSizeStr - font size string such as '1' or '10px'
//    whichString is being wrapped -- 'text', 'caption', or 'close'
function wrapStr(endWrap,fontSizeStr,whichString) {
	var fontStr, fontColor, isClose=((whichString=='close') ? 1 : 0), hasDims=/[%\-a-z]+$/.test(fontSizeStr);
	fontSizeStr = (olNs4) ? (!hasDims ? fontSizeStr : '1') : fontSizeStr;
	if (endWrap) return (hasDims&&!olNs4) ? (isClose ? '</span>' : '</div>') : '</font>';
	else {
		fontStr='o3_'+whichString+'font';
		fontColor='o3_'+((whichString=='caption')? 'cap' : whichString)+'color';
		return (hasDims&&!olNs4) ? (isClose ? '<span style="font-family: '+quoteMultiNameFonts(eval(fontStr))+'; color: '+eval(fontColor)+'; font-size: '+fontSizeStr+';">' : '<div style="font-family: '+quoteMultiNameFonts(eval(fontStr))+'; color: '+eval(fontColor)+'; font-size: '+fontSizeStr+';">') : '<font face="'+eval(fontStr)+'" color="'+eval(fontColor)+'" size="'+(parseInt(fontSizeStr)>7 ? '7' : fontSizeStr)+'">';
	}
}

// Quotes Multi word font names; needed for CSS Standards adherence in font-family
function quoteMultiNameFonts(theFont) {
	var v, pM=theFont.split(',');
	for (var i=0; i<pM.length; i++) {
		v=pM[i];
		v=v.replace(/^\s+/,'').replace(/\s+$/,'');
		if(/\s/.test(v) && !/[\'\"]/.test(v)) {
			v="\'"+v+"\'";
			pM[i]=v;
		}
	}
	return pM.join();
}

// dummy function which will be overridden
function isExclusive(args) {
	return false;
}

// Sets cellpadding style string value
function setCellPadStr(parameter) {
	var Str='', j=0, ary = new Array(), top, bottom, left, right;

	Str+='padding: ';
	ary=parameter.replace(/\s+/g,'').split(',');

	switch(ary.length) {
		case 2:
			top=bottom=ary[j];
			left=right=ary[++j];
			break;
		case 3:
			top=ary[j];
			left=right=ary[++j];
			bottom=ary[++j];
			break;
		case 4:
			top=ary[j];
			right=ary[++j];
			bottom=ary[++j];
			left=ary[++j];
			break;
	}

	Str+= ((ary.length==1) ? ary[0] + 'px;' : top + 'px ' + right + 'px ' + bottom + 'px ' + left + 'px;');

	return Str;
}

// function will delay close by time milliseconds
function hideDelay(time) {
	if (time&&!o3_delay) {
		if (o3_timerid > 0) clearTimeout(o3_timerid);

		o3_timerid=setTimeout("cClick()",(o3_timeout=time));
	}
}

// Was originally in the placeLayer() routine; separated out for future ease
function horizontalPlacement(browserWidth, horizontalScrollAmount, widthFix) {
	var placeX, iwidth=browserWidth, winoffset=horizontalScrollAmount;
	var parsedWidth = parseInt(o3_width);

	if (o3_fixx > -1 || o3_relx != null) {
		// Fixed position
		placeX=(o3_relx != null ? ( o3_relx < 0 ? winoffset +o3_relx+ iwidth - parsedWidth - widthFix : winoffset+o3_relx) : o3_fixx);
	} else {
		// If HAUTO, decide what to use.
		if (o3_hauto == 1) {
			if ((o3_x - winoffset) > (iwidth / 2)) {
				o3_hpos = LEFT;
			} else {
				o3_hpos = RIGHT;
			}
		}

		// From mouse
		if (o3_hpos == CENTER) { // Center
			placeX = o3_x+o3_offsetx-(parsedWidth/2);

			if (placeX < winoffset) placeX = winoffset;
		}

		if (o3_hpos == RIGHT) { // Right
			placeX = o3_x+o3_offsetx;

			if ((placeX+parsedWidth) > (winoffset+iwidth - widthFix)) {
				placeX = iwidth+winoffset - parsedWidth - widthFix;
				if (placeX < 0) placeX = 0;
			}
		}
		if (o3_hpos == LEFT) { // Left
			placeX = o3_x-o3_offsetx-parsedWidth;
			if (placeX < winoffset) placeX = winoffset;
		}

		// Snapping!
		if (o3_snapx > 1) {
			var snapping = placeX % o3_snapx;

			if (o3_hpos == LEFT) {
				placeX = placeX - (o3_snapx+snapping);
			} else {
				// CENTER and RIGHT
				placeX = placeX+(o3_snapx - snapping);
			}

			if (placeX < winoffset) placeX = winoffset;
		}
	}

	return placeX;
}

// was originally in the placeLayer() routine; separated out for future ease
function verticalPlacement(browserHeight,verticalScrollAmount) {
	var placeY, iheight=browserHeight, scrolloffset=verticalScrollAmount;
	var parsedHeight=(o3_aboveheight ? parseInt(o3_aboveheight) : (olNs4 ? over.clip.height : over.offsetHeight));

	if (o3_fixy > -1 || o3_rely != null) {
		// Fixed position
		placeY=(o3_rely != null ? (o3_rely < 0 ? scrolloffset+o3_rely+iheight - parsedHeight : scrolloffset+o3_rely) : o3_fixy);
	} else {
		// If VAUTO, decide what to use.
		if (o3_vauto == 1) {
			if ((o3_y - scrolloffset) > (iheight / 2) && o3_vpos == BELOW && (o3_y + parsedHeight + o3_offsety - (scrolloffset + iheight) > 0)) {
				o3_vpos = ABOVE;
			} else if (o3_vpos == ABOVE && (o3_y - (parsedHeight + o3_offsety) - scrolloffset < 0)) {
				o3_vpos = BELOW;
			}
		}

		// From mouse
		if (o3_vpos == ABOVE) {
			if (o3_aboveheight == 0) o3_aboveheight = parsedHeight;

			placeY = o3_y - (o3_aboveheight+o3_offsety);
			if (placeY < scrolloffset) placeY = scrolloffset;
		} else {
			// BELOW
			placeY = o3_y+o3_offsety;
		}

		// Snapping!
		if (o3_snapy > 1) {
			var snapping = placeY % o3_snapy;

			if (o3_aboveheight > 0 && o3_vpos == ABOVE) {
				placeY = placeY - (o3_snapy+snapping);
			} else {
				placeY = placeY+(o3_snapy - snapping);
			}

			if (placeY < scrolloffset) placeY = scrolloffset;
		}
	}

	return placeY;
}

// checks positioning flags
function checkPositionFlags() {
	if (olHautoFlag) olHautoFlag = o3_hauto=0;
	if (olVautoFlag) olVautoFlag = o3_vauto=0;
	return true;
}

// get Browser window width
function windowWidth() {
	var w;
	if (o3_frame.innerWidth) w=o3_frame.innerWidth;
	else if (eval('o3_frame.'+docRoot)&&eval("typeof o3_frame."+docRoot+".clientWidth=='number'")&&eval('o3_frame.'+docRoot+'.clientWidth'))
		w=eval('o3_frame.'+docRoot+'.clientWidth');
	return w;
}

// create the div container for popup content if it doesn't exist
function createDivContainer(id,frm,zValue) {
	id = (id || 'overDiv'), frm = (frm || o3_frame), zValue = (zValue || 1000);
	var objRef, divContainer = layerReference(id);

	if (divContainer == null) {
		if (olNs4) {
			divContainer = frm.document.layers[id] = new Layer(window.innerWidth, frm);
			objRef = divContainer;
		} else {
			var body = (olIe4 ? frm.document.all.tags('BODY')[0] : frm.document.getElementsByTagName("BODY")[0]);
			if (olIe4&&!document.getElementById) {
				body.insertAdjacentHTML("beforeEnd",'<div id="'+id+'"></div>');
				divContainer=layerReference(id);
			} else {
				divContainer = frm.document.createElement("DIV");
				divContainer.id = id;
				body.appendChild(divContainer);
			}
			objRef = divContainer.style;
		}

		objRef.position = 'absolute';
		objRef.visibility = 'hidden';
		objRef.zIndex = zValue;
		if (olIe4&&!olOp) objRef.left = objRef.top = '0px';
		else objRef.left = objRef.top =  -10000 + (!olNs4 ? 'px' : 0);
	}

	return divContainer;
}

// get reference to a layer with ID=id
function layerReference(id) {
	return (olNs4 ? o3_frame.document.layers[id] : (document.all ? o3_frame.document.all[id] : o3_frame.document.getElementById(id)));
}
////////
//  UTILITY FUNCTIONS
////////

// Checks if something is a function.
function isFunction(fnRef) {
	var rtn = true;

	if (typeof fnRef == 'object') {
		for (var i = 0; i < fnRef.length; i++) {
			if (typeof fnRef[i]=='function') continue;
			rtn = false;
			break;
		}
	} else if (typeof fnRef != 'function') {
		rtn = false;
	}

	return rtn;
}

// Converts an array into an argument string for use in eval.
function argToString(array, strtInd, argName) {
	var jS = strtInd, aS = '', ar = array;
	argName=(argName ? argName : 'ar');

	if (ar.length > jS) {
		for (var k = jS; k < ar.length; k++) aS += argName+'['+k+'], ';
		aS = aS.substring(0, aS.length-2);
	}

	return aS;
}

// Places a hook in the correct position in a hook point.
function reOrder(hookPt, fnRef, order) {
	var newPt = new Array(), match, i, j;

	if (!order || typeof order == 'undefined' || typeof order == 'number') return hookPt;

	if (typeof order=='function') {
		if (typeof fnRef=='object') {
			newPt = newPt.concat(fnRef);
		} else {
			newPt[newPt.length++]=fnRef;
		}

		for (i = 0; i < hookPt.length; i++) {
			match = false;
			if (typeof fnRef == 'function' && hookPt[i] == fnRef) {
				continue;
			} else {
				for(j = 0; j < fnRef.length; j++) if (hookPt[i] == fnRef[j]) {
					match = true;
					break;
				}
			}
			if (!match) newPt[newPt.length++] = hookPt[i];
		}

		newPt[newPt.length++] = order;

	} else if (typeof order == 'object') {
		if (typeof fnRef == 'object') {
			newPt = newPt.concat(fnRef);
		} else {
			newPt[newPt.length++] = fnRef;
		}

		for (j = 0; j < hookPt.length; j++) {
			match = false;
			if (typeof fnRef == 'function' && hookPt[j] == fnRef) {
				continue;
			} else {
				for (i = 0; i < fnRef.length; i++) if (hookPt[j] == fnRef[i]) {
					match = true;
					break;
				}
			}
			if (!match) newPt[newPt.length++]=hookPt[j];
		}

		for (i = 0; i < newPt.length; i++) hookPt[i] = newPt[i];
		newPt.length = 0;

		for (j = 0; j < hookPt.length; j++) {
			match = false;
			for (i = 0; i < order.length; i++) {
				if (hookPt[j] == order[i]) {
					match = true;
					break;
				}
			}
			if (!match) newPt[newPt.length++] = hookPt[j];
		}
		newPt = newPt.concat(order);
	}

	hookPt = newPt;

	return hookPt;
}

////////
//  PLUGIN ACTIVATION FUNCTIONS
////////

// Runs plugin functions to set runtime variables.
function setRunTimeVariables(){
	if (typeof runTime != 'undefined' && runTime.length) {
		for (var k = 0; k < runTime.length; k++) {
			runTime[k]();
		}
	}
}

// Runs plugin functions to parse commands.
function parseCmdLine(pf, i, args) {
	if (typeof cmdLine != 'undefined' && cmdLine.length) {
		for (var k = 0; k < cmdLine.length; k++) {
			var j = cmdLine[k](pf, i, args);
			if (j >- 1) {
				i = j;
				break;
			}
		}
	}

	return i;
}

// Runs plugin functions to do things after parse.
function postParseChecks(pf,args){
	if (typeof postParse != 'undefined' && postParse.length) {
		for (var k = 0; k < postParse.length; k++) {
			if (postParse[k](pf,args)) continue;
			return false;  // end now since have an error
		}
	}
	return true;
}


////////
//  PLUGIN REGISTRATION FUNCTIONS
////////

// Registers commands and creates constants.
function registerCommands(cmdStr) {
	if (typeof cmdStr!='string') return;

	var pM = cmdStr.split(',');
	pms = pms.concat(pM);

	for (var i = 0; i< pM.length; i++) {
		eval(pM[i].toUpperCase()+'='+pmCount++);
	}
}

// Registers no-parameter commands
function registerNoParameterCommands(cmdStr) {
	if (!cmdStr && typeof cmdStr != 'string') return;
	pmt=(!pmt) ? cmdStr : pmt + ',' + cmdStr;
}

// Register a function to hook at a certain point.
function registerHook(fnHookTo, fnRef, hookType, optPm) {
	var hookPt, last = typeof optPm;

	if (fnHookTo == 'plgIn'||fnHookTo == 'postParse') return;
	if (typeof hookPts[fnHookTo] == 'undefined') hookPts[fnHookTo] = new FunctionReference();

	hookPt = hookPts[fnHookTo];

	if (hookType != null) {
		if (hookType == FREPLACE) {
			hookPt.ovload = fnRef;  // replace normal overlib routine
			if (fnHookTo.indexOf('ol_content_') > -1) hookPt.alt[pms[CSSOFF-1-pmStart]]=fnRef;

		} else if (hookType == FBEFORE || hookType == FAFTER) {
			var hookPt=(hookType == 1 ? hookPt.before : hookPt.after);

			if (typeof fnRef == 'object') {
				hookPt = hookPt.concat(fnRef);
			} else {
				hookPt[hookPt.length++] = fnRef;
			}

			if (optPm) hookPt = reOrder(hookPt, fnRef, optPm);

		} else if (hookType == FALTERNATE) {
			if (last=='number') hookPt.alt[pms[optPm-1-pmStart]] = fnRef;
		} else if (hookType == FCHAIN) {
			hookPt = hookPt.chain;
			if (typeof fnRef=='object') hookPt=hookPt.concat(fnRef); // add other functions
			else hookPt[hookPt.length++]=fnRef;
		}

		return;
	}
}

// Register a function that will set runtime variables.
function registerRunTimeFunction(fn) {
	if (isFunction(fn)) {
		if (typeof fn == 'object') {
			runTime = runTime.concat(fn);
		} else {
			runTime[runTime.length++] = fn;
		}
	}
}

// Register a function that will handle command parsing.
function registerCmdLineFunction(fn){
	if (isFunction(fn)) {
		if (typeof fn == 'object') {
			cmdLine = cmdLine.concat(fn);
		} else {
			cmdLine[cmdLine.length++] = fn;
		}
	}
}

// Register a function that does things after command parsing.
function registerPostParseFunction(fn){
	if (isFunction(fn)) {
		if (typeof fn == 'object') {
			postParse = postParse.concat(fn);
		} else {
			postParse[postParse.length++] = fn;
		}
	}
}

////////
//  PLUGIN REGISTRATION FUNCTIONS
////////

// Runs any hooks registered.
function runHook(fnHookTo, hookType) {
	var l = hookPts[fnHookTo], k, rtnVal = null, optPm, arS, ar = arguments;

	if (hookType == FREPLACE) {
		arS = argToString(ar, 2);

		if (typeof l == 'undefined' || !(l = l.ovload)) rtnVal = eval(fnHookTo+'('+arS+')');
		else rtnVal = eval('l('+arS+')');

	} else if (hookType == FBEFORE || hookType == FAFTER) {
		if (typeof l != 'undefined') {
			l=(hookType == 1 ? l.before : l.after);

			if (l.length) {
				arS = argToString(ar, 2);
				for (var k = 0; k < l.length; k++) eval('l[k]('+arS+')');
			}
		}
	} else if (hookType == FALTERNATE) {
		optPm = ar[2];
		arS = argToString(ar, 3);

		if (typeof l == 'undefined' || (l = l.alt[pms[optPm-1-pmStart]]) == 'undefined') {
			rtnVal = eval(fnHookTo+'('+arS+')');
		} else {
			rtnVal = eval('l('+arS+')');
		}
	} else if (hookType == FCHAIN) {
		arS=argToString(ar,2);
		l=l.chain;

		for (k=l.length; k > 0; k--) if((rtnVal=eval('l[k-1]('+arS+')'))!=void(0)) break;
	}

	return rtnVal;
}

////////
// OBJECT CONSTRUCTORS
////////

// Object for handling hooks.
function FunctionReference() {
	this.ovload = null;
	this.before = new Array();
	this.after = new Array();
	this.alt = new Array();
	this.chain = new Array();
}

// Object for simple access to the overLIB version used.
// Examples: simpleversion:351 major:3 minor:5 revision:1
function Info(version, prerelease) {
	this.version = version;
	this.prerelease = prerelease;

	this.simpleversion = Math.round(this.version*100);
	this.major = parseInt(this.simpleversion / 100);
	this.minor = parseInt(this.simpleversion / 10) - this.major * 10;
	this.revision = parseInt(this.simpleversion) - this.major * 100 - this.minor * 10;
	this.meets = meets;
}

// checks for Core Version required
function meets(reqdVersion) {
	return (!reqdVersion) ? false : this.simpleversion >= Math.round(100*parseFloat(reqdVersion));
}


////////
// STANDARD REGISTRATIONS
////////
registerHook("ol_content_simple", ol_content_simple, FALTERNATE, CSSOFF);
registerHook("ol_content_caption", ol_content_caption, FALTERNATE, CSSOFF);
registerHook("ol_content_background", ol_content_background, FALTERNATE, CSSOFF);
registerHook("ol_content_simple", ol_content_simple, FALTERNATE, CSSCLASS);
registerHook("ol_content_caption", ol_content_caption, FALTERNATE, CSSCLASS);
registerHook("ol_content_background", ol_content_background, FALTERNATE, CSSCLASS);
registerPostParseFunction(checkPositionFlags);
registerHook("hideObject", nbspCleanup, FAFTER);
registerHook("horizontalPlacement", horizontalPlacement, FCHAIN);
registerHook("verticalPlacement", verticalPlacement, FCHAIN);
if (olNs4||(olIe5&&isMac)||olKq) olLoaded=1;
registerNoParameterCommands('sticky,autostatus,autostatuscap,fullhtml,hauto,vauto,closeclick,wrap,followmouse,mouseoff,compatmode');
///////
// ESTABLISH MOUSECAPTURING
///////

// Capture events, alt. diffuses the overlib function.
var olCheckMouseCapture=true;
if ((olNs4 || olNs6 || olIe4)) {
	olMouseCapture();
} else {
	overlib = no_overlib;
	nd = no_overlib;
	ver3fix = true;
}

/****************************************************************************
 * END overlib/overlib.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN instaview.js
 ****************************************************************************/

// $Id: instaview.js 1240 2006-02-24 11:51:03Z quarl $

// Synopsis:
//    foo.innerHTML = $instaview.convert("[[wiki]] markup");

// From User:Pilaf/instaview.js
//     Last update: 21:20, 11 February 2006 (UTC)

var $instaview = new Module('instaview.js');
$instaview.depend('md5.js', 'util.js', 'wikipage.js', 'wikins.js', 'wikiimg.js');

// Note: this file only contains the $instaview.convert library function; the
// functionality for annotating the 'edit' page is in instapreview.js.

/*
 * InstaView - a Mediawiki to HTML converter in JavaScript
 * Version 0.6
 * Copyright (C) Pedro Fayolle 2005
 * http://en.wikipedia.org/wiki/User:Pilaf
 * Distributed under the BSD license
 *
 * Changelog:
 *
 * 0.6
 * - Changed name to InstaView
 * - Some major code reorganizations and factored out some common functions
 * - Handled conversion of relative links (i.e. [[/foo]])
 * - Fixed misrendering of adjacent definition list items
 * - Fixed bug in table headings handling
 * - Changed date format in signatures to reflect Mediawiki's
 * - Fixed handling of [[:Image:...]]
 * - Updated MD5 function (hopefully it will work with UTF-8)
 * - Fixed bug in handling of links inside images
 *
 * To do:
 * - Better support for math
 * - Full support for nowiki
 * - Parser-based (against RegExp-based) inline wikicode handling (make it one pass)
 * - Support for templates (through AJAX)
 * - Support for coloured links (AJAX)
 */

// options
$instaview.options =
{
    default_thumb_width: 180,

    paths: {
        articles: '/wiki/',
        math: '/math/',
        magnify_icon: 'skins/common/images/magnify-clip.png'
    }
}

// define constants
$instaview.BLOCK_IMAGE = new RegExp('^\\[\\['+$wikins.Image+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');

$instaview.dump = function(from, to)
{
    if (typeof from == 'string') from = document.getElementById(from)
    if (typeof to == 'string') to = document.getElementById(to)
    to.innerHTML = this.convert(from.value)
}

$instaview.imgCounter = 0;

$instaview.convert = function(wiki)
{
    var     ll = (typeof wiki == 'string')? wiki.split(/\n/): wiki, // lines of wikicode
        o='',   // output
        p=0,    // paragraph flag
        $r  // result of passing $() a regexp

    // some shorthands
    function remain() { return ll.length }
    function sh() { return ll.shift() } // shift
    function ps(s) { o+=s } // push

    function f() // similar to C's printf, uses ? as placeholders, ?? to escape question marks
    {
        var i=1,a=arguments,f=a[0],o='',c,p
        for (;i<a.length; i++) if ((p=f.indexOf('?'))+1) {
            // allow character escaping
            i -= c=f.charAt(p+1)=='?'?1:0
            o += f.substring(0,p)+(c?'?':a[i])
            f=f.substr(p+1+c)
        } else break;
        return o+f
    }

    function strip_cr(s) { return s.replace(/\n\r/g,"\n").replace(/\r/g,'') }
    function html_entities(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;") }

    function max(a,b) { return (a>b)?a:b }
    function min(a,b) { return (a<b)?a:b }

    // return the first non matching character position between two strings
    function str_imatch(a, b)
    {
        for (var i=0, l=min(a.length, b.length); i<l; i++) if (a.charAt(i)!=b.charAt(i)) break
        return i
    }

    // compare against a string or regexp
    // if passed a regexp the result is stored in $r
    function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)) }

    function $$(c) { return ll[0]==c } // compare whole line
    function _(p) { return ll[0].charAt(p) } // return char at pos p

    function endl(s) { ps(s); sh() }

    function parse_list()
    {
        var prev='';

        while (remain() && $(/^([*#:;]+)(.*)$/)) {

            var l_match = $r

            sh()

            var ipos = str_imatch(prev, l_match[1])

            // close uncontinued lists
            for (var i=prev.length-1; i >= ipos; i--) {

                var pi = prev.charAt(i)

                if (pi=='*') ps('</ul>')
                else if (pi=='#') ps('</ol>')
                // close a dl only if the new item is not a dl item (:, ; or empty)
                else switch (l_match[1].charAt(i)) { case'':case'*':case'#': ps('</dl>') }
            }

            // open new lists
            for (var i=ipos; i<l_match[1].length; i++) {

                var li = l_match[1].charAt(i)

                if (li=='*') ps('<ul>')
                else if (li=='#') ps('<ol>')
                // open a new dl only if the prev item is not a dl item (:, ; or empty)
                else switch(prev.charAt(i)) { case'':case'*':case'#': ps('<dl>') }
            }

            switch (l_match[1].charAt(l_match[1].length-1)) {

                case '*': case '#':
                    ps('<li>' + parse_inline_nowiki(l_match[2])); break

                case ';':
                    ps('<dt>');

                    var dt_match;

                    // handle ;dt :dd format
                    if ((dt_match = l_match[2].match(/(.*?) (:.*?)$/))) {

                        ps(parse_inline_nowiki(dt_match[1]))
                        ll.unshift(dt_match[2])

                    } else ps(parse_inline_nowiki(l_match[2]))

                    break

                case ':':
                    ps('<dd>' + parse_inline_nowiki(l_match[2]))
            }

            prev=l_match[1]
        }

        // close remaining lists
        for (var i=prev.length-1; i>=0; i--)
            ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')))
    }

    function parse_table()
    {
        endl(f('<table?>', $(/^\{\|( .*)$/)? $r[1]: ''))

        for (;remain();) if ($('|')) switch (_(1)) {
            case '}': endl('</table>'); return
            case '-': endl(f('<tr ?>', $(/\|-*(.*)/)[1])); break
            default: parse_table_data()
        }
        else if ($('!')) parse_table_data()
        else sh()
    }

    function parse_table_data()
    {
        var td_line, match_i

        // 1: "|+", '|' or '+'
        // 2: ??
        // 3: attributes ??
        // TODO: finish commenting this regexp
        var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/)

        if (td_match[1] == '|+') ps('<caption');
        else ps('<t' + ((td_match[1]=='|')?'d':'h'))

        if (typeof td_match[3] != 'undefined') {

            ps(' ' + td_match[3])
            match_i = 4

        } else match_i = 2

        ps('>')

        if (td_match[1] != '|+') {

            // use || or !! as a cell separator depending on context
            // NOTE: when split() is passed a regexp make sure to use non-capturing brackets
            td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/)

            ps(parse_inline_nowiki(td_line.shift()))

            while (td_line.length) ll.unshift(td_match[1] + td_line.pop())

        } else ps(td_match[match_i])

        var tc = 0, td = []

        for (;remain(); td.push(sh()))
        if ($('|')) {
            if (!tc) break // we're at the outer-most level (no nested tables), skip to td parse
            else if (_(1)=='}') tc--
        }
        else if (!tc && $('!')) break
        else if ($('{|')) tc++

        if (td.length) ps($instaview.convert(td))
    }

    function parse_pre()
    {
        ps('<pre>')
        do endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); while (remain() && $(' '))
        ps('</pre>')
    }

    function parse_block_image()
    {
        ps(parse_image(sh()))
    }

    function parse_image(str)
    {
        // get what's in between "[[Image:" and "]]"
        var tag = str.substring($wikins.Image.length + 3, str.length - 2);

        var width;
        var attr = [], filename, caption = '';
        var thumb=0, frame=0, center=0;
        var align='';

        if (tag.match(/\|/)) {
            // manage nested links
            var nesting = 0;
            var last_attr;
            for (var i = tag.length-1; i > 0; i--) {
                if (tag.charAt(i) == '|' && !nesting) {
                    last_attr = tag.substr(i+1);
                    tag = tag.substring(0, i);
                    break;
                } else switch (tag.substr(i-1, 2)) {
                    case ']]':
                        nesting++;
                        i--;
                        break;
                    case '[[':
                        nesting--;
                        i--;
                }
            }

            attr = tag.split(/\s*\|\s*/);
            attr.push(last_attr);
            filename = attr.shift();

            var w_match;

            for (;attr.length; attr.shift()) {
                if ((w_match = attr[0].match(/^(\d*)px$/))) {
                    width = w_match[1];
                } else {
                    switch(attr[0]) {
                    case 'thumb':
                    case 'thumbnail':
                        thumb=true;
                    case 'frame':
                        frame=true;
                        break;
                    case 'none':
                    case 'right':
                    case 'left':
                        center=false;
                        align=attr[0];
                        break;
                    case 'center':
                        center=true;
                        align='none';
                        break;
                    default:
                        if (attr.length == 1) caption = attr[0];
                    }
                }
            }

        } else filename = tag;


        var o='';

        if (frame) {

            if (align=='') align = 'right';

            o += f("<div class='thumb t?'>", align);

            if (thumb) {
                if (!width) width = $instaview.options.default_thumb_width;

                o += f("<div style='width:?px;'>?", 2+width*1, make_image(filename, caption, width)) +
                    f("<div class='thumbcaption'><div class='magnify' style='float:right'><a href='?' class='internal' title='Enlarge'><img src='?'></a></div>?</div>",
                        $instaview.options.paths.articles + $wikins.Image + ':' + filename,
                        $instaview.options.paths.magnify_icon,
                        parse_inline_nowiki(caption)
                    )
            } else {
                o += '<div>' + make_image(filename, caption) + f("<div class='thumbcaption'>?</div>", parse_inline_nowiki(caption))
            }

            o += '</div></div>';

        } else if (align != '') {
            o += f("<div class='float?'><span>?</span></div>", align, make_image(filename, caption, width));
        } else {
            return make_image(filename, caption, width);
        }

        return center? f("<div class='center'>?</div>", o): o;
    }

    function parse_inline_nowiki(str)
    {
        var start, lastend=0
        var substart=0, nestlev=0, open, close, subloop;
        var html='';

        while (-1 != (start = str.indexOf('<nowiki>', substart))) {
            html += parse_inline_wiki(str.substring(lastend, start));
            start += 8;
            substart = start;
            subloop = true;
            do {
                open = str.indexOf('<nowiki>', substart);
                close = str.indexOf('</nowiki>', substart);
                if (close<=open || open==-1) {
                    if (close==-1) {
                        return html + html_entities(str.substr(start));
                    }
                    substart = close+9;
                    if (nestlev) {
                        nestlev--;
                    } else {
                        lastend = substart;
                        html += html_entities(str.substring(start, lastend-9));
                        subloop = false;
                    }
                } else {
                    substart = open+8;
                    nestlev++;
                }
            } while (subloop)
        }

        return html + parse_inline_wiki(str.substr(lastend));
    }

    function make_image(filename, caption, width)
    {
        var id = 'instaview-img-' + $instaview.imgCounter++;

        if (width) {
            var source = $wikiimg.imageThumbURL(filename, WikiPage.server, width);
        } else {
            var source = $wikiimg.imageURL(filename, WikiPage.server);
        }

        caption = $util.trimSpaces(strip_inline_wiki(caption));

        if (width) width = "width='" + width + "px'";

        var img = f("<img src='?' ? ?>", source, caption, width);

        // TODO: $wikiimg.makeAutoDlHtml
        // var img = f("<img id='?' ? ?>", id,
        //             caption ? "alt='" + caption + "'" : '',
        //             width);

        return f("<a class='image' ? href='?'>?</a>",
                 caption ? "title='" + caption + "'" : '',
                 $instaview.options.paths.articles + $wikins.Image + ':' + filename,
                 img);
    }

    function parse_inline_images(str)
    {
        var start, substart=0, nestlev=0;
        var loop, close, open, wiki, html;

        while (-1 != (start=str.indexOf('[[', substart))) {
            if(str.substr(start+2).match(RegExp('^' + $wikins.Image + ':','i'))) {
                loop=true;
                substart=start;
                do {
                    substart+=2;
                    close=str.indexOf(']]',substart);
                    open=str.indexOf('[[',substart);
                    if (close<=open||open==-1) {
                        if (close==-1) return str;
                        substart=close;
                        if (nestlev) {
                            nestlev--;
                        } else {
                            wiki=str.substring(start,close+2);
                            html=parse_image(wiki);
                            str=str.replace(wiki,html);
                            substart=start+html.length;
                            loop=false;
                        }
                    } else {
                        substart=open;
                        nestlev++;
                    }
                } while (loop)

            } else break;
        }

        return str;
    }

    function parse_inline_wiki(str)
    {
        var aux_match;

        str = parse_inline_images(str);

        while ((aux_match = str.match(/<(?:)math>(.*?)<\/math>/i))) {
            var math_md5 = $md5.hex_md5(aux_match[1]);
            str = str.replace(aux_match[0], f("<img src='?.png'>", $instaview.options.paths.math+math_md5));
        }

        // Build a Mediawiki-formatted date string
        var date = new Date;
        var minutes = date.getUTCMinutes();
        if (minutes < 10) minutes = '0' + minutes;
        var date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), $datetime.monthnames[date.getUTCMonth()], date.getUTCFullYear());

        // text formatting
        return str.
            replace(/'''''(.*?)''(.*?)'''/g, '<strong><em>$1</em>$2</strong>').
            replace(/'''''(.*?)'''(.*?)''/g, '<em><strong>$1</strong>$2</em>').
            replace(/'''(.*?)''(.*?)'''''/g, '<strong>$1<em>$2</em></strong>').
            replace(/'''(.*?)'''/g, '<strong>$1</strong>').
            replace(/''(.*?)''/g, '<em>$1</em>').

            // signatures
            replace(/~{5}(?!~)/g, date).
            replace(/~{4}(?!~)/g, wikiDoc.username+' '+date).
            replace(/~{3}(?!~)/g, wikiDoc.username).

            // [[:Category:...]], [[:Image:...]], etc...
            replace(RegExp('\\[\\[:((?:'+$wikins.Category+'|'+$wikins.Image+'|'+$wikins.interwiki+'):.*?)\\]\\]','gi'), "<a href='"+$instaview.options.paths.articles+"$1'>$1</a>").
            replace(RegExp('\\[\\[(?:'+$wikins.Category+'|'+$wikins.interwiki+'):.*?\\]\\]','gi'),'').

            // [[/Relative links]]
            replace(/\[\[(\/[^|]*?)\]\]/g, f("<a href='?$1'>$1</a>", location)).

            // [[/Replaced|Relative links]]
            replace(/\[\[(\/.*?)\|(.+?)\]\]/g, f("<a href='?$1'>$2</a>", location)).

            // [[Common links]]
            replace(/\[\[([^|]*?)\]\](\w*)/g, f("<a href='?$1'>$1$2</a>", $instaview.options.paths.articles)).

            // [[Replaced|Links]]
            replace(/\[\[(.*?)\|([^\]]+?)\]\](\w*)/g, f("<a href='?$1'>$2$3</a>", $instaview.options.paths.articles)).

            // [[Stripped:Namespace|Namespace]]
            replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, f("<a href='?$1$2$3'>$2</a>", $instaview.options.paths.articles)).

            // External links
            replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, "<a href='$1:$2$3'>$4</a>").
            replace(/\[http:\/\/(.*?)\]/g, "<a href='http://$1'>[#]</a>").
            replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, "<a href='$1:$2$3'>$1:$2$3</a>").
            replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*)/g, "$1<a href='$2:$3$4'>$2:$3$4</a>").

            replace('__NOTOC__','').
            replace('__NOEDITSECTION__','');
    }

    function strip_inline_wiki(str)
    {
        return str
            .replace(/\[\[[^\]]*\|(.*?)\]\]/g,'$1')
            .replace(/\[\[(.*?)\]\]/g,'$1')
            .replace(/''(.*?)''/g,'$1');
    }

    // begin parsing
    for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
        p=0
        endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]))

    } else if ($(/^[*#:;]/)) {
        p=0
        parse_list()

    } else if ($(' ')) {
        p=0
        parse_pre()

    } else if ($('{|')) {
        p=0
        parse_table()

    } else if ($(/^----+$/)) {
        p=0
        endl('<hr/>')

    } else if ($($instaview.BLOCK_IMAGE)) {
        p=0
        parse_block_image()

    } else {

        if ($$('')) {
            if ((p = (remain()>1 && ll[1]==('')))) endl('<p><br/>');
        } else {
            if(!p) {
                ps('<p>')
                p=1
            }
            ps(parse_inline_nowiki(ll[0]) + ' ')
        }

        sh();
    }

    // from Lupin.
    // TODO: fix this in the code itself!

    var fixHTML = function(s) {
        // work around quoting bug:
        // wiki2html('[[Foo\'s "bar"]]') gives <a href='Foo's "bar"'> which doesn't do very well
        // we change this into <a href="Foo's %22bar%22">
        var splitted=s.parenSplit(/href='([^>]*)'/g);
        var ret='';
        for (var i=0; i<splitted.length; ++i) {
            if(i%2==0) { ret += splitted[i]; continue; }
            if(i%2==1) { ret += 'href="' + splitted[i].split('"').join('%22') + '"'; }
        }
        return ret;
    }

    o = fixHTML(o);

    return o;
};

/****************************************************************************
 * END instaview.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN instaviewtiny.js
 ****************************************************************************/

// $Id: instaviewtiny.js 1245 2006-02-24 20:50:18Z quarl $

// instaviewtiny - make a 1-paragraph preview of a wiki page

// synopsis:
//   html = $instaviewtiny.makePreview(s)

// originally from Lupin's popups.js's previewMaker

var $instaviewtiny = new Module('instaviewtiny.js');
$instaviewtiny.depend('instaview.js');

$instaviewtiny.options = {
    max_sentences: 4,
    max_characters: 600,
    first_paragraph_only: true
};

$instaviewtiny.previewMaker = function(wikiText) {
    this.data=wikiText;
}

$instaviewtiny.previewMaker.prototype.killComments = function () {
    this.data=this.data.replace(RegExp('<!--(\\n|.)*?-->', 'g'), '');
};

$instaviewtiny.previewMaker.prototype.killDivs=function () {
    // say goodbye, divs (can be nested, so use * not *?)
    this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*< */ *div *>',
                                       'gi'), '');
};

$instaviewtiny.previewMaker.prototype.killGalleries=function () {
    this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>',
                                       'gi'), '');
};

$instaviewtiny.previewMaker.prototype.killBoxTemplates=function () {
    // taxobox hack... in fact, there's a saudiprincebox_begin, so let's be more general
    this.data=this.data.replace(RegExp('[{][{][^}\\s|]*box[ _](begin|start)' +
                                       '[\\s\\S]*[^{\\s|]*box[ _](end|finish) *[}][}]', 'gi'), '');

    // also, have float_begin, ... float_end
    this.data=this.data.replace(RegExp('[{][{][^}\\s|]*float[ _]begin' +
                                       '[\\s\\S]*[^{\\s|]*float[ _]end.*?[}][}]',
                                       'gi'), '');

    // from [[User:Zyxw/popups.js]]: kill frames too
    this.data=this.data.replace(RegExp('[{][{][^}\\s|]*frame' +
                                       '[\\s\\S]*[^{\\s|]*[Ee][_ ]+frame[}][}]', 'gi'), '');
};

$instaviewtiny.previewMaker.prototype.killTemplates = function () {
    this.data=this.data.replace(RegExp('[{][{]([{][{][^}]*[}][}]|[^{}])*[}][}]', 'g'), ' ');
};

$instaviewtiny.previewMaker.prototype.killMultilineTemplates = function() {
  this.data=this.data.replace(RegExp('\\s*[{][{][^{}]*\\n[^{}]*[}][}]', 'g'), ' ');
}

$instaviewtiny.previewMaker.prototype.stripLongTemplates = function() {
    // operates on the HTML!
    this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
    this.html=this.html.split('\n').join(' '); // workaround for <pre> templates
    this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), '');
    //this.html=this.html.replace(RegExp('[{][{]([^}]*|\\s){0,50}?<pre>[^{}]*?[}][}]', 'gi'), '');
    //window.h=this.html;
    //alert(this.html);
}


$instaviewtiny.previewMaker.prototype.killTables=function () {
    // tables are bad, too
    this.data=this.data.replace
    (RegExp('[{]\\|([{][|]([^\\|]|\\|[^}])*[|][}]|[^\\|]|\\|[^}])*\\|[}]', 'g')
     , '');
    // remove lines starting with a pipe
    this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
};

$instaviewtiny.previewMaker.prototype.killImages=function () {
    // images are a nono
    // who says regexes aren't fun?
    // i think we should match:
    // [[image: ...... ]] where ....... consists of repetitions of any of:
    // 1. not [ or ]
    // 2. [[ (not ])* ]]
    // 3. [ (not ])* ]
    var imagedetector =
    '[[][[]\\s*('+
    $wikins.Image + '|' + $wikins.Category+
    ')\\s*:([^\\[\\]]|\\[\\[[^\\]]*\\]\\]|\\[[^\\]]*\\])*\\]\\]';
    var crudeImageRegex = RegExp(imagedetector, 'gi');
    this.data=this.data.replace(crudeImageRegex, '');
};

$instaviewtiny.previewMaker.prototype.killHTML=function () {
    // kill html tables // this doesn't cope with embedded tables
    // may this is good enough?
    this.data=this.data.replace(RegExp('<table.*?>(.|\\n.)*?\\n?</ *table *>\\n+', 'gi'), '');

    // let's also delete entire lines starting with <. it's worth a try.
    this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');

    // and those pesky html tags
    this.data=this.data.replace(RegExp('<.*?>','g'),'');
};

$instaviewtiny.previewMaker.prototype.killChunks=function() { // heuristics alert
    // chunks of italic text? you crazy, man?
    var italicChunkRegex=new RegExp
    ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)*", 'g');
    this.data=this.data.replace(italicChunkRegex, '');
};

$instaviewtiny.previewMaker.prototype.mopup=function () {
    // we simply *can't* be doing with horizontal rules right now
    this.data=this.data.replace(RegExp('^-{4,}','mg'),'');

    // no indented lines
    this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), '\n');

    // replace __TOC__, __NOTOC__ and whatever else there is
    // this'll probably do
    this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
};

$instaviewtiny.previewMaker.prototype.firstBit=function () {
    // dont't be givin' me no subsequent paragraphs, you hear me?
    /// first we "normalize" section headings, removing whitespace after, adding before

    this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');

    /// then we want to get rid of paragraph breaks whose text ends badly
    this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');

    this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
    var d = this.data;
    if ($instaviewtiny.options.first_paragraph_only) {
        var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
        if (stuff) d = stuff[0];
    };

    /// now put \n\n after sections so that bullets and numbered lists work
    d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');

    // superfluous sentences are RIGHT OUT.
    // note: exactly 1 set of parens here needed to make the slice work
    d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
    // remove leading spaces
    d[0]=d[0].replace(/^\s+/, '');

    var notSentenceEnds=RegExp('([^.][a-z][.][a-z]|etc|sic|Dr|Mr|Mrs|Ms)$'
                               + '|' +
                               '\\[[^\\]]*$'
                               , 'i');

    d = $instaviewtiny.fixSentenceEnds(d, notSentenceEnds);

    var n = $instaviewtiny.options.max_sentences;
    var dd;

    do {dd=$instaviewtiny.firstSentences(d,n); --n; }
    while ( dd.length > $instaviewtiny.options.max_characters && n > 0 );

    this.data = dd;
};

$instaviewtiny.fixSentenceEnds=function(strs, reg) {
    // take an array of strings, strs
    // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg

    for (var i=0; i<strs.length-2; ++i) {
        if (reg.test(strs[i])) {
            a=[];
            for (var j=0; j<strs.length; ++j) {
                if (j<i)   a[j]=strs[j];
                if (j==i)  a[i]=strs[i]+strs[i+1]+strs[i+2];
                if (j>i+2) a[j-2]=strs[j];
            }
            return $instaviewtiny.fixSentenceEnds(a,reg);
        }
    }
    return strs;
};

$instaviewtiny.firstSentences=function(strs, howmany) {
    var t=strs.slice(0, 2*howmany);
    return t.join('');
};

$instaviewtiny.previewMaker.prototype.makePreview=function() {
    this.killComments();
    this.killDivs();
    this.killGalleries();
    this.killBoxTemplates();

    if (getValueOf('popupPreviewKillTemplates')) {
        this.killTemplates();
    } else {
        this.killMultilineTemplates();
    }

    this.killTables();
    this.killImages();
    this.killHTML();
    this.killChunks();
    this.mopup();

    this.firstBit();

    this.html = $instaview.convert(this.data);

    this.stripLongTemplates();

    this.html = $util.trimSpaces(this.html);

    return this.html;
};


$instaviewtiny.makePreview = function(s) {
    var v = new $instaviewtiny.previewMaker(s);
    v.makePreview();
    return v.html || null;
}

/****************************************************************************
 * END instaviewtiny.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN notes.js
 ****************************************************************************/

// $Id: notes.js 1122 2006-02-23 07:41:53Z quarl $

// from http://en.wikipedia.org/wiki/User:Zocky/PicturePopups.js

var $notes = new Module('notes.js');
$notes.depend('util.js', 'drag.js');
$notes.depend('notes.css');

$notes.load = function() {
    $notes.container = document.getElementById('globalWrapper');
}

$notes.top = 100;

$notes.Note = function(params) {
    if (!(this instanceof $notes.Note)) return new $notes.Note(params);
    var note = this;

    var div = note.div = document.createElement("div");

    div.className = 'note ' + params.className;
    div.setAttribute('minimized', 0);

    div.innerHTML = ('<table><tr class="note-titlebar">'
                     + '<td class="note-title"><span>'
                     + params.title + '</span></td>'
                     + '<td class="note-icons" align="right"></td></tr>'
                     + '<tr><td class="note-content" colspan="2">'
                     + params.content + '</td></tr></table>');
    var tds = div.getElementsByTagName('td');
    note.contentCell = tds[2];
    tds[1].appendChild(note._makeIcons());

    $drag.makeDraggable(div, null,
                        { onPickup: function() { note.bringToFront(); },
                          pickupCondition: function(e) { return !e.ctrlKey; } });

    note.setLocation(params.x, params.y);
    note.show();
    return note;
}

$notes.Note.prototype.setLocation = function(x, y) {
    var div = this.div;
    x>0 ? (div.style.left = x + "px") : (div.style.right = -x + "px");
    y>0 ? (div.style.top = y + "px")  : (div.style.bottom = -y + "px");
}

$notes.Note.prototype.show = function()
{
    this.closed = false;
    this.bringToFront();
    $notes.container.appendChild(this.div);
}

$notes.Note.prototype._makeIcons = function()
{
    var note = this;
    var span = document.createElement('nobr');
    span.innerHTML = ('[<a> - </a>] [<a> x </a>]');;

    var links = span.getElementsByTagName('a');
    links[0].onclick = function() { note.toggleMinimized() };
    links[1].onclick = function() { note.close(); };
    return span;
}

$notes.Note.prototype.close = function() {
    this.closed = true;
    $notes.container.removeChild(this.div);
}

$notes.Note.prototype.toggleShow = function() {
    if (this.closed) {
        // a previously closed note - just show it again in the same location
        this.show();
    } else {
        // currently showing the note; close it
        this.close();
    }
}

$notes.Note.prototype.toggleMinimized = function() {
    // 'minimized' attribute is used by stylesheet
    this.div.setAttribute('minimized', 1 - this.div.getAttribute('minimized'));
}

$notes.Note.prototype.bringToFront = function() {
    this.div.style.zIndex = ++$notes.top;
}

$util.addOnloadHook($notes.load);

/****************************************************************************
 * END notes.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN linkedit.js
 ****************************************************************************/

// $Id: linkedit.js 1228 2006-02-24 10:46:38Z quarl $

// quarl 2006-02-24 initial version - based on popups.js changeLinkTargetLink

// TODO: more intelligent bypassing for prefixes, e.g. [[Humankind]] ->
// [[Human]]kind rather than [[Human|Humankind]].

var $linkedit = new Module('linkedit.js');
$linkedit.depend('wikiedit.js');

$linkedit.options = {
    dab_summary: 'Disambiguating from [[$1]] to [[$2]]',
    dab_button: 'wpSave',
    redir_summary: 'Redirection bypass from [[$1]] to [[$2]]',
    redir_button: 'wpDiff',
    n_changes: ' ($1 changes)'
};

// changes links pointing to origTarget to newTarget.
//
// if newTarget is null, remove the link
//
// returns { wtext: new_wtext, count: number_of_changes }
$linkedit.changeLink = function(wtext, origTarget, newTarget)
{
    // newTarget = $util.wpaEscape(newTarget)

    var re = ($popups._reUpperOrLower($util.reEscape(origTarget.charAt(0))) +
              $util.reEscape(origTarget.substring(1)));

    re = re.replace(/[_ ]+/g, '[_ ]+');
    re = re.replace(/\(/g, '(?:%28|\\()');
    re = re.replace(/\)/g, '(?:%29|\\))');
    re = '\\s*(' + re + ')\\s*';

    // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%28|\()archaic(?:%28|\)))\s*

    var count = 0; var r;
    if (newTarget == null) {
        // delete link
        r = $util.reReplaceAll(wtext, '\\[\\['+re+'\\]\\]', '$1'); wtext = r.str; count += r.count;
        r = $util.reReplaceAll(wtext, '\\[\\['+re+'\\|(.*?)\\]\\]', '$2'); wtext = r.str; count += r.count;
    } else {
        // change link
        r = $util.reReplaceAll(wtext, '\\[\\['+re+'\\]\\]', '[['+newTarget+'|$1]]'); wtext = r.str; count += r.count;
        r = $util.reReplaceAll(wtext, '\\[\\['+re+'\\|', '[['+newTarget+'|'); wtext = r.str; count += r.count;
    }
    return {wtext: wtext, count: count};
};

$linkedit.editLink = function(wp, origTarget, newTarget, summary, button)
{
    wp.getEditorAsync(function(editor) {
            var r = $linkedit.changeLink(editor.wpTextbox1, origTarget, newTarget);
            if (r.count == 0) {
                alert("No changes made!");
                return;
            }
            editor.wpTextbox1 = r.wtext;
            editor.wpSummary = $util.pprintf(summary, origTarget, newTarget);
            if (r.count > 1) {
                editor.wpSummary += $util.pprintf($linkedit.options.n_changes, r.count);
            }
            editor.wpMinoredit = true;
            editor.submit(button);
        });
};

$linkedit.editLinkDisambig = function(wp, origTarget, newTarget)
{
    $linkedit.editLink(wp, origTarget, newTarget,
                       $linkedit.options.dab_summary,
                       $linkedit.options.dab_button);
};

$linkedit.editRedirectBypass = function(wp, origTarget, newTarget)
{
    $linkedit.editLink(wp, origTarget, newTarget,
                       $linkedit.options.redir_summary,
                       $linkedit.options.redir_button);
};

/****************************************************************************
 * END linkedit.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN sig.js
 ****************************************************************************/

// -*- coding: utf-8 -*-
// $Id: sig.js 465 2006-02-14 12:26:12Z quarl $

// sig.js - allows advanced custom signatures.
//
//   - "<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>" is replaced by anything you want (any string or function) when you hit 'submit'
//   - Defaults to nicely-formatted and [[ISO 8061]] timestamp
//       - Wiki-linked timestamp enables date preferences
//       - Uses <span class="user-Username"> tags around entire signature (including timestamp) - see [[User:HorsePunchKid]]
//   - the toolbar "--<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>" button is replaced by anything you want
//
//   - You could even use this to vary your signature based on the article or namespace
//     (for example an extra link on AFD pages)

///////////////////////////////////////////////////////////////
// EXTERNAL ENTRY POINTS
//
// makeSignature():     Returns the custom signature.
//

///////////////////////////////////////////////////////////////
// USER-CONFIGURABLE SETTINGS

// signature:           This is your signature. (can be string or function)
//
//                      To emulate normal behavior:
//                          signature = '<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
//
//                      Default:
//                          signature = build_default_signature;
//
//                      Other examples:
//                          signature = function() { return '--~~~ ' + wikiTimestampISO() }
//                          signature = '<i>&mdash;[[User:Quarl|Quarl]] <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>~</i>';
//
//
// toolbar_signature:   What the "signature" toolbar button inserts. (can be string or function)
//                      I recommend removing the '--' (or &mdash;) and having it instead be part of the signature.
//
//                      To emulate normal behavior:
//                          toolbar_signature = '--<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
//
//                      Default:
//                          toolbar_signature = '<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
//
//                      Other examples:
//                          toolbar_signature = makeSignature;
//                          toolbar_signature = '&mdash;<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
//

//
// The following are used by build_default_signature.
//
// sig_username:        Used by build_default_signature as username in CSS span tag class name.
//                      Result is <span user-Quarl> for sig_user='Quarl'.  (can be string or function)
//
//                      Default (should work for most people):
//                          sig_username = wikiDoc.username; // read from Wikipedia page
//
//                      Other examples:
//                          sig_username = 'QuarlXYZ';
//
// signature_user:      Used by build_default_signature for username wiki link. (can be string or function)
//                      I recommend embedding the &mdash; so you don't have to type it every time.
//
//                      To emulate normal behavior:
//                          signature_user = '~~~';
//
//                      Default:
//                          signature_user = '&mdash;~~~';
//
//                      Other examples:
//                          signature_user = '[[User:Quarl|Quarl]] <sup>([[User talk:quarl|talk]])</sup>';
//
// signature_timestamp: Used by build_default_signature for timestamp (can be string or function).
//                      I recommend using wikiTimestampISO since I love [[ISO 8601]] time format, and
//                      this would allow everyone to see their preferred time format.
//
//                      To emulate normal behavior:
//                          signature_timestamp = '<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>~';
//
//                      Default:
//                          signature_timestamp = wikiTimestampISO;
//

// To allow use of advanced_sig.js in other scripts but not require it, use the following:
//      if(typeof window.makeSignature=='undefined')makeSignature=function(){return "<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>"};

// quarl 2005-12-30 initial version
// quarl 2006-01-04 add feature to replace "<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>" in text; factored it so it's easy to customize

// If this script looks simple, it's because all the hard stuff has been
// factored out to the library :)

var $sig = new Module('sig.js');
$sig.depend('util.js', 'datetime.js', 'autoreplace.js', 'wikipage.js');

$sig.getUsername = function() { return wikiDoc.username; }

$sig.wikiTimestampISO = function() {
    // return "[["+$datetime.datestampUTCISO()+"]]&nbsp;"+$datetime.timestampUTCISO()+"[[ISO 8601|Z]]";
    return "[["+$datetime.datestampUTCISO()+"]] "+$datetime.timestampUTCISO()+"Z";
}

$sig.spansig = function(user, sigUser, sigTimestamp) {
    return '<span class="user-sig user-'+user+'"><i>' + sigUser +  ' <small>'+sigTimestamp+'</small></i></span>';
}

$sig.build_default_signature = function() {
    return $sig.spansig($util.funkyval($sig.options.sig_username),
                        $util.funkyval($sig.options.signature_user),
                        $util.funkyval($sig.options.signature_timestamp));
}

$sig.makeSignature = function() { return $util.funkyval($sig.options.signature) };

$sig.replaceSignatureButton = function() {
    // Note: a slightly more complicated version is required to replace the signature with the evaluation of getToolbarSignature() at the time of pressing the button rather than time of page load, but it doesn't matter if you are just replacing it with <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span> and using autoreplace as below.
    var toolbar = document.getElementById('toolbar');
    if (!toolbar) return;
    var links = toolbar.getElementsByTagName('a');
    for (var i = 0; i < links.length; i++) {
        links[i].href = links[i].href.replace(/--<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>/, $util.funkyval($sig.options.toolbar_signature));
    }
}

$sig.replaceSignatureOnSubmit = function() {
    $autoreplace.addReplacement('<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>', $sig.makeSignature);
}

///////////////////////////////////////////////////////////////
// user-configurable settings (see top of page for commentary)

$sig.options = {
    signature: $sig.build_default_signature,
    sig_username: $sig.getUsername,
    // signature_user: '&mdash;~~~',
    signature_user: '<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>',
    signature_timestamp: $sig.wikiTimestampISO,
    toolbar_signature: '<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>'
};

$util.addOnloadHook($sig.replaceSignatureButton);
$util.addOnloadHook($sig.replaceSignatureOnSubmit);

/****************************************************************************
 * END sig.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN rollback.js
 ****************************************************************************/

// $Id: rollback.js 1206 2006-02-24 09:10:37Z quarl $

// rollback.js - rollback button for non-admins

// USAGE:
//    $module.depend('rollback.js');
//    $rollback.widgetLoad();

// based on http://sam.zoy.org/wikipedia/godmode-light.js
//   - more robust (works with Popups, etc)
//   - factored code


// -----------------------------------------------------------------------------
// God-like Monobook skin
// (c) 2005 Sam Hocevar <[email protected]>
// Forked from: Id: godmode-light.js 980 2005-11-12 01:51:51Z sam
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Language support, taken from phase3/languages/*
// -----------------------------------------------------------------------------

var $rollback = new Module('rollback.js');
$rollback.depend('util.js', 'md5.js', 'wikipage.js', 'wikiedit.js');

$rollback.options = {
    link_text: 'rollback',

    // var $rollback.cantrollback = 'Cannot revert edit; last contributor is only author of this page.';
    // var $rollback.alreadyrolled = 'Cannot rollback last edit of [[$1]] by [[User:$2|$2]] ([[User talk:$2|Talk]]); someone else has edited or rolled back the page already. Last edit was by [[User:$3|$3]] ([[User talk:$3|Talk]]). ';

    summary: 'Reverted edits by [[User:$2|$2]] ([[User talk:$2|t]]) ([[Special:Contributions/$2|c]]) to last version by $1'
};

$rollback.revert = function() {
    var token = WikiDocument.queryVars['token'];
    $rollback.vandal = WikiDocument.queryVars['vandal'];

    document.cont = document.getElementById('bodyContent');

    document.cont.innerHTML = 'Please wait, reverting edits by ' + $rollback.vandal + '...';
    // Avoid XSS attacks via authentication token
    if (!token || token != $rollback._mkToken(wikiPage, $rollback.vandal)) {
        document.cont.innerHTML += 'ERROR: Bad authentication token!';
        return;
    }

    document.getElementById('bodyContent').innerHTML += '<br />Getting article history...';
    $util.asyncDownloadXML(wikiPage.qurl + '&action=history&limit=50', $rollback._revert2);
}

// Get the vandal and new editor names
$rollback._parseHistory = function(doc) {
    var l = doc.getElementById('pagehistory').getElementsByTagName('li');
    for (i = 0; i < l.length; i++) {
        var name = l[i].getElementsByTagName('span')[0].getElementsByTagName('a')[0].innerHTML.replace(/_/g,' ');
        if (!name) continue;
        if (i == 0) {
            if (name != $rollback.vandal) {
                document.cont.innerHTML += '<br />Error: Last editor is ' + name + ', not ' + $rollback.vandal + '!';
                return {};
            }
        } else {
            if (name != $rollback.vandal) {
                var oldid = l[i].getElementsByTagName('input')[0].value;
                var editor = name;
                return {'oldid':oldid, 'editor':editor};
            }
        }
    }
    return {};
}

$rollback._revert2 = function(xmlhttp) {
    doc = xmlhttp.responseXML;

    var r = $rollback._parseHistory(doc);
    var oldid = r.oldid;
    $rollback.editor = r.editor;
    if (!$rollback.editor) {
        document.cont.innerHTML += '<br />Error: ' + $rollback.vandal + ' is the only editor!';
        return;
    }

    var url = wikiPage.qurl + '&action=edit&oldid=' + oldid;
    document.cont.innerHTML += '<br />Getting article edit form...';
    $util.asyncDownloadXML(url, $rollback._revert3);
}

$rollback._revert3 = function(xmlhttp) {
    summary_text = $util.pprintf($rollback.options.summary,
                                 $rollback.editor,
                                 $rollback.vandal);

    document.cont.innerHTML += '<br />Submitting form...';

    var wd = new WikiDocument(xmlhttp.responseXML, wikiPage);
    var editor = new WikiEditor(wd);
    editor.wpSummary = summary_text;
    editor.submit();
}

$rollback._mkToken = function(wp, vandal) {
    return $token.makeAuthToken('rollback', wp.page, vandal);
}

$rollback._mkUrl = function(wp, vandal) {
    return $util.urlGetPath(wp.qurl + '&fakeaction=rollback&vandal=' +
                       escape(vandal) + '&token=' + escape($rollback._mkToken(wp, vandal)));
}

// -----------------------------------------------------------------------------
// Add revert buttons to the page
// -----------------------------------------------------------------------------
$rollback._addButtonDiff = function() {
    if (wikiDoc.editingP) return; // preview diff pages should not have
                                  // rollback buttons; detect them since they
                                  // will also be edit pages

    var difftag = $util.getElementsByClass('diff-ntitle',document.getElementById('bodyContent'),'td')[0];
    if (!difftag) return;

    // if toplink has an oldid then this is not a diff against current revision.
    var toplink = difftag.getElementsByTagName('a')[0].href;
    if (toplink.match(/oldid=/)) return;

    var vandal = $wikipage.getUsernameFromLink(difftag.getElementsByTagName('a')[1]);
    if (!vandal) { alert("Couldn't parse username in diff page!"); return; }

    var url = $rollback._mkUrl(wikiPage,vandal);
    var newtext = ' &nbsp;&nbsp;&nbsp;<strong>[<a href="' + url + '">' + $rollback.options.link_text + '</a>]</strong> ';

    difftag.innerHTML = difftag.innerHTML.replace(/(<\/a>\))( *<br)/i, '$1'+newtext+'$2');
}

$rollback._addButtonContributions = function() {
    if (wikiPage.page != 'Special:Contributions') return;

    var vandal = wikiPage.relevantUser;
    if (!vandal) return;

    var l = document.getElementById('bodyContent').getElementsByTagName('li');
    for (i = 0; i < l.length; i++) {
        var t = l[i].innerHTML;
        // If we are already a sysop on this wiki, abort
        if (t.match(/>rollback<\/a>]/)) return;

        if (t.match(/<strong> \(/)) {
            var wp = new WikiPage(l[i].getElementsByTagName('a')[0].href);
            var url = $rollback._mkUrl(wp,vandal);
            l[i].innerHTML += ' [<a href="' + url + '">' + $rollback.options.link_text + '</a>]';
        }
    }
}

$rollback.load = function() {
    if (WikiDocument.queryVars['fakeaction'] == 'rollback') {
        $rollback.revert();
    }
}

$rollback.widgetLoad = function() {
    $util.addOnloadHook(function() {
            $rollback._addButtonDiff();
            $rollback._addButtonContributions();
        });
}

$util.addOnloadHook($rollback.load);


/****************************************************************************
 * END rollback.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autotag.js
 ****************************************************************************/

// $Id: autotag.js 737 2006-02-21 01:43:30Z quarl $

// autotag.js - automatically add custom tags to article

// usage:
//    $module.depend('autotag.js');
//    $autotag.widgetLoad();

// quarl 2006-01-03 initial version
// quarl 2006-01-23 use wikiedit.js

var $autotag = new Module('autotag.js');
$autotag.depend('wikipage.js', 'wikiedit.js', 'shortcuts.js', 'wikiwidget.js', 'datetime.js', 'util.js');

// TODO: sort the tags by this order (note that these are primary names of
// tags since they all have many aliases; subsituted tags such as afd are
// tricky; support piped data such as db|reason)

//order : 'delete,afd,hoax,original research,npov,cleanup,importance'.split(','),

$autotag.options = {
    mark_minor : true
};

$autotag.shortcuts = Shortcuts({
        'cleanup,clean' : 'cleanup-date|' + $datetime.datestampMonthYYYY(),
        'or,original' : 'original research',
        'afd,vfd' : 'subst:afd',
        'catz' : 'categorize',
        'cped' : 'copyedit',
        'wfy' : 'wikify' // equivalent, but easier for someone reading wikisource/history
});

$autotag.run = function() {
    $autotag.widget.showStatus('<a><b>Tagging...</b></a>');
    var tags = window.prompt("Enter tags to add, separated by &&. Example: hoax && not verified && original research && npov && mergeto|another article, cleanup");
    $autotag.tag(tags);
};

$autotag._iscat = function(s) {
    return Boolean(s.match(/^category:/i));
};

$autotag.remove_braces = function(s) {
    // remove any brackets or braces
    s = s.replace(/^\{\{(.*)\}\}$/, '$1');
    s = s.replace(/^\[\[(.*)\]\]$/, '$1');
    return s;
};

$autotag._addBraces = function(s) {
    if ($autotag._iscat(s)) {
        return '[['+s+']]';
    } else {
        return '{{'+s+'}}';
    }
};

$autotag._canonicalizeCategory = function(s) {
    if (s.match(/^cat(egory|egories)? *: */i)) {
        s = 'Category: ' + $util.capitalizeFirstChar(RegExp.rightContext);
    }
    return s;
};

$autotag._expandShortcuts = function(s) {
    s = $autotag.shortcuts.subst(s);
    // common mistake: {{unverified}} is for images only
    if (!wikiPage.nsImageP) s = s.replace(/unverified/, 'not verified');
    s = $autotag._canonicalizeCategory(s);
    // common mistake: {{d|reason}} should be {{db|reason}}
    s = s.replace(/^d\|/, 'db|');
    return s;
};

$autotag._tagAtTopP = function(t) {
    if ($autotag._iscat(t)) return false;
    if (t.match(/stub$/)) return false;
    return true;
};

$autotag.tag = function(tags) {
    wikiPage.getEditorAsync($autotag._edit, tags);
};

$autotag._edit = function(editor, tags) {
    tags = tags.split(/&&/);
    //var ntags = Array();
    var ttags = Array();
    var ttags_top = Array();
    var ttags_bot = Array();
    for (i in tags) {
        var tag = tags[i];
        tag = $autotag._expandShortcuts($autotag.remove_braces($util.trimSpaces(tag)));
        if (!tag) continue;
        var btag = $autotag._addBraces(tag);
        //ntags.push(tag);
        ttags.push(btag);
        if ($autotag._tagAtTopP(tag)) {
            ttags_top.push(btag);
        } else {
            ttags_bot.push(btag);
        }
    }
    if (!ttags.length) return;

    if (editor.refuseCreate()) return;
    var prepend = ttags_top.length ? (ttags_top.join('\n\n')+'\n\n') : '';
    var append = ttags_bot.length ? ('\n\n'+ttags_bot.join('\n\n')) : '';
    editor.wpTextbox1 = prepend + $util.trimLines(editor.wpTextbox1) + append;
    editor.wpSummary = 'Tagged as ' + ttags.join(', ');
    editor.wpMinoredit = $autotag.options.mark_minor;
    editor.submit();
};

$autotag.load = function() {
    if (wikiPage.nsSpecialP) return;

    if (WikiDocument.queryVars['fakeaction'] == 'autotag') {
        $autotag.run();
    }

    $autotag.widget = new WikiWidget({
        href: wikiPage.qurl + '&fakeaction=autotag',
        onclick: $autotag.run,
        name: 'Tag',
        id: 'ca-autotag',
        title: "Add tag(s)",
        default_location: {portlet:'Actions'}});
}

$autotag.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
                if ($autotag.widget) $autotag.widget.add(location);
            });
};

$util.addOnloadHook($autotag.load);


/****************************************************************************
 * END autotag.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN copyvio.js
 ****************************************************************************/

// $Id: copyvio.js 924 2006-02-22 06:19:19Z quarl $

// copyvio.js - add copyvio tag and add entry to 'copyright
// problems' page

// USAGE:
//    $module.depend('copyvio.js');
//    $copyvio.widgetLoad();

var $copyvio = new Module('copyvio.js');
$copyvio.depend('wikipage.js', 'wikiwidget.js');

$copyvio.logEntry = function(wp, url, callback) {
    $copyvio._getLogWP().getEditorAsync($copyvio._logEntryEdit, wp, url, callback);
};

$copyvio._logEntryEdit = function(editor, wp, url, callback) {
    if (editor.wpTextbox1.indexOf('[['+wp.page+']]') == -1) {
        editor.wpTextbox1 += '* {{subst:article-cv|'+wp.page+'}} from ['+url+']. <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
        editor.wpSummary += 'Listing [['+wp.page+']] as copyvio of '+url;
        editor.submitAsync(callback, wp, url);
    } else {
        alert("[["+wp.page+"]] already on log page; not relisting!");
        callback(wp, url);
    }
};

$copyvio.tagEntry = function(wp, url) {
    wp.getEditorAsync($copyvio._tagEntryEdit, url);
};

$copyvio._tagEntryEdit = function(editor, url) {
    // **replace** content!
    editor.wpTextbox1 = '{' + '{' + 'copyvio|url='+url+'}}\n<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>';
    editor.wpSummary = '[[Wikipedia:Copyrights|copyvio]] of '+url+' ([['+$copyvio._getLogWP().page+'|log]])';
    editor.submit();
};

$copyvio._getLogWP = function() {
    var logPageName = 'Wikipedia:Copyright problems/' + $datetime.datestampYYYYMonthD();
    return new WikiPage(null, logPageName);
};

$copyvio.logAndTagWp = function(wp, url) {
    $copyvio.logEntry(wp, url, $copyvio.tagEntry)
};

// query for URL; add {{copyvio}} tag and add to log page
$copyvio.run = function() {
    var url = window.prompt("Copyvio of which URL?");
    if (!url) return false;

    $copyvio.logAndTagWp(wikiPage, url);
    return false;
};

$copyvio.load = function() {
    // copyvios generally only in article space
    if (!wikiPage.nsMainP) return;

    if (WikiDocument.queryVars['fakeaction'] == 'copyvio') {
        $copyvio.run();
    }

    $copyvio.widget = new Widget( {name: 'CopyVio',
                                   title: 'Tag as copyright violation',
                                   id: 'ca-copyvio',
                                   onclick: $copyvio.run,
                                   href: wikiPage.qurl+'&action=edit&fakeaction=copyvio',
                                   default_location: {portlet:'Actions'}} );
}

$copyvio.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($copyvio.widget) $copyvio.widget.add(location);
        });
}

/****************************************************************************
 * END copyvio.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN edittop.js
 ****************************************************************************/

// $Id: edittop.js 438 2006-02-14 11:18:33Z quarl $

// edittop.js - adds an [edit top] link at the top of pages

// USAGE:
//    $module.depend('edittop.js');
//    $edittop.widgetLoad();

// based on http://en.wikipedia.org/wiki/Wikipedia:WikiProject_User_scripts/Scripts/Add_Edit_Top_Link
// by User:Pile0nades

var $edittop = new Module('edittop.js');
$edittop.depend('wikipage.js', 'util.js');

$edittop.widgetAdd = function() {
    // if this is preview page or generated page, stop
    if (wikiDoc.previewP || wikiPage.nsSpecialP) return;

    var editURL = wikiPage.qurl + '&action=edit&section=0';

    var style = 'float:right;margin-left:5px;margin-top:3px;';

    // create div and set innerHTML to link
    var divContainer = document.createElement("div");
    divContainer.innerHTML = ('<div class="editsection" style="'+style+'">' +
                              '[<a href="'+editURL+' title="'+wikiPage.page+'">edit top</a>]' +
                              '</div>');

    // insert divContainer into the DOM before the h1
    document.getElementById("content").insertBefore(divContainer, document.getElementsByTagName("h1")[0]);
};

$edittop.widgetLoad = function() {
    $util.addOnloadHook($edittop.widgetAdd);
};

/****************************************************************************
 * END edittop.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN watchlist.js
 ****************************************************************************/

// $Id: watchlist.js 1136 2006-02-23 09:18:43Z quarl $

// watchlist.js - adds buttons to watchlist: "unwatch", "diff
// since"

//  UNWATCH button asynchronously unwatches and crosses the entry off the
//  watchlist.
//
//  DIFF SINCE button shows differences since last edited.

// USAGE:
//   $module.depend('watchlist.js');
//   $watchlist.widgetLoad();

// quarl 2006-01-09 added asynchronous feature.
// quarl 2006-02-03 factored; added diff since.

// originally based on http://en.wikipedia.org/wiki/User:Omegatron/monobook.js
// see also http://en.wikipedia.org/wiki/User:Matthewmayer/monobook.js

// see also Bug 424 http://bugzilla.wikipedia.org/show_bug.cgi?id=424

var $watchlist = new Module('watchlist.js');
$watchlist.depend('wikipage.js', 'util.js', 'wikiwatch.js', 'diffsince.js');

$watchlist.options = {
    enabled: true,
    textlink_diffsince: 'since',
    textlink_unwatch: 'unwatch',
    textlink_watch: 'watch'
};

$watchlist.wp = {};

$watchlist.wuwatchAsync = function(wp) {
    if (!wp) { return $watchlist.error("internal error 72192d74-ab57-4a98-917f-8c6ca03b0559"); }

    var updateWuwLink = function(wpX, nextWuwAction) {
        if (!wpX) return;
        // don't use just "$util.findHref(wpX.url)", because if the target is
        // a User or User talk page, we don't want to accidentally match the
        // contributor link.
        if (!$watchlist._addremoveStrikeThrough(nextWuwAction, wpX.targetLink)) {
            alert("$watchlist.wuwatchAsync: Couldn't annotate link for '" + wpX.page + "'");
        }

        wpX.nextWuwAction = nextWuwAction;
        wpX.unwatchLink.innerHTML = (
            nextWuwAction ? $watchlist.options.textlink_watch : $watchlist.options.textlink_unwatch);
        wpX.unwatchLink.href = $wikiwatch.makeUrl(wp, nextWuwAction);
    }

    var lookupWp = function(wp) {
        return wp && $watchlist.wp[wp.page];
    }

    var cb = function() {
        // note: wp.talkPage can be null for certain non-talkable pages
        var wpNT = lookupWp(wp.notalkPage());
        var wpT = lookupWp(wp.talkPage());
        if (!wpT && !wpNT) {
            $watchlist.error("wuwatchAsync: neither talkPage nor notalkPage found (error 16e495b9-d198-4b80-8e9d-ba4088b77a98)");
            return;
        }
        var nextWuwAction = !wp.nextWuwAction;
        updateWuwLink(wpT, nextWuwAction);
        updateWuwLink(wpNT, nextWuwAction);
    }

    $wikiwatch.wuwatchAsync(wp.nextWuwAction, wp, cb, wp.unwatchSpan);
    return false;
}

$watchlist.diffSince = function(wp) {
    if (!wp) { return $watchlist.error("## internal error 72192d74-ab57-4a98-917f-8c6ca03b0559"); }
    return $diffsince.diffPageAsync(wp, wp.diffsinceSpan);
}

$watchlist._addremoveStrikeThrough = function(addp, node) {
    // return node && $util.insertNode(node, document.createElement('s'));
    if (!node) return 0;

    if (addp) {
        $util.addClass($util.ensureSpan(node), 'history-deleted');
    } else {
        $util.removeClass($util.ensureSpan(node), 'history-deleted');
    }
    return 1;
}


$watchlist.widgetLoad = function()
{
    $util.addOnloadHook(function() {
            if (wikiPage.page.match(/^Special:Watchlist/)) $watchlist._annotatePage();
        });
}

$watchlist._annotatePage = function() {
    // everything disabled?
    if (!$watchlist.options.enabled) return;

    var annotateLine = function(histLink, targetLink) {
        // note: 'wp' is needed by the onclick closure below -- do not just
        // put this inside the 'for' loop!
        var wp = new WikiPage(histLink.href);
        wp.targetLink = targetLink;
        $watchlist.wp[wp.page] = wp;

        wp.nextWuwAction = 0;
        wp.unwatchSpan = document.createElement('span');
        wp.unwatchLink = document.createElement('a');
        wp.unwatchLink.innerHTML = $watchlist.options.textlink_unwatch;
        wp.unwatchLink.href = $wikiwatch.makeUrl(wp, 0);
        wp.unwatchLink.onclick = function() { return $watchlist.wuwatchAsync(wp) };
        wp.unwatchSpan.appendChild(wp.unwatchLink);
        $util.addNodeAfter(histLink, wp.unwatchSpan);
        $util.addNodeAfter(histLink, document.createTextNode('; '));

        wp.diffsinceSpan = document.createElement('span');
        var diffsinceLink = document.createElement('a');
        diffsinceLink.innerHTML = $watchlist.options.textlink_diffsince;
        diffsinceLink.href = $diffsince.makeUrl(wp);
        diffsinceLink.onclick = function() { return $watchlist.diffSince(wp) };
        wp.diffsinceSpan.appendChild(diffsinceLink);
        $util.addNodeBefore(histLink, wp.diffsinceSpan);
        $util.addNodeBefore(histLink, document.createTextNode('; '));
    }

    var links = $util.copyArray(document.getElementById('bodyContent').getElementsByTagName('a'));
    var targetLink;
    for (i in links) {
        var link = links[i];
        if (!link.href) continue;
        if (link.href.match(/^http:\/\/(?:[^\/]+?)\/wiki\//)) {
            targetLink = link;
            continue;
        }
        if (link.href.match(/&action=history$/)) {
            annotateLine(link, targetLink);
        }
    }
}


/****************************************************************************
 * END watchlist.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN watchbutton.js
 ****************************************************************************/

// $Id: watchbutton.js 612 2006-02-17 03:12:55Z quarl $

// watchbutton.js - change standard watch/unwatch tab to be
// asynchronous.

// USAGE:
//   $module.depend('watchbutton.js');
//   $watchbutton.widgetLoad();

// quarl 2006-02-03 initial version

var $watchbutton = new Module('watchbutton.js');
$watchbutton.depend('wikiwatch.js', 'util.js');

$watchbutton.options = { enabled: true };

$watchbutton.toggleAsync = function() {
    $wikiwatch.wuwatchAsync(! $watchbutton.currentlyWatched, wikiPage,
                            $watchbutton._wuwSuccess, $watchbutton.tab);
    return false;
}

$watchbutton._wuwSuccess = function() {
    $watchbutton.currentlyWatched = !$watchbutton.currentlyWatched;
    if ($watchbutton.currentlyWatched) {
        $watchbutton.link.innerHTML = 'Unwatch';
        $watchbutton.link.href = wikiPage.qurl + '&action=unwatch';
        $watchbutton.link.title = 'Remove this page from your watchlist';
    } else {
        $watchbutton.link.innerHTML = 'Watch';
        $watchbutton.link.href = wikiPage.qurl + '&action=watch';
        $watchbutton.link.title = 'Add this page to your watchlist';
    }
}

$watchbutton.widgetLoad_ = function()
{
    if (!$watchbutton.options.enabled) return;

    if (($watchbutton.tab = document.getElementById('ca-unwatch'))) {
        $watchbutton.currentlyWatched = true;
    } else if (($watchbutton.tab = document.getElementById('ca-watch'))) {
        $watchbutton.currentlyWatched = false;
    } else {
        // couldn't find watch/unwatch button.
        return;
    }

    $watchbutton.link = $watchbutton.tab.getElementsByTagName('a')[0];
    $watchbutton.link.onclick = $watchbutton.toggleAsync;

}

$watchbutton.widgetLoad = function() {
    $util.addOnloadHook($watchbutton.widgetLoad_);
}

/****************************************************************************
 * END watchbutton.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autofocus.js
 ****************************************************************************/

// $Id: autofocus.js 441 2006-02-14 11:21:27Z quarl $

// autofocus.js - Auto-focus the cursor to appropriate edit fields.

// Edit page: focus the main edit area.
// Move page: focus the new page title.

//  usage:
//     $module.depend('autofocus.js');
//     $autofocus.enable();

// quarl 2006-01-03 initial version

var $autofocus = new Module('autofocus.js');
$autofocus.depend('wikipage.js');

$autofocus.run = function() {
    if (wikiDoc.editingP && !wikiDoc.previewP) {
        if (wikiDoc.newSectionP) {
            document.forms.editform.wpSummary.focus();
        } else {
            document.forms.editform.wpTextbox1.focus();
        }
        return;
    }

    if (wikiDoc.movePageP) {
        document.forms.movepage.wpNewTitle.focus();
        return;
    }
}

$autofocus.enable = function() {
    $util.addOnloadHook($autofocus.run);
}


/****************************************************************************
 * END autofocus.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN directredirect.js
 ****************************************************************************/

// $Id: directredirect.js 398 2006-02-14 09:21:10Z quarl $

// directredirect.js - automatically fix double redirects
//   Annotates Special:Whatlinkshere pages with [fix] and [fix all] buttons
//   that asynchronously change the target to a direct redirect.

// quarl 2006-01-31 initial version

var $directredirect = new Module('directredirect.js');
$directredirect.depend('wikipage.js', 'wikiedit.js', 'util.js');

$directredirect.options = { enabled: true };

$directredirect.annotatePage = function() {
    var contentDiv = document.getElementById('bodyContent');
    var links = contentDiv.getElementsByTagName('a');
    $directredirect.wpTarget = null;
    var redirects = $directredirect.redirects = [];

    for (var i in links) {
        var link = links[i];
        if (!link.href) continue;
        if (!link.href.match(/redirect=no/)) continue;

        var wp = new WikiPage(link.href);
        if (!$directredirect.wpTarget) {
            // first "redirect=no" link, this is the target
            $directredirect.wpTarget = wp;
        } else {
            // redirect page; is it a double (or worse) redirect?

            if (link.parentNode.parentNode.parentNode == contentDiv) {
                // if the parent is contentDiv, then it's a first-level
                // redirect.
            } else {
                // double redirect; add button
                var url = "javascript:$directredirect.fix(" + $util.stringQuoteEscape(wp.page) + ")";
                var button = document.createElement('span');
                button.innerHTML = ' [<a href="'+url+'"><b>fix</b></a>]';
                $util.addNodeAfter(link.nextSibling, button);
                var o = { wp: wp, link: link, button: button };
                redirects[wp.page] = o;
                redirects.push(o);
            }
        }
    }

    if (redirects.length) {
        var p = contentDiv.getElementsByTagName('p')[0];
        var button = document.createElement('blockquote');
        button.innerHTML = 'There are '+redirects.length+' indirect redirects. [<a href="javascript:$directredirect.fixall()">fix all</a>]';
        $util.addNodeBefore(p, button);
    }
}

$directredirect.fix = function(pagename) {
    $directredirect.fix0( $directredirect.redirects[pagename] );
}

$directredirect.fixall = function() {
    for (var i in $directredirect.redirects) {
        $directredirect.fix0( $directredirect.redirects[i] );
    }
}

$directredirect.fix0 = function(redirect) {
    if (!redirect) { alert ("## internal error 8e747379-406c-4bcf-b85f-770c855d9db1"); return; }
    redirect.button.innerHTML = ' [<b>fixing</b>: downloading...]';
    redirect.wp.getEditorAsync($directredirect.edit, redirect);
}

$directredirect.edit = function(editor, redirect) {
    redirect.button.innerHTML = ' [<b>fixing</b>: submitting...]';
    var pagename = $directredirect.wpTarget.page;
    if (!pagename) { alert ("## internal error 4cf6e5b6-5ed4-4d97-98d3-6288eb8e4f39"); return; }
    var redir = '#REDIRECT [['+pagename+']]';
    editor.wpTextbox1 = redir;
    editor.wpSummary = 'Direct redirect '+redir;
    editor.wpMinoredit = true;
    editor.submitAsync(null, $directredirect.editCompleted, redirect);
}

$directredirect.editCompleted = function(req, redirect) {
    if (req.status != 200) {
        alert( "Error submitting new redirect content!" );
        return;
    }

    redirect.button.innerHTML = ' [<b>fixed</b>]';
}

$directredirect.load = function() {
    if (!$directredirect.options.enabled) return;
    if (wikiPage.page != 'Special:Whatlinkshere') return;
    $directredirect.annotatePage();
}

$util.addOnloadHook($directredirect.load);

/****************************************************************************
 * END directredirect.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN locz.js
 ****************************************************************************/

// $Id: locz.js 912 2006-02-22 06:06:30Z quarl $

// locz.js - canonicalizes location WikiLinks
// as per [[Wikipedia:WikiProject Location Format]]

//  Example: [[Seattle, Washington]] becomes [[Seattle, Washington|Seattle]], [[Washington]], [[USA]].

// USAGE:
//   $module.depend('locz.js');
//   $locz.widgetLoad();

// quarl 2006-01-22 initial version
// quarl 2006-02-08 refactored to $autoedit.js

var $locz = new Module('locz.js');
$locz.depend('autoedit.js');

$locz.ae = new $autoedit.AutoEditor(
    'locz',
    'LocZ', 'ca-locz', 'Canonicalize location wikilinks',
    'Location canonicalization');

$locz.ae.initData = function() {
    var CountryData = function(states, link_country, regexp_country) {
        this.states = states;
        this.link_country = link_country.match(/\[/) ? link_country : '[['+link_country+']]';
        regexp_country = regexp_country || '\\[\\['+link_country+'\\]\\]';
        this.regexp_country = new RegExp(regexp_country);
        this.regexp_country_sq = new RegExp('^, *'+regexp_country);
        this.regexp_substate = (
            new RegExp('^([^,]+), *(' + this.states.join('|') + ')$'));
        this.regexp_state = (
            new RegExp('^(?:' + this.states.join('|') + ')$'));
    }

    this.countries = [
        new CountryData( // USA
            ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado',
             'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho',
             'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine',
             'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi',
             'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey',
             'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio',
             'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina',
             'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia',
             'Washington', 'West Virginia', 'Wisconsin', 'Wyoming',
             'Washington, DC', 'Washington, D.C.' // not strictly a state, but needs to be qualified with country also
             ],
            '[[United States]]', // '[[United States|USA]]',
            '\\[\\[(?:United[ _]States(?:[ _][^|\\\]]+?)?|USA)(?:\\|[^|\\\]]+?)?\\]\\]'),

        new CountryData( // Canada
            ['British Columbia', 'Alberta', 'Saskatchewan', 'Manitoba',
             'Ontario', 'Quebec', 'New Brunswick', 'Nova Scotia',
             'Prince Edward Island', 'Newfoundland and Labrador'],
            'Canada'),

        new CountryData( // England
            ['Bedfordshire', 'Berkshire', 'City of Bristol',
             'Buckinghamshire', 'Cambridgeshire', 'Cheshire',
             'Cornwall', 'Cumbria', 'Derbyshire', 'Devon', 'Dorset',
             'Durham', 'East Riding of Yorkshire', 'East Sussex', 'Essex',
             'Gloucestershire', 'Greater London', 'Greater Manchester',
             'Hampshire', 'Herefordshire', 'Hertfordshire', 'Isle of Wight',
             'Kent', 'Lancashire', 'Leicestershire', 'Lincolnshire',
             'City of London', 'Merseyside', 'Norfolk', 'Northamptonshire',
             'Northumberland', 'North Yorkshire', 'Nottinghamshire',
             'Oxfordshire', 'Rutland', 'Shropshire', 'Somerset',
             'South Yorkshire', 'Staffordshire', 'Suffolk', 'Surrey',
             'Tyne and Wear', 'Warwickshire', 'West Midlands', 'West Sussex',
             'West Yorkshire', 'Wiltshire', 'Worcestershire'],
            'England'),

        ];
}

$locz.ae.splitText = function(input) {
    var inputs = [];

    // special case for hat link, if there is one
    if (input.match(/^: *''.*/)) {
        var infobox = RegExp.lastMatch;
        var right = RegExp.rightContext;

        inputs.push(infobox);
        input = right;
    }

    // special case the first Infobox, if there is one
    if (input.match(/^(?:{{Infobox(?:.|\n)*?\n}}|{\|(?:.|\n)*?\n\|})/i)) {
        // var left = RegExp.leftContext;
        var infobox = RegExp.lastMatch;
        var right = RegExp.rightContext;

        // treat the infobox separately, so that USA links get added to main
        // article.
        inputs.push(infobox);
        input = right;
    }

    inputs.push(input);
    return inputs;
}

$locz.ae.buildRegExp = function() {
    return /\[\[ *(?:([^|\]]+?) *\| *)?([^\]]+?) *\]\]/;
}

$locz.ae.replaceRegExp = function(d, m) {
    var wlink = m[1] || m[2];
    var wtext = m[2];

    // non-main namespace - usually a category
    if (wtext.match(/:/)) return;

    if (wlink != wtext) return;

    for (i in this.countries) {
        var c = this.countries[i];

        var changes = 0;
        var wfull;
        if (wtext.match(c.regexp_substate)) {
            var city = RegExp.$1, state = RegExp.$2;
            wfull = '[[' + wtext + '|' + city + ']]';
            // only add link to state if we haven't link it yet.
            if (d.left.match('\\[\\['+state+'\\]\\]')) {
                wfull += ', ' + state;
            } else {
                wfull += ', [['+state+']]';
            }
            ++changes;
        } else if (wtext.match(c.regexp_state)) {
            // state link -- just need to add country link as necessary
            wfull = '[['+wtext+']]';
        }

        if (!wfull) continue;

        if (d.left.match(c.regexp_country)) {
            // Already mentioned country.  Delete redundant subsequent
            // country links
            if (d.right.match(c.regexp_country_sq)) {
                d.right = RegExp.rightContext;
                // only count as a change if we actually delete it!
                ++changes;
            }
        } else {
            // Haven't mentioned country earlier
            if (d.right.match(c.regexp_country_sq)) {
                // it's right after the current link; good.
            } else {
                // not there; add it.
                wfull += ', ' + c.link_country;
                ++changes;
            }
        }

        if (changes) {
            d.text = wfull;
        }
        return;
    }

    return;
}

$locz.widgetLoad = $locz.ae.widgetLoad;

/****************************************************************************
 * END locz.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN datez.js
 ****************************************************************************/

// $Id: datez.js 913 2006-02-22 06:07:05Z quarl $

// date_canonicalize.js - canonicalizes date WikiLinks

//  Example: July 17, 1982 becomes [[1982-07-17]]

// USAGE:
//   $module.depend('datez.js');
//   $datez.widgetLoad();

// quarl 2006-01-31 initial version

var $datez = new Module('datez.js');
$datez.depend('autoedit.js', 'datetime.js');

$datez.ae = new $autoedit.AutoEditor(
    'datez',
    'DateZ',
    'ca-datez',
    'Canonicalize dates',
    'Date canonicalization');

// Return a string for a regexp that matches possibly wiki-linked dates in
// various date formats.  This is a monster regexp, the hardest part of this
// script!
$datez.ae.buildRegExp = function() {

    var groupIfNeccessary = function(s) {
        // I don't know a good way to check against e.g. "(foo)|(bar)"; for now just be conservative in adding grouping
        if (s.match(/\|/) /*&& !s.match(/^\(/) */ ) {
            return '(?:' + s + ')';
        } else {
            return s;
        }
    }

    var joinRE = function() {
        // desplice arguments
        var args = Array.concat.apply(Array, arguments).map(groupIfNeccessary);

        return '(?:' + args.join('|') + ')';
    }

    var linked = function(s) {
        return '\\[\\[ *'+s+' *\\]\\]';
    }

    var maybelinked = function(s) {
        return joinRE(linked(s), s);
    }

    var abbrevMonth = function(s) {
        return s.substr(0,3);
    }

    var word = function(s) {
        return '\\b' + s + '\\b';
    }

    var year4 = word('[012][0-9][0-9][0-9]');
    var year42 = word(joinRE(year4, '[890][0-9]'));
    var month = word(joinRE('0?[1-9]', '1[012]'));
    var monthS = word(joinRE($datetime.monthnames, $datetime.monthnames.map(abbrevMonth)));
    var day = word('[0123]?[0-9](?:\'?st|nd|rd|th)?');

    var delimz = '[ ,/.-]+';

    var all = joinRE(
        // YYYY-MM-DD formats
        linked( year4 + '-' + month + '-' + day ),
        maybelinked(year4)+'-'+maybelinked(month+'-'+day),
        maybelinked(year4)+'/'+month+'/'+day,
        maybelinked(year4)+'\\.'+month+'\\.'+day,
        year4 + ' ' + month + ' ' + day,

        maybelinked(year4 + delimz + monthS + delimz + day),

        // MM-DD-YYYY formats
        month + '-' + day + '-' + year4,
        month + '/' + day + '/' + year42,

        linked(monthS + delimz + day + delimz + year42),
        maybelinked(monthS + delimz + day) + delimz + maybelinked(year42),
        linked(monthS) + delimz + linked(day) + delimz + '(?:of\s*)?' + linked(year42),

        // DD-MM-YYYY formats: only support monthS, because it's ambiguous
        // otherwise
        linked( day + delimz + monthS + delimz + year4 ),
        maybelinked(day + delimz + monthS) + delimz + maybelinked(year4)
        );
    return new RegExp(all, 'i');
}

$datez.ae.replaceRegExp = function(d, m) {
    s = m[0];
    s = s.replace(/[\[\]]/g, '');
    s = s.replace(/[-.]/g, '/');     // Date only understands '/' as delimiter
    s = s.replace(/([0-9])\'?(?:st|nd|rd|th)\b/, '$1'); // get rid of ordinals
    var date = new Date(s);                                // parses date string

    if (!date.getFullYear()) {
        // couldn't parse
        return null;
    }

    return '[[' + $datetime.datestampUTCISO(date) + ']]';
}

$datez.widgetLoad = $datez.ae.widgetLoad;

/****************************************************************************
 * END datez.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN coorz.js
 ****************************************************************************/

// $Id: coorz.js 447 2006-02-14 11:30:43Z quarl $

// coorz.js - canonicalizes geographical coordinates

//  Example: 59° 55′ N, 10° 44′ E becomes {{coor dm|59|55|N|10|44|E|}}

// USAGE:
//   $module.depend('coorz.js');
//   $coorz.widgetLoad();

// quarl 2006-02-01 initial version

var $coorz = new Module('coorz.js');
$coorz.depend('autoedit.js');

$coorz.ae = new $autoedit.AutoEditor(
    'coorz',
    'CoorZ', 'ca-coorz', 'Canonicalize geographic coordinates',
    'Coor canonicalization');

$coorz.ae.buildRegExp = function() {
    var sp = function(s) { return ' *' + s + ' *'; }

    var d = sp("(?:°|&deg;)");
    var m = sp("(?:′|'|&prime;|&#x2032;)");
    var s = sp('(?:″|"|&Prime;|&#x2033;)');

    var C = '(-?[0-9.]+)';

    var dms = C + d + '(?:' + C + m + '(?:' + C + s + ')?' + ')?';

    var all = '\\b' + dms + '\\s*(N|S|[Nn]orth|[Ss]outh)[ \t,]*' + dms + '\\s*(E|W|[Ee]ast|[Ww]est)' + '\\b';
    return new RegExp(all);
}

$coorz.ae.replaceRegExp = function(d, m)
{
    var latitude = [m[1], m[2], m[3]];
    var latitude_pole = m[4][0].toUpperCase();
    var longitude = [m[5], m[6], m[7]];
    var longitude_pole = m[8][0].toUpperCase();

    if (latitude[2] || longitude[2]) {
        // dms
        return ('{{coor dms|' +
                latitude[0] + '|' + latitude[1] + '|' + latitude[2] + '|' + latitude_pole + '|' +
                longitude[0] + '|' + longitude[1] + '|' + longitude[2] + '|' + longitude_pole + '|}}');
    } else if (latitude[1] || longitude[1]) {
        // dm
        return ('{{coor dm|' +
                latitude[0] + '|' + latitude[1] + '|' + latitude_pole + '|' +
                longitude[0] + '|' + longitude[1] + '|' + longitude_pole + '|}}');
    } else if (latitude[0] || longitude[0]) {
        // d
        return ('{{coor d|' +
                latitude[0] + '|' + latitude_pole + '|' +
                longitude[0] + '|' + longitude_pole + '|}}');
    } else {
        alert ("## internal error a86f430f-f362-4324-b8ce-df5f84e8f65b");
        return null;
    }
}

$coorz.widgetLoad = $coorz.ae.widgetLoad;

/****************************************************************************
 * END coorz.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN imdbz.js
 ****************************************************************************/

// $Id: imdbz.js 447 2006-02-14 11:30:43Z quarl $

// imdbz.js - canonicalizes IMDB external links

//  Example: [http://www.imdb.com/title/tt0403832/ IMDB Entry] becomes
//           {{imdb title|id=0403832|title=Quarl/imdb canonicalize.js}}

// USAGE:
//   $module.depend('idmbz.js');
//   $idmbz.widgetLoad();

// quarl 2006-02-06 initial version


var $imdbz = new Module('imdbz.js');
$imdbz.depend('autoedit.js');

$imdbz.ae = new $autoedit.AutoEditor(
    'imdbz',
    'ImdbZ',
    'ca-imdbz',
    'Canonicalize IMDB links',
    'IMDB canonicalization');

$imdbz.ae.buildRegExp = function() {
    return new RegExp(
        '\\[http://(?:www\\.)?imdb\\.com/(title|name)/(?:tt|nm)([0-9]+)/?[^\\]]*\\]');
}

$imdbz.ae.replaceRegExp = function(d, m)
{
    var type = m[1];
    var idnum = m[2];
    if (type == 'name') {
        return '{{imdb name|id='+idnum+'|name={{subst:PAGENAME}}}}';
    } else if (type == 'title') {
        return '{{imdb title|id='+idnum+'|name={{subst:PAGENAME}}}}';
    }
    alert ("## internal error 781cea49-9cb7-44b1-8656-368608c5457c");
    return null;
}

$imdbz.widgetLoad = $imdbz.ae.widgetLoad;

/****************************************************************************
 * END imdbz.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN alexafy.js
 ****************************************************************************/

// $Id: alexafy.js 457 2006-02-14 11:53:01Z quarl $

// alexafy.js - adds "alexa" links to external links

// usage:
//        $module.depend('alexafy.js');
//        $alexafy.widgetLoad();

// quarl 2005-01-10 initial version

var $alexafy = new Module('alexafy.js');
$alexafy.depend('wikiwidget.js', 'util.js');

$alexafy._whitelistedP = function(url) {
    if (url.match(/wikipedia.org/)) return true;
    return false;
}

$alexafy.run = function() {
    var content = document.getElementById('content');
    var externallinks = $util.getElementsByClass('external',content,'a');
    for (i in externallinks) {
        var alink = externallinks[i];
        if ($alexafy._whitelistedP(alink.href)) continue;
        $alexafy._annotateLink(alink);
    }
}

$alexafy._annotateLink = function(alink) {
    var alexaUrl = $alexafy.makeUrl(alink.href);
    var dnode = document.createElement('span');
    dnode.innerHTML = ' [<a href="'+alexaUrl+'">Alexa</a>]';
    $util.addNodeAfter(alink, dnode);
}

$alexafy.makeUrl = function(url) {
    return 'http://www.alexa.com/data/details/traffic_details?q=&url=' + url;
}

$alexafy.load = function() {
    $alexafy.widget = new WikiWidget({ default_location: {portlet: 'Toolbox'},
                                       onclick: $alexafy.run,
                                       name: 'Alexafy links',
                                       id: 'ca-alexafy'});
}

$util.addOnloadHook($alexafy.load);

$alexafy.widgetLoad = function() {
    $util.addOnloadHook(function() {
            $alexafy.widget.add();
        });
}

/****************************************************************************
 * END alexafy.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN userscript.js
 ****************************************************************************/

// $Id: userscript.js 637 2006-02-17 09:51:30Z quarl $

// userscript.js - utilities to help develop user scripts and
// style sheets

// - adds "raw" tab to user script pages
// - adds "refresh" tab to user script pages (refresh the raw script)
// - automatically refreshes the raw version when you save a user script page

// quarl 2006-01-10 initial version

var $userscript = new Module('userscript.js');
$userscript.depend('wikipage.js', 'kookie.js', 'wikiwidget.js');

$userscript.rawUrl = function(wp) {
    // note: wp.qurl is parsed from source and is not the same as blindly
    // escaping the input!  String has to exactly match what the user is
    // using, e.g. "User:Quarl/script", not "User%3AQuarl/script"!

    if (wp.page.match(/[.]js$/)) {
        return wp.qurl + '&action=raw&ctype=text/javascript&dontcountme=s';
    } else if (wp.page.match(/\/monobook[.]css$/)) {
        return wp.qurl + '&action=raw&ctype=text/css';
    } else if (wp.page.match(/[.]css$/)) {
        return wp.qurl + '&action=raw&ctype=text/css&dontcountme=s';
    } else {
        return null;
    }
}

$userscript._load = function() {
    var url = $userscript.rawUrl(wikiPage);
    if (!url) return;

    $userscript.widgetRaw = new WikiWidget({
        default_location: {portlet: 'Actions'},
        url: url,
        name: 'Raw', id: 'ca-raw', title: "Show raw version of user script"});
    $userscript.widgetRefresh = new WikiWidget({
        default_location: {portlet: 'Actions'},
        onclick: $userscript.doRefresh,
        name: 'Refresh', id: 'ca-refresh',
        title: "Force browser to refresh the raw version of this script"});

    $userscript.refreshIfChanged();
}

$userscript.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($userscript.widgetRaw) {
                $userscript.widgetRaw.add(location);
                $userscript.widgetRefresh.add(location);
            }
        });
}

// Check if the current script has changed since last time we looked at it;
// if so, refresh.  Use a cookie to check.
$userscript.refreshIfChanged = function() {
    // if we're not looking at current revision, don't do anything.
    if (wikiDoc.permalinkP) return;

    // we don't have the oldid for some reason (e.g. we're currently editing)
    if (!wikiDoc.oldid) return;

    //var cookie_name = 'usl_oldid_' + wikiPage.pageQuoted;
    var cookie_name = 'usl_oldid';

    // by using a different cookie-path we avoid sending tons of cookies for every page,
    // and we don't have to uniquify the cookie name.  Note that if unspecified, the path
    // is equivalent to "/wiki/User:Quarl/" instead of "/wiki/User:Quarl/foo.js", which is
    // just as bad as "/"
    var path = $util.urlGetPath(wikiPage.url); // only bother with canonical URL
    last_oldid = $kookie.get(cookie_name);
    if (last_oldid != wikiDoc.oldid) {
        // first time we're looking at the page, or the page has changed. refresh.
        $userscript.doRefresh();
    }
    $kookie.set(cookie_name, wikiDoc.oldid, 7, path);
}

$userscript.doRefresh = function() {
    $userscript._forceRefresh($userscript.rawUrl(wikiPage));
}

$userscript._forceRefresh = function(url) {
    // We can't do frm.location.reload() right now because frm.location will still be "about:blank", but if we complete this event and start a new one, it works.

    //frm.location.replace(url);
    //frm.location.href = url;

    $util.buttonShowStatus($userscript.widgetRefresh.li, '<b><a>Refreshing...</a></b>');

    document.getElementById('userscript_refresher').src = url;
    setTimeout($userscript._forceRefresh_1, 0);
}

$userscript._forceRefresh_1 = function() {
    var frm = window.frames['userscript_refresher'];
    frm.location.reload();

    $util.buttonRestoreStatus($userscript.widgetRefresh.li, '<b><a>Refreshed</a></b>');
}

$util.addOnloadHook($userscript._load);

// hidden iframe
if (false) // debug
document.write('<div style="float:right;" id="hidden_userscript_iframe_div"><iframe src="about:blank" height="80" width="80" name="userscript_refresher" id="userscript_refresher"></iframe></div>');
else
document.write('<div style="position:absolute;left:0px;top:0px;visibility:hidden;" id="hidden_userscript_iframe_div"><iframe src="about:blank" height="0" width="0" name="userscript_refresher" id="userscript_refresher"></iframe></div>');

/****************************************************************************
 * END userscript.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autosummary.js
 ****************************************************************************/

// $Id: autosummary.js 1248 2006-02-25 06:22:25Z quarl $

// autosummary.js - automatically fill in edit summary based on diff; short cuts

// quarl 2006-01-26 added shortcuts
// quarl 2006-01-30 auto diff

// TODO: add a checkbox for "auto" next to summary, to disable auto diff

var $autosummary = new Module('autosummary.js');
$autosummary.depend('wikipage.js', 'wikiedit.js', 'util.js', 'diff.js', 'shortcuts.js');
// recommends: smartsubmit.js

// see also: [[Wikipedia:Edit_summary_legend]]
$autosummary.shortcuts = Shortcuts({
  // 'ed' : 'editing', // 'edit'
  'cped,cpediting,cpyed,copyed,copyedit' : 'copy-editing',
  'mn ' : 'minor',
  'mnf ' : 'minor fixes',
  'fmt' : 'formatting',
  'mfmt ' : 'minor formatting',
  'rv' : 'reverting',
  'rvv' : 'reverting vandalism',
  'gr' : 'grammar',
  'sp' : 'spelling',
  'rd ' : 'redirect',
  'cmt' : 'commenting',
  'cla' : 'clarifying',
  'xl,xlink' : 'external link',
  'sa' : 'see also',
  'cap' : 'capitalization',
  'catz' : 'categorizing',
  'cl,cu' : 'cleaning up',
  'newart,creat' : 'creating new article',
  'dab,disamb,disam,disambig' : 'disambiguating',
  'rddab' : 'replacing redirect with disambiguation page',
  //'st' : 'see Talk page',
  // 'style' : 'style',
  'punc,punct,pnct' : 'punctuation',
  'wfy,wkfy' : 'wikifying'
});

$autosummary.options = {
    prompt : 'Enter edit summary.',

    // whether to query even when filled via auto diff
    query: true,

    // whether to default edit summary to diff (not needed if
    // $autosummary.auto_diff is enabled)
    diff: true,

    // whether to automatically prepend "(auto diff)" to edit summary while
    // editing.  Use integer number of seconds for update interval (in sec),
    // or false to disable.
    auto_diff: 3,

    // whether to automatically select text after the /* section */ and « diff
    // »
    auto_select: true,

    // whether shortcut expansion is enabled
    shortcuts_enabled: false
};

$autosummary.load = function()
{
    if (!wikiDoc.editingP) return;
    if (wikiDoc.newSectionP) return;

    if ($autosummary.options.auto_diff) {
        $autosummary._autoDiffSetup();
    }

    if ($autosummary.options.auto_select) {
        $util.hookEventObj(document.editform.wpSummary, 'focus', $autosummary._focusSummaryEvent);
    }

    $util.hookEventObj(document.editform.wpSave, 'click', $autosummary._preSubmitEvent);
}

$autosummary._autoDiffSetup = function() {
    $util.hookEventObj(document.editform.wpTextbox1, 'blur', $autosummary._updateAutoDiff);
    $autosummary.intervalHandle = setInterval($autosummary._updateAutoDiff,
                                              1000*$autosummary.options.auto_diff);
}

$autosummary._textFilter = function(s) {
    // ignore signature when diffing
    return s.replace(/<span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>/g,'').replace(/<span class=[\'\"]user-sig.*?<\/span>/g, '');
}

$autosummary._diffStrings = function(s1,s2) {
    return $diff.diffSummary($autosummary._textFilter(s1), $autosummary._textFilter(s2));
}

$autosummary.diffTextbox = function() {
    var editor = wikiPage.getEditor();
    return $autosummary._diffStrings(editor.wpTextbox1_orig, editor.wpTextbox1);
}

// update the edit summary with diff
$autosummary._updateAutoDiff = function() {
    var editor = wikiPage.getEditor();
    editor.updateThis();

    if (editor.wpTextbox1_prev == editor.wpTextbox1) {
        // no change since last update
        return;
    }
    editor.wpTextbox1_prev = editor.wpTextbox1;

    var s = $autosummary.diffTextbox();
    if (s) {
        s = "«" + s + "» ";
    }

    if (editor.wpSummary.match(/\«/)) {
        editor.wpSummary = editor.wpSummary.replace(/«.*?» */, s);
    } else if (s && !$autosummary._pruneSection(editor.wpSummary)) {
        editor.wpSummary = $util.trimSpaces(editor.wpSummary);
        if (editor.wpSummary) editor.wpSummary += ' ';
        editor.wpSummary += s;
    }

    editor.updateForm();
}

$autosummary._preSubmitEvent = function(event)
{
    if ($autosummary.options.auto_diff) $autosummary._updateAutoDiff();
    var editor = wikiPage.getEditor();
    editor.updateThis();
    var r = $autosummary._edit(editor);
    editor.updateForm();

    if (!r) {
        event.preventDefault();
        event.stopPropagation();
    }
}

// auto focus
$autosummary._focusSummaryEvent = function(event) {
    var sumField = document.editform.wpSummary;
    if (sumField.value.match(/^(?:\/\*.*?\*\/)?\s*(?:«(?:.*)»)? ?/)) {
        var n = RegExp.lastMatch.length;
        var m = sumField.value.length;
        // apparently you can't setSelectionRange in an onFocus handler, but
        // you can set a timer to do it 0 seconds from now.
        setTimeout(function() { sumField.setSelectionRange(n, m) }, 0);
    }
}

$autosummary._pruneSection = function(s) {
    return $util.trimSpaces(s.replace(/^\/\\*.*?\\*\//,''));
}

$autosummary._edit = function(editor)
{
    if (editor.wpTextbox1_orig == editor.wpTextbox1) {
        // no change
        return true;
    }

    var auto = false;

    if (!editor.wpSummary.match(/REDIRECT/i) &&
        editor.wpTextbox1.match(/^#REDIRECT/i))
    {
        // it's a redirect.  Annotate with REDIRECT.
        if ($autosummary.options.auto_diff) {
            // don't need auto diff
            // editor.wpSummary = editor.wpSummary.replace(/^〈.*?〉 */, '');
        }
        editor.wpSummary += editor.wpTextbox1;
        auto = true;
    } else if ($autosummary._pruneSection(editor.wpSummary)) {
        // non-negligible summary exists; continue with submission
        if ($autosummary.options.shortcuts_enabled) {
            editor.wpSummary = $autosummary.shortcuts.substWords(editor.wpSummary);
        }
        return true;
    } else if ($autosummary.options.diff) {
        // if we get here then we're not using auto diff, or user manually
        // removed it
        var s = $autosummary.diffTextbox();
        if (s) {
            editor.wpSummary += s;
            auto = true;
        }
    }

    if (!auto || $autosummary.options.query) {
        var pr = $autosummary.options.prompt;
        if ($autosummary.options.shortcuts_enabled) {
            pr += '  ' + $autosummary.shortcuts.msg();
        }

        var r = window.prompt(pr, editor.wpSummary);
        if(r == null) { return false; } // cancel
        if ($autosummary.options.shortcuts_enabled) {
            editor.wpSummary = $autosummary.shortcuts.substWords(r);
        }
    }

    return true;
}

$util.addOnloadHook($autosummary.load);


/****************************************************************************
 * END autosummary.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN smartsubmit.js
 ****************************************************************************/

// $Id: smartsubmit.js 915 2006-02-22 06:07:39Z quarl $

// smartsubmit.js - smarter diff/preview buttons

//  Instead of navigating to a new page, the "Show preview" and "Show
//  changes" buttons download the target asynchronously and update the current
//  page.
//
// Advantages:
//  - Can continue editing without losing changes
//  - Can use up to 250 characters in edit summary (normally, preview/diff
//    would cut to 200 characters)
//  - enhances auto_summary.js: the auto diff is based on the "previous"
//    version of the page, but normally pressing preview/diff would cause the
//    preview version to be "previous", as there is no way to save the
//    original text
//  - enhances $autoreplace.js (including advanced_sig.js): replacements not
//    substituted in the edit box on preview/diff
//  - don't lose cursor position in text box

// Side effects:
//  - Since the browser doesn't navigate the window to a new location, it
//    doesn't register as a history event (i.e. "back" goes to the page
//    visited before any initial editing)

// quarl 2006-02-02 initial version

var $smartsubmit = new Module('smartsubmit.js');
$smartsubmit.depend('wikiedit.js', 'util.js');
// enhances: wikiedit.js, $autoreplace.js, auto_summary.js

$smartsubmit.options = {
    enabled: true,
    focus_after_download: true
};

$smartsubmit.load = function() {
    if (!$smartsubmit.options.enabled) return;

    if (!wikiDoc.editingP) return;

    if (!$smartsubmit.annotatePageInit()) return;

    document.editform.wpDiff.preventPreSumitHook = true;
    $util.hookEventObj(document.editform.wpDiff, 'click',
                      function(event){$smartsubmit.click(event,'wpDiff');});
    document.editform.wpPreview.preventPreSumitHook = true;
    $util.hookEventObj(document.editform.wpPreview, 'click',
                      function(event){$smartsubmit.click(event,'wpPreview');});
}

// returns true value on success
$smartsubmit.annotatePageInit = function() {
    var divLoc = document.getElementById('jump-to-nav');
    if (!divLoc) {
        return $smartsubmit.error("couldn't get 'jump-to-nav' (error 1639a197-bdd2-4e9d-9777-9c8a5987fe2c)");
    }
    $smartsubmit.div = document.createElement('div');
    $smartsubmit.div.id = 'smartsubmit-preview';
    $util.addNodeAfter(divLoc, $smartsubmit.div);

    // create an empty <a> so that we can focus on it
    $smartsubmit.focusloc = document.createElement('a');
    $smartsubmit.focusloc.id = 'smartsubmit-focusloc';
    $util.addNodeAfter(divLoc, $smartsubmit.focusloc);

    return true;
}

$smartsubmit.focusPreview = function() {
    $smartsubmit.focusloc.focus();
}

// The user clicked wpDiff or wpPreview.  Instead of submitting, we're going
// to make a new asynchronous request, download the data, insert into the
// current page.
$smartsubmit.click = function(event, button) {
    if (!$smartsubmit.options.enabled) {
        return true;
    }

    // don't allow multiple concurrent clicks
    document.editform[button].disabled = true;
    $smartsubmit.div.innerHTML = 'Submitting...';

    wikiPage.getEditor().submitAsync(button, $smartsubmit.updatePage, button);

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

$smartsubmit.updatePage = function(req, button) {
    if (button == 'wpDiff') {
        var divId = 'wikiDiff';
    } else if (button == 'wpPreview') {
        var divId = 'wikiPreview';
    } else {
        $smartsubmit.error("internal error dcd4ab5a-4e10-4576-9cd5-874d589455da");
        return;
    }

    var editor = this;
    // re-enable button
    document.editform[button].disabled = false;
    if (req.status != 200) {
        $smartsubmit.error("downloading page failed! (error af129b48-ac16-4459-bf8c-86ed9e38d8f0)");
        $smartsubmit.options.enabled = false;
        wikiPage.getEditor().submit(button);
        return;
    }

    var newDoc = req.responseXML;

    var newDiv = newDoc.getElementById(divId);

    if (!newDiv) {
        $smartsubmit.warning("Couldn't get "+divId+" from downloaded document (error e3f57370-85c8-4ab6-8430-00d6441b828b)");
        // fall back: disable and do normal submit
        $smartsubmit.options.enabled = false;
        wikiPage.getEditor().submit(button);
        return;
    }

    // clear and add newDiv
    $smartsubmit.div.innerHTML = '';
    $smartsubmit.div.appendChild(newDiv);

    if ($smartsubmit.options.focus_after_download) {
        $smartsubmit.focusPreview();
    }
}

$util.addOnloadHook($smartsubmit.load);


/****************************************************************************
 * END smartsubmit.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN newmessages.js
 ****************************************************************************/

// $Id: newmessages.js 636 2006-02-17 08:40:02Z quarl $

// newmessages.js
// - annotate the "You have new messages" alert to add "diff since" and "hist"
//   links.
// - tag fake "new messages" boxes as such

// quarl 2006-01-16 initial version

var $newmessages = new Module('newmessages.js');
$newmessages.depend('wikipage.js', 'diffsince.js', 'util.js');

$newmessages.options = {
    add_since_hist: true,
    tag_fake: true
};

$newmessages.load = function() {
    var boxes = $util.getElementsByClass(
        'usermessage', document.getElementById('bodyContent'),'div');

    var seenreal = 0, seenfake = 0;

    for (var i in boxes) {
        var box = boxes[i];

        // People love to create lookalikes as a practical joke.  The easiest
        // way to tell is that real talk message boxes are before the <div
        // id="jump-to-nav"> tag.

        if (box.textContent == "You have new messages (diff)." &&
            box.nextSibling.nextSibling.id == 'jump-to-nav')
        {
            if (seenreal++) continue;               // at most one real
            $newmessages.annotateRealMessageBox(box);
            continue;
        }

        if (box.textContent.match(/You have new messages/)) {
            // only bother tagging at most one fake box
            if (seenfake++) continue;
            $newmessages.annotateFakeMessageBox(box);
        }
    }
}

$newmessages.annotateRealMessageBox = function(talkmessagebox)
{
    if (!$newmessages.options.add_since_hist) return;
    $newmessages.wpTalk = new WikiPage(null,'User talk:' + wikiDoc.username);
    var histUrl = $newmessages.wpTalk.qurl + '&action=history';
    var diffSinceUrl = $diffsince.makeUrl($newmessages.wpTalk);

    talkmessagebox.className += ' plainlinks';

    var t = (' (<span id="newmessages-diffsince">' +
             '<a onclick="javascript:return $newmessages.diffSince()" href="'+diffSinceUrl+'">' +
             'diff since</a></span>)' +
             ' (<a href="'+histUrl+'">history</a>)');

    // insert before final period
    talkmessagebox.innerHTML = talkmessagebox.innerHTML.replace(/(?=[.]$)/, t);
}

$newmessages.annotateFakeMessageBox = function(talkmessagebox)
{
    if (!$newmessages.options.tag_fake) return;
    var sp = document.createElement('span');
    sp.style.color = 'red';
    sp.innerHTML = '(FAKE)';

    talkmessagebox.insertBefore(sp, talkmessagebox.firstChild);
}

$newmessages.diffSince = function() {
    return $diffsince.diffPageAsync($newmessages.wpTalk,
                            document.getElementById('newmessages-diffsince'),
                            '<b>diff since...</b>');
}

$util.addOnloadHook($newmessages.load);

/****************************************************************************
 * END newmessages.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN tabdiff.js
 ****************************************************************************/

// $Id: tabdiff.js 1253 2006-02-25 06:39:02Z quarl $

// tabdiff.js - add "diff" tab to do most recent diff

// USAGE:
//   $module.depend('tabdiff.js');
//   $tabdiff.widgetLoad();

var $tabdiff = new Module('tabdiff.js');
$tabdiff.depend('wikiwidget.js', 'wikipage.js');

$tabdiff.load = function()
{
    if (wikiPage.nsSpecialP) return;
    $tabdiff.widget = new WikiWidget({
        default_location: {portlet: 'Actions'},
        url: wikiPage.qurl + "&diff=0",
        name: "Diff",
        id: "ca-last",
        title: "Show most recent diff"});
}

$tabdiff.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($tabdiff.widget) $tabdiff.widget.add(location);
        });
}


$util.addOnloadHook($tabdiff.load);


/****************************************************************************
 * END tabdiff.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN tabsince.js
 ****************************************************************************/

// $Id: tabsince.js 1253 2006-02-25 06:39:02Z quarl $

// tabsince.js - add 'since' tab to show change since I last edited

// Left-click navigates asynchronously; new tab/window works as well.

// USAGE:
//   $module.depend('tabsince.js');
//   $tabsince.widgetLoad();

// quarl 2006-01-16 rewritten to asynchronously download history page

var $tabsince = new Module('tabsince.js');
$tabsince.depend('wikipage.js', 'wikiwidget.js', 'diffsince.js');

$tabsince.load = function() {
    if (wikiPage.nsSpecialP) return;
    $tabsince.widget = new WikiWidget({
        default_location: {portlet: 'Actions'},
        href: $diffsince.makeUrl(wikiPage),
        onclick: $tabsince.run,
        name: "Since",
        id: "ca-since",
        title: "Show changes since I last edited"});
}

$tabsince.run = function() {
    return $diffsince.diffThis($tabsince.tab);
}

$tabsince.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($tabsince.widget) $tabsince.widget.add(location);
        });
}


$util.addOnloadHook($tabsince.load);


/****************************************************************************
 * END tabsince.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN purge.js
 ****************************************************************************/

// $Id: purge.js 567 2006-02-15 04:45:36Z quarl $

// purge.js - add "purge" tab to the purge server cache

// USAGE:
//   $module.depend('purge.js');
//   $purge.widgetLoad();

var $purge = new Module('purge.js');
$purge.depend('wikiwidget.js', 'wikipage.js', 'util.js');

$purge.load = function()
{
    if (wikiPage.nsSpecialP) return;
    $purge.widget = new WikiWidget({
        default_location: {portlet: 'Actions'},
        url: $purge.makeUrl(),
        onclick: $purge.asyncPurge,
        name: "Purge",
        id: "ca-purge",
        title: "Purge the internal cache for this page"});
}

$purge.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($purge.widget) $purge.widget.add(location);
        });
}

$purge.makeUrl = function() {
    return wikiPage.qurl + "&action=purge";
}

$purge.asyncPurge = function() {
    var tab = $purge.widget.li;

    var callback = function(req) {
        if (req.status != 200) {
            $purge.alert("Error purging! (error 3c0bf0ac-62f2-46d1-a19d-4436a2fb2f75)");
        }

        $util.buttonRestoreStatus(tab, '<a><b>Purged</b></a>');
    };

    $util.buttonShowStatus(tab);
    $util.asyncDownloadXML($purge.makeUrl, callback);
    return false;
}

$util.addOnloadHook($purge.load);

/****************************************************************************
 * END purge.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN editcount.js
 ****************************************************************************/

// $Id: editcount.js 1232 2006-02-24 11:13:06Z quarl $

// editcount.js - link to Interiot's edit counter when viewing user pages

// USAGE:
//     $module.depend('editcount.js');
//     $editcount.widgetLoad();

// quarl 2006-01-16 initial version

var $editcount = new Module('editcount.js');
$editcount.depend('wikipage.js', 'wikiwidget.js');

$editcount.interiotLink = function(username)
{
    if (!username) return null;

    return ('http://tools.wikimedia.de/~interiot/cgi-bin/count_edits?user=' +
            encodeURIComponent(username)+'&dbname=' + WikiPage.dbname + '_p');
}

$editcount.load = function()
{
    var url = $editcount.interiotLink(wikiPage.relevantUser);
    if (url) {
        $editcount.widget = new WikiWidget({
            default_location: {portlet:'Toolbox'},
            url: url,
            name: 'Edit count',
            id: 'pt-editcount',
            title: "Show Interiot's edit count for "+wikiPage.relevantUser});
    }
}

$editcount.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($editcount.widget) $editcount.widget.add(location);
        });
}

$util.addOnloadHook($editcount.load);

/****************************************************************************
 * END editcount.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN nav_logs.js
 ****************************************************************************/

// $Id: nav_logs.js 707 2006-02-20 23:22:11Z quarl $

// toolbox_logs.js - adds relevant Logs links to Toolbox

// based on http://en.wikipedia.org/wiki/Wikipedia:WikiProject_User_scripts/Scripts/Logs_link

// adds a 'logs for this page' link to the navigation bar
// if the page is a user's page, talk page (but not subpage), the link will go to logs for the user instead
// if the page is a special page, then no link is displayed

var $nav_logs = new Module('nav_logs.js');
$nav_logs.depend('wikipage.js', 'wikiwidget.js');

$nav_logs.load = function() {
    var url;
    var show;
    if (wikiPage.relevantUser) {
        url = "http://en.wikipedia.org/w/index.php?title=Special:Log&user=" + wikiPage.relevantUser;
        show = "User " + wikiPage.relevantUser;
    } else if (wikiPage.nsSpecialP) {
        // don't display link for special pages (other than those with relevantUser)
        return;
    } else {
        url = "http://en.wikipedia.org/w/index.php?title=Special:Log&page=" + wikiPage.page;
        show = wikiPage.page;
    }

    $nav_logs.widget = new WikiWidget({
        default_location: {portlet: 'Toolbox'},
        url: url,
        name: "Logs",
        id: "pt-logs",
        title: "Show logs for " + show});
}

$nav_logs.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($nav_logs.widget) $nav_logs.widget.add(location);
        });
}

$util.addOnloadHook($nav_logs.load);

/****************************************************************************
 * END nav_logs.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN nav_afd.js
 ****************************************************************************/

// $Id: nav_afd.js 1194 2006-02-24 07:34:05Z quarl $

// nav_afd.js - add AFD/Today and AFD/Yesterday links to Toolbox

// USAGE:
//    $module.depend('nav_afd.js');
//    $nav_afd.widgetLoad();

// quarl 2006-01-03 initial version
// quarl 2006-01-24 show three direct links (avoid WP:AFD/Today, etc)

var $nav_afd = new Module('nav_afd.js');
$nav_afd.depend('wikipageXfd.js', 'datetime.js', 'wikiwidget.js');

$nav_afd.options = { num: 2 };

$nav_afd.load = function() {
    $nav_afd.widgets = [];

    for (var i=0; i < $nav_afd.options.num; ++i) {
        var d = $datetime.previousDay(i);
        var date = $datetime.datestampUTCISO(d);

        $nav_afd.widgets[i] = new WikiWidget( { default_location: { portlet: 'Navigation' },
                                            url: afdLogPage(d).url,
                                            name: "AFD/"+date,
                                            id: "pt-afd-"+i,
                                            title: "Show AFD log for "+date } );
    }
}

$nav_afd.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            for (var i in $nav_afd.widgets) {
                $nav_afd.widgets[i].add(location);
            }
        });
}

$util.addOnloadHook($nav_afd.load);

/****************************************************************************
 * END nav_afd.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN hideown.js
 ****************************************************************************/

// $Id: hideown.js 1180 2006-02-23 22:10:45Z quarl $

// hideown.js - change the "my watchlist" navigation button to default to hide own.

var $hideown = new Module('hideown.js');
$hideown.depend('util.js');

$hideown.options = { enabled: true };

$hideown.load = function() {
    if (!$hideown.options.enabled) return;

    var wl = document.getElementById('pt-watchlist');
    if (!wl) return;

    var a = wl.getElementsByTagName('a')[0];
    if (!a || !a.href || !a.href.match(/Special:Watchlist/)) return;

    $hideown.debug("load(): Annotating pt-watchlist link");
    a.href += '?hideOwn=1';
}

$util.addOnloadHook($hideown.load);


/****************************************************************************
 * END hideown.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN google.js
 ****************************************************************************/

// $Id: google.js 1182 2006-02-23 22:11:34Z quarl $

// quarl 2006-02-13 initial version

// usage:
//    $module.depend('google.js');
//    $google.widgetLoad();

var $google = new Module('google.js');
$google.depend('wikipage.js');

$google.options = {
    google_server: 'http://www.google.com',
    site: null
    // site: 'en.wikipedia.org'
};

$google.click = function() {
    var t = document.forms.searchform.searchInput.value;
    $google.debug("click(): searchInput = " + $util.stringQuoteEscape(t));
    if (!t) return;

    var site = $google.options.site || WikiPage.server;

    var url = $google.options.google_server + '/search?&q=site:' + site + '+' + encodeURIComponent(t);
    document.location = url;
}

$google.widgetLoad_ = function() {
    $google.debug("widgetLoad(): adding Google button");
    var button = document.createElement('input');
    button.type = "button";
    button.onclick = $google.click;
    button.value = "Google";
    button.className = "searchButton";

    var form = document.getElementById('searchform');
    form.appendChild(button);
}

$google.widgetLoad = function() {
    $util.addOnloadHook($google.widgetLoad_);
}

/****************************************************************************
 * END google.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN debugnote.js
 ****************************************************************************/

// $Id: debugnote.js 1190 2006-02-23 22:24:40Z quarl $

// debugnote -- displays a popup window that scrolls the debug log

// quarl 2006-02-22 initial version

var $debugnote = new Module('debugnote.js');
$debugnote.depend('msg.js', 'notes.js', 'wikiwidget.js');
$debugnote.depend('debugnote.css');

$debugnote.options = {
    log_update_interval: 1000,
    log_display_size: 40
};

$debugnote.logContainer = null;

$debugnote.div = null;
$debugnote.showlogDiv = function()
{
    if (!$debugnote.div) {
        $debugnote.div = document.createElement('div');
        $debugnote.div.id = 'debugnote_div';
        $debugnote.div.innerHTML = '<pre id="debugnote_log" />';

        var content = document.getElementById('bodyContent');
        var contentSub = document.getElementById('contentSub');
        content.insertBefore($debugnote.div, contentSub);

        $debugnote.logContainer = document.getElementById('debugnote_log');
        $debugnote.logContainerText = document.createTextNode('');
        $debugnote.logContainer.appendChild($debugnote.logContainerText);
    }

    $debugnote.initUpdate();
}

$debugnote.note = null;
$debugnote.showlogNote = function()
{
    if ($debugnote.note) {
        $debugnote.note.toggleShow();
    } else {
        $debugnote.note = new $notes.Note({
            x: Math.round(Math.random()*200),
                    y: Math.round(Math.random()*200),
                    title:'Debug log',
                    content:'<pre id="debugnote_log" />',
                    className: 'debugnote_note'
                    });
        $debugnote.logContainer = document.getElementById('debugnote_log');
        $debugnote.logContainerText = document.createTextNode('');
        $debugnote.logContainer.appendChild($debugnote.logContainerText);
    }
    $debugnote.initUpdate();
}

$debugnote.interval = null;
$debugnote.initUpdate = function()
{
    if (!$debugnote.interval && $debugnote.options.log_update_interval) {
        $debugnote.interval = setInterval($debugnote._updatelog,
                                    $debugnote.options.log_update_interval);
    }
    $debugnote._updatelog();
}

$debugnote.lastNElements = function(array, n) {
    var max = function(a,b) { return a>b ? a : b }
    return array.slice(max(0, array.length - n));
}

$debugnote._updatelog = function()
{
    if ($debugnote.note && $debugnote.note.closed) return;

    if ($debugnote.logContainerText) {
        // use a Text node instead of innerHTML so we don't need to
        // entity-escape the text as HTML
        $debugnote.logContainerText.nodeValue = (
            $debugnote.lastNElements($msg.debuglog, $debugnote.options.log_display_size).join('\n'));
    }
}

$debugnote.load = function()
{
    $debugnote.widget = new WikiWidget({ default_location: {portlet: 'Navigation'},
                                       onclick: $debugnote.showlogNote,
                                       name: 'PowerTools Debug log',
                                       id: 'ca-debugnote'});
}

$debugnote.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            $debugnote.widget.add(location);
        });
}

$util.addOnloadHook($debugnote.load);

/****************************************************************************
 * END debugnote.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN querysave.js
 ****************************************************************************/

// $Id: querysave.js 1173 2006-02-23 22:00:51Z quarl $

// querysave.js -- confirm that the user wants to lose unsaved changes.

// SYNOPSIS:
//    $querysave.enable();

// note: this relies on onbeforeunload, which is not a W3C standard event; it
// only exists in MSIE and very new Mozilla (1.7a+)

// quarl 2006-02-23 initial version

var $querysave = new Module('querysave.js');
$querysave.depend('wikiedit.js', 'util.js');
// recommends: smartsubmit.js

$querysave.saved = false;

$querysave.enable = function()
{
    $util.addOnloadHook(function() {
            if (wikiDoc.editingP) {
                $util.hookEventObj(window, 'beforeunload', $querysave._exit);
                $util.hookEventObj(window, 'submit', $querysave._submit);
            }
        });
};

$querysave._submit = function()
{
    var editor = wikiPage.getEditor();
    if (!editor) {
        // this function should only even be called if wikiDoc.editingP, so
        // lack of editor at this point is an error
        $querysave.error("_submit(): No editor?! (error 0c2eac53-6d7f-43a9-8e7f-792e8f2fadda)");
        return;
    }

    editor.updateThis();
    editor.wpTextbox1_saved = editor.wpTextbox1;
    $querysave.debug("_submit(): marked as clean " + $util.describeCharCount(editor.wpTextbox1_saved));
}


$querysave._exit = function(e)
{
    var editor = wikiPage.getEditor();
    if (!editor) {
        $querysave.debug("_exit(): editor=" + editor);
        return;
    }

    editor.updateThis();
    if (editor.wpTextbox1_saved == editor.wpTextbox1) {
        $querysave.debug("_exit(): data is clean (saving)");
        return;
    }

    if (editor.wpTextbox1_orig == editor.wpTextbox1) {
        $querysave.debug("_exit(): data is clean (never modified)");
        return;
    }

    $querysave.debug("_exit(): data is dirty");
    e.returnValue = "Your wiki text has not been saved.";
};

/****************************************************************************
 * END querysave.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN extedit.js
 ****************************************************************************/

// $Id: extedit.js 438 2006-02-14 11:18:33Z quarl $

// extedit.js - "ext-edit" widget

// USAGE:
//    $module.depend('extedit.js');
//    $extedit.widgetLoad();

var $extedit = new Module('extedit.js');
$extedit.depend('wikipage.js', 'wikiwidget.js');

$extedit.load = function() {
    if (wikiPage.protectedP) return;
    if (wikiPage.nsSpecialP) return;

    $extedit.widget = new Widget({default_location: {portlet:'Actions'},
                                  url: wikiPage.qurl + '&action=edit&externaledit=true',
                                  name: 'ext-edit',
                                  id: 'ca-extedit',
                                  title: 'Edit with external editor'});
}

$extedit.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($extedit.widget) $extedit.widget.add(location);
        });
}


/****************************************************************************
 * END extedit.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN autoafd.js
 ****************************************************************************/

// $Id: autoafd.js 387 2006-02-14 09:17:19Z quarl $

// autoafd.js - automatic AFD nomination

// quarl 2006-01-23 new version that opens the nomination page on invocation; adds
//                  checkboxes for tag & log (default on); do the tagging and logging
//                  asynchronously on 'submit'.

// TODO: add buttons for manually tagging/listing

var $autoafd = new Module('autoafd.js');
$autoafd.depend('wikipage.js', 'wikipageXfd.js', 'wikiwidget.js', 'util.js');

$autoafd.makeBeginUrl = function() {
    var wp = wikiPage.afdPageX();
    return wp.qurl + '&action=edit&fakeaction=autoafd';
}

$autoafd.load = function()
{
    if (WikiDocument.queryVars['fakeaction'] == 'autoafd') {
        $autoafd.begin();
        return;
    }

    if (!wikiPage.nsMainP) {
        // don't bother with non-main-namespace articles
        return;
    }

    if (document.getElementById('afd')) {
        // this article already has an AFD tag.
        return;
    }

    if (wikiPage.xfdType() == 'afd')
        $wikiwidget.addTab($autoafd.makeBeginUrl(), 'Afd', 'ca-autoafd', 'Nominate for deletion');
}

$autoafd.begin = function() {
    if (window.afdEditor) return; // already initialized

    // TODO: some of this is redundant with wikipageAfd.js
    window.wpAfd = wikiPage;
    window.wpAfdTarget = wpAfd.afdTargetPage();
    if (!wpAfdTarget) {
        alert("Doesn't look like an AFD page?!");
        return;
    }
    window.wpAfdLog = afdLogPage();

    var editor = window.afdEditor = wpAfd.getEditor();
    if (editor.wpTextbox1) {
        // TODO: automatically go to #2 etc.
        alert("There's an old AFD at the default location already.\n\n" +
              'Please manually edit [[' + wikiPage.page + ' (2)]]).');
        return;
    }

    editor.wpTextbox1 = '===[[' + wpAfdTarget.page + ']]===\n' +
              "Nomination.  '''Delete''' <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>\n*\n*\n*\n";
    editor.wpSummary = "Initial nomination for deletion of [[" + wpAfdTarget.page + "]]";
    editor.updateForm();

    $autoafd.annotate();
}

$autoafd.annotate = function() {
    $autoafd.add_new_checkboxes();
    $util.hookEventObj(document.editform.wpSave, 'click', $autoafd.submitHook_Event);
    window.$autoafd.async_wait = null;
    // Don't submit! Let the user type in peace.
}

$autoafd.add_new_checkboxes = function() {
    if (document.getElementById('autoafd-options')) return;

    var tagTitle = 'Add {{subst:afd}} to ' + $util.stringQuoteEscape(wpAfdTarget.page)
    var tagText = 'Tag <a href="' + $util.urlGetPath(wpAfdTarget.url) + '">' +
                  wpAfdTarget.page + '</a> with {{afd}}';

    var listTitle = 'List this AFD on today\'s AFD log page';
    var listText = 'List in <a href="'+$util.urlGetPath(wpAfdLog.url) + '">' +
                   //wpAfdLog.page +
                   'log page' +
                   '</a>';

    $autoafd.newstuff = document.createElement('div');
    $autoafd.newstuff.id = 'autoafd-options';
    $autoafd.newstuff.innerHTML = (
        '<input type="checkbox" value="1" name="wpTagAFD" id="wpTagAFD" checked="1" />'+
        '<label for="wpTagAFD" title="' + tagTitle + '">' +
        tagText + '</label>' +
        '&nbsp;&nbsp;&nbsp;' +
        '<input type="checkbox" value="1" name="wpListAFD" id="wpListAFD"  checked="1" />'+
        '<label for="wpListAFD" title="' + listTitle + '">' +
        listText + '</label>');
    $util.addNodeBefore(document.editform.wpMinoredit, $autoafd.newstuff);
}


$autoafd.submitHook_Event = function(event)
{
    if (!($autoafd.submitHook())) {
        event.preventDefault();
        event.stopPropagation();
    }
}

$autoafd.submitHook = function() {
    window.afdEditor.updateThis();

    // $autoafd.async_wait is initially set to null.  The first time we arrive here
    // we set it to 0.  We wait for up to two asynchronous submissions to occur first
    // before actually submitting the form.

    if ($autoafd.async_wait == 0) {
        return true;
    }
    if ($autoafd.async_wait == null) {
        $autoafd.async_wait = 0;
    }

    if (document.editform.wpTagAFD.checked && !window.asyncTagAFDStarted) {
        window.asyncTagAFDStarted = true;
        $autoafd.async_wait++;
        var status = window.$autoafd.status_tag = document.createElement('div');
        status.innerHTML = 'Tagging page: Downloading...';
        status.id = 'status-tagafd';
        $util.addNodeAfter($autoafd.newstuff, status);
        wpAfdTarget.getEditorAsync($autoafd.TagAFD);
    }
    if (document.editform.wpListAFD.checked && !window.asyncListAFDStarted) {
        window.asyncListAFDStarted = true;
        $autoafd.async_wait++;
        var status = window.$autoafd.status_list = document.createElement('div');
        status.innerHTML = 'Listing: Downloading...';
        status.id = 'status-listafd';
        $util.addNodeAfter($autoafd.newstuff, status);
        wpAfdLog.getEditorAsync($autoafd.ListAFD);
    }

    if ($autoafd.async_wait == 0) return true;

    // prevent repeat clicks
    document.editform.wpSave.disabled = true;
    return false;
}

$autoafd.TagAFD = function(editor) {
    if (editor.refuseCreate()) return;

    if (editor.wpTextbox1.match(/{{afd/) ||
        editor.wpTextbox1.match(/\[\[Category:Pages for deletion\]\]/))
    {
         $autoafd.status_tag.innerHTML += ' Already tagged!';
    } else {
        $autoafd.status_tag.innerHTML += ' submitting...';
        editor.wpTextbox1 = '{{subst:afd}}\n\n' + $util.trimLines(editor.wpTextbox1);
        editor.wpSummary = 'Nominating [['+wpAfdTarget.page+']] for [['+wpAfd.page+'|AFD]]';
        editor.submitAsync(null, $autoafd.TagAFD_done);
    }
}

$autoafd.ListAFD = function(editor) {
    if (editor.refuseCreate()) return; // safety check
    var newText = '{{'+wpAfd.page+'}}';
    if (editor.wpTextbox1.indexOf(newText)!=-1) {
        $autoafd.status_list.innerHTML += ' already listed!';
    } else {
        $autoafd.status_list.innerHTML += ' submitting...';
        editor.wpTextbox1 = $util.trimLines(editor.wpTextbox1) + '\n' + newText + '\n';
        editor.wpSummary = 'Listing [['+wpAfdTarget.page+']] for [['+wpAfd.page+'|AFD]]';
        editor.submitAsync(null, $autoafd.ListAFD_done);
    }
}

$autoafd.TagAFD_done = function(req) {
    if (req.status != 200) {
        alert("Error tagging article for AFD!"); return;
    }
    $autoafd.status_tag.innerHTML += ' done!';
    $autoafd.async_done_1();
}

$autoafd.ListAFD_done = function(req) {
    if (req.status != 200) {
        alert("Error listing article for AFD!"); return;
    }
    $autoafd.status_list.innerHTML += ' done!';
    $autoafd.async_done_1();
}

$autoafd.async_done_1 = function() {
    if (--$autoafd.async_wait == 0) {
        document.editform.wpSave.disabled = false;
        //document.editform.wpSave.click();
        document.editform.submit();
    }
}

$util.addOnloadHook($autoafd.load);

/****************************************************************************
 * END autoafd.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN afd_vote.js
 ****************************************************************************/

// $Id: afd_vote.js 408 2006-02-14 09:50:41Z quarl $

// afd_vote.js - AFD auto-voting and shortcuts

// originally based on http://en.wikipedia.org/wiki/User:Jnothman/afd_helper/script.js
// [[User:Jnothman/afd_helper/script.js]]

//  - asynchronous updating (never opens new windows)
//  - live updating of log page
//  - huge list of shortcut expansions and other shortcut features
//  - more "vote" buttons such as in original article
//  - escaping bugs ('&&', '+', etc) fixed
//  - refuse to create AFD pages (in case of more escaping bugs)

var $afd_vote = new Module('afd_vote.js');
$afd_vote.depend('wikipageXfd.js', 'wikiedit.js', 'util.js', 'shortcuts.js', 'datetime.js');
// enhanced by: advanced_sig.js

$afd_vote.summary_prompt = true;

$afd_vote.shortcuts = Shortcuts({
            'D'         : 'Delete',
            'DA'        : 'Delete all',
            'K'         : 'Keep',
            'M'         : 'Merge',
            'MV'        : 'Move',
            'R,RD'      : 'Redirect',
            'RW'        : 'Rewrite',
            'T'         : 'Transwiki',
            'WD'        : 'Weak delete',
            'SD'        : 'Strong delete',
            'SP'        : 'Speedy delete',
            'SK'        : 'Strong keep',
            'SM'        : 'Slight merge',
            'WK'        : 'Weak keep',
            //'SPK'     : '[[Wikipedia:Speedy keep|Speedy keep]]',
            'SPK'       : 'Speedy keep',
            'C'         : 'Comment',
            'MC'        : 'Metacomment',
            'BJAODN,BJ' : 'BJAODN'
            });

$afd_vote.comment_shortcuts = Shortcuts( {
            'PN'                          : 'per nomination',
            'NN'                          : '[[WP:N|non-notable]]',
            'V,VAIN,VANITY'               : '[[WP:VAIN|vanity]]',
            'NNBIO'                       : '[[WP:BIO|non-notable biography]]',
            'NNVBIO'                      : '[[WP:BIO|non-notable vanity biography]]',
            'NNWEB'                       : '[[WP:WEB|non-notable website]]',
            'NNWEBCOMIC '                 : '[[WP:WEB|non-notable webcomic]]',
            'NNBLOG,NNWEBLOG'             : '[[WP:WEB|non-notable weblog]] ',
            'NNFORUM,NNWEBFORUM'          : '[[WP:WEB|non-notable web forum]] ',
            'NNSOFTWARE,NNSOFT,NNSW'      : '[[WP:SOFTWARE|non-notable software]] ',
            'NNCORP,NNCOMPANY'            : '[[WP:CORP|non-notable corporation]]',
            'NNMUSIC'                     : '[[WP:MUSIC|non-notable musical group]]',
            'NNBAND'                      : '[[WP:MUSIC|non-notable band]]',
            'NNUNEO'                      : '[[Wikipedia:Avoid_neologisms|non-notable unstable neologism, i.e. protologism]]',
            'NNUUNEO'                     : '[[Wikipedia:Avoid_neologisms|non-notable unverifiable unstable neologism, i.e. protologism]]',
            'NNFICT,NNFICTION'            : '[[WP:FICT|non-notable reference to fictional work]]',
            'NNFICTC,NNFICTCHAR,NNCHAR'   : '[[WP:FICT|non-notable character from fictional work]] ',
            'FANCRUFT,NNFAN'              : '[[Wikipedia:Fancruft|fancruft]]',
            'NNFANFIC'                    : '[[Wikipedia:Fancruft|non-notable fan fiction]]',
            'NNGAME'                      : '[[WP:N|non-notable]] online gaming group',
            'U'                           : '[[WP:V|unverifiable]]',
            'UPH'                         : '[[WP:V|unverifiable]], possible [[WP:HOAX|hoax]]',
            'OR'                          : '[[WP:NOR|original research]]',
            'UOR'                         : '[[WP:V|unverifiable]] and/or [[WP:NOR|original research]]',
            'H'                           : '[[WP:HOAX|hoax]]',
            'HSANC,HSANCT,HSANCTION'      : '[[WP:HOAX|hoax]], and sanction article author',
            'ATTACK'                      : '[[WP:CSD|attack page]]',
            'WISHSP,WISHEXPAND'           : 'I wish for expansion of [[WP:CSD]] so that this kind of article could be speedy-deleted when no notability is asserted ',
            'UNENC,UNENCYCLO,NOTPEDIC'    : '[[WP:NOT|unencyclopedic]]',
            'NOT'                         : '[[WP:NOT|Wikipedia is not]]',
            'NOTADVERT'                   : '[[WP:NOT|Wikipedia is not a vehicle for advertising]] ',
            'NOTBALL,NOTCRYSTAL'          : '[[WP:NOT|Wikipedia is not a crystal ball]] ',
            'NOTCRUFT'                    : '[[WP:NOT|Wikipedia is not an indiscriminate collection of information]] ',
            'NOTDICT,NOTDIC'              : '[[WP:NOT|Wikipedia is not a dictionary]] (but [[Wiktionary]] is) ',
            'NOTMEMORIAL'                 : '[[WP:NOT|Wikipedia is not a memorial]] ',
            'NOTOR,NOTORIGINAL'           : '[[WP:NOT|Wikipedia is not a publisher of original thought]] ',
            'NOTSOAPBOX'                  : '[[WP:NOT|Wikipedia is not a soapbox]] ',
            'NOTSW,NOTSWDIR'              : '[[WP:NOT|Wikipedia is not a software directory]] ',
            'NOTWEBHOST,NOTFREEHOST'      : '[[WP:NOT|Wikipedia is not a free host or webspace provider]] ',
            'NOTWEBDIR'                   : '[[WP:NOT|Wikipedia is not a web directory]] ',
            'NFT,NOTSCHDAY,NOTSCH'        : '[[WP:NFT|Wikipedia is not for things made up in school one day]] ',
            'XBIO,BIOX'                   : 'Recommend the article author see [http://www.wikime.org/ WikiMe] for writing biographies and/or [http://www.wikitree.org WikiTree] for writing genealogies ',
            'XUSERFY,USERFYX'             : 'Article author may want to consider moving the content to his [[Wikipedia:User page|user page]] ',
            'XPROTO,XPROTOLOGISM,PROTOX'  : 'Protologisms may deserve listing at [[Wiktionary:List_of_protologisms]] ',
            'BALLS,BALL'                  : '[[WP:BALLS|Complete bollocks]]'
            });

$afd_vote._load = function() {
    if (afdLogP) {
        // log page
        $afd_vote.annotateAfd();
    } else if (afdP) {
        // AFD page
        $afd_vote.annotateAfd();
        $wikiwidget.addTab('javascript:$afd_vote.doVote()', 'vote', 'ca-vote', "Vote on this AFD");
    } else {
        $afd_vote.annotateArticle();
    }
}

$afd_vote.annotateArticle = function() {
    // is this a regular article that has an AFD notice?
    var afd = document.getElementById('afd');
    if (!afd) return;

    var href = 'javascript:$afd_vote.doVote()';
    var title = 'Vote on deletion of '+wikiPage.page;

    var anchors = $util.copyArray(afd.getElementsByTagName('a'));
    for (i in anchors) {
        if (anchors[i].text == "this article's entry" &&
            anchors[i].href.match(/Wikipedia:Articles_for_deletion\/.*/))
        {
            var span = document.createElement('span');
            span.innerHTML = ' [<a href="'+href+'" title="'+title+'">vote</a>]';
            $util.addNodeAfter(anchors[i], span);
            break;
        }
    }

    $wikiwidget.addTab(href, 'vote', 'ca-vote', title);
}

$afd_vote.annotateAfd = function() {
    var url_re = /(\/w\/index.php\?title=Wikipedia:Articles_for_deletion\/([^&]+))&action=edit&/;
    var url, matches;
    $afd_vote.sectionDivs = $util.getElementsByClass('editsection', document.getElementById('bodyContent'), 'div');
    $afd_vote.labeledSectionDivs = {};

    for (var i in $afd_vote.sectionDivs) {
        var div = $afd_vote.sectionDivs[i];
        div.i = i;
        var anchor = div.getElementsByTagName('a')[0];
        if (!( anchor.text == "edit"
               && (matches = anchor.href.match(url_re))
               && (matches[2].substr(0, 4) != 'Log/')) )
            continue;
        var title = ""+unescape(matches[2]).replace(/_/g,' ');

        // setup for easy lookup and traversal later
        $afd_vote.labeledSectionDivs[title] = div;

        var closed = Boolean(anchor.parentNode.parentNode.getAttribute('class')=='boilerplate metadata vfd');

        if (!closed) {
            var vote_href = "javascript:$afd_vote.doVote("+$util.stringQuoteEscape(title)+")";
            $util.addNodeAfter(anchor, $util.createHref(vote_href, 'Vote on deletion of '+title, 'vote'));
            $util.addNodeAfter(anchor, document.createTextNode("] ["));
        }
        var log_href = "/w/index.php?title=Special:Log&page=" + $util.wpaEscape(title);
        $util.addNodeBefore(anchor, $util.createHref(log_href, title, 'log'));
        $util.addNodeBefore(anchor, document.createTextNode("] ["));
        var afd_href = matches[1];
        $util.addNodeBefore(anchor, $util.createHref(afd_href, title, 'afd'));
        $util.addNodeBefore(anchor, document.createTextNode("] ["));
    }
}

// return true if string ends with period (can also have symbols such as closing paren after period)
$afd_vote._ends_with_period = function(str) {
    return Boolean(str.match(/[.?!][^a-zA-Z]*$/));
}

// return true if comment needs to be prefixed by 'as '
$afd_vote._comment_needs_as = function(comment) {
    var m = comment.match(/^([a-zA-Z]+)(.*)$/);
    var word1 = m && m[1];
    if (!word1) return false;
    if (word1 == 'or') return false; // special case for lowercase 'or'
    if (word1.toUpperCase() == 'PN') return false; // special case for 'PN'
    return $afd_vote.comment_shortcuts.substP(word1);
}

$afd_vote.expand_vote = function(vote) {
    vote = $afd_vote.shortcuts.substFirstWord(vote);
    vote = $afd_vote.shortcuts.substUppercaseWords(vote);
    return vote;
}

$afd_vote.expand_comment = function(vote, comment) {
    // if first word is a shortcut other than 'per nomination', prefix with 'as'

    var need_as = $afd_vote._comment_needs_as(comment);

    comment = $afd_vote.comment_shortcuts.substUppercaseWords(comment);
    if (!comment.match(/^or /)) {
        // "or" is too common as first word... use uppercase "OR" if that's intended.
        comment = $afd_vote.comment_shortcuts.substFirstWord(comment);
    }

    if (need_as) {
        comment = 'as ' + comment;
    }

    if (!$afd_vote._ends_with_period(comment)) {
        comment += ".";
    }

    // prefix with space if necessary
    if (!comment.match(/^[.,:; ]/)) {
        comment = " " + comment;
        if (vote == 'Comment') comment = ":" + comment;
    }

    // common mistake
    comment = comment.replace(/{{(nn-?bio|nn-?band|db-attack|db-repost)}}/, '{{tl|$1}}');

    return comment;
}

$afd_vote._comment_possibly_unexpanded = function(comment) {
    // did user typo one of the shortcuts?
    return comment.match(/[A-Z][A-Z][A-Z]+(?![\]A-Z\|])/);
}

$afd_vote.doVote = function(pagename) {
    if (pagename) {
        $afd_vote.doVoteWP(new WikiPage(null,pagename));
    } else {
        $afd_vote.doVoteWP(wikiPage);
    }
}

$afd_vote.doVoteWP = function(wp) {
    wp = wp.afdPageX();
    if (!(wp instanceof WikiPage)) { alert("## internal error bfc4b745-0e83-4e9a-9a16-7107c8e046ef: $afd_vote: not a WikiPage"); return; }
    var vote0 = window.prompt("Enter your vote.   " + $afd_vote.shortcuts.msg());
    if (!vote0) return;
    var vote = $afd_vote.expand_vote(vote0);
    var vote_used_shortcut = (vote != vote0);

    var comment0prev;
    var comment0 = '';
    var comment;
    var pr = "Enter your comment.  ";

    while(true) {
        comment0 = window.prompt(pr + $afd_vote.comment_shortcuts.msg(), comment0);
        if (typeof comment0 != 'string') return;
        comment = $afd_vote.expand_comment(vote, comment0);

        if (comment0 != comment0prev &&
            $afd_vote._comment_possibly_unexpanded(comment))
        {
            comment0prev = comment0;
            pr = "Did you really mean '"+RegExp.lastMatch+"'?  Correct if you want.  ";
            continue;
        }
        break;
    }

    var default_summary = "vote '"+vote+"'";
    var summary;

    if ($afd_vote.summary_prompt && !vote_used_shortcut) {
        summary = window.prompt("Enter the edit summary:", default_summary);
        if (typeof summary != 'string') return;
    }
    summary = summary || default_summary;

    var newtext = "* '''"+vote+"'''"+comment+" <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>";
    wp.getEditorAsync($afd_vote._edit, newtext, summary);
}

$afd_vote._edit = function(editor, newtext, summary) {
    if (editor.refuseCreate()) return;

    editor.wpTextbox1 = $util.trimLines(editor.wpTextbox1) + '\n' + newtext;
    editor.wpSummary += summary;

    // are we at a log page?  (Note that 'window.location.href' is not as good as wikiPage because of shortcut redirects such as [[WP:AFD/Today]])

    var title = editor.wp.afdTargetPage().page;
    if (!title) {
        alert("## $afd_vote._edit: bad page name (error b2e39d30-fd3d-405e-adf1-5f0c7c034e53)");
    }

    var div, sec_end;
    if (afdP || afdLogP) {
        // show status if we're on an AFD or AFD Log page
        if (afdLogP) {
            div = $afd_vote.labeledSectionDivs[title];
            if (!div) { alert("## No labeledSectionDivs['"+title+"']"); return; }
            sec_end = $afd_vote.sectionDivs[parseInt(div.i)+1];
            if (!sec_end) {
                // No next entry, must be end of page.  Add an empty div.
                sec_end = document.createElement('div');
                div.parentNode.appendChild(sec_end);
            } else if (sec_end.parentNode.id != 'bodyContent') {
                // if the next entry is a closed discussion then we need to go
                // one node up the tree
                sec_end = sec_end.parentNode;
            }
        } else {
            sec_end = $util.getElementsByClass('printfooter', document, 'div')[0];
        }
        var statusDiv = document.createElement('div');
        statusDiv.innerHTML = "<i>(submitting...)</i>";
        $util.addNodeBefore( sec_end, statusDiv );
    }

    if (afdLogP) {
        // We're looking at a log page.  Submit this asynchronously and replace
        // the content of this section of the log page with new content.

        editor.submitAsync(null, $afd_vote.log_update, div, sec_end);
    } else {
        // submit and go to changed page
        editor.submit();
    }
}

$afd_vote.log_update = function(req, div, sec_end) {
    if (req.status != 200) { alert ("Error submitting vote!"); return; }
    if (!div || !sec_end) { alert ("## $afd_vote.log_update error"); return; }

    // Replace nodes between div and next div with new content.
    // Start at the <h3> tag, because the afd page text doesn't have a
    // section number (1.123), but the log text does.
    var newnodes_start = $util.getElementsByClass('editsection', req.responseXML, 'div')[0].nextSibling.nextSibling.nextSibling.nextSibling.nextSibling;
    var newnodes_end = $util.getElementsByClass('printfooter', req.responseXML, 'div')[0];
    var replacement_nodes = $util.getNodesInRange(newnodes_start, newnodes_end);
    var newnode = document.createElement('div');
    for (i in replacement_nodes) { newnode.appendChild(replacement_nodes[i]); }

    var oldnodes_start = div.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling;
    $util.removeNodesInRange(oldnodes_start, sec_end);
    $util.addNodeAfter(div.nextSibling.nextSibling.nextSibling.nextSibling, newnode);
}

$util.addOnloadHook($afd_vote._load);

/****************************************************************************
 * END afd_vote.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN xfdclose.js
 ****************************************************************************/

// $Id: xfdclose.js 438 2006-02-14 11:18:33Z quarl $

// xfdclose.js - close afd/mfd/etc.

// quarl 2006-01-04 initial version

// USAGE:
//   $module.depend('xfdclose.js');
//   $xfdclose.widgetLoad();

// TODO: shortcuts

var $xfdclose = new Module('xfdclose.js');
$xfdclose.depend('wikipage.js', 'wikipageXfd.js', 'wikiedit.js', 'wikiwidget.js');

$xfdclose.load = function() {
    if (window.afdP || wikiPage.sandboxP) {
        $xfdclose.widget = new WikiWidget({
            default_location: {portlet: 'Actions'},
            onclick: $xfdclose.run,
            // url: TODO,
            name: 'Close',                          // TODO: close AFD/MFD/etc
            id: 'ca-xfdclose',
            title: "Close this AFD discussion"      // TODO: close AFD/MFD/etc
            });
    }
};

$xfdclose.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($xfdclose.widget) $xfdclose.widget.add(location);
        });
};

$xfdclose.run = function() {
    $xfdclose.close(wikiPage, window.prompt("Enter closing comment.  Example: '''Delete''' as non-notable band."));
}

$xfdclose.close = function(wp, comment) {
    wp.getEditorAsync($xfdclose._edit, comment);
}

$xfdclose._edit = function(editor, comment) {
    if (!comment) return;
    var add_before = "{{subst:afd top}} " + comment + " <span class="user-sig user-Adrian"><i>—~~~ <small>[[2006-02-26]] 01:57Z</small></i></span>\n\n";
    var add_after = "\n\n{{subst:afd bottom}}\n";
    var summary = 'AFD closed; result: ' + comment;

    editor.wpTextbox1 = add_before + $util.trimLines(editor.wpTextbox1) + add_after;
    editor.wpSummary = summary;
    editor.submit();
}

$util.addOnloadHook($xfdclose.load);


/****************************************************************************
 * END xfdclose.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN obsolete/autowarn.js
 ****************************************************************************/

// $Id: autowarn.js 926 2006-02-22 06:19:49Z quarl $

// autowarn.js -- obsoleted by userwarn.js




// [[User:Quarl/autowarn.js]] - warn user using templates.  Adds a "warn"
// menu.  (formerly [[User:Quarl/auto_testn.js]])

// Prompts for relevant pages; e.g. enter "foo && bar && baz", and outputs
// "This message concerns the pages [[foo]], [[bar]], and [[baz]].  Thanks for
// experimenting..."

// depends: wikipage.js, wikiedit.js, wikitabs.js
// enhanced by: advanced_sig.js

// <pre><nowiki>

var $autowarn = new Module('obsolete/autowarn.js');
var autowarn = $autowarn;

autowarn.warn = function(template) {
    var pagenames = prompt("Vandalism to which article(s) (separate using &&)?");
    if (typeof pagenames != 'string') return;

    wikiPage.getEditorAsync(autowarn._edit, template, pagenames);
}

autowarn._englishJoin = function(words) {
    if (words.length == 0) {
        return '';
    } else if (words.length == 1) {
        return words[0];
    } else if (words.length == 2) {
        return words[0] + ' and ' + words[1];
    } else {
        words[words.length-1] = 'and ' + words[words.length-1];
        return words.join(', ');
    }
}

autowarn._splitPageNames = function(s) {
    var words = s.split('&&');
    var pages = [];
    for (var i in words) {
        var word = $util.trimSpaces(words[i]);
        if (!word) continue;
        word = word.replace(/^\[\[/, '');
        word = word.replace(/\]\]$/, '');
        pages.push('[[' + word + ']]');
    }
    return pages;
}

autowarn._edit = function(editor, template, pagenames) {
    if (typeof(template) != 'string') { alert("autowarn Internal error 5f95d195-b1c8-4f7e-b751-740230b1926a"); return; }

    var pagesw = autowarn._splitPageNames(pagenames);
    var pages = autowarn._englishJoin(pagesw);

    var text = '{{subst:' + template + '}} ' + '~~~~' + '\n';

    if (pages) {
        text = ('This message concerns the ' + (pagesw.length==1?'page':'pages') + ' ' +
                pages + '.  ' + text);
    }
    text = '\n\n' + text;

    var summary = "Warning {{" + template + "}}";
    if (pages) {
        summary += ", concerning " + pages;
    }

    // if (editor.refuseCreate()) return;
    editor.wpTextbox1 = $util.trimLines(editor.wpTextbox1) + text;
    editor.wpSummary = summary;
    editor.submit();
}

autowarn._load = function() {
    // Only add tab to User talk pages (but not subpages)

    if (wikiPage.sandboxP ||
        wikiPage.namespace == 'User talk' && !wikiPage.subarticle)
    {
        autowarn._addTabs();
    }
}

autowarn.widgetLoad_ = function()
{
    if (!wikiPage.nsUser) return;

    var menu = $wikiwidget.addTabMenu('Warn', 'mn-warn');

    var qt = function(s) { return s ? '"'+s+'"' : 'null'; }

    var warning = function(template /*, alttemplate */, title) {
        title = "Warn user using template {{"+template+"}}: " + title;
        $wikiwidget.addLiLink(menu, 'javascript:autowarn.warn(' + qt(template) /* + ', ' + qt(alttemplate)*/ + ')',
                            template, 'ca-autowarn-'+template,
                            title);
    }

    warning('nn-test', 'Non-notable additions');
    // warning('test0', 'Before making controversial edits, please discuss');
    warning('selftest', 'Thanks for reverting yourself');
    warning('test1', 'Your test worked, and has been reverted');
    warning('test2', 'Stop adding nonsense ');
    warning('test2a', 'Vandalism level 2 (blanking)');
    warning('test3', 'Stop vandalizing, or face block');
    warning('test4', 'Last warning before block');
    warning('test4im', 'This is your ONLY WARNING before block');
    warning('test5', 'You have been temporarily BLOCKED');
    warning('test6', 'You have been BLOCKED for repeated vandalism');
    warning('bv', 'Blatant vandal');

    // warning('spam1',);
    // warning('spam2',);


}

autowarn.widgetLoad = function() {
    $util.addOnloadHook(autowarn.widgetLoad_);
}

//

// </nowiki></pre>

/****************************************************************************
 * END obsolete/autowarn.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN redirector.js
 ****************************************************************************/

// $Id: redirector.js 1128 2006-02-23 07:53:07Z quarl $

// redirector.js - "Redirector" application

// When you invoke the Redirector (through a tab), adds a new form to the
// document.  Start typing page names and press ENTER after each one.  A table
// will show the current status of each page (already a redirect, doesn't
// exist, etc.), and if you click "Redirect!", the target page will be updated
// to redirect to the current page.

// USAGE:
//   $module.depend('redirector.js');
//   $redirector.widgetLoad();

// quarl 2006-02-09 initial version

var $redirector = new Module('redirector.js');
$redirector.depend('util.js', 'wikipage.js', 'wikiedit.js', 'wikiwidget.js', 'wikiparse.js');
$redirector.depend('redirector.css');

$redirector.options = {
    // seconds between updates
    update_interval: 1.0,

    // maximum number of concurrent requests
    max_concurrent_requests: 5
};

$redirector._entries = [];
$redirector._requests_in_progress = 0;

$redirector.run = function() {
    $redirector.widget.li.innerHTML = '<a><b>Redirector</b></a>'; // prevent future clicks
    $redirector.annotateDocument();
}

$redirector.annotateDocument = function() {
    if (document.getElementById('redirector')) {
        // already done, called again for some reason
        return;
    }

    var divLoc = document.getElementById('jump-to-nav');
    if (!divLoc) {
        alert("Redirector: couldn't get 'jump-to-nav' (error d5153643-a8c1-46ad-8d8e-c5d3fe066c77)");
        return;
    }

    $redirector.doc = new Object();
    $redirector.doc.div = document.createElement('div');
    $redirector.doc.div.id = 'redirector';

    $redirector.doc.div.innerHTML = (
        '<h2>Redirector</h2>' +
        'Enter names of articles to redirect to <tt>'+wikiPage.page+'</tt>, and press ENTER after each one:' +
        '<form id="rcInputForm"><input type="text" id="rcConsole" name="rcConsole" size="60" />' +
        '<input type="submit" onclick="javascript:return $redirector.inputClicked()" value="Enter" />' +
        '<input type="submit" onclick="javascript:return $redirector.resetClicked()" value="Clear all" />' +
        '</form>' +
        '<table id="rcStatus" class="prettytable"><tr><th>Page</th><th>Status</th><th>Action</th></table>'
        );
    $util.addNodeAfter(divLoc, $redirector.doc.div);

    $redirector.doc.rcConsole = document.getElementById('rcConsole');
    $redirector.doc.rcStatus = document.getElementById('rcStatus');

    $redirector.doc.rcConsole.value = wikiPage.page;

    $redirector.doc.rcConsole.focus();

    $redirector.intervalHandle = setInterval($redirector.update, $redirector.options.update_interval * 1000);
}

$redirector.inputClicked = function() {
    var target = $util.trimSpaces($redirector.doc.rcConsole.value);
    if (!target) return false;
    if (target == wikiPage.page) return false;
    $redirector.addEntry(target);
    // $redirector.doc.rcConsole.value = wikiPage.page;
    return false;
}

$redirector.resetClicked = function() {
    for (var i in $redirector._entries) {
        $redirector._entries[i].clearentry = true;
    }
    return false;
}

$redirector.addEntry = function(s) {
    var t = new Object();

    // wiki page
    t.wp = new WikiPage(null, s);
    if (!t.wp) return;

    // delete old entry if any
    for (var i in $redirector._entries) {
        if ($redirector._entries[i].wp.page == t.wp.page) {
            $redirector._entries[i].clearentry = true;
        }
    }

    // object for document status nodes.  null = not yet added
    t.statusnode = null;

    // download request status.  0 = not yet initiated; 1 = initiated; 2 =
    // finished
    t.downloadstatus = 0;

    // WikiEditor instance
    t.editor = null;

    // string value of old contents of target page
    t.oldcontents = null;
    t.oldredirect = null;

    // whether user has clicked "update" button
    t.updatewanted = false;

    // whether update requires confirmation
    t.updatedangerous = false;

    // update request status.  0 = not yet initiated; 1 = iniatited; 2 =
    // completed
    t.updatestatus = 0;

    // whether this entry should be cleared when done with it
    t.clearentry = false;
    t.ignore = false;

    $redirector._entries.push(t);
    $redirector.update();
}

// main action loop
$redirector.update = function() {
    for (var i in $redirector._entries) {
        var t = $redirector._entries[i];

        if (t.ignore) continue;
        if (t.clearentry) {
            // only clear it if nothing in progress
            if ((t.downloadstatus == 0 || t.downloadstatus == 2) &&
                (t.updatestatus == 0 || t.updatestatus == 2))
            {
                t.statusnode.tr.parentNode.removeChild(t.statusnode.tr);
                t.ignore = true;
                continue;
            }
        }

        if (!t.statusnode) {
            $redirector._addStatusNode(t);
        }

        if ($redirector._requests_in_progress < $redirector.options.max_concurrent_requests) {
            if (t.downloadstatus == 0) {
                $redirector._initiateDownload(t);
            } else if (t.downloadstatus == 2 && t.updatewanted && t.updatestatus == 0) {
                $redirector._initiateUpdate(t);
            }
        }
    }
}

$redirector._addStatusNode = function(t) {
    t.statusnode = new Object();
    t.statusnode.tr = document.createElement('tr');

    t.statusnode.page = document.createElement('td');
    var url = t.wp.qurl + '&redirect=no';
    var urlEdit = t.wp.qurl + '&action=edit';
    t.statusnode.page.innerHTML = (
        '<a href="' + url + '">' + t.wp.page + '</a>' +
        ' [<a href="' + urlEdit +'">edit</a>]');

    t.statusnode.status = document.createElement('td');
    t.statusnode.status.className = 'redirectorStatus';
    t.statusnode.status.innerHTML = '...';

    t.statusnode.action = document.createElement('td');
    t.statusnode.button = document.createElement('button');
    t.statusnode.button.onclick = function() { $redirector._actionClick(t) };
    t.statusnode.button.disabled = true;
    t.statusnode.button.innerHTML = 'please wait';
    t.statusnode.action.appendChild(t.statusnode.button);

    t.statusnode.tr.appendChild(t.statusnode.page);
    t.statusnode.tr.appendChild(t.statusnode.status);
    t.statusnode.tr.appendChild(t.statusnode.action);

    $redirector.doc.rcStatus.appendChild(t.statusnode.tr);
}

$redirector._actionClick = function(t) {
    if (t.updatedangerous) {
        if (!window.confirm("The target is not a redirect.  Are you sure?")) return;
    }

    t.updatewanted = true;
    t.statusnode.button.disabled = true;
}

$redirector._initiateDownload = function(t) {
    t.downloadstatus = 1;
    $redirector._requests_in_progress ++;
    t.statusnode.status.innerHTML = '<span class="downloading">downloading...</span>';
    t.wp.getEditorAsync($redirector._downloadComplete, t);
}

$redirector._downloadComplete = function(editor, t) {
    t.downloadstatus = 2;
    $redirector._requests_in_progress --;
    t.editor = editor;
    t.oldcontents = t.editor.wpTextbox1;

    if (t.oldcontents.match(/^(?:\s|\n)*$/)) {
        t.status = 'empty';
        t.statustext = "empty";
        t.statusnode.button.innerHTML = 'Create redirect!';
        t.statusnode.button.disabled = false;
    } else if ( (t.oldredirect = $wikiparse.isRedirect(t.oldcontents)) ) {
        t.oldredirectwp = new WikiPage(null, t.oldredirect);
        // use the parsed version (e.g. some people use _ or % escapes)
        t.oldredirect = t.oldredirectwp.page;
        if (t.oldredirect == wikiPage.page) {
            t.status = 'toHere';
            t.statustext = 'redirect to here';
            t.statusnode.button.innerHTML = 'Nothing to do!';
            t.statusnode.button.disabled = true;
        } else {
            t.status = 'toElsewhere';
            t.statustext = 'redirect to <a href="'+t.oldredirectwp.url+'">'+t.oldredirectwp.page+'</a>';
            t.statusnode.button.innerHTML = 'Alter redirect!';
            t.updatedangerous = true;
            t.statusnode.button.disabled = false;
        }
    } else {
        t.status = 'notRedirect';
        t.statustext = 'non-redirect (' + $util.wordCount(t.oldcontents) + ' words)';
        t.statusnode.button.innerHTML = 'Change to redirect';
        t.updatedangerous = true;
        t.statusnode.button.disabled = false;
    }

    t.statusnode.status.innerHTML = '<span class="'+t.status+'">' + t.statustext + '</span>';
}

$redirector._initiateUpdate = function(t) {
    t.updatestatus = 1;
    t.statusnode.button.innerHTML = 'Submitting...';
    $redirector._requests_in_progress ++;
    var rd = t.editor.wpTextbox1 = '#REDIRECT [[' + wikiPage.page + ']]\n';
    if (t.status == 'empty') {
        t.editor.wpSummary = 'Creating ' + rd;
    } else if (t.status == 'toElsewhere') {
        t.editor.wpSummary = 'Changing redirect from [[' + t.oldredirectwp.page + ']] to ' + rd;
    } else if (t.status == 'notRedirect') {
        t.editor.wpSummary = 'Changing article (' + $util.wordCount(t.oldcontents) + ' words) to ' + rd;
    } else {
        alert("internal error 3056fdaf-1147-4433-8334-d92eb2ffd8b7");
        return;
    }

    t.editor.submitAsync(null, $redirector._updateComplete, t);
}

$redirector._updateComplete = function(editor, t) {
    t.updatestatus = 2;
    t.statusnode.button.innerHTML = 'Done!';
    $redirector._requests_in_progress --;

    t.statusnode.status.innerHTML = '<span class="done">Redirected to here! (<span class="previous">' + t.statustext + '</span>)</span>';
}

$redirector.load = function() {
    if (wikiPage.nsSpecialP) return;

    $redirector.widget = new WikiWidget({
        default_location: {portlet: 'Toolbox'},
        onclick: $redirector.run,
        name: 'Redirector',
        id: 'ca-rediretor',
        title: 'Run the Redirector'});
};

$util.addOnloadHook($redirector.load);

$redirector.widgetLoad = function(location) {
    $util.addOnloadHook(function() {
            if ($redirector.widget) $redirector.widget.add(location);
        });
};

/****************************************************************************
 * END redirector.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN popups.js
 ****************************************************************************/

// -*- coding:utf-8 -*-
// $Id: popups.js 1250 2006-02-25 06:27:07Z quarl $

// originally from http://en.wikipedia.org/wiki/User:Lupin/popups.js
// [[User:Lupin/popups.js]]

var $popups = new Module('popups.js');
$popups.depend('overlib/overlib.js',
               'instaviewtiny.js',
               'md5.js', 'drag.js',
               'kookie.js', 'util.js',
               'wikipage.js', 'wikins.js',
               'wikiimg.js', 'wikiparse.js',
               'download.js', 'linkedit.js',
               'editcount.js');
$popups.depend('popups.css');


var ipUserRegex=RegExp('('+$wikins.User+':)?' +
                       '((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
                       '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])');

var oldidRegex=RegExp('[?&]oldid=([0-9]*)');

var popupImageSize=60;

$popups.popupIdCount = 0;

// for setPopupHTML - needed for timers and stuff
var popupHTMLTimers=new Array();
var popupHTMLLoopFunctions = new Array();

// options

// if there's no cookie, this should expand to something like
// if (window.foo===null) window.foo=window.dfoo;
function defaultize(x) {
  var val=null;
  var a='window.'+x;
  var b='window.d'+x;
  if (x!='popupCookies') {
    defaultize('popupCookies');
    if (popupCookies && (val=$kookie.get(x))) {
      eval(a + '=' + val + ';');
      return;
    }
  }
  eval('if ('+a+'===null) '+a+'=' + b + ';');
};

// this should expand to something like
// if (typeof window.foo=='undefined') { window.foo=null; }; window.dfoo = 0.5;
function newOption(x, def) {
  var a='window.'  + x;
  var b='window.d' + x;
  eval('if (typeof '+ a + '==\'undefined\') {' + a + '=null; }; ' + b + '=' + def + ';');
};

function getValueOf(varName) {defaultize(varName); return eval(varName);};

// user-settable parameters and defaults
// names beginning with a d are probably a bad idea!
// NB strings must be "'double-quoted'"
newOption('popupDelay', 0.5);
newOption('removeTitles', true);
newOption('imagePopupsForImages', true);
newOption('popupSummaryData', true);
newOption('simplePopups',  false);
newOption('popupAdminLinks',  false);
newOption('popupImages', true);
newOption('popupPreviews', true);
newOption('popupNavLinks', true);
newOption('popupNavLinkSeparator', "' &sdot; '");
newOption('popupOnlyArticleLinks', true);
newOption('popupNeverGetThumbs', false);
newOption('popupImagesFromThisWikiOnly', false);
newOption('popupAppendRedirNavLinks', true);
newOption('popupMaxWidth', 300);
newOption('popupSimplifyMainLink', true);
newOption('popupMinImageWidth', 50);
newOption('popupLastEditLink', true);
newOption('popupHistoricalLinks', true);
newOption('popupFixRedirs', true);
newOption('popupLiveOptions', false);
newOption('popupCookies', false);
newOption('popupLinkShowPreview', false);
newOption('popupShortcutKeys', false);
newOption('popupLiveOptionsExpanded', false);
newOption('popupFixDabs', true);
newOption('popupImagesToggleSize',true);
newOption('popupNewWindows', false);
// see later - default for popupStructure is "'original'" if simplePopups is true
newOption('popupStructure', "'menus'");
newOption('popupInitialWidth', false); // integer or false
newOption('popupNavLinkStyle', "'default'");
newOption('popupDragging', true);
newOption('popupActionsMenu', true);
newOption('popupRevertSummary', "'Reversion to revision %s'");
newOption('popupPreviewKillTemplates', true);

// browser-specific hacks

if (typeof window.opera != 'undefined') {
  dpopupStructure='original';
}

if ((self.navigator.appName)=='Konqueror') {
  dpopupNavLinkSeparator=' &bull; ';
} else if ((self.navigator.appName).indexOf("Microsoft")!=-1) {
  dpopupNavLinkSeparator=' &#183; ';
  dpopupStructure='original';
}

$popups.Popup = function(a) {
    this.a = a;
    if (!a.wp) {
        a.wp = WikiPageRelevant(a.href);
    }
    this.wp = a.wp;
    this.popupId = $popups.popupIdCount++;
}

$popups.Popup.prototype.debug = function(s) {
    return $popups.debug('Popup('+this.popupId+')' + s);
}

// generate html for popup image
// <a id="popupImageLinkn"><img id="popupImagen">
// where n=popupId
$popups.Popup.prototype.imageHTML = function(article) {
  var ret='';
  ret += '<a id="popupImageLink' + this.popupId + '">';
  ret += '<img align="right" valign="top" id="popupImg' + this.popupId + '" '
    + 'style="display: none;"></img>';
  ret += '</a>';
  return ret;
};

function isInToc(a) {
    return $util.isDescendent(a, document.getElementById('toc'), '_descendent_toc');
}

function isInArticle(a) {
    // 'content' is for monobook
    if (document.getElementById('article'))
        return $util.isDescendent(a, document.getElementById('article'), '_descendent_article');
    else
        return $util.isDescendent(a, document.getElementById('content'), '_descendent_content');
};

function WikiPageRelevant(h, safe) {
    var wp = WikiPage(h, null, null, safe);
    if (!wp) return null;
    if (wp.relevantUser && !wp.nsUserP) {
        wp = WikiPage(null, $wikins.User + ':' + wp.relevantUser);
    }
    return wp;
};

function oldidFromURL(a) { var m=oldidRegex.exec(a); if (m) return m[1]; return null; };

// if redir is present and true then redirTarget is mandatory
$popups.Popup.prototype.fillEmptySpans = function(args) {
    if (!this.a) {
        $popups.error("fillEmptySpans: no 'a' (error 9b76dad9-f13a-4f4a-9b6f-2afc5247c1c4)");
        return;
    }

    var wp, hint, oldid;
    var redir = false;
    if (args && args.redir) {
        wp = args.redir; hint=null;
        redir = true;
    } else {
        wp = this.wp;
        hint = this.a.originalTitle || wp.page;
        oldid=(getValueOf('popupHistoricalLinks'))?oldidFromURL(this.a.href):null;
    }

    this.debug('.fillEmptySpans(): redir='+redir+ ', page='+wp.page);

    var x={wp: wp,
           hint: hint,
           oldid: oldid };

    defaultize('popupStructure');
    var structure=window.popupStructures[popupStructure];
    if (typeof structure != 'object') {
        this.setPopupHTML('popupError', 'Unknown structure: '+ popupStructure);
        return;
    }
    var spans=$util.flatten(window.popupLayout);
    var redirs=window.popupRedirSpans;
    this.debug('.fillEmptySpans(): spans.length='+spans.length);
    for (var i=0; i<spans.length; ++i) {
        var f=findThis(redirs, spans[i]);
        // $popups.debug('redir='+redir+', f='+f+', spans[i]='+spans[i]);
        if ( (f!=null && !redir) || (f==null && redir) ) {
            // $popups.debug('skipping this set of the loop');
            continue;
        }
        var structurefn=structure[spans[i]];
        switch (typeof structurefn) {
        case 'function':
            this.debug('.fillEmptySpans(): running '+spans[i]+'({wp:'+x.wp+', hint:'+x.hint+', oldid: '+x.oldid+'})');
            this.setPopupHTML(structurefn(x), spans[i]);
            break;
        case 'string':
            this.setPopupHTML(structurefn, spans[i]);
            break;
        case 'undefined':
            break;
        default:
            $popups.error("unknown thing with label "+spans[i] + ", type "+(typeof structurefn) + " (error 64fcb997-630c-46a7-a337-8aa0fd01eb01)");
            break;
        }
    }
};

window.popupStructures = new Object;

window.popupStructures.original=new Object;

window.popupStructures.original.popupLayout=function () {
  return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupData', 'popupOtherLinks',
          'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
          'popupMiscTools', ['popupLiveOptions', 'popupShowPreview'],
          'popupPreview', 'popupFixDab'];
};
// TODO: ripe for major cleanup, partition popupLayout, or at least set
// properties
window.popupStructures.original.popupRedirSpans=function () {
  return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'];
};
window.popupStructures.original.popupTitle=function (x) {
  $popups.debug('defaultstructure.popupTitle');
  if (!getValueOf('popupNavLinks')) return navlinkStringToHTML('<b><<mainlink>></b>',x.wp,x.oldid);
  return '';
};
window.popupStructures.original.popupTopLinks=function (x) {
  $popups.debug('defaultstructure.popupTopLinks');
  if (getValueOf('popupNavLinks')) return navLinksHTML(x.wp, x.hint, x.oldid);
  return '';
};
window.popupStructures.original.popupImage=function(x) {
  $popups.debug('original.popupImage, x.wp='+x.wp+', popupId='+$popups.currentPopup.popupId); // TODO
  return $popups.currentPopup.imageHTML(x.wp);
};
window.popupStructures.original.popupShowPreview=function(x) {
  if (getValueOf('popupLinkShowPreview')) return '<br/><span onClick="javascript:$popups.currentPopup.showPreview()" '
    + 'style="cursor:pointer; border: thin dotted black" '
    + 'title="Download preview data from the Wikipedia servers">'
    + 'Get preview data'
    + '</span>';
  return '';
};
window.popupStructures.original.popupRedirTitle=window.popupStructures.original.popupTitle;
window.popupStructures.original.popupRedirTopLinks=window.popupStructures.original.popupTopLinks;


var prop;

function copyStructure(oldStructure, newStructure) {
  window.popupStructures[newStructure]=new Object();
  for (var prop in window.popupStructures[oldStructure])
    window.popupStructures[newStructure][prop]=window.popupStructures[oldStructure][prop];
}

/** -- fancy -- **/
copyStructure('original', 'fancy');
window.popupStructures.fancy.popupTitle=function (x) {
  return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.wp,x.oldid);
};

window.popupStructures.fancy.popupTopLinks=function(x) {
  var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>';
  var watch='<<unwatch|un>>|<<watch|shortcut=w>>';
  var move='<<move|shortcut=m>>';
  return navlinkStringToHTML('if(talk){'
                            +'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*'
                            +'<<article|shortcut=a|article>>|<<editArticle|edit>>' + '*' + watch + '*' + move
                            +'}else{<<edit|shortcut=e>>*' + hist
                            +'*<<talk|shortcut=t>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>'
                            +'*' + watch + '*' + move+'}<br/>', x.wp, x.oldid);
};

window.popupStructures.fancy.popupOtherLinks=function(x) {
  var admin='<<unprotect|un>>|<<protect|shortcut=p>>*<<undelete|un>>|<<delete|shortcut=d|del>>';
  var user='<<contribs|shortcut=c>>';
  if (WikiPage.wikimediaWikiP) user+='if(ipuser){}else{|<<count|shortcut=#|#>>}';
  user+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}';

  var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
  return navlinkStringToHTML('<br/>if(user){' + user + '*}if(admin){'+admin+'if(user){<br/>}else{*}}'
            +normal, x.wp, x.oldid);
};

window.popupStructures.fancy.popupRedirTitle=window.popupStructures.fancy.popupTitle;
window.popupStructures.fancy.popupRedirTopLinks=window.popupStructures.fancy.popupTopLinks;
window.popupStructures.fancy.popupRedirOtherLinks=window.popupStructures.fancy.popupOtherLinks;


/** -- fancy2 -- **/
// hack for [[User:MacGyverMagic]]
copyStructure('fancy', 'fancy2');
window.popupStructures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning
  return '<br>'+window.popupStructures.fancy.popupTopLinks(x).replace(RegExp('<br ?/?>$','i'),'');
};
window.popupStructures.fancy2.popupLayout=function () { // move toplinks to after the title
  return ['popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
          'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
          'popupMiscTools', ['popupLiveOptions', 'popupShowPreview'],
          'popupPreview', 'popupFixDab'];
};

/** -- menus -- **/
copyStructure('original', 'menus');
window.popupStructures.menus.popupLayout=function () {
  return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
          'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
          'popupData', 'popupMiscTools', ['popupLiveOptions', 'popupShowPreview'],
          'popupPreview', 'popupFixDab'];
};

window.popupStructures.menus.popupTopLinks = function (x) {
  var s='';
  var hist='<<history|shortcut=h>>';
  var lastedit='<<lastEdit|shortcut=/|show last edit>>';
  var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
  var related='<<relatedChanges|shortcut=r|related changes>>';
  var search='<<search|shortcut=s>>';
  if (WikiPage.wikimediaWikiP) search += '|<<globalsearch|shortcut=g|global>>';
  var watch='<<unwatch|un>>|<<watch|shortcut=w>>';
  var protect='<<unprotect|un>>|<<protect|shortcut=p>>';
  var del='<<undelete|un>>|<<delete|shortcut=d>>';
  var move='<<move|shortcut=m|move page>>';
  var dropdiv='<div class="popup_drop">';
  var menuspan='<span class="popup_menu">';
  var rowspan='<span class="popup_menu_row">';
  var enddiv='</div>';
  var endspan='</span>';
  if (getValueOf('popupActionsMenu')) s += '<<mainlink>>*' + dropdiv + '<a href="#">actions</a>';
  else s+= dropdiv + '<<mainlink>>';
  s+= menuspan
        + 'if(oldid){'+rowspan+'<<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>>' + endspan
        + '<<revert|shortcut=v|revert>>'
        + '}else{<<edit|shortcut=e>>}'
        + 'if(talk){<<new|shortcut=+|new topic>>}'
        + hist + lastedit + move
        + linkshere + related
        // + '<<nullEdit|shortcut=n|null edit>>'
        + rowspan + search + endspan
        + '<hr/>'
        + rowspan + watch + endspan
        + 'if(admin){' + rowspan + protect + endspan + rowspan + del + endspan + '}'
        + 'if(talk){'
        + '<hr/>'
        + '<<article|shortcut=a|view article>>'
        + '<<editArticle|edit article>>'
        + '}else{'
        + '<hr/>'
        + '<<talk|shortcut=t|talk page>>'
        + '<<editTalk|edit talk>>'
        + '<<newTalk|shortcut=+|new topic>>'
        + '}'
        + endspan
  + enddiv;
  s+='if(user){*' + dropdiv + '<a href="#">user</a>' + menuspan
        + rowspan + '<<userPage|shortcut=u|user page>>|<<userSpace|space>>' + endspan
        + '<<userTalk|shortcut=t|user talk>>'
        + '<<editUserTalk|edit user talk>>'
        + '<<newUserTalk|shortcut=+|leave comment>>'
        + 'if(ipuser){}else{<<email|shortcut=E|email user>>}'
        + '<hr/>'
        + '<<contribs|shortcut=c|contributions>>'
        + '<<userlog|shortcut=L|user log>>'
        + ((WikiPage.wikimediaWikiP)?'<<count|shortcut=#|edit counter>>':'')
        + 'if(admin){' + rowspan + '<<unblock|un>>|<<block|shortcut=b|block user>>' + endspan +'}'
        + '<<blocklog|shortcut=B|block log>>'
        + endspan + enddiv
        + '}';
  return navlinkStringToHTML(s, x.wp, x.oldid);
};

window.popupStructures.menus.popupRedirTitle=window.popupStructures.menus.popupTitle;
window.popupStructures.menus.popupRedirTopLinks=window.popupStructures.menus.popupTopLinks;

// Generate html for whole popup
$popups.Popup.prototype.generateHTMLspans = function() { // TODO: cleanup
    defaultize('popupStructure');
    var structure=window.popupStructures[popupStructure];
    if (typeof structure != 'object') {
        return $popups.error('Unknown structure: '+popupStructure+" (error fc49e7b6-1d0c-4b57-98a9-f3c75358feb0)");
    }
    if (typeof structure.popupLayout != 'function') {
        return $popups.error("Bad structure.popupLayout (error 90049255-00a1-4ff5-a0f2-53e01ea9c12c)");
    }
    window.popupLayout=structure.popupLayout();
    if (typeof structure.popupRedirSpans == 'function') {
        window.popupRedirSpans=structure.popupRedirSpans();
    } else {
        window.popupRedirSpans=[];
    }
    return this.makeEmptySpans(window.popupLayout);
};

$popups.Popup.prototype.makeEmptySpans = function (list) {
    var ret='';
    for (var i=0; i<list.length; ++i) {
        if (typeof list[i] == typeof '')
            ret += emptySpanHTML(list[i], this.popupId);
        else if (typeof list[i] == typeof [] ) {
            ret = ret.substring(0,ret.length-'</span>'.length) + this.makeEmptySpans(list[i]) + '</span>';
        }
    }
    return ret;
};

function isIpUser(user) {return user && ipUserRegex.test(user);};

function popupToggleVar(varstring) {
  defaultize(varstring);
  //eval('window.'+varstring+'=!window.' + varstring);
  window[varstring] = !window[varstring];
  defaultize('popupCookies');
  if (popupCookies) $kookie.set(varstring, String(window[varstring]));
};

function popupToggleShowOptions (dummy) {
  // just update state if dummy is true
  defaultize('popupLiveOptionsExpanded');
  if (!dummy) window.popupLiveOptionsExpanded=!window.popupLiveOptionsExpanded;
  $popups.currentPopup.setPopupHTML(
      (window.popupLiveOptionsExpanded) ? '&lt;&lt;' : '&gt;&gt;',
      'optionPopped');
  var s=document.getElementById('popupOptionsDiv');
  // if (!s) return;
  if (window.popupLiveOptionsExpanded) s.style.display='inline';
  else s.style.display='none';
};

function popupOptionsCheckboxHTML(varstring, label, title) {
  var html='<br/>';
  html += '<span title="'+title+'">';
  html += '<input type="checkbox" id="'+varstring+'Checkbox" ';
  html += (window[varstring]) ? 'checked="checked" ' : '';
  html += 'onClick="javascript:popupToggleVar(' + "'" + varstring + "'" +
    ')">' + label + '</input></span>';
  return html;
};

function popupLiveOptionsHTML() {
  var html = '';
  html += '<br/>';
  html += '<span title="Show/hide options" ';
  html += 'style="border: thin dotted black; cursor: pointer" ';
  html += 'onClick="javascript:popupToggleShowOptions()">';
  html += 'Options <span id="optionPopped' + $popups.currentPopup.popupId + '"></span>'; // TODO
  html += '</span>';
  html += '<div style="display: none" id="popupOptionsDiv">';
  html += popupOptionsCheckboxHTML('simplePopups', 'Simple popups',
               'Never download extra stuff for images/previews');
  html += popupOptionsCheckboxHTML('popupLinkShowPreview', 'Preview only on click',
               'Only start downloading when told to do so');
  //html += popupOptionsCheckboxHTML('popupCookies', 'cookies',
  //             'Use cookies to store popups options');
  html += popupOptionsCheckboxHTML('popupNavLinks', 'Show navigation links',
               'Display navigation links at the top of the popup');
  html += popupOptionsCheckboxHTML('popupImages', 'Show image previews',
               'Load images');
  html += popupOptionsCheckboxHTML('popupSummaryData', 'Show summary data',
               'Show page summary data');
  html += popupOptionsCheckboxHTML('popupPreviews', 'Show text previews',
               'Show previews');

  var extraOptions=[
                    'imagePopupsForImages',
                    'popupAdminLinks',
                    'popupAppendRedirNavLinks',
                    'popupCookies',
                    'popupFixDabs',
                    'popupFixRedirs',
                    'popupHistoricalLinks',
                    // 'popupImagesFromThisWikiOnly',
                    'popupImagesToggleSize',
                    'popupLastEditLink',
                    'popupLiveOptions',
                    'popupNeverGetThumbs',
                    'popupOnlyArticleLinks',
                    'popupPreviewKillTemplates',
                    'popupPreviewFirstParOnly',
                    'popupShortcutKeys',
                    'popupSimplifyMainLink',
                    'removeTitles' // no ,
  ];
  for (var i=0; i<extraOptions.length; ++i) {
    html += popupOptionsCheckboxHTML(extraOptions[i], extraOptions[i], 'Toggle this option');
  }

  html += '</div>';
  return html;
};

// TODO
function emptySpanHTML(name, id) {return '<span id="' + name + id + '"></span>';};

// function emptySpanHTML(name, id, tag, classname) {
//   tag = tag || 'span';
//   classname = classname || name;
//   return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
// }

function addLinkProperty(html, property) {
  // take "<a href=...>...</a> and add a property
  // not sophisticated at all, easily broken
  var i=html.indexOf('>');
  if (i<0) return html;
  return html.substring(0,i) + ' ' + property + html.substring(i);
};

// TODO: CLEANUP!
function addPopupShortcut(html, key) {
  if (!getValueOf('popupShortcutKeys')) return html;
  var ret= addLinkProperty(html, 'popupkey="'+key+'"');
  if (key==' ') key='space';
  return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4');
};

function crypticNavlinkSpec() {
  if (typeof popupNavLinkSeparator=='undefined') window.dpopupNavLinkSeparator=window.dpopupNavLinkSeparator.split(' ').join('');
  var str= '<b><<mainlink|shortcut= >></b><<imagestatus>>if(admin){<br/>}else{*}if(user){<<contribs|shortcut=c|c>>' +
    'if(ipuser){}else{|<<email|shortcut=E|e>>}';
  if (WikiPage.wikimediaWikiP) str += 'if(ipuser){}else{|<<count|shortcut=#|#>>}';
  str+='if(admin){|<<block|shortcut=b|b>>}*}if(talk){if(oldid){<<editOld|shortcut=e|e>>|<<revert|shortcut=v|rv>>|<<edit|c>>*}else{<<edit|shortcut=e|e>>}|<<new|shortcut=+|+>>*<<history|shortcut=h|h>>*<<unwatch|un|u>>/<<watch|shortcut=w|w>>*<b><<article|shortcut=a|a>></b>|<<editArticle|edit|e>>}else{if(oldid){<<editOld|shortcut=e|e>>|<<revert|shortcut=v|rv>>|<<edit|cur|c>>}else{<<edit|shortcut=e|e>>}*<<history|shortcut=h|h>>*<<unwatch|un|u>>/<<watch|shortcut=w|w>>*<b><<talk|shortcut=t|t>></b>|<<editTalk|edit|e>>|<<newTalk|shortcut=+|+>>}*<<whatLinksHere|shortcut=l|l>>*<<relatedChanges|shortcut=r|r>>*<<move|shortcut=m|m>>if(admin){*<<unprotect|un|u>>/<<protect|shortcut=p|p>>*<<undelete|un|u>>/<<delete|shortcut=d|d>>}';

  return str;
};

function defaultNavlinkSpec() {
  var str='';
  str += '<b><<mainlink|shortcut= >></b>';
  if (getValueOf('popupLastEditLink')) str += '*<<lastEdit|shortcut=/>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
  str+='<<imagestatus>>';

  // user links
  // contribs - log - count - email - block
  // count only if applicable; block only if popupAdminLinks
  str += 'if(user){<br/><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
  if (WikiPage.wikimediaWikiP) str+='if(ipuser){}else{*<<count|shortcut=#>>}';
  str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';

  // editing links
  // talkpage   -> edit|new - history - un|watch - article|edit
  // other page -> edit - history - un|watch - talk|edit|new
  var editstr='<<edit|shortcut=e>>';
  var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'
  var historystr='<<history|shortcut=h>>';
  var watchstr='<<unwatch|un>>|<<watch|shortcut=w>>';

  str+='<br/>if(talk){' +
    editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>'
    + '}else{' // not a talk page
    + editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>'
    + '}';

  // misc links
  str += '<br/><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';

  // admin links
  str += 'if(admin){<br/><<unprotect|un>>|<<protect|shortcut=p>>*' + '<<undelete|un>>|<<delete|shortcut=d>>}';
  return str;
};

function navLinksHTML (wp, hint, oldid) {
  //  defaultize('popupNavLinkSeparator');
  var str = '<span class="popupNavLinks">';
  var style=getValueOf('popupNavLinkStyle');
  switch (style) {
  case 'cryptic':
    str += crypticNavlinkSpec();
    break;
  case 'default':
    str += defaultNavlinkSpec();
    break;
  default:
    if (typeof style == 'function') str += style();
    else str+=String(style);
  }
  str += '</span>';

  // BAM
  return navlinkStringToHTML(str, wp, oldid);
};

function expandConditionalNavlinkString(s,wp,oldid,recursionCount) {
  // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
  if (typeof recursionCount!=typeof 0) recursionCount=0;
  var conditionalSplitRegex=RegExp(
    '(;?\\s*if\\s*\\(\\s*([a-z]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
  var splitted=s.parenSplit(conditionalSplitRegex);
  // $1: whole conditional
  // $2: user|talk
  // $3: true expansion
  // $4: else clause (possibly empty)
  // $5: false expansion (possibly null)
  var numParens=5;
  var ret = splitted[0];
  for (var i=1; i<splitted.length; i=i+numParens+1) {

    var testString=splitted[i+2-1];
    var trueString=splitted[i+3-1];
    var falseString=splitted[i+5-1];
    if (typeof falseString=='undefined' || !falseString) falseString='';
    var testResult=null;

    switch (testString) {
    case 'user':
      testResult=wp.relevantUser?true:false;
      break;
    case 'talk':
        if (!wp || !wp.talkPage) { testResult=false;break;}//XXX
        testResult=wp.talkPage()?false:true; // talkPage converts _articles_ to talkPages
      break;
    case 'admin':
      testResult=getValueOf('popupAdminLinks')?true:false;
      break;
    case 'oldid':
      testResult=(typeof oldid != 'undefined' && oldid)?true:false;
      break;
    case 'ipuser':
      testResult=(isIpUser(wp.relevantUser))?true:false;
      break;
    case 'mainspace':
        testResult=wp.nsMainP;
        break;
    case 'wikimedia':
        testResult= WikiPage.wikimediaWikiP;
      break;
    }

    switch(testResult) {
    case null: ret+=splitted[i];  break;
    case true: ret+=trueString;   break;
    case false: ret+=falseString; break;
    }


    // append non-conditional string
    ret += splitted[i+numParens];
  }
  if (conditionalSplitRegex.test(ret) && recursionCount < 10)
    return expandConditionalNavlinkString(ret,wp,oldid,recursionCount+1);
  else return ret;
};

function navlinkStringToArray(s, wp, oldid) {
  s=expandConditionalNavlinkString(s,wp,oldid);
  var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
  var ret=[];
  for (var i=0; i<splitted.length; ++i) {
    if (i%2) { // i odd, so s is a tag
      var t=new navlinkTag();
      var ss=splitted[i].split('|');
      t.id=ss[0];
      for (var j=1; j<ss.length; ++j) {
        var sss=ss[j].split('=');
        if (sss.length>1)
          // eval ('t.'+sss[0]+'="'+sss[1]+'";');
          t[sss[0]]=sss[1];
        else
          t.text=sss[0];
      }
      t.wp = wp;
      if (typeof oldid != 'undefined' && oldid != null) t.oldid=oldid;
      ret.push(t);
    }
    else { // plain HTML
      ret.push(splitted[i].split('*').join(getValueOf('popupNavLinkSeparator')));
    }
  }
  return ret;
};

// navlinkString: * becomes the separator
//                <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
//                                      and visible text 'fubar'
//                if(test){...} and if(test){...}else{...} work too (nested ok)

function navlinkStringToHTML(s,wp,oldid) {
  var p=navlinkStringToArray(s,wp,oldid);
  var html='';
  for (var i=0; i<p.length; ++i) {
    if (typeof p[i] == typeof '') {
      html+=p[i];
    } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') {
      html+=p[i].html();
    }
  }
  return html;
}

function navlinkTag() {this.type='navlinkTag';};

navlinkTag.prototype.html=function () {
  this.getPrintFunction();
  var html='';
  if (typeof this.print!='function') {
    $popups.error('Oh dear - invalid print function for a navlinkTag, id='+this.id);
  } else {
    html=this.print(this);
    if (typeof html != typeof '') {html='';}
    else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut);
  }
  return '<span class="popup_'+this.id+'">'+html+'</span>';
};

navlinkTag.prototype.getPrintFunction=function() {
    if (!this.id || !this.wp) return;

    var html='';
    var a,t;

    switch (this.id) {
    case 'email':     case 'contribs':  case 'block':     case 'unblock':
    case 'userlog':   case 'blocklog':  case 'userSpace':
        this.wp = WikiPage(null, $wikins.User+':'+ this.wp.relevantUser);//TODO
    }

    switch (this.id) {
        case 'blocklog': this.wp = WikiPage(null, $wikins.User+':'+this.wp.relevantUser); // TODO
    }

    switch (this.id) {
        case 'userTalk': case 'newUserTalk': case 'editUserTalk':
        case 'userPage':
        delete this.oldid;
        this.wp = WikiPage(null, $wikins.User+':'+this.wp.relevantUser); // TODO
    }

    if (this.id != 'mainlink') {
        if (typeof this.text=='undefined') this.text=this.id;
    }

    var wpT = this.wp.talkPage();
    var wpNT = this.wp.notalkPage();

    switch (this.id) {
    case 'undelete':       this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
    case 'whatLinksHere':  this.print=specialLink; this.specialpage='Whatlinkshere'; break;
    case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
    case 'move':           this.print=specialLink; this.specialpage='Movepage'; break;

    case 'contribs':       this.print=specialLink; this.specialpage='Contributions'; break;
    case 'email':          this.print=specialLink; this.specialpage='Emailuser'; break;
    case 'block':          this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
    case 'unblock':        this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
    case 'userlog':        this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
    case 'blocklog':       this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
    case 'userSpace':      this.print=specialLink; this.specialpage='Prefixindex'; this.sep='&namespace=2&from='; break;
    case 'search':         this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
    case 'history': case 'unwatch': case 'watch':
    case 'unprotect': case 'protect': case 'delete':
        this.print=wikiLink; this.action=this.id; break;

    case 'edit': case 'view':
        this.print=wikiLink;
        delete this.oldid;
        this.action=this.id; break;

    case 'new':
        this.print=wikiLink; this.action='edit&section=new'; break;

    case 'mainlink':
        if (!this.text) this.text=this.wp.page;
        if (getValueOf('popupSimplifyMainLink') && this.wp.namespace != "") {
            // get last path component
            var s=this.text.split('/'); this.text=s[s.length-1];
            if (this.text=='' && s.length > 1) this.text=s[s.length-2];
        }
        this.print=titledWikiLink;
        if (typeof this.title=='undefined' &&
            $popups.currentPopup &&
            $popups.currentPopup.a)
        {
            if ($popups.currentPopup.a.originalTitle) {//TODO
                this.title = /*$util.wpaDecode(*/$popups.currentPopup.a.originalTitle;
            } else {
                this.title = this.wp.page;
            }
            if (typeof this.oldid != 'undefined' && this.oldid) this.title='Revision ' + this.oldid + ' of ' + this.title;
        }
        this.action='view'; break;

    case 'userPage':
    case 'article':
        if (wpNT) { this.wp = wpNT; delete this.oldid; }
        this.print=wikiLink;
        this.action='view'; break;

    case 'editArticle':
        if (wpNT) { this.wp = wpNT; delete this.oldid; }
        this.print=wikiLink;
        this.action='edit'; break;

    case 'userTalk':
    case 'talk':
        t = this.wp.talkPage();
        if (t) { this.wp = t; delete this.oldid; }
        this.print=wikiLink;
        this.action='view'; break;

    case 'count':
        this.print=kateLink; break;
    case 'globalsearch':
        this.print=globalSearchLink; break;

    case 'lastEdit':
        this.print=titledDiffLink;
        this.title='Show the last edit';
        this.from='prev'; this.to='cur'; break;

    case 'oldEdit':
        this.print=titledDiffLink;
        this.title='Show the edit made to get revision '+this.oldid;
        this.from='prev'; this.to=this.oldid; break;

    case 'editOld':
        this.print=wikiLink; this.action='edit'; break;
    case 'revert':
        this.print=wikiLink; this.action='revert'; break;

    // case 'nullEdit':
    //     this.print=wikiLink; this.action='nullEdit'; break;

    case 'diffCur':
        this.print=titledDiffLink;
        this.title='Show changes since revision '+ this.oldid;
        this.from=this.oldid; this.to='cur'; break;

    case 'editUserTalk':
    case 'editTalk':
        if (wpT) { this.wp = wpT; delete this.oldid; }
        this.action='edit'; this.print=wikiLink; break;

    case 'newUserTalk':
    case 'newTalk':
        if (wpT) { this.wp = wpT; delete this.oldid; }
        this.action='edit&section=new'; this.print=wikiLink; break;

    case 'imagestatus':
        this.print=function () { return emptySpanHTML('popupImageStatus', $popups.currentPopup.popupId); } // TODO
        break;

    default:
        this.print=function () {return 'Unknown navlink type: '+this.id+''};
    }
};

$popups._makeRawDownloadUrl = function(wp, oldid) {
    // set ctype=text/css to get around opera bug
    var url = wp.qurl + '&action=raw&ctype=text/css';
    if (oldid) url += '&oldid='+oldid;
    return url;
}


/////////////////////
// LINK GENERATION //
/////////////////////

// titledDiffLink --> titledWikiLink --> generalLink
// wikiLink       --> titledWikiLink --> generalLink
// kateLink --> generalLink

function titledDiffLink(l) { // wp, text, title, from, to) {
    return titledWikiLink({wp: l.wp, action: l.to + '&oldid=' + l.from,
                                  text: l.text, title: l.title,
                                  /* hack: no oldid here */
                                  actionName: 'diff'});
};

function wikiLink(l) {
    //{wp:wp, action:action, text:text, oldid}) {
    var prehint=null;
    if (! (l.wp
           && typeof l.action == typeof '' && typeof l.text==typeof '')) return null;
    if (typeof l.oldid == 'undefined') l.oldid=null;
    if (l.action!='edit' && l.action!='view' && l.action != 'revert') l.oldid=null;
    switch (l.action) {
    case 'edit':             prehint = 'Edit ';                 break;
    case 'history':          prehint = 'Show history for ';     break;
    case 'unwatch':          prehint = 'Stop watching ';        break;
    case 'watch':            prehint = 'Watch ';                break;
    case 'view':             prehint = 'Go to ';                break;
    case 'unprotect':        prehint = 'Unprotect ';            break;
    case 'protect':          prehint = 'Protect ';              break;
    case 'delete':           prehint = 'Delete ';               break;
    case 'edit&section=new': prehint = 'Start a new topic on '; break;
    case 'revert':           prehint = 'Revert to ';
        // TODO: show the timestamp and username of revert target
        l.action='edit&autoclick=wpSave&autosummary=' + $util.printf(getValueOf('popupRevertSummary'), l.oldid);
        break;
    // case 'nullEdit':         prehint = 'Make a null edit to ';
    //     l.action='edit&autoclick=wpSave&autosummary=null';
    //     break;
    }


  var hint;
  if (prehint != null) {
    if (l.oldid) prehint += 'revision '+l.oldid +' of ';
    hint=prehint + l.wp.page;
  }
  else hint = (l.wp.page + '&action=' + l.action
               + (l.oldid) ? '&oldid='+l.oldid : '');

  return titledWikiLink({wp: l.wp, action: l.action, text: l.text,
                                title: hint, oldid: l.oldid});
};


var defaultNavlinkClassname='popupNavLink';

function titledWikiLink(l) {
    // possible properties of argument:
    // wp, action, text, title, oldid, actionName, className
    // oldid = null is fine here

    // wp and action are mandatory args
    if (!l.wp || typeof l.action=='undefined') return null;
    var base = l.wp.qurl;
    var url=base;

    if (typeof l.actionName=='undefined' || !l.actionName) l.actionName='action';

    // no need to add &action=view, and this confuses anchors
    if (l.action != 'view') url = base + '&' + l.actionName + '=' + l.action;

    if (typeof l.oldid!='undefined' && l.oldid) url+='&oldid='+l.oldid;

    var cssClass=defaultNavlinkClassname;
    if (typeof l.className!='undefined' && l.className) cssClass=l.className;

    return generalLink({url: url,
                               title: (typeof l.title != 'undefined') ? l.title : null,
                               newWin: getValueOf('popupNewWindows'),
                               className: cssClass,
                               text: (typeof l.text!='undefined')?l.text:null});
};

function specialLink(l) {
  // properties: wp, specialpage, text, sep
  if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
  var base = 'http://'+WikiPage.server+'/w/index.php?title=' + $wikins.Special+':'+l.specialpage;
  if (typeof l.sep == 'undefined' || l.sep===null) l.sep='&target=';
  var wp = l.wp;
  var prehint=null;
  var user = false;
  switch (l.specialpage) {
  case 'Whatlinkshere':       prehint='Show articles which link to ';                                  break;
  case 'Recentchangeslinked': prehint='Show changes in articles related to ';                          break;
  case 'Contributions':       prehint='Show contributions made by '; user=true;                        break;
  case 'Emailuser':           prehint='Email '; user=true;                                             break;
  case 'Blockip':             prehint='Block '; user=true;                                             break;
  case 'Ipblocklist':         prehint='Unblock '; user=true;                                           break;
  case 'Movepage':            prehint='Move '; user=true;                                              break;
  case 'Undelete':            prehint='Show deletion history for ';                                    break;
  case 'Log':                 prehint=(l.sep=='&user=')?'Show log for ' : 'Block log for '; user=true; break;
  case 'Prefixindex':         prehint='Show the userspace pages of '; user=true;                       break;
  case 'Search':              prehint='Search for ';                                                   break;
  default: prehint=l.specialpage+':'; break;
  }
  var url = base + l.sep + (user ? wp.relevantUser : wp.page);
  var hint = prehint + wp.page;

  return generalLink({url: url, title: hint, text: l.text,
                             newWin: getValueOf('popupNewWindows'),
                             className: 'popupNavLink'});
};

function generalLink(l) {
    // l.url, l.text, l.title, l.newWin, l.className

    if (typeof l.url=='undefined') return null;

    // only quotation marks in the url can screw us up now... I think
    var url=l.url.split('"').join('%22');

    var ret='<a href="' + url + '"';
    if (typeof l.title!='undefined' && l.title) ret += ' title="' + l.title + '"';
    if (typeof l.newWin!='undefined' && l.newWin) ret += ' target="_blank"';
    if (typeof l.className!='undefined'&&l.className) ret+=' class="'+l.className+'"';
    ret += '>';
    if (typeof l.text==typeof '') ret+= l.text;
    ret +='</a>';
    return ret;
}

function literalizeRegex(str){
  return str.replace(RegExp('([-.()\\+?*^${}\\[\\]])', 'g'), '\\$1');
};

function appendParamsToLink(linkstr, params) {
  var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
  if (sp.length<2) return null;
  var ret=sp.shift() + sp.shift();
  ret += '&' + params + '"';
  ret += sp.join('');
 return ret;
};

$popups._reUpperOrLower = function(c) {
    return '[' + c.toUpperCase() + c.toLowerCase() + ']';
}

$popups.Popup.prototype.redirLink = function(wpRedirTarget) {
    var ret='';

    if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
        ret += '<hr/>';
        if (getValueOf('popupFixRedirs')) {
            ret += addPopupShortcut(
                (
                    // TODO: this will be much cleaner once we're manipulating DOM elements
                    '<a href="javascript:$linkedit.editRedirectBypass(wikiPage,' +
                    $util.stringQuoteEscape(this.wp.page) + ', ' +
                    $util.stringQuoteEscape(wpRedirTarget.page) + ')"' +
                    ' class=' + $util.stringQuoteEscape(defaultNavlinkClassname) +
                    ' title="Bypass redirect">Redirects</a>') ,
                'R');
            ret += ' to ';
        } else {
            ret += 'Redirects to ';
        }

        this.fillEmptySpans({redir: wpRedirTarget});
        return ret;
    } else {
        return ('<br/> Redirects to ' +
                titledWikiLink({wp: wpRedirTarget, action: 'view',
                                text: wpRedirTarget.page, title: 'Bypass redirect'}));
    }
};

function kateLink(l) {
    if (!l.wp || typeof l.text != typeof '') return null;
    var uN = l.wp.relevantUser;
    if (!uN || isIpUser(uN) || ! WikiPage.wikimediaWikiP) return null;

    var newWin='';
    if (getValueOf('popupNewWindows')) newWin=' target="_blank"';

    return generalLink({url:$editcount.interiotLink(uN),
                               title: 'Count the countributions made by '+uN,
                               newWin: getValueOf('popupNewWindows'),
                               className: defaultNavlinkClassname,
                               text: l.text});
};

function globalSearchLink(l) {
    if (!l.wp || typeof l.text != typeof '') return null;

    var newWin='';
    if (getValueOf('popupNewWindows')) newWin=' target="_blank"';

    var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';

    return generalLink({url:base + l.wp.pageQuoted,
                               title: 'Search all wikipedias for '+l.wp.page,
                               newWin: getValueOf('popupNewWindows'),
                               className: defaultNavlinkClassname,
                               text: l.text});
};


////////////////////////////
// MANIPULATION FUNCTIONS //
////////////////////////////

function listLinks(wikitext, wpOrigTarget) {
    // $popups.debug("## listLinks: origTarget="+origTarget);

    var links = $wikiparse.getLinks(wikitext);

    // ^[a-z]+ should match interwiki links, hopefully (case-sensitive) and
    // ^[a-z]* should match those and [[:Category...]] style links too
    // TODO: improve this
    var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');

    var ret = [];
    for (var i in links) {
        var link = links[i];
        if (omitRegex.test(link)) continue;
        ret.push(
            // TODO: this will be much cleaner once we're manipulating DOM elements
            '<a href="javascript:$linkedit.editLinkDisambig(wikiPage,' +
            $util.stringQuoteEscape(wpOrigTarget.page) + ', ' +
            $util.stringQuoteEscape(link) + ')"' +
            ' class=' + $util.stringQuoteEscape(defaultNavlinkClassname) +
            ' title=' + $util.stringQuoteEscape("Disambiguate this link to [["+link+"]]") + '>' +
            link.replace(/ /g,'&nbsp;') +
            '</a>');
    }

    ret.push(
        // TODO: this will be much cleaner once we're manipulating DOM elements
        '<a href="javascript:$linkedit.editLinkDisambig(wikiPage,' +
        $util.stringQuoteEscape(wpOrigTarget.page) + ', ' +
        'null' + ')"' +
        ' class=' + $util.stringQuoteEscape(defaultNavlinkClassname) +
        ' title=' + $util.stringQuoteEscape("Remove all links to this disambig page from this article") + '>' +
        'remove&nbsp;this&nbsp;link' +
        '</a>');

    return ret;

};

function makeFixDab(data, wpOrigTarget) {
    var list=listLinks(data, wpOrigTarget);
    if (list.length==0) return null;
    var html='<hr/>Disambiguate this link to:<br/>' + list.join(', ');
    return html;
};

$popups._plural = function(num, pl, sing) {
    if (num == 1) return num + '&nbsp;' + sing;
    else return num + '&nbsp;' + pl;
}

function formatBytes(num) {
    return (num > 949) ? (Math.round(num/100)/10+'kB') : (num +'&nbsp;bytes') ;
};

function popupFilterStubDetect(download) {
    return ($wikiparse.isStub(download.data)) ? 'stub' : '';
};

function popupFilterDisambigDetect(download) {
    if ($wikiparse.isDisambig(download.wp, download.data)) return 'disambig';
};

function popupFilterPageSize(download) {
    return formatBytes(download.data.length);
};

function popupFilterCountLinks(download) {
    return $popups._plural($wikiparse.countLinks(download.data), 'wikiLinks', 'wikiLink');
};

function popupFilterCountImages(download) {
    return $popups._plural($wikiparse.countImages(download.data), 'images', 'image');
};

function popupFilterCountCategories(download) {
    return $popups._plural($wikiparse.countCategories(download.data), 'categories', 'category');
};

function popupFilterLastModified(download) {
    if (!download.lastModified) return null;

    var age = (new Date()) - download.lastModified;
    return ($datetime.formatAge(age) + ' old').replace(/ /g, '&nbsp;');
}

var popupFilters=[popupFilterStubDetect,popupFilterDisambigDetect,popupFilterPageSize,popupFilterCountLinks,
                  popupFilterCountImages,popupFilterCountCategories,popupFilterLastModified];

if (typeof extraPopupFilters=='undefined') { var extraPopupFilters=new Array(); }

$popups.getPageInfo = function(download) {
    if (!download || download.data == null) {
        return $popups.error("error 8c099fa4-d6f7-4b15-a431-61a494436aec");
    }
    if (!download.data) return 'Empty page';

    var pageInfoArray = [];
    var i,s;
    for (i=0; i<     popupFilters.length; ++i) {s=     popupFilters[i](download);if (s) pageInfoArray.push(s);}
    for (i=0; i<extraPopupFilters.length; ++i) {s=extraPopupFilters[i](download);if (s) pageInfoArray.push(s);}

    return $util.capitalizeFirstChar(pageInfoArray.join(', '));
};

/////////////
// ACTIONS //
/////////////

$popups.Popup.prototype.loadImage = function(filename)
{
    this.debug('loadImage(' + filename + ')');
    var popup = this;
    var cb = function(filename, img) {
        if (typeof window.$popups == 'undefined') return; // unloaded

        popup.debug('loadImage(' + filename + ') => img=' + (img&&img.src));

        var popupImage = document.getElementById("popupImg"+popup.popupId);

        if (!popupImage || typeof popupImage.src == 'undefined') {
            popup.debug('loadImage(' + filename + '): no popupImage div!');
            return;
        }

        if (img) {
            // success
            // popup.setImageStatus('');
            popupImage.src = img.src;
            popupImage.width = popupImageSize;
            popupImage.style.display = 'inline';
            this.setPopupImageLink(img, img.wiki);
        } else {
            // failure
            // popup.setImageStatus(' :-(');
        }
    }
    $wikiimg.downloadImage(filename, cb);
}

function findThis(array, value) {
  if (typeof array.length == 'undefined') return null;
  for (var i=0; i<array.length; ++i) {
    if (array[i]==value) return i;
  }
  return null;
};

// this has to use a timer loop as we don't know if the DOM element exists
// when we want to set the text
$popups.Popup.prototype.setPopupHTML = function (str, elementId, onSuccess) {
    this.debug('.setPopupHTML(): ' + 'elementId='+elementId + ', str='+$util.strAbbrev(str,50));
    var popupElement=document.getElementById(elementId+this.popupId);
    var timer;

    if (typeof popupHTMLTimers[elementId] == 'undefined') timer=null;
    else timer=popupHTMLTimers[elementId];

    if (popupElement != null) {
        if(timer) clearInterval(timer);
        popupHTMLTimers[elementId]=null;
        popupElement.innerHTML=str;
        if (onSuccess) onSuccess();
        return true;
    } else {
        // TODO: GET RID OF THIS!!
        // call this function again in a little while...
        var popup = this;
        var loopFunction=function() { popup.setPopupHTML(str,elementId,onSuccess);}
        popupHTMLLoopFunctions[elementId] = loopFunction;
        if (!timer) {
            var doThis = 'popupHTMLLoopFunctions["'+elementId+'"]()';
            popupHTMLTimers[elementId] = setInterval(doThis, 600);
        }
    }
    return null;
};

// $popups.Popup.prototype.setImageStatus = function(str) = {
//     return this.setPopupHTML(str, 'popupImageStatus');
// };

$popups.Popup.prototype.setPopupTrailer = function(str) {
    return this.setPopupHTML(str, 'popupData');
};

function toggleSize() {
  var imgContainer=this;
  if (!imgContainer) { alert('imgContainer is null :/'); return;}
  img=imgContainer.firstChild;
  if (!img) { alert('img is null :/'); return;}
  if (!img.style.width || img.style.width=='') img.style.width='100%';
  else img.style.width=''; // popupImageSize+'px';
};

$popups.Popup.prototype.setPopupImageLink = function (img, wiki) {
    if( wiki === null || img === null ) return null;

    var a=document.getElementById("popupImageLink"+this.popupId);
    if (a === null) return null;

    var linkURL = $wikiimg.imageURL(img, wiki);
    if (linkURL != null) {
        if (getValueOf('popupImagesToggleSize')) {
            a.onclick=toggleSize;
            a.title='Toggle image size';
        } else {
            a.href=linkURL;
            a.title='Open full-size image';
        }
    }
    return linkURL;
};

$popups.loaded = false;
$popups.unloaded = false;

$popups.load = function() {
    if ($popups.loaded) {
        $popups.error("load() called again");
        return;
    }
    $popups.loaded = true;

    var anchors;

    // article/content is a structure-dependent thing
    if (getValueOf('popupOnlyArticleLinks'))
        anchors = ( document.getElementById('article') ||
                    document.getElementById('content')   ).getElementsByTagName('A');
    else
        anchors = document.getElementsByTagName('A');

    for (var i=0; i<anchors.length; ++i) {
        var a = anchors[i];
        if (a.onclick) continue;
        if (isInToc(a)) continue;
        var h = a.href;
        if (!h) continue;
        if (h.match(/limit=/)) continue;
        var wp = WikiPageRelevant(h, true);
        if (!wp) continue;
        if (wp.nsSpecialP) continue;

        a.wp = wp;
        $popups._setupTooltipLink(a);
        if (getValueOf('removeTitles') && typeof a.originalTitle=='undefined') {
            a.originalTitle = a.title;
            a.title='';
        }
    }

    // popups for "User" tab
    var tab = document.getElementById('ca-nstab-user');
    if (tab) {
        $popups._setupTooltipLink(tab.getElementsByTagName('a')[0]);
        if (getValueOf('removeTitles')) {
            $util.addOnloadHook(function() {
                                    tab.originalTitle = tab.title;
                                    tab.title = '';
                                    // have to do this to prevent title from
                                    // reappearing on the next akeytt() call (this
                                    // remove the access key itself, just the
                                    // tooltip)
                                    window.ta['ca-nstab-user'] = ['',''];
                                });
        }
    }
};

$popups._setupTooltipLink = function(a) {
    if (!a) return;
    a.onmouseover = $popups.mouseOverWikiLink;
    a.onmouseout = $popups.mouseOutWikiLink;
    a.onclick = $popups.killPopup;
}

$popups.unload = function() {
    $popups.unloaded = true;
    $popups.killPopup();
}



//////////////
// THINGIES //
//////////////

function popupHandleKeypress(evt) {
  var keyCode = evt.keyCode ? evt.keyCode :
    evt.charCode ? evt.charCode : evt.which;
  if (!keyCode || !over) return;
  if (keyCode==27) { // escape
    $popups.killPopup();
    return;
  }

  var letter=String.fromCharCode(keyCode);
  var links=over.getElementsByTagName('A');
  var startLink=0;
  var i,j;

  if (window.lastPopupLinkSelected) {
    for (i=0; i<links.length; ++i) {
      if (links[i]==window.lastPopupLinkSelected) startLink=i;
    }
  }
  for (j=0; j<links.length; ++j) {
    i=(startLink + j + 1) % links.length;
    if (links[i].getAttribute('popupkey')==letter) {
      if (evt.preventDefault) evt.preventDefault();
      links[i].focus();
      window.lastPopupLinkSelected=links[i];
      break;
    }
  }
};

function addPopupShortcuts() {
  if (document.onkeypress==popupHandleKeypress) return;
  document.oldPopupOnkeypress=document.onkeypress;
  document.onkeypress=popupHandleKeypress;
};

function rmPopupShortcuts() {
  if (String(document.oldPopupOnkeypress)==String(popupHandleKeypress)) {
    // panic
    document.onkeypress=function () {};
    return;
  }
  document.onkeypress=document.oldPopupOnkeypress;
};

// add CSS class to table

function addPopupStylesheetClasses () {
  var tables=over.getElementsByTagName('table');
  tables[0].className='popupBorderTable';
  tables[1].className='popupTable';
  var fonts=over.getElementsByTagName('font');
  fonts[0].className='popupFont';
  fonts[0].id='overFontWrapper';
};

// TODO: get rid of this hook stuff!
$popups._alreadyRegisteredOverlibHooks = false;
$popups._registerOverlibHooks = function() {
  if ($popups._alreadyRegisteredOverlibHooks) return;
  $popups._alreadyRegisteredOverlibHooks = true;

  defaultize('popupMaxWidth');

  if (typeof popupMaxWidth == 'number') {
    window.setmaxwidth = function () {
      over.style.maxWidth = popupMaxWidth+'px';

      // hack for IE
      // see http://www.svendtofte.com/code/max_width_in_ie/
      // who knows if this will work? not me.


      // this seems to be the source of that error.... grr.....
      if (over.style.setExpression) {
        over.style.setExpression('width',
                                 'document.body.clientWidth > '
                                 + popupMaxWidth + ' ? "'
                                 + popupMaxWidth + 'px": "auto"');
      }
    };
    registerHook("createPopup", window.setmaxwidth, FAFTER);
  }

  registerHook('createPopup', function(){ $popups.currentPopup.fillEmptySpans()}, FAFTER);

  if (getValueOf('popupShortcutKeys')) {
    registerHook("createPopup", window.addPopupShortcuts, FAFTER);
    registerHook("hideObject", window.rmPopupShortcuts, FAFTER);
  }

  if (getValueOf('popupDragging'))
    registerHook("createPopup", $popups.makeOverDraggable, FAFTER);

  registerHook("createPopup", window.addPopupStylesheetClasses, FAFTER);
};

$popups.makeOverDraggable = function() {
    if (typeof over == 'undefined' || !over) return;
    $drag.makeDraggable(over, null,
                        {
                        pickupCondition: function(e) {
                                // only drag while shift is pressed
                                return e.shiftKey;
                            }
                        });
};

$popups.currentPopup = null;

$popups.currentlyDraggingP = function() {
    return (typeof over != 'undefined' && over != null
            && typeof over.dragging != 'undefined' && over.dragging);
}

$popups.mouseOverWikiLink = function() {
    // if no $popups then we're totally unloaded
    if (typeof window.$popups == 'undefined') return;
    if (!$popups.loaded || $popups.unloaded) return;

    var a = this;

    // TODO: should not generate the HTML until the delay has elapsed,
    // and then popup immediately. Can be a CPU hog otherwise.

    $popups.debug('mouseOverWikiLink: a='+a);

    if ($popups.currentlyDraggingP()) return;
    if ($popups.currentPopup && $popups.currentPopup.a == a) return;

    $popups.currentPopup = new $popups.Popup(a);
    $popups.currentPopup.startPopup();
}

$popups.Popup.prototype.startPopup = function()
{
    this.debug('.startPopup()');
    this.redirCount = 0;

    if (getValueOf('simplePopups') && window.popupStructure===null) {
        // reset *default value* of popupStructure
        $popups.debug('simplePopups is true and no popupStructure selected. Defaulting to "original"');
        newOption('popupStructure', "'original'");
    }

    var html = this.generateHTMLspans();
    this.oldid = oldidFromURL(this.a.href);                 // TODO

    // keep this (or fix other refs)
    defaultize('popupImages');

    // $popups.debug('running overlib now');
    $popups._registerOverlibHooks();

    defaultize('popupInitialWidth');
    if (typeof popupInitialWidth == typeof 0) {
        $overlib.overlib(html, STICKY, WIDTH, popupInitialWidth,
                         CELLPAD, 5, OFFSETX, 2, OFFSETY, 2,
                         DELAY, getValueOf('popupDelay')*1000);
    } else {
        $overlib.overlib(html, STICKY, WRAP,
                         CELLPAD, 5, OFFSETX, 2, OFFSETY, 2,
                         DELAY, getValueOf('popupDelay')*1000);
    }

    if ($popups.checkPopupPositionTimer) clearInterval($popups.checkPopupPositionTimer);
    $popups.checkPopupPositionTimer=setInterval($popups.checkPopupPosition, 600);

    if (getValueOf('popupLiveOptions')) {
        this.setPopupHTML(popupLiveOptionsHTML(), 'popupLiveOptions',
                     function () { popupToggleShowOptions(true); } );
    }

    if (getValueOf('simplePopups')) return;

    if (getValueOf('popupLinkShowPreview')) return;

    this.showPreview();
};

$popups._anchorContainsImage = function(a) {
    return a.getElementsByTagName('IMG').length > 0;
};

$popups.Popup.prototype.showPreview = function () {
    if (!this.wp) { $popups.error("showPreview: error bab74883-6615-4db3-8b72-c08f59917bdf"); return; }
    if (!this.a) { $popups.error("showPreview: error acae3e5d-f872-40af-99f7-935facfbc63c"); return; }

    if (this.wp.nsImageP) {
        if (getValueOf('popupImages') &&
            (getValueOf('imagePopupsForImages') || !$popups._anchorContainsImage(this.a) ))
        {
            this.loadImage(this.wp.article);
        }
    } else {
        this.loadPreviewImage(this.wp, this.oldid);
    }

    var s = document.getElementById('popupLinkShowPreview' + this.popupId);
    if (s && s.style) {
        s.style.display='none';
    }
};


$popups.Popup.prototype.loadPreviewImage = function(wp, oldid) {
    var popup = this;

    this.debug('.loadPreviewImage(): wikipage='+wp.page + ', oldid='+oldid);

    $download.downloadUrl($popups._makeRawDownloadUrl(wp, oldid),
                          function(d) {
                              popup.insertPreviewImage(wp, oldid, d);
                          });
};

$popups.Popup.prototype.loadPreviewImageFromRedir = function(redirPage, redirTarget) {
    this.debug('.loadPreviewImageFromRedir(): redirCount='+this.redirCount + ', redirPage=' + redirPage, ', redirTarget=' + redirTarget);
    this.redirCount ++;
    var wpTarget = WikiPage(null,redirTarget);
    var warnRedir = this.redirLink(wpTarget);

    this.setPopupHTML(warnRedir, 'popupWarnRedir');

    return this.loadPreviewImage(wpTarget);
};

// TODO: wp
$popups.Popup.prototype.makeFixDabs = function(wikiText, wpOrigTarget)
{
    // this.debug('.makeFixDabs(): origTarget='+origTarget);
    if (getValueOf('popupFixDabs') && $wikiparse.isDisambig(this.wp,wikiText) &&
        !wikiPage.nsSpecialP &&
        this.wp.editableP)
    {
        this.setPopupHTML(makeFixDab(wikiText, wpOrigTarget), 'popupFixDab');
    }
}

$popups.Popup.prototype.insertPreviewImage = function(wp, oldid, download) {
    this.debug('.insertPreviewImage(): redirCount='+this.redirCount +
               ', download.data = ' + $util.describeCharCount(download.data==null));

    var wikiText = download.data;
    if (wikiText == null) {
        // TODO: download failed
        return;
    }

    if (this.redirCount==0) {
        var redir = $wikiparse.isRedirect(wikiText);
        if (redir) {
            this.loadPreviewImageFromRedir(wp.page, redir);
            return;
        }
    }

    this.makeFixDabs(wikiText, this.wp);

    if (getValueOf('popupSummaryData')) {
        download.wp = wp;
        var pgInfo = $popups.getPageInfo(download);
        this.setPopupTrailer('<br/>' + pgInfo);
    }

    defaultize('popupMinImageWidth');
    var wpImage = $wikiparse.getValidImageFromWikiText(wikiText, popupMinImageWidth);
    if (wpImage && wpImage.nsImageP) {
        this.loadImage(wpImage.article);
    }

    if (getValueOf('popupPreviews') && wikiText) {
        this.insertPopupPreview(wikiText);
    }
};

$popups.Popup.prototype.insertPopupPreview = function(wikiText)
{
    this.debug('.insertPopupPreview(' + $util.describeCharCount(wikiText) + ')');
    if ( this.wp.nsTemplateP ) {
        // TODO: once we write this using DOM it'll be much more
        // efficient to just use createTextNode directly instead of
        // $util.escapeHTML
        var h='<hr/><tt>' + $util.escapeHTML(wikiText) + '</tt>';
        this.setPopupHTML(h, 'popupPreview');
        // TODO     getValueOf('popupSubpopups') ? function() { pop.runOnce( popTips, 250 ); } : null );
    } else {
        var html = $instaviewtiny.makePreview(wikiText.substring(0,10000));
        if (html) {
            this.setPopupHTML('<hr/>'+html, 'popupPreview');
        }
    }
}


function fuzzyCursorOffMenus(fuzz) {
  if(!over) return null;
  var spans=over.getElementsByTagName('span');
  for (var i=0; i<spans.length; ++i) {
    if (typeof spans[i].className != 'undefined' && spans[i].className=='popup_menu') {
      if (!fuzzyCursorOffDiv(fuzz, spans[i])) return false;
    } // else {document.title+='.';}
  }
  return true;
};

function fuzzyCursorOffDiv(fuzz, div) {
  // $popups.debug('fuzzyCursorOffDiv: mouse (x,y)=('+o3_x+',' + o3_y+')');
  var left=findPosX(div)-fuzz;
  var top=findPosY(div) - fuzz;
  var right=left + div.offsetWidth + 2*fuzz;
  var height=0;
  for (var i=0; i<div.childNodes.length;++i) {
    // $popups.debug('adding child '+div.childNodes[i]+'.offsetHeight: '+div.childNodes[i].offsetHeight);
    if (typeof div.childNodes[i].offsetHeight==typeof 1)
      height+=div.childNodes[i].offsetHeight;
  }
  var bottom = top + height + 2*fuzz;
  // $popups.debug(left+'->'+right+' , '+top+'->'+bottom);
  //document.title+=' offsetHeight:'+div.offsetHeight;
  if (typeof left != 'number' || typeof right != 'number' || typeof top != 'number') return true;
  if (o3_x < left || o3_x > right || o3_y < top) // || o3_y > bottom) // FIXME: should check the bottom too
    return true;
  return false;
};

// TODO: don't do this as a timer; just set it right the first time; and run
// it when content changes.  otherwise it also interacts poorly with dragging.

// TODO: stop it from running off bottom!
$popups.checkPopupPosition = function () { // stop the popup running off the right of the screen
  if (typeof over == 'undefined' || !over) return null;
  var x=findPosX(over);
  var w=parseInt(over.offsetWidth);
  if ( (x+w) >= document.body.clientWidth ) {
    // for what I am about to do, may the Lord forgive me
    over.style.left=0;
    var ww=parseInt(over.offsetWidth);
    over.style.left=(document.body.clientWidth-ww-1)+'px';
  }
  /*
  var y=findPosY(over);
  var h=parseInt(over.offsetHeight);
  var winH=window.innerHeight + window.pageYOffset;
  document.title=[y,h,winH].join(',');
  if ( h < winH && (y+h) >= winH ) {
    over.style.top=0;
    var hh=parseInt(over.offsetHeight);
    document.title+=','+hh;
    over.style.top=(winH-hh-24)+'px';
  }
  */
  return true;
};


function findPosX(obj)
{
    var curleft = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curleft += obj.offsetLeft
            obj = obj.offsetParent;
        }
    }
    else if (obj.x)
        curleft += obj.x;
    return curleft;
}

function findPosY(obj)
{
    var curtop = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curtop += obj.offsetTop
            obj = obj.offsetParent;
        }
    }
    else if (obj.y)
        curtop += obj.y;
    return curtop;
}


function fuzzyCursorOffOver(fuzz) {
  if (!over) return null;
  var left = parseInt(over.style.left);
  var top = parseInt(over.style.top);
  var right = left +
    (over.offsetWidth >= parseInt(o3_width) ? over.offsetWidth : parseInt(o3_width));
  var bottom = top +
    (over.offsetHeight >= o3_aboveheight ? over.offsetHeight : o3_aboveheight);

  if (o3_x < left-fuzz || o3_x > right+fuzz || o3_y < top-fuzz || o3_y > bottom + fuzz)
   return true;
  return false;
};

window.fuzzyCursorOff=function(fuzz) {
  return fuzzyCursorOffOver(fuzz) && fuzzyCursorOffMenus(fuzz);
}

// seems that fuzzyCursorOff should precede mouseOutWikiLink in the source
// or sometimes during page loads, errors are generated

var stopPopupTimer=null;

$popups.mouseOutWikiLink = function () {
    // TODO: cleanup
    if (typeof window.$popups == 'undefined') return;
    if (!$popups.loaded || $popups.unloaded) return;
    if ($popups.currentlyDraggingP()) return;
    if (fuzzyCursorOff(5)) {
        if (window.stopPopupTimer) {
            clearInterval(window.stopPopupTimer);
            window.stopPopupTimer=null;
        }
        $popups.killPopup();
        return;
    }
    if (!window.stopPopupTimer)
        window.stopPopupTimer=setInterval($popups.mouseOutWikiLink, 500);
};

$popups.killPopup = function() {
    // TODO: this is currently used to close a single popup  through regular
    // mouseout as well as total unload abort.  factor this.
    // TODO: don't actually "kill" popups on mouse out, just hide

    // o3_showingsticky should be defined in overlib.js
    //if ( typeof o3_showingsticky != "undefined" && o3_showingsticky == 0 ) {
    if (getValueOf('popupShortcutKeys')) rmPopupShortcuts();
    if (window.over) {
        cClick(); // hide overlay
    }
    $popups.currentPopup = null;
    // $downloads.abortAllDownloads();
    $wikiimg.abortAllDownloads();
    if ($popups.checkPopupPositionTimer) { clearInterval($popups.checkPopupPositionTimer); }
    return true; // preserve default action (eg from onclick)
};

$util.addOnloadHook($popups.load);
$util.addOnunloadHook($popups.unload);

/****************************************************************************
 * END popups.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN picturepopups.js
 ****************************************************************************/

// $Id: picturepopups.js 1143 2006-02-23 09:36:43Z quarl $

// picture popups - clicking on an image shows a popup.

// originally based on
// http://en.wikipedia.org/wiki/User:Zocky/PicturePopups.js

var $picturepopups = new Module('picturepopups.js');
$picturepopups.depend('picturepopups.css');
$picturepopups.depend('util.js', 'notes.js');

$picturepopups.notes = {};

// Handle clicks on images
$picturepopups.onImageClick = function(e)
{
    // loaded yet?
    if (! (document.getElementById('content')||
           document.getElementById('article'))) return;

    if (e.ctrlKey || e.shiftKey || !$util.eventLeftButtonP(e)) return;
    if (!(e.target &&
          e.target.tagName &&
          e.target.tagName.toUpperCase() == 'IMG'))
    {
        return;
    }

    var t=e.target;
    while (t.parentNode && t.tagName!='A') { t=t.parentNode; }

    if (!t.href) return;
    var wp = WikiPage(t.href);
    if (!wp.url) return;

    var note = $picturepopups[wp.page];
    if (note) {
        note.toggleShow();
    } else {
        $picturepopups[wp.page] = $picturepopups.showImageNote(wp);
    }

    e.preventDefault();
}

$picturepopups.load = function() {
    // adding this hook in an onload hook helps prevent the user from clicking
    // before we're ready
    $util.hookEventObj(document, 'click', $picturepopups.onImageClick);
};
$util.addOnloadHook($picturepopups.load);

$picturepopups.showImageNote = function(wp) {
    var note = new $notes.Note({
        x: Math.round(Math.random()*200),
        y: Math.round(Math.random()*200),
        title: '[<a href="'+wp.url+'"> &gt; </a>] '+wp.article,
        content:'<blink><small><i>loading...</i></small></blink>',
        className: 'note-picturepopup'
        });

    note.contentCell.addEventListener('click',$picturepopups.noteContentClick,true);

    $util.asyncDownloadXML(wp.url, $picturepopups._imgLoaded, null, note);
    return note;
}

// Stop popup images from linking to hi-res pages
$picturepopups.noteContentClick = function(e)
{
    if (e.target.tagName && e.target.tagName.toUpperCase()=='IMG') {
        e.preventDefault();
    }
}

$picturepopups._imgLoaded = function(req, note) {
    if (!note) {
        $picturepopups.error("_loaded(): no note?! (error 7f66ea29-8cb9-41cb-aada-3a9769a33d26)");
        return;
    }

    var contentCell = note.contentCell;

    if (req.status == 200) {
        var file = $util.findDescendantById(req.responseXML,'file');
        if (!file) {
            contentCell.innerHTML = "Failed. <a>retry</a>";
            return;
        }
        contentCell.innerHTML = '';
        contentCell.appendChild(file);
        var license = $util.findDescendantById(req.responseXML,'imageLicense');
        if (license) {
            contentCell.appendChild(license);
        }
    } else {
        contentCell.innerHTML == req.statusText;
        alert(req.statusText);
    }
}

/****************************************************************************
 * END picturepopups.js
 ****************************************************************************/

/****************************************************************************
 * BEGIN instapreview.js
 ****************************************************************************/

// $Id: instapreview.js 967 2006-02-22 08:56:49Z quarl $

// Instapreview -- adds "InstaView" button to edit pages.

// factored from instaview.js.  Uses CSS; fixes autoedit.js compatibility bug.

var $instapreview = new Module('instapreview.js');
$instapreview.depend('instaview.js', 'util.js', 'wikipage.js');
$instapreview.depend('instapreview.css');

// embed InstaView in MediaWiki's edit page
$instapreview.widgetLoad_ = function() {
    if (wikiDoc.editingP) {
        var wpPreview = document.getElementById('wpPreview');

        $instapreview.dumpdiv = document.createElement('div');
        $instapreview.dumpdiv.id = 'instapreviewDump'; // (see CSS)

        var instapreviewButton = document.createElement('input');

        instapreviewButton.setAttribute('type', 'button');
        // instapreviewButton.setAttribute('style', 'font-style: italic');
        instapreviewButton.type = 'button';
        instapreviewButton.value = 'InstaView';
        instapreviewButton.onclick = function() {
            $instaview.dump(document.getElementById('wpTextbox1'), $instapreview.dumpdiv)};

        $util.addNodeBefore(wpPreview, instapreviewButton);
        $util.addNodeAfter(wpPreview.parentNode, $instapreview.dumpdiv);
        // note: don't use "innerHTML +=" because that resets any existing
        // onclick handlers
    }
};

$instapreview.widgetLoad = function() {
    $util.addOnloadHook($instapreview.widgetLoad_);
}

/****************************************************************************
 * END instapreview.js
 ****************************************************************************/