User:Omegatron/monobook.js/quarl.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.
/* <pre><nowiki> */
/* module.js */

// $Id: module.js 1367 2006-03-22 06:02:44Z 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);
    }

    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;
    }
}


/* 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;
}

/* msg.js */

// $Id: msg.js 1267 2006-02-25 08:30:54Z 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 = {
    // debug log is only pruned if it grows larger than this.
    debuglogsize: 500
};

$msg.alert_disabled = false;
$msg.debuglog = [];

$msg.alert = function(msg) {
    $msg.debug(msg);
    if ($msg.alert_disabled) return void(0);
    msg += "\nPress CANCEL to disable alerts for this page (they will still be visible in the debug log)";
    if (!window.confirm(msg)) {
        $msg.alert_disabled = true;
    }
    return void(0);
}

$msg.error = function(msg) {
    $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) {
    $msg.alert(msg);
    return void(0);
}

$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 --");


/* util.js */

// $Id: util.js 1351 2006-03-08 22:24:43Z 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() {
    $util.debug("Running onload hooks...");
    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;
    $util.debug("Running onload hooks... done.");
}

// 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) {
        $util.error("$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.
addOnloadHook($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) {
    if (!s) return 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(s) {
    var div = document.createElement('div');
    var text = document.createTextNode(s);
    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;
};

// similar to pprintf; takes an array which is the result of a String.match.
// Starts at $0 instead of $1.
// e.g. $util.mprintf("$1, my name is $2", 'Hello Quarl'.match(/(.+) (.+)/))
//                 => "Hello, my name is Quarl"
// Useful when String.replace cannot be used.
$util.mprintf = function(fmt, m) {
    for (var i = m.length; i >= 0; --i) {
        fmt = fmt.replace(new RegExp('\\$' + i,'g'), m[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 };
};

$util.regexpNotGlobal = function(re) {
    var opts = '';
    if (re.ignoreCase) opts += 'i';
    if (re.multiline) opts += 'm';
    return new RegExp(re.source, opts);
}

// like regular String.replace, but takes a lambda for replacement func
String.prototype.replacef = function(re, func) {
    if (!(re instanceof RegExp)) re = new RegExp(re);
    str = this;
    var m;
    var result = '';
    if (re.global) {
        re = $util.regexpNotGlobal(re);
        while ( (m=str.match(re)) ) {
            str = RegExp.rightContext;
            result += RegExp.leftContext;
            result += func(m);
        }
        result += str;
        return result;
    } else {
        if ( (m=str.match(re)) ) {
            str = RegExp.rightContext;
            result += RegExp.leftContext;
            result += func(m);
            result += str;
            return result;
        } else {
            return str;
        }
    }
};


////////////////////////////////////////////////////////////
// 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) {
        $util.error("## removeNodesInRange: node_end==null");
        return null;
    }
    if (!$util.getNodesInRange(node_start, node_end)) {
        $util.error("## 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');
    if (callback) {
        // 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;
}


/* wikiwidget.js */

// $Id: wikiwidget.js 1351 2006-03-08 22:24:43Z 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) {
        $wikiwidget.error("## 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) {
            $wikiwidget.error("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 {
        $wikiwidget.error("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}.toSource());
    location = location || this.default_location;
    var li = this.li = document.createElement('li');
    if (this.id) li.id = this.id;
    if (this.className) li.className = this.className;

    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') {
        $wikiwidget.error("## 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);


/* 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();



/* wikipage.js */

// $Id: wikipage.js 1361 2006-03-16 08:45:20Z 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);
        try {
            this.page = $util.wpaDecode(this.pageQuoted);
        } catch (e) {
            if (usafe) { this.failed=1; return null; }
            return $wikipage.error($util.pprintf("Couldn't decode page name '$1': $2", this.pageQuoted, e));
        }
    } 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] != null) {
        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);
    this.editingNewP  = Boolean(doc.getElementById('newarticletext'));
    // 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) {
    // dbnames for all Wikimedia Foundation servers:
    switch(server) {
    case 'commons.wikimedia.org': return 'commonswiki';
    case 'meta.wikimedia.org': return 'metawiki';
    case 'species.wikipedia.org': return 'specieswiki';
    case 'wikimediafoundation.org': return 'foundationwiki';
    }
    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';
    }
    if (server.match(/^([a-z][a-z])[.]wikibooks[.]org$/)) {
        return RegExp.$1 + 'wikibooks';
    }
    if (server.match(/^([a-z][a-z])[.]wiktionary[.]org$/)) {
        return RegExp.$1 + 'wiktionary';
    }
    if (server.match(/^([a-z][a-z])[.]wikinews[.]org$/)) {
        return RegExp.$1 + 'wikinews';
    }
    if (server.match(/^([a-z][a-z])[.]wikiquote[.]org$/)) {
        return RegExp.$1 + 'wikiquote';
    }
    $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'));



/* wikiedit.js */

// $Id: wikiedit.js 1351 2006-03-08 22:24:43Z 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) {
        $wikiedit.error("## 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) {
            $wikiedit.error("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);



/* diff.js */

// $Id: diff.js 1259 2006-02-25 08:07:15Z 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.options = {
    // if more than this many words of changes, use overflow string
    summary_maxwords: 40,
    summary_overflow: "$1 words changed"
};

$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 };
}

$diff._split = function(s) {
    //return $util.trimSpaces(s).split(/(?:\s|[.,;\'\"`])+/);
    return 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._wikiQuoteWords = function(s)
{
    var words = $diff._split(s);
    if (words.length > $diff.options.summary_maxwords) {
        return $util.pprintf('($1 words)', words.length);
    } else {
        return $diff.wikiQuote(words.join(' '));
    }
}

$diff.diffSummary = function(o, n) {
    o = $util.trimSpaces(o);
    n = $util.trimSpaces(n);
    if (o == n) return "";
    if (!o) {
        return "new: +" + $diff._wikiQuoteWords(n);
    }
    if (!n) {
        return "blank: -" + $diff._wikiQuoteWords(o);
    }
    var words = $diff.diff( $diff._split(o), $diff._split(n) );
    var r = $diff.aggregate(words);
    if (!r.wordcount) return "";
    if (r.wordcount > $diff.options.summary_maxwords) {
        return $util.pprintf($diff.options.summary_overflow, 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(", ");
}



/* 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;
}



/* diffsince.js */

// $Id: diffsince.js 1351 2006-03-08 22:24:43Z 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 {
        $diffsince.error("Error downloading history page! (error cafe5f9c-5d9d-4f91-ba31-35c68c401c4f)");
    }
}

$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 void(0);
        }
    }

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

$util.addOnloadHook($diffsince._load);



/* wikiwatch.js */

// $Id: wikiwatch.js 1351 2006-03-08 22:24:43Z 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 {
            $wikiwatch.error("$wikiwatch.wuwatchAsync "+(wuwp?'watch':'unwatch')+" '"+wp.page+"' failed!");
        }
    };
    $util.asyncDownloadXML($wikiwatch.makeUrl(wp, wuwp), cb);
}



/* watchlist.js */

// $Id: watchlist.js 1351 2006-03-08 22:24: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)) {
            $watchlist.error("$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;
    $watchlist.debug("Annotating watchlist with unwatch & since links");

    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$/)) {
            if (link.id == 'newmessages-hist') continue;
            annotateLine(link, targetLink);
        }
    }
}




/* 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_);
}


/* 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);
}


/* autosummary.js */

// $Id: autosummary.js 1257 2006-02-25 08:05:33Z 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 number of seconds for update interval (in sec), or false
    // to disable.
    auto_diff: 1.5,

    // 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(/~~~~/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) {
        if (wikiDoc.editingNewP) {
            // add "Creating [[page]]" since edit summary survives page moves
            s = s.replace(/^new /,
                          $util.pprintf("Creating [[$1]] ", wikiPage.page));
        }

        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);




/* 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);




/* newmessages.js */

// $Id: newmessages.js 1356 2006-03-11 21:03:42Z quarl $

// newmessages.js
// - annotate the "You have new messages" alert to add "diff since" and "hist"
//   links.
// - tag fake "new messages" boxes as such
// - touch talk on diff

// quarl 2006-01-16 initial version

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

$newmessages.options = {
    // whether to add "since" and "hist" buttons to new messages boxes
    add_since_hist: true,

    // whether fake "New messages" boxes should be marked as such
    tag_fake: true,

    // whether to access the regular talk page when viewing diff of
    // User_talk:Username so that Mediawiki registers message reads
    touch_talk_on_diff: true
};

$newmessages.load = function() {
    $newmessages.wpTalk = new WikiPage(null,'User talk:' + wikiDoc.username);

    if ($newmessages.options.touch_talk_on_diff &&
        wikiPage.page == $newmessages.wpTalk.page &&
        WikiDocument.queryVars['diff'])
    {
        $newmessages.touchTalk();
    }

    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.match(/^You have new messages \((?:diff|changes|last change)\)\.$/) &&
            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;
    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 id="newmessages-hist" href="'+histUrl+'">history</a>)');

    // insert before final period
    talkmessagebox.innerHTML = talkmessagebox.innerHTML.replace(/(?=[.]$)/, t);
}

// People like to write a message box on their user/talk pages that look like
// you have new messages.  It's fun for a while but it gets old...
$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>');
};

// touch the talk page to register timestamp with Mediawiki.  Idea by
// User:Omegatron
$newmessages.touchTalk = function() {
    $newmessages.debug("Touching " + $newmessages.wpTalk.url);
    $util.asyncDownloadXML($newmessages.wpTalk.url, null);
}

$util.addOnloadHook($newmessages.load);


/* tabsince.js */

// $Id: tabsince.js 1314 2006-02-27 11:12:21Z 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'},
        url: $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);

/* </nowiki></pre> */